keycloak-aplcache
Changes
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java 88(+34 -54)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java 7(+4 -3)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java 3(+2 -1)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientAdapterException.java 32(+32 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java 6(+2 -4)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java 10(+9 -1)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java 3(+2 -1)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java 3(+2 -1)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java 64(+64 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java 40(+40 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java 107(+107 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java 37(+37 -0)
adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java 12(+11 -1)
adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java 3(+2 -1)
adapters/oidc/js/src/main/resources/keycloak.js 77(+59 -18)
dependencies/server-all/pom.xml 7(+6 -1)
distribution/adapters/fuse-adapter-zip/pom.xml 173(+173 -0)
distribution/adapters/pom.xml 1(+1 -0)
distribution/downloads/pom.xml 17(+11 -6)
distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/federation-sssd-setup.sh 31(+31 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml 1(+1 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml 1(+1 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml 1(+1 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-sssd-federation/main/module.xml 46(+14 -32)
distribution/pom.xml 1(+0 -1)
examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java 22(+13 -9)
examples/demo-template/pom.xml 1(+1 -0)
examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java 3(+2 -1)
examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json 1(+0 -1)
examples/providers/event-store-mem/README.md 24(+12 -12)
examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java 2(+1 -1)
examples/providers/rest/README.md 10(+5 -5)
examples/themes/README.md 22(+11 -11)
federation/ldap/pom.xml 5(+5 -0)
federation/pom.xml 1(+1 -0)
federation/sssd/pom.xml 70(+70 -0)
federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java 39(+39 -0)
federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java 18(+18 -0)
federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java 21(+21 -0)
federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java 20(+20 -0)
federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java 20(+20 -0)
federation/sssd/src/main/java/org/keycloak/federation/sssd/ReadonlySSSDUserModelDelegate.java 77(+77 -0)
federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProviderFactory.java 92(+34 -58)
federation/sssd/src/main/resources/DBUS-JAVA-LICENSE 680(+680 -0)
federation/sssd/src/main/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory 2(+1 -1)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java 12(+12 -0)
misc/HackingOnKeycloak.md 3(+2 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java 40(+40 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java 9(+9 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java 37(+34 -3)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCacheProviderFactory.java 16(+5 -11)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java 78(+48 -30)
model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.UserCacheProviderFactory 18(+18 -0)
model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java 27(+27 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java 215(+133 -82)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java 78(+44 -34)
model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java 111(+111 -0)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java 113(+113 -0)
model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java 16(+16 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java 2(+2 -0)
pom.xml 32(+26 -6)
README.md 4(+2 -2)
server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java 13(+13 -0)
services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java 3(+3 -0)
services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java 103(+103 -0)
services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java 102(+102 -0)
services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java 26(+5 -21)
services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java 5(+4 -1)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java 172(+36 -136)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java 88(+88 -0)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java 73(+73 -0)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointQueryStringParser.java 52(+52 -0)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java 88(+88 -0)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java 122(+122 -0)
services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java 1(+0 -1)
services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java 1(+0 -1)
services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java 125(+125 -0)
services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java 11(+11 -0)
services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperValidator.java 113(+113 -0)
services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java 60(+51 -9)
services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java 44(+31 -13)
services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java 4(+2 -2)
services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java 2(+2 -0)
services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java 44(+30 -14)
services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory 3(+2 -1)
testsuite/integration/pom.xml 53(+53 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java 4(+4 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java 18(+14 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/DummyUserFederationProvider.java 150(+0 -150)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java 18(+14 -4)
testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory 1(+0 -1)
testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl 17(+15 -2)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingExportImportResource.java 142(+142 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java 152(+152 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java 11(+10 -1)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java 39(+36 -3)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java 101(+11 -90)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json 6(+6 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl 114(+114 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties 18(+18 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html 72(+72 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties 18(+18 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl 95(+95 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl 131(+131 -0)
testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties 18(+18 -0)
testsuite/integration-arquillian/servers/migration/pom.xml 424(+194 -230)
testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/add-dialect-logger.xsl 45(+0 -45)
testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/datasource.xsl 111(+0 -111)
testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/add-dialect-logger.xsl 45(+0 -45)
testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/datasource.xsl 111(+0 -111)
testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/add-dialect-logger.xsl 45(+0 -45)
testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/datasource.xsl 111(+0 -111)
testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/add-dialect-logger.xsl 45(+0 -45)
testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/datasource.xsl 111(+0 -111)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java 35(+29 -6)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java 6(+6 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BasicAuth.java 61(+61 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/BasicAuthServlet.java 27(+27 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java 40(+14 -26)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java 3(+0 -3)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java 3(+3 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java 4(+4 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResourceUrls.java 54(+54 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingExportImportResource.java 93(+93 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java 63(+3 -60)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java 69(+69 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java 20(+20 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java 7(+7 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java 36(+36 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java 23(+23 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java 21(+15 -6)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java 86(+86 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractExampleAdapterTest.java 14(+7 -7)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java 3(+2 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseExampleAdapterTest.java 17(+9 -8)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java 23(+10 -13)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java 294(+293 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java 23(+15 -8)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java 2(+1 -1)
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 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java 4(+3 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java 1(+1 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractAuthorizationTest.java 7(+7 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java 50(+50 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java 133(+133 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java 75(+69 -6)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java 5(+0 -5)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java 164(+152 -12)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java 97(+38 -59)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java 34(+34 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java 71(+27 -44)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java 114(+99 -15)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java 18(+15 -3)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/META-INF/context.xml 24(+3 -21)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/jetty-web.xml 46(+46 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak.json 11(+11 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak-relative.json 11(+11 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/web.xml 46(+46 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml 6(+6 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json 836(+538 -298)
testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json 751(+0 -751)
testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPDefaultAuthzConfigAdapterTest.java 30(+30 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPPhotozExampleAdapterTest.java 30(+30 -0)
testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.java 33(+33 -0)
testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientAuthorizationServicesAvailableTest.java 65(+65 -0)
testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java 174(+174 -0)
testsuite/integration-arquillian/tests/pom.xml 190(+17 -173)
testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java 280(+280 -0)
themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html 8(+4 -4)
themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider.html 31(+28 -3)
themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html 27(+10 -17)
themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html 21(+7 -14)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
index d7985b0..925a0db 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
@@ -17,26 +17,21 @@
package org.keycloak.adapters;
-import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
+import org.keycloak.adapters.authorization.PolicyEnforcer;
+import org.keycloak.adapters.rotation.PublicKeyLocator;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig;
-import org.keycloak.representations.idm.PublishedRealmRepresentation;
-import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.KeycloakUriBuilder;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.net.URI;
-import java.security.PublicKey;
import java.util.Map;
/**
@@ -57,7 +52,7 @@ public class AdapterDeploymentContext {
* during the application deployment's life cycle.
*
* @param deployment A KeycloakConfigResolver, possibly missing the Auth
- * Server URL and/or Realm Public Key
+ * Server URL
*/
public AdapterDeploymentContext(KeycloakDeployment deployment) {
this.deployment = deployment;
@@ -79,7 +74,6 @@ public class AdapterDeploymentContext {
/**
* For single-tenant deployments, it complements KeycloakDeployment
* by resolving a relative Auth Server's URL based on the current request
- * and, if needed, will lazily resolve the Realm's Public Key.
*
* For multi-tenant deployments, defers the resolution of KeycloakDeployment
* to the KeycloakConfigResolver .
@@ -98,8 +92,8 @@ public class AdapterDeploymentContext {
if (deployment.getAuthServerBaseUrl() == null) return deployment;
KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
- if (resolvedDeployment.getRealmKey() == null) {
- resolveRealmKey(resolvedDeployment);
+ if (resolvedDeployment.getPublicKeyLocator() == null) {
+ throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
}
return resolvedDeployment;
}
@@ -115,45 +109,6 @@ public class AdapterDeploymentContext {
}
}
- public void resolveRealmKey(KeycloakDeployment deployment) {
- if (deployment.getClient() == null) {
- throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
- }
- HttpGet get = new HttpGet(deployment.getRealmInfoUrl());
- try {
- HttpResponse response = deployment.getClient().execute(get);
- int status = response.getStatusLine().getStatusCode();
- if (status != 200) {
- close(response);
- throw new RuntimeException("Unable to resolve realm public key remotely, status = " + status);
- }
- HttpEntity entity = response.getEntity();
- if (entity == null) {
- throw new RuntimeException("Unable to resolve realm public key remotely. There was no entity.");
- }
- InputStream is = entity.getContent();
- try {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- int c;
- while ((c = is.read()) != -1) {
- os.write(c);
- }
- byte[] bytes = os.toByteArray();
- String json = new String(bytes);
- PublishedRealmRepresentation rep = JsonSerialization.readValue(json, PublishedRealmRepresentation.class);
- deployment.setRealmKey(rep.getPublicKey());
- } finally {
- try {
- is.close();
- } catch (IOException ignored) {
-
- }
- }
- } catch (IOException e) {
- throw new RuntimeException("Unable to resolve realm public key remotely", e);
- }
- }
-
/**
* This delegate is used to store temporary, per-request metadata like request resolved URLs.
* Ever method is delegated except URL get methods and isConfigured()
@@ -208,6 +163,11 @@ public class AdapterDeploymentContext {
}
@Override
+ public String getJwksUrl() {
+ return (this.jwksUrl != null) ? this.jwksUrl : delegate.getJwksUrl();
+ }
+
+ @Override
public String getResourceName() {
return delegate.getResourceName();
}
@@ -223,13 +183,13 @@ public class AdapterDeploymentContext {
}
@Override
- public PublicKey getRealmKey() {
- return delegate.getRealmKey();
+ public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
+ delegate.setPublicKeyLocator(publicKeyLocator);
}
@Override
- public void setRealmKey(PublicKey realmKey) {
- delegate.setRealmKey(realmKey);
+ public PublicKeyLocator getPublicKeyLocator() {
+ return delegate.getPublicKeyLocator();
}
@Override
@@ -466,6 +426,26 @@ public class AdapterDeploymentContext {
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
}
+
+ @Override
+ public PolicyEnforcer getPolicyEnforcer() {
+ return delegate.getPolicyEnforcer();
+ }
+
+ @Override
+ public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
+ delegate.setPolicyEnforcer(policyEnforcer);
+ }
+
+ @Override
+ public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
+ delegate.setMinTimeBetweenJwksRequests(minTimeBetweenJwksRequests);
+ }
+
+ @Override
+ public int getMinTimeBetweenJwksRequests() {
+ return delegate.getMinTimeBetweenJwksRequests();
+ }
}
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
index f1be944..1c900b8 100644
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/KeycloakAdapterPolicyEnforcer.java
@@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.authorization.client.AuthorizationDeniedException;
import org.keycloak.authorization.client.AuthzClient;
@@ -120,7 +121,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
if (authzResponse != null) {
- return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
+ return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
}
return null;
@@ -130,7 +131,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
if (token.getAuthorization() == null) {
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
- return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
+ return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
} else {
EntitlementRequest request = new EntitlementRequest();
PermissionRequest permissionRequest = new PermissionRequest();
@@ -139,7 +140,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
request.addPermission(permissionRequest);
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
- return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
+ return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
}
}
} catch (AuthorizationDeniedException e) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
index 9af6214..70b90b1 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java
@@ -19,6 +19,7 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
@@ -84,7 +85,7 @@ public class BearerTokenRequestAuthenticator {
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
try {
- token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+ token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
} catch (VerificationException e) {
log.error("Failed to verify token", e);
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
index 7f66dbf..2093645 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
import org.keycloak.constants.AdapterConstants;
@@ -73,7 +74,7 @@ public class CookieTokenStore {
try {
// Skip check if token is active now. It's supposed to be done later by the caller
- AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true);
+ AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true);
IDToken idToken;
if (idTokenString != null && idTokenString.length() > 0) {
try {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpAdapterUtils.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpAdapterUtils.java
new file mode 100644
index 0000000..e01f7dc
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpAdapterUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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.adapters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class HttpAdapterUtils {
+
+
+ public static <T> T sendJsonHttpRequest(KeycloakDeployment deployment, HttpRequestBase httpRequest, Class<T> clazz) throws HttpClientAdapterException {
+ try {
+ HttpResponse response = deployment.getClient().execute(httpRequest);
+ int status = response.getStatusLine().getStatusCode();
+ if (status != 200) {
+ close(response);
+ throw new HttpClientAdapterException("Unexpected status = " + status);
+ }
+ HttpEntity entity = response.getEntity();
+ if (entity == null) {
+ throw new HttpClientAdapterException("There was no entity.");
+ }
+ InputStream is = entity.getContent();
+ try {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ int c;
+ while ((c = is.read()) != -1) {
+ os.write(c);
+ }
+ byte[] bytes = os.toByteArray();
+ String json = new String(bytes);
+ return JsonSerialization.readValue(json, clazz);
+ } finally {
+ try {
+ is.close();
+ } catch (IOException ignored) {
+
+ }
+ }
+ } catch (IOException e) {
+ throw new HttpClientAdapterException("IO error", e);
+ }
+ }
+
+
+ private static void close(HttpResponse response) {
+ if (response.getEntity() != null) {
+ try {
+ response.getEntity().getContent().close();
+ } catch (IOException e) {
+
+ }
+ }
+ }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientAdapterException.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientAdapterException.java
new file mode 100644
index 0000000..7d303e2
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientAdapterException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.adapters;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class HttpClientAdapterException extends Exception {
+
+ public HttpClientAdapterException(String message) {
+ super(message);
+ }
+
+ public HttpClientAdapterException(String message, Throwable t) {
+ super(message, t);
+ }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
index 51be551..75086e7 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java
@@ -43,6 +43,7 @@ import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.FindFile;
import org.keycloak.representations.AccessToken;
@@ -88,9 +89,6 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
try {
InputStream is = FindFile.findFile(keycloakConfigFile);
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
- if (kd.getRealmKey() == null) {
- new AdapterDeploymentContext().resolveRealmKey(kd);
- }
return kd;
} catch (RuntimeException e) {
getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e);
@@ -190,7 +188,7 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
protected Auth bearerAuth(String tokenString) throws VerificationException {
- AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+ AccessToken token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
boolean verifyCaller;
if (deployment.isUseResourceRoleMappings()) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index 901b3ea..30c40c3 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -21,6 +21,7 @@ import org.apache.http.client.HttpClient;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
import org.keycloak.adapters.authorization.PolicyEnforcer;
+import org.keycloak.adapters.rotation.PublicKeyLocator;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired;
@@ -29,7 +30,6 @@ import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.common.util.KeycloakUriBuilder;
import java.net.URI;
-import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
@@ -43,7 +43,7 @@ public class KeycloakDeployment {
protected RelativeUrlsUsed relativeUrls;
protected String realm;
- protected volatile PublicKey realmKey;
+ protected PublicKeyLocator publicKeyLocator;
protected String authServerBaseUrl;
protected String realmInfoUrl;
protected KeycloakUriBuilder authUrl;
@@ -52,6 +52,7 @@ public class KeycloakDeployment {
protected String accountUrl;
protected String registerNodeUrl;
protected String unregisterNodeUrl;
+ protected String jwksUrl;
protected String principalAttribute = "sub";
protected String resourceName;
@@ -79,13 +80,14 @@ public class KeycloakDeployment {
protected volatile int notBefore;
protected int tokenMinimumTimeToLive;
+ protected int minTimeBetweenJwksRequests;
private PolicyEnforcer policyEnforcer;
public KeycloakDeployment() {
}
public boolean isConfigured() {
- return getRealm() != null && getRealmKey() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
+ return getRealm() != null && getPublicKeyLocator() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
}
public String getResourceName() {
@@ -100,12 +102,12 @@ public class KeycloakDeployment {
this.realm = realm;
}
- public PublicKey getRealmKey() {
- return realmKey;
+ public PublicKeyLocator getPublicKeyLocator() {
+ return publicKeyLocator;
}
- public void setRealmKey(PublicKey realmKey) {
- this.realmKey = realmKey;
+ public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
+ this.publicKeyLocator = publicKeyLocator;
}
public String getAuthServerBaseUrl() {
@@ -147,6 +149,7 @@ public class KeycloakDeployment {
accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
+ jwksUrl = authUrlBuilder.clone().path(ServiceUrlConstants.JWKS_URL).build(getRealm()).toString();
}
public RelativeUrlsUsed getRelativeUrls() {
@@ -181,6 +184,10 @@ public class KeycloakDeployment {
return unregisterNodeUrl;
}
+ public String getJwksUrl() {
+ return jwksUrl;
+ }
+
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
@@ -369,6 +376,14 @@ public class KeycloakDeployment {
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
}
+ public int getMinTimeBetweenJwksRequests() {
+ return minTimeBetweenJwksRequests;
+ }
+
+ public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
+ this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
+ }
+
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
this.policyEnforcer = policyEnforcer;
}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 5d54df9..04e16f8 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -22,6 +22,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.adapters.authorization.PolicyEnforcer;
+import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
+import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.PemUtils;
import org.keycloak.enums.TokenStore;
@@ -59,11 +61,16 @@ public class KeycloakDeploymentBuilder {
PublicKey realmKey;
try {
realmKey = PemUtils.decodePublicKey(realmKeyPem);
+ HardcodedPublicKeyLocator pkLocator = new HardcodedPublicKeyLocator(realmKey);
+ deployment.setPublicKeyLocator(pkLocator);
} catch (Exception e) {
throw new RuntimeException(e);
}
- deployment.setRealmKey(realmKey);
+ } else {
+ JWKPublicKeyLocator pkLocator = new JWKPublicKeyLocator();
+ deployment.setPublicKeyLocator(pkLocator);
}
+
if (adapterConfig.getSslRequired() != null) {
deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase()));
} else {
@@ -97,6 +104,7 @@ public class KeycloakDeploymentBuilder {
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
+ deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
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 73aa0f5..02637c0 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
@@ -20,6 +20,7 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
@@ -342,7 +343,7 @@ public class OAuthRequestAuthenticator {
refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken();
try {
- token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+ token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
if (idTokenString != null) {
try {
JWSInput input = new JWSInput(idTokenString);
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
index 1a8c735..5a1df8c 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -17,7 +17,10 @@
package org.keycloak.adapters;
+import java.security.PublicKey;
+
import org.jboss.logging.Logger;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.UserSessionManagement;
import org.keycloak.jose.jws.JWSInputException;
@@ -198,7 +201,8 @@ public class PreAuthActionsHandler {
try {
JWSInput input = new JWSInput(token);
- if (RSAProvider.verify(input, deployment.getRealmKey())) {
+ PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input, deployment);
+ if (RSAProvider.verify(input, publicKey)) {
return input;
}
} catch (JWSInputException ignore) {
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
index 7cfc7a6..75f0cb8 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java
@@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Time;
import org.keycloak.representations.AccessToken;
@@ -130,7 +131,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
String tokenString = response.getToken();
AccessToken token = null;
try {
- token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+ token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
log.debug("Token Verification succeeded!");
} catch (VerificationException e) {
log.error("failed verification of token");
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.java
new file mode 100644
index 0000000..c69ee38
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/AdapterRSATokenVerifier.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.adapters.rotation;
+
+import java.security.PublicKey;
+
+import org.jboss.logging.Logger;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.common.VerificationException;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.representations.AccessToken;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AdapterRSATokenVerifier {
+
+ private static final Logger log = Logger.getLogger(AdapterRSATokenVerifier.class);
+
+ public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException {
+ return verifyToken(tokenString, deployment, true, true);
+ }
+
+
+ public static PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) throws VerificationException {
+ PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
+
+ PublicKey publicKey = pkLocator.getPublicKey(input, deployment);
+ if (publicKey == null) {
+ log.errorf("Didn't find publicKey for kid: %s", input.getHeader().getKeyId());
+ throw new VerificationException("Didn't find publicKey for specified kid");
+ }
+
+ return publicKey;
+ }
+
+ public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
+ JWSInput input;
+ try {
+ input = new JWSInput(tokenString);
+ } catch (Exception e) {
+ throw new VerificationException("Couldn't parse token", e);
+ }
+
+ PublicKey publicKey = getPublicKey(input, deployment);
+ return RSATokenVerifier.verifyToken(input, publicKey, deployment.getRealmInfoUrl(), checkActive, checkTokenType);
+ }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java
new file mode 100644
index 0000000..40fb71a
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/HardcodedPublicKeyLocator.java
@@ -0,0 +1,40 @@
+/*
+ * 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.adapters.rotation;
+
+import java.security.PublicKey;
+
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.jose.jws.JWSInput;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class HardcodedPublicKeyLocator implements PublicKeyLocator {
+
+ private PublicKey publicKey;
+
+ public HardcodedPublicKeyLocator(PublicKey publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ @Override
+ public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) {
+ return publicKey;
+ }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java
new file mode 100644
index 0000000..5003923
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/JWKPublicKeyLocator.java
@@ -0,0 +1,107 @@
+/*
+ * 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.adapters.rotation;
+
+import java.security.PublicKey;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.http.client.methods.HttpGet;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.HttpAdapterUtils;
+import org.keycloak.adapters.HttpClientAdapterException;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.common.util.Time;
+import org.keycloak.jose.jwk.JSONWebKeySet;
+import org.keycloak.jose.jwk.JWK;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.util.JWKSUtils;
+
+/**
+ * When needed, publicKeys are downloaded by sending request to realm's jwks_url
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWKPublicKeyLocator implements PublicKeyLocator {
+
+ private static final Logger log = Logger.getLogger(JWKPublicKeyLocator.class);
+
+ private Map<String, PublicKey> currentKeys = new ConcurrentHashMap<>();
+
+ private volatile int lastRequestTime = 0;
+
+ @Override
+ public PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment) {
+ String kid = input.getHeader().getKeyId();
+ return getPublicKey(kid, deployment);
+ }
+
+
+ private PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
+ int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
+
+ // Check if key is in cache.
+ PublicKey publicKey = currentKeys.get(kid);
+ if (publicKey != null) {
+ return publicKey;
+ }
+
+ int currentTime = Time.currentTime();
+
+ // Check if we are allowed to send request
+ if (currentTime > lastRequestTime + minTimeBetweenRequests) {
+ synchronized (this) {
+ currentTime = Time.currentTime();
+ if (currentTime > lastRequestTime + minTimeBetweenRequests) {
+ sendRequest(deployment);
+ lastRequestTime = currentTime;
+ } else {
+ // TODO: debug
+ log.infof("Won't send request to realm jwks url. Last request time was %d", lastRequestTime);
+ }
+ }
+ }
+
+ return currentKeys.get(kid);
+
+ }
+
+
+ private void sendRequest(KeycloakDeployment deployment) {
+ // Send the request
+ // TODO: trace or remove?
+ log.infof("Going to send request to retrieve new set of realm public keys for client %s", deployment.getResourceName());
+
+ HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
+ try {
+ JSONWebKeySet jwks = HttpAdapterUtils.sendJsonHttpRequest(deployment, getMethod, JSONWebKeySet.class);
+
+ Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
+
+ // TODO: Debug with condition
+ log.infof("Realm public keys successfully retrieved for client %s. New kids: %s", deployment.getResourceName(), publicKeys.keySet().toString());
+
+ // Update current keys
+ currentKeys.clear();
+ currentKeys.putAll(publicKeys);
+
+ } catch (HttpClientAdapterException e) {
+ log.error("Error when sending request to retrieve realm keys", e);
+ }
+ }
+}
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.java
new file mode 100644
index 0000000..bda80dc
--- /dev/null
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/rotation/PublicKeyLocator.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.adapters.rotation;
+
+import java.security.PublicKey;
+
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.jose.jws.JWSInput;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface PublicKeyLocator {
+
+ /**
+ * @param input
+ * @param deployment
+ * @return publicKey, which should be used for verify signature on given "input"
+ */
+ PublicKey getPublicKey(JWSInput input, KeycloakDeployment deployment);
+
+}
diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
index 4be5110..d1ce748 100644
--- a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
+++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java
@@ -21,6 +21,8 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.junit.Test;
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
+import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
+import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.enums.TokenStore;
@@ -39,7 +41,11 @@ public class KeycloakDeploymentBuilderTest {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
assertEquals("demo", deployment.getRealm());
assertEquals("customer-portal", deployment.getResourceName());
- assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), deployment.getRealmKey());
+
+ assertTrue(deployment.getPublicKeyLocator() instanceof HardcodedPublicKeyLocator);
+ assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"),
+ deployment.getPublicKeyLocator().getPublicKey(null, deployment));
+
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
assertTrue(deployment.isUseResourceRoleMappings());
@@ -62,12 +68,16 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
assertEquals("email", deployment.getPrincipalAttribute());
assertEquals(10, deployment.getTokenMinimumTimeToLive());
+ assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
}
@Test
public void loadNoClientCredentials() throws Exception {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
+
+ assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
+ assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
}
@Test
diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak.json b/adapters/oidc/adapter-core/src/test/resources/keycloak.json
index 7bf269f..a8afd22 100644
--- a/adapters/oidc/adapter-core/src/test/resources/keycloak.json
+++ b/adapters/oidc/adapter-core/src/test/resources/keycloak.json
@@ -29,5 +29,6 @@
"register-node-period": 1000,
"token-store": "cookie",
"principal-attribute": "email",
- "token-minimum-time-to-live": 10
+ "token-minimum-time-to-live": 10,
+ "min-time-between-jwks-requests": 20
}
\ No newline at end of file
diff --git a/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json b/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json
index 5f223ac..a3c4026 100644
--- a/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json
+++ b/adapters/oidc/adapter-core/src/test/resources/keycloak-no-credentials.json
@@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "customer-portal",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "https://localhost:8443/auth",
"public-client": true,
"expose-token": true
diff --git a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
index fc0e47f..c52a44a 100644
--- a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
+++ b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
@@ -20,6 +20,7 @@ package org.keycloak.adapters.installed;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
@@ -213,7 +214,7 @@ public class KeycloakInstalled {
refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken();
- token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+ token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
if (idTokenString != null) {
try {
JWSInput input = new JWSInput(idTokenString);
adapters/oidc/js/src/main/resources/keycloak.js 77(+59 -18)
diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js
index 563f7ca..2d8f421 100755
--- a/adapters/oidc/js/src/main/resources/keycloak.js
+++ b/adapters/oidc/js/src/main/resources/keycloak.js
@@ -154,21 +154,34 @@
return;
} else if (initOptions) {
if (initOptions.token || initOptions.refreshToken) {
- setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken, false);
- kc.timeSkew = initOptions.timeSkew || 0;
+ setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
if (loginIframe.enable) {
setupCheckLoginIframe().success(function() {
checkLoginIframe().success(function () {
+ kc.onAuthSuccess && kc.onAuthSuccess();
initPromise.setSuccess();
}).error(function () {
+ kc.onAuthError && kc.onAuthError();
if (initOptions.onLoad) {
onLoad();
+ } else {
+ initPromise.setError();
}
});
});
} else {
- initPromise.setSuccess();
+ kc.updateToken(-1).success(function() {
+ kc.onAuthSuccess && kc.onAuthSuccess();
+ initPromise.setSuccess();
+ }).error(function() {
+ kc.onAuthError && kc.onAuthError();
+ if (initOptions.onLoad) {
+ onLoad();
+ } else {
+ initPromise.setError();
+ }
+ });
}
} else if (initOptions.onLoad) {
onLoad();
@@ -349,11 +362,10 @@
throw 'Not authenticated';
}
- var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew;
+ var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew;
if (minValidity) {
expiresIn -= minValidity;
}
-
return expiresIn < 0;
}
@@ -368,7 +380,20 @@
minValidity = minValidity || 5;
var exec = function() {
- if (!kc.isTokenExpired(minValidity)) {
+ var refreshToken = false;
+ if (kc.timeSkew == -1) {
+ console.info('Skew ' + kc.timeSkew);
+ refreshToken = true;
+ console.info('[KEYCLOAK] Refreshing token: time skew not set');
+ } else if (minValidity == -1) {
+ refreshToken = true;
+ console.info('[KEYCLOAK] Refreshing token: forced refresh');
+ } else if (kc.isTokenExpired(minValidity)) {
+ refreshToken = true;
+ console.info('[KEYCLOAK] Refreshing token: token expired');
+ }
+
+ if (!refreshToken) {
promise.setSuccess(false);
} else {
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
@@ -380,6 +405,7 @@
var req = new XMLHttpRequest();
req.open('POST', url, true);
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+ req.withCredentials = true;
if (kc.clientId && kc.clientSecret) {
req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret));
@@ -392,18 +418,21 @@
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
+ console.info('[KEYCLOAK] Token refreshed');
+
timeLocal = (timeLocal + new Date().getTime()) / 2;
var tokenResponse = JSON.parse(req.responseText);
- setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], true);
- kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
+ setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal);
kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
p.setSuccess(true);
}
} else {
+ console.warn('[KEYCLOAK] Failed to refresh token');
+
kc.onAuthRefreshError && kc.onAuthRefreshError();
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
p.setError(true);
@@ -433,7 +462,7 @@
kc.clearToken = function() {
if (kc.token) {
- setToken(null, null, null, true);
+ setToken(null, null, null);
kc.onAuthLogout && kc.onAuthLogout();
if (kc.loginRequired) {
kc.login();
@@ -514,18 +543,16 @@
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
timeLocal = (timeLocal + new Date().getTime()) / 2;
- setToken(accessToken, refreshToken, idToken, true);
+ setToken(accessToken, refreshToken, idToken, timeLocal);
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
- console.log('invalid nonce!');
+ console.info('[KEYCLOAK] Invalid nonce, clearing token');
kc.clearToken();
promise && promise.setError();
} else {
- kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
-
if (fulfillPromise) {
kc.onAuthSuccess && kc.onAuthSuccess();
promise && promise.setSuccess();
@@ -598,7 +625,7 @@
return promise.promise;
}
- function setToken(token, refreshToken, idToken, useTokenTime) {
+ function setToken(token, refreshToken, idToken, timeLocal) {
if (kc.tokenTimeoutHandle) {
clearTimeout(kc.tokenTimeoutHandle);
kc.tokenTimeoutHandle = null;
@@ -617,10 +644,24 @@
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
+ if (timeLocal) {
+ kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
+ console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
+ } else {
+ kc.timeSkew = -1;
+ }
+
if (kc.onTokenExpired) {
- var start = useTokenTime ? kc.tokenParsed.iat : (new Date().getTime() / 1000);
- var expiresIn = kc.tokenParsed.exp - start;
- kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn * 1000);
+ if (kc.timeSkew == -1) {
+ kc.onTokenExpired();
+ } else {
+ var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
+ if (expiresIn <= 0) {
+ kc.onTokenExpired();
+ } else {
+ kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
+ }
+ }
}
} else {
@@ -1055,7 +1096,7 @@
if (!(this instanceof CookieStorage)) {
return new CookieStorage();
}
-
+
var cs = this;
cs.get = function(state) {
diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java
new file mode 100755
index 0000000..ac16874
--- /dev/null
+++ b/common/src/main/java/org/keycloak/common/Profile.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.common;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Properties;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class Profile {
+
+ private enum ProfileValue {
+ PRODUCT, PREVIEW, COMMUNITY
+ }
+
+ private static ProfileValue value = load();
+
+ static ProfileValue load() {
+ String profile = null;
+ try {
+ profile = System.getProperty("keycloak.profile");
+ if (profile == null) {
+ String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
+ if (jbossServerConfigDir != null) {
+ File file = new File(jbossServerConfigDir, "profile.properties");
+ if (file.isFile()) {
+ Properties props = new Properties();
+ props.load(new FileInputStream(file));
+ profile = props.getProperty("profile");
+ }
+ }
+ }
+ } catch (Exception e) {
+ }
+
+ if (profile == null) {
+ return ProfileValue.valueOf(Version.DEFAULT_PROFILE.toUpperCase());
+ } else {
+ return ProfileValue.valueOf(profile.toUpperCase());
+ }
+ }
+
+ public static String getName() {
+ return value.name().toLowerCase();
+ }
+
+ public static boolean isPreviewEnabled() {
+ return value.ordinal() >= ProfileValue.PREVIEW.ordinal();
+ }
+
+}
diff --git a/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
new file mode 100755
index 0000000..56226e0
--- /dev/null
+++ b/common/src/main/java/org/keycloak/common/util/ConcurrentMultivaluedHashMap.java
@@ -0,0 +1,102 @@
+/*
+ * 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.common.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@SuppressWarnings("serial")
+public class ConcurrentMultivaluedHashMap<K, V> extends ConcurrentHashMap<K, List<V>>
+{
+ public void putSingle(K key, V value)
+ {
+ List<V> list = new CopyOnWriteArrayList<>();
+ list.add(value);
+ put(key, list);
+ }
+
+ public void addAll(K key, V... newValues)
+ {
+ for (V value : newValues)
+ {
+ add(key, value);
+ }
+ }
+
+ public void addAll(K key, List<V> valueList)
+ {
+ for (V value : valueList)
+ {
+ add(key, value);
+ }
+ }
+
+ public void addFirst(K key, V value)
+ {
+ List<V> list = get(key);
+ if (list == null)
+ {
+ add(key, value);
+ }
+ else
+ {
+ list.add(0, value);
+ }
+ }
+ public final void add(K key, V value)
+ {
+ getList(key).add(value);
+ }
+
+
+ public final void addMultiple(K key, Collection<V> values)
+ {
+ getList(key).addAll(values);
+ }
+
+ public V getFirst(K key)
+ {
+ List<V> list = get(key);
+ return list == null ? null : list.get(0);
+ }
+
+ public final List<V> getList(K key)
+ {
+ List<V> list = get(key);
+ if (list == null)
+ put(key, list = new CopyOnWriteArrayList<V>());
+ return list;
+ }
+
+ public void addAll(ConcurrentMultivaluedHashMap<K, V> other)
+ {
+ for (Entry<K, List<V>> entry : other.entrySet())
+ {
+ getList(entry.getKey()).addAll(entry.getValue());
+ }
+ }
+
+}
diff --git a/common/src/main/java/org/keycloak/common/Version.java b/common/src/main/java/org/keycloak/common/Version.java
index 42ba52a..862ccd2 100755
--- a/common/src/main/java/org/keycloak/common/Version.java
+++ b/common/src/main/java/org/keycloak/common/Version.java
@@ -32,6 +32,7 @@ public class Version {
public static String VERSION;
public static String RESOURCES_VERSION;
public static String BUILD_TIME;
+ public static String DEFAULT_PROFILE;
static {
Properties props = new Properties();
@@ -40,6 +41,7 @@ public class Version {
props.load(is);
Version.NAME = props.getProperty("name");
Version.NAME_HTML = props.getProperty("name-html");
+ Version.DEFAULT_PROFILE = props.getProperty("default-profile");
Version.VERSION = props.getProperty("version");
Version.BUILD_TIME = props.getProperty("build-time");
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
diff --git a/common/src/main/resources/keycloak-version.properties b/common/src/main/resources/keycloak-version.properties
index 643b6de..f66e436 100755
--- a/common/src/main/resources/keycloak-version.properties
+++ b/common/src/main/resources/keycloak-version.properties
@@ -18,4 +18,5 @@
name=${product.name}
name-html=${product.name-html}
version=${product.version}
-build-time=${product.build-time}
\ No newline at end of file
+build-time=${product.build-time}
+default-profile=${product.default-profile}
\ No newline at end of file
diff --git a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java
index a21f262..36b4f1d 100755
--- a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java
+++ b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java
@@ -30,5 +30,6 @@ public interface ServiceUrlConstants {
public static final String REALM_INFO_PATH = "/realms/{realm-name}";
public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/register-node";
public static final String CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/unregister-node";
+ public static final String JWKS_URL = "/realms/{realm-name}/protocol/openid-connect/certs";
}
diff --git a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java
index 7a31f72..a20d253 100755
--- a/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java
+++ b/core/src/main/java/org/keycloak/jose/jwk/JWKParser.java
@@ -21,7 +21,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
import org.keycloak.common.util.Base64Url;
import org.keycloak.util.JsonSerialization;
-import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
@@ -66,8 +65,8 @@ public class JWKParser {
}
public PublicKey toPublicKey() {
- String algorithm = jwk.getKeyType();
- if (isAlgorithmSupported(algorithm)) {
+ String keyType = jwk.getKeyType();
+ if (isKeyTypeSupported(keyType)) {
BigInteger modulus = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.MODULUS).toString()));
BigInteger publicExponent = new BigInteger(1, Base64Url.decode(jwk.getOtherClaims().get(RSAPublicJWK.PUBLIC_EXPONENT).toString()));
@@ -77,12 +76,12 @@ public class JWKParser {
throw new RuntimeException(e);
}
} else {
- throw new RuntimeException("Unsupported algorithm " + algorithm);
+ throw new RuntimeException("Unsupported keyType " + keyType);
}
}
- public boolean isAlgorithmSupported(String algorithm) {
- return RSAPublicJWK.RSA.equals(algorithm);
+ public boolean isKeyTypeSupported(String keyType) {
+ return RSAPublicJWK.RSA.equals(keyType);
}
}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index 91fb5f0..c4818b4 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -36,7 +36,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
"client-keystore", "client-keystore-password", "client-key-password",
"always-refresh-token",
"register-node-at-startup", "register-node-period", "token-store", "principal-attribute",
- "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live",
+ "proxy-url", "turn-off-change-session-id-on-login", "token-minimum-time-to-live", "min-time-between-jwks-requests",
"policy-enforcer"
})
public class AdapterConfig extends BaseAdapterConfig {
@@ -71,6 +71,8 @@ public class AdapterConfig extends BaseAdapterConfig {
protected Boolean turnOffChangeSessionIdOnLogin;
@JsonProperty("token-minimum-time-to-live")
protected int tokenMinimumTimeToLive = 0;
+ @JsonProperty("min-time-between-jwks-requests")
+ protected int minTimeBetweenJwksRequests = 10;
@JsonProperty("policy-enforcer")
protected PolicyEnforcerConfig policyEnforcerConfig;
@@ -216,4 +218,11 @@ public class AdapterConfig extends BaseAdapterConfig {
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
}
+ public int getMinTimeBetweenJwksRequests() {
+ return minTimeBetweenJwksRequests;
+ }
+
+ public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
+ this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
+ }
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
index 524cbb3..2466f12 100755
--- a/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/IdentityProviderRepresentation.java
@@ -123,10 +123,15 @@ public class IdentityProviderRepresentation {
this.updateProfileFirstLoginMode = updateProfileFirstLoginMode;
}
+ /**
+ * @deprecated Replaced by configuration option in identity provider authenticator
+ */
+ @Deprecated
public boolean isAuthenticateByDefault() {
return authenticateByDefault;
}
+ @Deprecated
public void setAuthenticateByDefault(boolean authenticateByDefault) {
this.authenticateByDefault = authenticateByDefault;
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 67c1678..89e0c01 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -123,6 +123,8 @@ public class RealmRepresentation {
protected String resetCredentialsFlow;
protected String clientAuthenticationFlow;
+ protected Map<String, String> attributes;
+
protected String keycloakVersion;
@Deprecated
@@ -864,4 +866,12 @@ public class RealmRepresentation {
return identityProviders != null && !identityProviders.isEmpty();
}
+ public void setAttributes(Map<String, String> attributes) {
+ this.attributes = attributes;
+ }
+
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+
}
diff --git a/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java
new file mode 100644
index 0000000..3c474d0
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/info/ProfileInfoRepresentation.java
@@ -0,0 +1,53 @@
+/*
+ * 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.representations.info;
+
+import org.keycloak.common.Profile;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProfileInfoRepresentation {
+
+ private String name;
+ private boolean previewEnabled;
+
+ public static ProfileInfoRepresentation create() {
+ ProfileInfoRepresentation info = new ProfileInfoRepresentation();
+ info.setName(Profile.getName());
+ info.setPreviewEnabled(Profile.isPreviewEnabled());
+ return info;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public boolean isPreviewEnabled() {
+ return previewEnabled;
+ }
+
+ public void setPreviewEnabled(boolean previewEnabled) {
+ this.previewEnabled = previewEnabled;
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java
index 59d400e..8c98ce3 100755
--- a/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java
@@ -32,6 +32,7 @@ public class ServerInfoRepresentation {
private SystemInfoRepresentation systemInfo;
private MemoryInfoRepresentation memoryInfo;
+ private ProfileInfoRepresentation profileInfo;
private Map<String, List<ThemeInfoRepresentation>> themes;
@@ -66,6 +67,14 @@ public class ServerInfoRepresentation {
this.memoryInfo = memoryInfo;
}
+ public ProfileInfoRepresentation getProfileInfo() {
+ return profileInfo;
+ }
+
+ public void setProfileInfo(ProfileInfoRepresentation profileInfo) {
+ this.profileInfo = profileInfo;
+ }
+
public Map<String, List<ThemeInfoRepresentation>> getThemes() {
return themes;
}
diff --git a/core/src/main/java/org/keycloak/RSATokenVerifier.java b/core/src/main/java/org/keycloak/RSATokenVerifier.java
index 27a4301..562261d 100755
--- a/core/src/main/java/org/keycloak/RSATokenVerifier.java
+++ b/core/src/main/java/org/keycloak/RSATokenVerifier.java
@@ -38,6 +38,12 @@ public class RSATokenVerifier {
public static AccessToken verifyToken(String tokenString, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
AccessToken token = toAccessToken(tokenString, realmKey);
+ tokenVerifications(token, realmUrl, checkActive, checkTokenType);
+
+ return token;
+ }
+
+ private static void tokenVerifications(AccessToken token, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
String user = token.getSubject();
if (user == null) {
throw new VerificationException("Token user was null.");
@@ -60,9 +66,9 @@ public class RSATokenVerifier {
throw new VerificationException("Token is not active.");
}
- return token;
}
+
public static AccessToken toAccessToken(String tokenString, PublicKey realmKey) throws VerificationException {
JWSInput input;
try {
@@ -81,6 +87,23 @@ public class RSATokenVerifier {
return token;
}
+
+ public static AccessToken verifyToken(JWSInput input, PublicKey realmKey, String realmUrl, boolean checkActive, boolean checkTokenType) throws VerificationException {
+ if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature.");
+
+ AccessToken token;
+ try {
+ token = input.readJsonContent(AccessToken.class);
+ } catch (JWSInputException e) {
+ throw new VerificationException("Couldn't parse token signature", e);
+ }
+
+ tokenVerifications(token, realmUrl, checkActive, checkTokenType);
+
+ return token;
+ }
+
+
private static boolean isPublicKeyValid(JWSInput input, PublicKey realmKey) throws VerificationException {
try {
return RSAProvider.verify(input, realmKey);
diff --git a/core/src/main/java/org/keycloak/util/JWKSUtils.java b/core/src/main/java/org/keycloak/util/JWKSUtils.java
new file mode 100644
index 0000000..72ffe91
--- /dev/null
+++ b/core/src/main/java/org/keycloak/util/JWKSUtils.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util;
+
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.keycloak.jose.jwk.JSONWebKeySet;
+import org.keycloak.jose.jwk.JWK;
+import org.keycloak.jose.jwk.JWKParser;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JWKSUtils {
+
+ public static Map<String, PublicKey> getKeysForUse(JSONWebKeySet keySet, JWK.Use requestedUse) {
+ Map<String, PublicKey> result = new HashMap<>();
+
+ for (JWK jwk : keySet.getKeys()) {
+ JWKParser parser = JWKParser.create(jwk);
+ if (jwk.getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) {
+ result.put(jwk.getKeyId(), parser.toPublicKey());
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/core/src/test/java/org/keycloak/JsonParserTest.java b/core/src/test/java/org/keycloak/JsonParserTest.java
index e346fe6..965a13c 100755
--- a/core/src/test/java/org/keycloak/JsonParserTest.java
+++ b/core/src/test/java/org/keycloak/JsonParserTest.java
@@ -17,17 +17,6 @@
package org.keycloak;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonUnwrapped;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.representations.IDToken;
@@ -36,6 +25,13 @@ import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.util.JsonSerialization;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@@ -139,6 +135,18 @@ public class JsonParserTest {
}
@Test
+ public void testReadOIDCClientRepWithPairwise() throws IOException {
+ String stringRep = "{\"subject_type\": \"pairwise\", \"jwks_uri\": \"https://op.certification.openid.net:60720/export/jwk_60720.json\", \"contacts\": [\"roland.hedberg@umu.se\"], \"application_type\": \"web\", \"grant_types\": [\"authorization_code\"], \"post_logout_redirect_uris\": [\"https://op.certification.openid.net:60720/logout\"], \"redirect_uris\": [\"https://op.certification.openid.net:60720/authz_cb\"], \"response_types\": [\"code\"], \"require_auth_time\": true, \"default_max_age\": 3600}";
+ OIDCClientRepresentation clientRep = JsonSerialization.readValue(stringRep, OIDCClientRepresentation.class);
+ Assert.assertEquals("pairwise", clientRep.getSubjectType());
+ Assert.assertTrue(clientRep.getRequireAuthTime());
+ Assert.assertEquals(3600, clientRep.getDefaultMaxAge().intValue());
+ Assert.assertEquals(1, clientRep.getRedirectUris().size());
+ Assert.assertEquals("https://op.certification.openid.net:60720/authz_cb", clientRep.getRedirectUris().get(0));
+ Assert.assertNull(clientRep.getJwks());
+ }
+
+ @Test
public void testReadOIDCClientRepWithJWKS() throws IOException {
String stringRep = "{\"token_endpoint_auth_method\": \"private_key_jwt\", \"subject_type\": \"public\", \"jwks_uri\": null, \"jwks\": {\"keys\": [{\"use\": \"enc\", \"e\": \"AQAB\", \"d\": \"lZQv0_81euRLeUYU84Aodh0ar7ymDlzWP5NMra4Jklkb-lTBWkI-u4RMsPqGYyW3KHRoL_pgzZXSzQx8RLQfER6timRWb--NxMMKllZubByU3RqH2ooNuocJurspYiXkznPW1Mg9DaNXL0C2hwWPQHTeUVISpjgi5TCOV1ccWVyksFruya_VNL1CIByB-L0GL1rqbKv32cDwi2A3_jJa61cpzfLSIBe-lvCO6tuiDsR4qgJnUwnndQFwEI_4mLmD3iNWXrc8N-poleV8mBfMqBB5fWwy_ZTFCpmQ5AywGmctaik_wNhMoWuA4tUfY6_1LdKld-5Cjq55eLtuJjtvuQ\", \"n\": \"tx3Hjdbc19lkTiohbJrNj4jf2_90MEE122CRrwtFu6saDywKcG7Bi7w2FMAK2oTkuWfqhWRb5BEGmnSXdiCEPO5d-ytqP3nwlZXHaCDYscpP8bB4YLhvCn7R8Efw6gwQle24QPRP3lYoFeuUbDUq7GKA5SfaZUvWoeWjqyLIaBspKQsC26_Umx1E4IXLrMSL6nkRnrYcVZBAXrYCeTP1XtsV38_lZVJfHSaJaUy4PKaj3yvgm93EV2CXybPti7CCMXZ34VqqWiF64pQjZsPu3ZTr7ha_TTQq499-zYRQNDvIVsBDLQQIgrbctuGqj6lrXb31Jj3JIEYqH_4h5X9d0Q\", \"q\": \"1q-r-bmMFbIzrLK2U3elksZq8CqUqZxlSfkGMZuVkxgYMS-e4FPzEp2iirG-eO11aa0cpMMoBdTnVdGJ_ZUR93w0lGf9XnQAJqxP7eOsrUoiW4VWlWH4WfOiLgpO-pFtyTz_JksYYaotc_Z3Zy-Szw6a39IDbuYGy1qL-15oQuc\", \"p\": \"2lrYPppRbcQWu4LtWN6tOVUrtCOPv1eLTKTc7q8vCMcem1Ox5QFB7KnUtNZ5Ni7wnZUeVDfimNebtjNsGvDSrpgIlo9dEnFBQsQIkzZ2SkoYfgmF8hNdi6P-BfRjdgYouy4c6xAnGDgSMTip1YnPRyvbMaoYT9E_tEcBW5wOeoc\", \"kid\": \"a0\", \"kty\": \"RSA\"}, {\"use\": \"sig\", \"e\": \"AQAB\", \"d\": \"DodXDEtkovWWGsMEXYy_nEEMCWyROMOebCnCv0ey3i4M4bh2dmwqgz0e-IKQAFlGiMkidGL1lNbq0uFS04FbuRAR06dYw1cbrNbDdhrWFxKTd1L5D9p-x-gW-YDWhpI8rUGRa76JXkOSxZUbg09_QyUd99CXAHh-FXi_ZkIKD8hK6FrAs68qhLf8MNkUv63DTduw7QgeFfQivdopePxyGuMk5n8veqwsUZsklQkhNlTYQqeM1xb2698ZQcNYkl0OssEsSJKRjXt-LRPowKrdvTuTo2p--HMI0pIEeFs7H_u5OW3jihjvoFClGPynHQhgWmQzlQRvWRXh6FhDVqFeGQ\", \"n\": \"zfZzttF7HmnTYwSMPdxKs5AoczbNS2mOPz-tN1g4ljqI_F1DG8cgQDcN_VDufxoFGRERo2FK6WEN41LhbGEyP6uL6wW6Cy29qE9QZcvY5mXrncndRSOkNcMizvuEJes_fMYrmP_lPiC6kWiqItTk9QBWqJfiYKhCx9cSDXsBmJXn3KWQCVHvj1ANFWW0CWLMKlWN-_NMNLIWJN_pEAocTZMzxSFBK1b5_5J8ZS7hfWRF6MQmjsJcz2jzA21SQZNpre3kwnTGRSwo05sAS-TyeadDqQPWgbqX69UzcGq5irhzN8cpZ_JaTk3Y_uV6owanTZLVvCgdjaAnMYeZhb0KFw\", \"q\": \"5E5XKK5njT-zzRqqTeY2tgP9PJBACeaH_xQRHZ_1ydE7tVd7HdgdaEHfQ1jvKIHFkknWWOBAY1mlBc4YDirLShB_voShD8C-Hx3nF5sne5fleVfU-sZy6Za4B2U75PcE62oZgCPauOTAEm9Xuvrt5aMMovyzR8ecJZhm9bw7naU\", \"p\": \"5vJHCSM3H3q4RltYzENC9RyZZV8EUmpkv9moyguT5t-BUGA-T4W_FGIxzOPXRWOckIplKkoDKhavUeNmTZMCUcue0nkICSJpvNE4Nb2p5PZk_QqSdQNvCasQtdojEG0AmfVD85SU551CYxJdLdDFOqyK2entpMr8lhokem189As\", \"kid\": \"a1\", \"kty\": \"RSA\"}, {\"d\": \"S4_OufhLBgXFMgIDMI1zlVe2uCExpcEAQ80J_lXfS8I\", \"use\": \"sig\", \"crv\": \"P-256\", \"kty\": \"EC\", \"y\": \"DBdNyq30mXmUs_BIvKMqaTTNO7HDhCi0YiC8GciwNYk\", \"x\": \"cYwzBoyjRjxj334bRTqanONf7DUYK-6TgiuN0DixJAk\", \"kid\": \"a2\"}, {\"d\": \"33TnYgdJtWAiVosKqUnz0zSmvWTbsx5-6pceynW6Xck\", \"use\": \"enc\", \"crv\": \"P-256\", \"kty\": \"EC\", \"y\": \"Cula95Eix1Ia77St3OULe6-UKWs5I06nmdfUzhXUQTs\", \"x\": \"wk8HBVxNNzj1gJBxPmmx9XYW1L61ObBGzxpRa6_OqWU\", \"kid\": \"a3\"}]}, \"application_type\": \"web\", \"contacts\": [\"roland.hedberg@umu.se\"], \"post_logout_redirect_uris\": [\"https://op.certification.openid.net:60784/logout\"], \"redirect_uris\": [\"https://op.certification.openid.net:60784/authz_cb\"], \"response_types\": [\"code\"], \"require_auth_time\": true, \"grant_types\": [\"authorization_code\"], \"default_max_age\": 3600}";
OIDCClientRepresentation clientRep = JsonSerialization.readValue(stringRep, OIDCClientRepresentation.class);
dependencies/server-all/pom.xml 7(+6 -1)
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 809890a..8922532 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -83,7 +83,6 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-kerberos-federation</artifactId>
</dependency>
-
<!-- saml -->
<dependency>
<groupId>org.keycloak</groupId>
@@ -110,6 +109,12 @@
</exclusions>
</dependency>
+ <!-- Dependencies for RHEL IdM -->
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-sssd-federation</artifactId>
+ </dependency>
+
<!-- Built-in Authorization Policy Providers -->
<dependency>
<groupId>org.keycloak</groupId>
distribution/adapters/fuse-adapter-zip/pom.xml 173(+173 -0)
diff --git a/distribution/adapters/fuse-adapter-zip/pom.xml b/distribution/adapters/fuse-adapter-zip/pom.xml
new file mode 100644
index 0000000..c649720
--- /dev/null
+++ b/distribution/adapters/fuse-adapter-zip/pom.xml
@@ -0,0 +1,173 @@
+<!--
+ ~ 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>2.2.0-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>keycloak-fuse-adapter-dist</artifactId>
+ <packaging>pom</packaging>
+ <name>Keycloak Fuse Adapter Distro</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-osgi-features</artifactId>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <classifier>features</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-osgi-thirdparty</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.module</groupId>
+ <artifactId>jackson-module-jaxb-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-base</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.jaxrs</groupId>
+ <artifactId>jackson-jaxrs-json-provider</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-authz-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-osgi-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jetty-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jetty-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jetty81-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-osgi-jaas</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jetty92-adapter</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-dependencies</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/system</outputDirectory>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteSnapshots>false</overWriteSnapshots>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ <useRepositoryLayout>true</useRepositoryLayout>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>assemble</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+ <descriptor>assembly.xml</descriptor>
+ </descriptors>
+ <outputDirectory>target</outputDirectory>
+ <workDirectory>target/assembly/work</workDirectory>
+ <appendAssemblyId>false</appendAssemblyId>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
\ No newline at end of file
distribution/adapters/pom.xml 1(+1 -0)
diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml
index cd72e9a..23d5135 100755
--- a/distribution/adapters/pom.xml
+++ b/distribution/adapters/pom.xml
@@ -32,6 +32,7 @@
<modules>
<module>as7-eap6-adapter</module>
+ <module>fuse-adapter-zip</module>
<module>jetty81-adapter-zip</module>
<module>jetty91-adapter-zip</module>
<module>jetty92-adapter-zip</module>
diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl
index 855efc0..ca45244 100755
--- a/distribution/demo-dist/src/main/xslt/standalone.xsl
+++ b/distribution/demo-dist/src/main/xslt/standalone.xsl
@@ -42,7 +42,7 @@
<xsl:template match="//ds:datasources">
<xsl:copy>
<xsl:apply-templates select="node()[name(.)='datasource']"/>
- <datasource jndi-name="java:jboss/datasources/KeycloakDS" jta="false" pool-name="KeycloakDS" use-java-context="true">
+ <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" use-java-context="true">
<connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
<driver>h2</driver>
<security>
distribution/downloads/pom.xml 17(+11 -6)
diff --git a/distribution/downloads/pom.xml b/distribution/downloads/pom.xml
index e1356d6..e186545 100755
--- a/distribution/downloads/pom.xml
+++ b/distribution/downloads/pom.xml
@@ -112,12 +112,6 @@
<type>zip</type>
<destFileName>keycloak-examples-${project.version}.zip</destFileName>
</artifactItem>
- <artifactItem>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-src-dist</artifactId>
- <type>zip</type>
- <destFileName>keycloak-src-${project.version}.zip</destFileName>
- </artifactItem>
</artifactItems>
<outputDirectory>target/${project.version}</outputDirectory>
</configuration>
@@ -262,6 +256,17 @@
<artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>tar.gz</type>
</artifactItem>
+
+ <artifactItem>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-fuse-adapter-dist</artifactId>
+ <type>zip</type>
+ </artifactItem>
+ <artifactItem>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-fuse-adapter-dist</artifactId>
+ <type>tar.gz</type>
+ </artifactItem>
</artifactItems>
<outputDirectory>target/${project.version}/adapters/keycloak-oidc</outputDirectory>
</configuration>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/federation-sssd-setup.sh b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/federation-sssd-setup.sh
new file mode 100644
index 0000000..6a0eae2
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/federation-sssd-setup.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# Setup for SSSD
+SSSD_FILE="/etc/sssd/sssd.conf"
+
+if [ -f "$SSSD_FILE" ];
+then
+ sed -i '/ldap_tls_cacert/a ldap_user_extra_attrs = mail:mail, sn:sn, givenname:givenname, telephoneNumber:telephoneNumber' $SSSD_FILE
+ sed -i 's/nss, sudo, pam/nss, sudo, pam, ifp/' $SSSD_FILE
+ sed -i '/\[ifp\]/a allowed_uids = root\nuser_attributes = +mail, +telephoneNumber, +givenname, +sn' $SSSD_FILE
+ systemctl restart sssd
+else
+ echo "Please make sure you have $SSSD_FILE into your system! Aborting."
+ exit 1
+fi
+
+# Setup for PAM
+PAM_FILE="/etc/pam.d/keycloak"
+
+if [ ! -f "$PAM_FILE" ];
+then
+cat <<EOF > $PAM_FILE
+ auth required pam_sss.so
+ account required pam_sss.so
+EOF
+else
+ echo "$PAM_FILE already exists. Skipping it..."
+ exit 0
+fi
+
+
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
index 5373067..94c80a2 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-model-jpa/main/module.xml
@@ -25,6 +25,7 @@
</resources>
<dependencies>
+ <module name="javax.transaction.api"/>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-server-spi"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
index 95f34b6..8b5632e 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi/main/module.xml
@@ -33,5 +33,6 @@
<module name="javax.ws.rs.api"/>
<module name="org.apache.httpcomponents"/>
<module name="org.jboss.resteasy.resteasy-jaxrs"/>
+ <module name="javax.transaction.api"/>
</dependencies>
</module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
index de03ed8..8cf1cde 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
@@ -35,6 +35,7 @@
<module name="org.keycloak.keycloak-js-adapter" services="import"/>
<module name="org.keycloak.keycloak-kerberos-federation" services="import"/>
<module name="org.keycloak.keycloak-ldap-federation" services="import"/>
+ <module name="org.keycloak.keycloak-sssd-federation" services="import"/>
<module name="org.keycloak.keycloak-server-spi" services="import"/>
<module name="org.keycloak.keycloak-model-jpa" services="import"/>
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
distribution/pom.xml 1(+0 -1)
diff --git a/distribution/pom.xml b/distribution/pom.xml
index 74e0e40..746d5ae 100755
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -40,7 +40,6 @@
<module>proxy-dist</module>
<module>server-dist</module>
<module>server-overlay</module>
- <module>src-dist</module>
<module>feature-packs</module>
</modules>
diff --git a/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java b/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java
index 887a461..493637a 100644
--- a/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java
+++ b/examples/authz/hello-world/src/main/java/org/keycloak/authz/helloworld/AuthorizationClientExample.java
@@ -43,7 +43,7 @@ public class AuthorizationClientExample {
}
private static void introspectRequestingPartyToken() {
- // create a new instance based on the configuration define at keycloak-authz.json
+ // create a new instance based on the configuration defined in keycloak-authz.json
AuthzClient authzClient = AuthzClient.create();
// query the server for a resource with a given name
@@ -51,8 +51,9 @@ public class AuthorizationClientExample {
.resource()
.findByFilter("name=Default Resource");
- // obtian a Entitlement API Token in order to get access to the Entitlement API.
- // this token is just an access token issued to a client on behalf of an user with a scope kc_entitlement
+ // obtain an Entitlement API Token in order to get access to the Entitlement API.
+ // this token is just an access token issued to a client on behalf of an user
+ // with a scope = kc_entitlement
String eat = getEntitlementAPIToken(authzClient);
// create an entitlement request
@@ -63,7 +64,8 @@ public class AuthorizationClientExample {
request.addPermission(permission);
- // send the entitlement request to the server in order to obtain a RPT with all permissions granted to the user
+ // send the entitlement request to the server in order to
+ // obtain an RPT with all permissions granted to the user
EntitlementResponse response = authzClient.entitlement(eat).get("hello-world-authz-service", request);
String rpt = response.getRpt();
@@ -79,7 +81,7 @@ public class AuthorizationClientExample {
}
private static void createResource() {
- // create a new instance based on the configuration define at keycloak-authz.json
+ // create a new instance based on the configuration defined in keycloak-authz.json
AuthzClient authzClient = AuthzClient.create();
// create a new resource representation with the information we want
@@ -111,8 +113,9 @@ public class AuthorizationClientExample {
// create a new instance based on the configuration define at keycloak-authz.json
AuthzClient authzClient = AuthzClient.create();
- // obtian a Entitlement API Token in order to get access to the Entitlement API.
- // this token is just an access token issued to a client on behalf of an user with a scope kc_entitlement
+ // obtain an Entitlement API Token in order to get access to the Entitlement API.
+ // this token is just an access token issued to a client on behalf of an user
+ // with a scope = kc_entitlement
String eat = getEntitlementAPIToken(authzClient);
// create an entitlement request
@@ -123,7 +126,8 @@ public class AuthorizationClientExample {
request.addPermission(permission);
- // send the entitlement request to the server in order to obtain a RPT with all permissions granted to the user
+ // send the entitlement request to the server in order to obtain a RPT
+ // with all permissions granted to the user
EntitlementResponse response = authzClient.entitlement(eat).get("hello-world-authz-service", request);
String rpt = response.getRpt();
@@ -133,7 +137,7 @@ public class AuthorizationClientExample {
}
private static void obtainAllEntitlements() {
- // create a new instance based on the configuration define at keycloak-authz.json
+ // create a new instance based on the configuration defined in keycloak-authz.json
AuthzClient authzClient = AuthzClient.create();
// obtian a Entitlement API Token in order to get access to the Entitlement API.
diff --git a/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json
index a492837..b5a1bbf 100644
--- a/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/authz/hello-world-authz-service/src/main/webapp/WEB-INF/keycloak.json
@@ -1,6 +1,5 @@
{
"realm": "hello-world-authz",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "hello-world-authz-service",
diff --git a/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json b/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json
index c1dee24..affafdd 100644
--- a/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json
+++ b/examples/authz/photoz/photoz-html5-client/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
{
"realm": "photoz",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "photoz-html5-client",
diff --git a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
index 6849d07..9e06730 100644
--- a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/keycloak.json
@@ -1,6 +1,5 @@
{
"realm": "photoz",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required": "external",
"resource": "photoz-restful-api",
diff --git a/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json b/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
index eaffea8..f6b9c90 100644
--- a/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/authz/servlet-authz/src/main/webapp/WEB-INF/keycloak.json
@@ -1,6 +1,5 @@
{
"realm": "servlet-authz",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "servlet-authz-app",
diff --git a/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json b/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json
index 4502199..9da7ed4 100644
--- a/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/basic-auth/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "basic-auth",
"resource" : "basic-auth-service",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"enable-basic-auth" : "true",
diff --git a/examples/broker/facebook-authentication/src/main/webapp/keycloak.json b/examples/broker/facebook-authentication/src/main/webapp/keycloak.json
index 55446af..17743f6 100644
--- a/examples/broker/facebook-authentication/src/main/webapp/keycloak.json
+++ b/examples/broker/facebook-authentication/src/main/webapp/keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "facebook-identity-provider-realm",
"resource" : "facebook-authentication",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"public-client" : true
diff --git a/examples/broker/google-authentication/src/main/webapp/keycloak.json b/examples/broker/google-authentication/src/main/webapp/keycloak.json
index 93c25b4..f05da2f 100644
--- a/examples/broker/google-authentication/src/main/webapp/keycloak.json
+++ b/examples/broker/google-authentication/src/main/webapp/keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "google-identity-provider-realm",
"resource" : "google-authentication",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"public-client" : true
diff --git a/examples/broker/twitter-authentication/src/main/webapp/keycloak.json b/examples/broker/twitter-authentication/src/main/webapp/keycloak.json
index 7243636..2f74514 100644
--- a/examples/broker/twitter-authentication/src/main/webapp/keycloak.json
+++ b/examples/broker/twitter-authentication/src/main/webapp/keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "twitter-identity-provider-realm",
"resource" : "twitter-authentication",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"public-client" : true
diff --git a/examples/cors/angular-product-app/src/main/webapp/keycloak.json b/examples/cors/angular-product-app/src/main/webapp/keycloak.json
index 40b35a1..d685459 100755
--- a/examples/cors/angular-product-app/src/main/webapp/keycloak.json
+++ b/examples/cors/angular-product-app/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
{
"realm" : "cors",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost-auth:8080/auth",
"ssl-required" : "external",
"resource" : "angular-cors-product",
diff --git a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
index 265049f..61da408 100755
--- a/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/cors/database-service/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "cors",
"resource" : "cors-database-service",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost-auth:8080/auth",
"bearer-only" : true,
"ssl-required": "external",
diff --git a/examples/demo-template/angular2-product-app/pom.xml b/examples/demo-template/angular2-product-app/pom.xml
index 7eecc42..d461196 100644
--- a/examples/demo-template/angular2-product-app/pom.xml
+++ b/examples/demo-template/angular2-product-app/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-examples-demo-parent</artifactId>
<groupId>org.keycloak</groupId>
- <version></version>
+ <version>2.2.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/.gitignore b/examples/demo-template/angular2-product-app/src/main/webapp/.gitignore
new file mode 100644
index 0000000..1c790fa
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/.gitignore
@@ -0,0 +1,4 @@
+app/*.js
+app/*.js.map
+node_modules
+typings
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts
new file mode 100644
index 0000000..f345fa3
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts
@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { HttpModule } from '@angular/http';
+import { KeycloakService } from './keycloak.service';
+import { AppComponent } from './app.component';
+
+@NgModule({
+ imports: [
+ BrowserModule,
+ HttpModule
+ ],
+ declarations: [
+ AppComponent
+ ],
+ providers: [
+ KeycloakService,
+ ],
+ bootstrap: [ AppComponent ]
+})
+export class AppModule {}
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts
index 73613b2..6bf99bf 100644
--- a/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts
@@ -1,14 +1,11 @@
-import 'rxjs/Rx';
-import {bootstrap} from 'angular2/platform/browser';
-import {HTTP_BINDINGS} from 'angular2/http';
-import {KeycloakService} from './keycloak';
-import {AppComponent} from './app';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { AppModule } from './app.module';
-KeycloakService.init().then(
- o=>{
- bootstrap(AppComponent,[HTTP_BINDINGS, KeycloakService]);
- },
- x=>{
- window.location.reload();
- }
-);
\ No newline at end of file
+import {KeycloakService} from './keycloak.service';
+
+KeycloakService.init()
+ .then(() => {
+ const platform = platformBrowserDynamic();
+ platform.bootstrapModule(AppModule);
+ })
+ .catch(() => window.location.reload());
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/index.html b/examples/demo-template/angular2-product-app/src/main/webapp/index.html
index 2da600c..1edeb56 100644
--- a/examples/demo-template/angular2-product-app/src/main/webapp/index.html
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/index.html
@@ -2,48 +2,23 @@
<html>
<head>
<title>Angular 2 QuickStart</title>
+ <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
-
-
-
-
-
-
- </head>
-
- <!-- 3. Display the application -->
- <body>
- <my-app>Loading...</my-app>
-
-
-
<!-- 1. Load libraries -->
- <!-- IE required polyfills, in this exact order -->
- <script src="node_modules/es6-shim/es6-shim.min.js"></script>
- <script src="node_modules/systemjs/dist/system-polyfills.js"></script>
-
- <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
+ <!-- Polyfill(s) for older browsers -->
+ <script src="node_modules/core-js/client/shim.min.js"></script>
+ <script src="node_modules/zone.js/dist/zone.js"></script>
+ <script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
- <script src="node_modules/rxjs/bundles/Rx.js"></script>
- <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
- <script src="node_modules/angular2/bundles/http.js"></script>
-
-
<script src="/auth/js/keycloak.js"></script>
-
<!-- 2. Configure SystemJS -->
+ <script src="systemjs.config.js"></script>
<script>
- System.config({
- packages: {
- app: {
- format: 'register',
- defaultExtension: 'js'
- }
- }
- });
- System.import('app/main')
- .then(null, console.error.bind(console));
+ System.import('app').catch(function(err){ console.error(err); });
</script>
+ </head>
+ <!-- 3. Display the application -->
+ <body>
+ <my-app>Loading...</my-app>
</body>
-
</html>
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json b/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json
index ddd1f2e..87a2ad6 100644
--- a/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
{
"realm": "demo",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required": "external",
"resource": "angular2-product",
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/package.json b/examples/demo-template/angular2-product-app/src/main/webapp/package.json
index f66b44c..5bd783b 100644
--- a/examples/demo-template/angular2-product-app/src/main/webapp/package.json
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/package.json
@@ -2,24 +2,36 @@
"name": "angular2-product-app",
"version": "1.0.0",
"scripts": {
+ "start": "tsc && concurrently \"npm run tsc:w\" \"npm run lite\" ",
+ "lite": "lite-server",
+ "postinstall": "typings install",
"tsc": "tsc",
"tsc:w": "tsc -w",
- "lite": "lite-server",
- "start": "concurrent \"npm run tsc:w\" \"npm run lite\" "
+ "typings": "typings"
},
"license": "ISC",
"dependencies": {
- "angular2": "2.0.0-beta.3",
- "systemjs": "0.19.6",
- "es6-promise": "^3.0.2",
- "es6-shim": "^0.33.3",
- "reflect-metadata": "0.1.2",
- "rxjs": "5.0.0-beta.0",
- "zone.js": "0.5.11"
+ "@angular/common": "2.0.0",
+ "@angular/compiler": "2.0.0",
+ "@angular/core": "2.0.0",
+ "@angular/forms": "2.0.0",
+ "@angular/http": "2.0.0",
+ "@angular/platform-browser": "2.0.0",
+ "@angular/platform-browser-dynamic": "2.0.0",
+ "@angular/router": "3.0.0",
+ "@angular/upgrade": "2.0.0",
+ "angular2-in-memory-web-api": "0.0.20",
+ "bootstrap": "^3.3.6",
+ "core-js": "^2.4.1",
+ "reflect-metadata": "^0.1.3",
+ "rxjs": "5.0.0-beta.12",
+ "systemjs": "0.19.27",
+ "zone.js": "^0.6.21"
},
"devDependencies": {
- "concurrently": "^1.0.0",
- "lite-server": "^2.0.1",
- "typescript": "^1.7.5"
+ "concurrently": "^2.2.0",
+ "lite-server": "^2.2.2",
+ "typescript": "^2.0.2",
+ "typings": "^1.3.2"
}
}
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/systemjs.config.js b/examples/demo-template/angular2-product-app/src/main/webapp/systemjs.config.js
new file mode 100644
index 0000000..de199e6
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/systemjs.config.js
@@ -0,0 +1,43 @@
+/**
+ * System configuration for Angular 2 samples
+ * Adjust as necessary for your application needs.
+ */
+(function (global) {
+ System.config({
+ paths: {
+ // paths serve as alias
+ 'npm:': 'node_modules/'
+ },
+ // map tells the System loader where to look for things
+ map: {
+ // our app is within the app folder
+ app: 'app',
+ // angular bundles
+ '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
+ '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
+ '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
+ '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
+ '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
+ '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
+ '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
+ '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
+ // other libraries
+ 'rxjs': 'npm:rxjs',
+ 'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
+ },
+ // packages tells the System loader how to load when no filename and/or no extension
+ packages: {
+ app: {
+ main: './main.js',
+ defaultExtension: 'js'
+ },
+ rxjs: {
+ defaultExtension: 'js'
+ },
+ 'angular2-in-memory-web-api': {
+ main: './index.js',
+ defaultExtension: 'js'
+ }
+ }
+ });
+})(this);
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json b/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json
index 52c77a5..e6a6eac 100644
--- a/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json
@@ -1,15 +1,12 @@
{
"compilerOptions": {
"target": "es5",
- "module": "system",
+ "module": "commonjs",
"moduleResolution": "node",
- "sourceMap": false,
+ "sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false
- },
- "exclude": [
- "node_modules"
- ]
-}
\ No newline at end of file
+ }
+}
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/typings.json b/examples/demo-template/angular2-product-app/src/main/webapp/typings.json
new file mode 100644
index 0000000..7da31ca
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/typings.json
@@ -0,0 +1,7 @@
+{
+ "globalDependencies": {
+ "core-js": "registry:dt/core-js#0.0.0+20160725163759",
+ "jasmine": "registry:dt/jasmine#2.2.0+20160621224255",
+ "node": "registry:dt/node#6.0.0+20160909174046"
+ }
+}
diff --git a/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json b/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json
index 72ecb5b..174053e 100755
--- a/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json
+++ b/examples/demo-template/angular-product-app/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
{
"realm" : "demo",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "/auth",
"ssl-required" : "external",
"resource" : "angular-product",
diff --git a/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json
index c2241b3..2061909 100755
--- a/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/customer-app/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "customer-portal",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"expose-token": true,
diff --git a/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json b/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json
index 51c8775..1c61433 100644
--- a/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json
+++ b/examples/demo-template/customer-app-cli/src/main/resources/META-INF/keycloak.json
@@ -1,6 +1,5 @@
{
"realm" : "demo",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "customer-portal-cli",
diff --git a/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json
index 14e56f5..3766330 100755
--- a/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/customer-app-filter/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "customer-portal-filter",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"expose-token": true,
diff --git a/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json b/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json
index 224c70b..4a60ffa 100644
--- a/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json
+++ b/examples/demo-template/customer-app-js/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
{
"realm" : "demo",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "/auth",
"ssl-required" : "external",
"resource" : "customer-portal-js",
diff --git a/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
index cb93854..72e4903 100755
--- a/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/database-service/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "demo",
"resource" : "database-service",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"bearer-only" : true,
"ssl-required" : "external"
diff --git a/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json
index dff976c..600dcfa 100644
--- a/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/offline-access-app/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "offline-access-portal",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"credentials": {
examples/demo-template/pom.xml 1(+1 -0)
diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml
index b7fc027..dbc0218 100755
--- a/examples/demo-template/pom.xml
+++ b/examples/demo-template/pom.xml
@@ -51,6 +51,7 @@
<module>example-ear</module>
<module>admin-access-app</module>
<module>angular-product-app</module>
+ <module>angular2-product-app</module>
<module>database-service</module>
<module>third-party</module>
<module>third-party-cdi</module>
diff --git a/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
index 0a86c04..1092701 100755
--- a/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/product-app/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "demo",
"resource" : "product-portal",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "/auth",
"ssl-required" : "external",
"credentials" : {
diff --git a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
index e4d6a40..72ffd49 100644
--- a/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
+++ b/examples/demo-template/service-account/src/main/java/org/keycloak/example/ProductServiceAccountServlet.java
@@ -41,6 +41,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants;
import org.keycloak.RSATokenVerifier;
+import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
@@ -163,7 +164,7 @@ public abstract class ProductServiceAccountServlet extends HttpServlet {
private void setTokens(HttpServletRequest req, KeycloakDeployment deployment, AccessTokenResponse tokenResponse) throws IOException, VerificationException {
String token = tokenResponse.getToken();
String refreshToken = tokenResponse.getRefreshToken();
- AccessToken tokenParsed = RSATokenVerifier.verifyToken(token, deployment.getRealmKey(), deployment.getRealmInfoUrl());
+ AccessToken tokenParsed = AdapterRSATokenVerifier.verifyToken(token, deployment);
req.getSession().setAttribute(TOKEN, token);
req.getSession().setAttribute(REFRESH_TOKEN, refreshToken);
req.getSession().setAttribute(TOKEN_PARSED, tokenParsed);
diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json
index 7eec22a..1a9322d 100644
--- a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json
+++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-secret.json
@@ -1,6 +1,5 @@
{
"realm" : "demo",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "product-sa-client",
diff --git a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json
index 3e90c34..3c99da7 100644
--- a/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json
+++ b/examples/demo-template/service-account/src/main/webapp/WEB-INF/keycloak-client-signed-jwt.json
@@ -1,6 +1,5 @@
{
"realm" : "demo",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"resource" : "product-sa-client-jwt-auth",
diff --git a/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index a4796cc..56550d6 100644
--- a/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -26,7 +26,6 @@
<bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
<property name="realm" value="demo"/>
<property name="resource" value="admin-camel-endpoint"/>
- <property name="realmKey" value="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"/>
<property name="bearerOnly" value="true"/>
<property name="authServerUrl" value="http://localhost:8080/auth" />
<property name="sslRequired" value="EXTERNAL"/>
diff --git a/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json b/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json
index b5d6b30..c7f61f6 100755
--- a/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/fuse/customer-app-fuse/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "customer-portal",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials": {
diff --git a/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json b/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json
index f5d7e1a..a8c4b75 100644
--- a/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json
+++ b/examples/fuse/cxf-jaxrs/src/main/resources/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "builtin-cxf-app",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials": {
diff --git a/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml b/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml
index 2d15ae0..8a808c6 100644
--- a/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml
+++ b/examples/fuse/cxf-jaxws/src/main/resources/META-INF/spring/beans.xml
@@ -32,7 +32,6 @@
<bean id="kcAdapterConfig" class="org.keycloak.representations.adapters.config.AdapterConfig">
<property name="realm" value="demo"/>
<property name="resource" value="custom-cxf-endpoint"/>
- <property name="realmKey" value="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"/>
<property name="bearerOnly" value="true"/>
<property name="authServerUrl" value="http://localhost:8080/auth" />
<property name="sslRequired" value="EXTERNAL"/>
diff --git a/examples/fuse/external-config/external-config-keycloak.json b/examples/fuse/external-config/external-config-keycloak.json
index 920e99a..469da82 100644
--- a/examples/fuse/external-config/external-config-keycloak.json
+++ b/examples/fuse/external-config/external-config-keycloak.json
@@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "external-config",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials": {
diff --git a/examples/fuse/fuse-admin/keycloak-direct-access.json b/examples/fuse/fuse-admin/keycloak-direct-access.json
index 8e5ac0f..2441134 100644
--- a/examples/fuse/fuse-admin/keycloak-direct-access.json
+++ b/examples/fuse/fuse-admin/keycloak-direct-access.json
@@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "ssh-jmx-admin-client",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"ssl-required" : "external",
"auth-server-url" : "http://localhost:8080/auth",
"credentials": {
diff --git a/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json b/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json
index 2a52d24..e90433a 100644
--- a/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json
+++ b/examples/fuse/product-app-fuse/src/main/resources/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm": "demo",
"resource": "product-portal",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials": {
diff --git a/examples/js-console/src/main/webapp/keycloak.json b/examples/js-console/src/main/webapp/keycloak.json
index c0c04d5..cc4bab3 100644
--- a/examples/js-console/src/main/webapp/keycloak.json
+++ b/examples/js-console/src/main/webapp/keycloak.json
@@ -1,6 +1,5 @@
{
"realm" : "example",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "/auth",
"ssl-required" : "external",
"resource" : "js-console",
diff --git a/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json b/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json
index db1223c..7e9d91a 100644
--- a/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/kerberos/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "kerberos-demo",
"resource" : "kerberos-app",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"credentials": {
diff --git a/examples/ldap/src/main/webapp/WEB-INF/keycloak.json b/examples/ldap/src/main/webapp/WEB-INF/keycloak.json
index 84e1129..f43107b 100644
--- a/examples/ldap/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/ldap/src/main/webapp/WEB-INF/keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "ldap-demo",
"resource" : "ldap-app",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "/auth",
"ssl-required" : "external",
"credentials": {
diff --git a/examples/multi-tenant/src/main/resources/tenant1-keycloak.json b/examples/multi-tenant/src/main/resources/tenant1-keycloak.json
index 57be277..34c1773 100644
--- a/examples/multi-tenant/src/main/resources/tenant1-keycloak.json
+++ b/examples/multi-tenant/src/main/resources/tenant1-keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "tenant1",
"resource" : "multi-tenant",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials" : {
diff --git a/examples/multi-tenant/src/main/resources/tenant2-keycloak.json b/examples/multi-tenant/src/main/resources/tenant2-keycloak.json
index 4f221dc..5877082 100644
--- a/examples/multi-tenant/src/main/resources/tenant2-keycloak.json
+++ b/examples/multi-tenant/src/main/resources/tenant2-keycloak.json
@@ -1,7 +1,6 @@
{
"realm" : "tenant2",
"resource" : "multi-tenant",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA0oJjgPQJhnVhOo51KauQGfLLreMFu64OJdKXRnfvAQJQTuKNwc5JrR63l/byyW1B6FgclABF818TtLvMCAkn4EuFwQZCZhg3x3+lFGiB/IzC6UAt4Bi0JQrTbdh83/U97GIPegvaDqiqEiQESEkbCZWxM6sh/34hQaAhCaFpMwIDAQAB",
"auth-server-url" : "http://localhost:8080/auth",
"ssl-required" : "external",
"credentials" : {
diff --git a/examples/providers/authenticator/README.md b/examples/providers/authenticator/README.md
index 1edf0ed..54dc752 100755
--- a/examples/providers/authenticator/README.md
+++ b/examples/providers/authenticator/README.md
@@ -6,13 +6,12 @@ of Keycloak. To deploy, build this directory then take the jar and copy it to
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.secret-question --resources=target/authenticator-required-action-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-services,org.jboss.resteasy.resteasy-jaxrs,javax.ws.rs.api"
-Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
-
- "providers": [
- ....
- "module:org.keycloak.examples.secret-question"
- ],
+Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
+ <providers>
+ ...
+ <provider>module:org.keycloak.examples.secret-question</provider>
+ </providers>
You then have to copy the secret-question.ftl and secret-question-config.ftl files to the themes/base/login directory.
diff --git a/examples/providers/domain-extension/README.md b/examples/providers/domain-extension/README.md
index e1aa2cd..51e2b3c 100644
--- a/examples/providers/domain-extension/README.md
+++ b/examples/providers/domain-extension/README.md
@@ -6,12 +6,12 @@ To run, deploy as a module by running:
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.domain-extension-example --resources=target/domain-extension-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,javax.ws.rs.api,javax.persistence.api,org.hibernate,org.javassist"
-Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
+Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
- "providers": [
- ....
- "module:org.keycloak.examples.domain-extension-example"
- ],
+ <providers>
+ ...
+ <provider>module:org.keycloak.examples.domain-extension-example</provider>
+ </providers>
Then start (or restart) the server.
diff --git a/examples/providers/event-listener-sysout/README.md b/examples/providers/event-listener-sysout/README.md
index 57519f3..8e1a085 100644
--- a/examples/providers/event-listener-sysout/README.md
+++ b/examples/providers/event-listener-sysout/README.md
@@ -5,23 +5,25 @@ To deploy copy target/event-listener-sysout-example.jar to providers directory.
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
-Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
+Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
- "providers": [
- ....
- "module:org.keycloak.examples.event-sysout"
- ],
+ <providers>
+ ...
+ <provider>module:org.keycloak.examples.event-sysout</provider>
+ </providers>
Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events,
followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and
login again to see events printed to System.out.
The example event listener can be configured to exclude certain events, for example to exclude REFRESH_TOKEN and
-CODE_TO_TOKEN events add the following to keycloak-server.json:
+CODE_TO_TOKEN events add the following to `standalone.xml`:
...
- "eventsListener": {
- "sysout": {
- "exclude": [ "REFRESH_TOKEN", "CODE_TO_TOKEN" ]
- }
- }
+ <spi name="eventsListener">
+ <provider name="sysout">
+ <properties>
+ <property name="exclude-events" value="["REFRESH_TOKEN", "CODE_TO_TOKEN"]"/>
+ </properties>
+ </provider
+ </spi>
examples/providers/event-store-mem/README.md 24(+12 -12)
diff --git a/examples/providers/event-store-mem/README.md b/examples/providers/event-store-mem/README.md
index d533fda..682ff42 100644
--- a/examples/providers/event-store-mem/README.md
+++ b/examples/providers/event-store-mem/README.md
@@ -5,24 +5,24 @@ To deploy copy target/event-store-mem-example.jar to providers directory. Altern
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-inmem --resources=target/event-store-mem-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
-Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
+Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
- "providers": [
- ....
- "module:org.keycloak.examples.event-inmem"
- ],
+ <providers>
+ ...
+ <provider>module:org.keycloak.examples.event-inmem</provider>
+ </providers>
-Then edit standalone/configuration/keycloak-server.json, change:
+Then edit `standalone/configuration/standalone.xml`, change:
- "eventsStore": {
- "provider": "jpa"
- }
+ <spi name="eventsStore">
+ <default-provider>jpa</default-provider>
+ </spi>
to:
- "eventsStore": {
- "provider": "in-mem"
- }
+ <spi name="eventsStore">
+ <default-provider>in-mem</default-provider>
+ </spi>
Then start (or restart)the server. Once started open the admin console, select your realm, then click on Events,
followed by config. Set the toggle for Enabled to ON. After this try to logout and login again then open the Events tab
diff --git a/examples/providers/federation-provider/README.md b/examples/providers/federation-provider/README.md
index c8b4430..c90e791 100755
--- a/examples/providers/federation-provider/README.md
+++ b/examples/providers/federation-provider/README.md
@@ -6,13 +6,12 @@ key pairs. To deploy, build this directory then take the jar and copy it to pro
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.userprops --resources=target/federation-properties-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi"
-Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
-
- "providers": [
- ....
- "module:org.keycloak.examples.userprops"
- ],
+Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
+ <providers>
+ ...
+ <provider>module:org.keycloak.examples.userprops</provider>
+ </providers>
You will then have to restart the authentication server.
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
index 2d5abc3..e9ec451 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
@@ -96,7 +96,7 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
}
/**
- * You can import additional plugin configuration from keycloak-server.json here.
+ * You can import additional plugin configuration from standalone.xml here.
*
* @param config
*/
examples/providers/rest/README.md 10(+5 -5)
diff --git a/examples/providers/rest/README.md b/examples/providers/rest/README.md
index 5124f88..5ee9327 100644
--- a/examples/providers/rest/README.md
+++ b/examples/providers/rest/README.md
@@ -5,12 +5,12 @@ To deploy copy target/hello-rest-example.jar to providers directory. Alternative
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.hello-rest-example --resources=target/hello-rest-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,javax.ws.rs.api"
-Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
+Then registering the provider by editing `standalone/configuration/standalone.xml` and adding the module to the providers element:
- "providers": [
- ....
- "module:org.keycloak.examples.hello-rest-example"
- ],
+ <providers>
+ ...
+ <provider>module:org.keycloak.examples.hello-rest-example</provider>
+ </providers>
Then start (or restart) the server. Once started open http://localhost:8080/realms/master/hello and you should see the message _Hello master_.
You can also invoke the endpoint for other realms by replacing `master` with the realm name in the above url.
\ No newline at end of file
examples/themes/README.md 22(+11 -11)
diff --git a/examples/themes/README.md b/examples/themes/README.md
index ea160bc..5089ca1 100644
--- a/examples/themes/README.md
+++ b/examples/themes/README.md
@@ -17,14 +17,14 @@ Alternatively you can deploy as modules. This can be done by first running:
mvn clean install
$KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.example.themes --resources=target/keycloak-example-themes.jar"
-Then open $KEYCLOAK_HOME/standalone/configuration/keycloak-server.json and register the theme module by adding:
-
- "theme": {
- "module": {
- "modules": [ "org.keycloak.example.themes" ]
- }
- }
+Then open `standalone/configuration/standalone.xml` and register the theme module by adding:
+ <theme>
+ ...
+ <modules>
+ <module>org.keycloak.example.themes</module>
+ </modules>
+ </theme>
Address Theme
-------------------
@@ -45,11 +45,11 @@ Change Logo Theme
To enable the theme open the admin console, select your realm, click on `Theme`. In the dropdowns for `Login Theme`, `Account Theme` and `Admin Console Theme` select `logo-example`. Click `Save` and login to the realm to see the new theme in action.
-To change the theme for the welcome pages open `standalone/configuration/keycloak-server.json` find the config for `theme` and add 'welcomeTheme':
+To change the theme for the welcome pages open `standalone/configuration/standalone.xml` find the config for `theme` and add 'welcomeTheme':
- "theme": {
+ <theme>
...
- "welcomeTheme": "logo-example"
- },
+ <welcomeTheme>logo-example</welcomeTheme>
+ </theme>
One thing to note is that to change the admin console for the master admin console (`/auth/admin`) you need to change the theme for the master realm. Changing the admin console theme for any other realms will only change the admin console for that specific realm (for example `/auth/admin/myrealm/console`).
federation/ldap/pom.xml 5(+5 -0)
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index f6a8c4b..257c617 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -74,6 +74,11 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.transaction</groupId>
+ <artifactId>jboss-transaction-api_1.2_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
<build>
federation/pom.xml 1(+1 -0)
diff --git a/federation/pom.xml b/federation/pom.xml
index 663f857..dc702cc 100755
--- a/federation/pom.xml
+++ b/federation/pom.xml
@@ -35,6 +35,7 @@
<modules>
<module>ldap</module>
<module>kerberos</module>
+ <module>sssd</module>
</modules>
</project>
federation/sssd/pom.xml 70(+70 -0)
diff --git a/federation/sssd/pom.xml b/federation/sssd/pom.xml
new file mode 100644
index 0000000..d3e3afb
--- /dev/null
+++ b/federation/sssd/pom.xml
@@ -0,0 +1,70 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>2.2.0-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-sssd-federation</artifactId>
+ <name>Keycloak SSSD Federation</name>
+ <description/>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>2.3</version>
+ <executions>
+ <!-- Run shade goal on package phase -->
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.java.dev.jna</groupId>
+ <artifactId>jna</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-server-spi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/federation/sssd/src/main/java/cx/ath/matthew/debug/Debug.java b/federation/sssd/src/main/java/cx/ath/matthew/debug/Debug.java
new file mode 100644
index 0000000..30c4d85
--- /dev/null
+++ b/federation/sssd/src/main/java/cx/ath/matthew/debug/Debug.java
@@ -0,0 +1,671 @@
+/* Copyright (C) 1991-2015 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+/* This header is separate from features.h so that the compiler can
+ include it implicitly at the start of every compilation. It must
+ not itself include <features.h> or any other header that includes
+ <features.h> because the implicit include comes before any feature
+ test macros that may be defined in a source file before it first
+ explicitly includes a system header. GCC knows the name of this
+ header in order to preinclude it. */
+/* glibc's intent is to support the IEC 559 math functionality, real
+ and complex. If the GCC (4.9 and later) predefined macros
+ specifying compiler intent are available, use them to determine
+ whether the overall intent is to support these features; otherwise,
+ presume an older compiler has intent to support these features and
+ define these macros by default. */
+/* wchar_t uses Unicode 7.0.0. Version 7.0 of the Unicode Standard is
+ synchronized with ISO/IEC 10646:2012, plus Amendments 1 (published
+ on April, 2013) and 2 (not yet published as of February, 2015).
+ Additionally, it includes the accelerated publication of U+20BD
+ RUBLE SIGN. Therefore Unicode 7.0.0 is between 10646:2012 and
+ 10646:2014, and so we use the date ISO/IEC 10646:2012 Amd.1 was
+ published. */
+/* We do not support C11 <threads.h>. */
+/*
+ * Java Debug Library
+ *
+ * Copyright (c) Matthew Johnson 2005
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * To Contact the author, please email src@matthew.ath.cx
+ *
+ */
+package cx.ath.matthew.debug;
+
+import cx.ath.matthew.utils.Hexdump;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Add debugging to your program, has support for large projects with multiple
+ * classes and debug levels per class. Supports optional enabling of debug
+ * per-level per-class and debug targets of files, Streams or stderr.
+ * Also supports timing between debug outputs, printing of stack traces for Throwables
+ * and files/line numbers on each message.
+ * <p>
+ * Debug now automatically figures out which class it was called from, so all
+ * methods passing in the calling class are deprecated.
+ * </p>
+ * <p>
+ * The defaults are to print all messages to stderr with class and method name.
+ * </p>
+ * <p>
+ * Should be called like this:
+ * </p>
+ * <pre>
+ * if (Debug.debug) Debug.print(Debug.INFO, "Debug Message");
+ * </pre>
+ */
+public class Debug {
+ /**
+ * This interface can be used to provide custom printing filters
+ * for certain classes.
+ */
+ public static interface FilterCommand {
+ /**
+ * Called to print debug messages with a custom filter.
+ *
+ * @param output The PrintStream to output to.
+ * @param level The debug level of this message.
+ * @param location The textual location of the message.
+ * @param extra Extra information such as timing details.
+ * @param message The debug message.
+ * @param lines Other lines of a multiple-line debug message.
+ */
+ public void filter(PrintStream output, int level, String location, String extra, String message, String[] lines);
+ }
+
+ /**
+ * Highest priority messages
+ */
+ public static final int CRIT = 1;
+ /**
+ * Error messages
+ */
+ public static final int ERR = 2;
+ /**
+ * Warnings
+ */
+ public static final int WARN = 3;
+ /**
+ * Information
+ */
+ public static final int INFO = 4;
+ /**
+ * Debug messages
+ */
+ public static final int DEBUG = 5;
+ /**
+ * Verbose debug messages
+ */
+ public static final int VERBOSE = 6;
+ /**
+ * Set this to false to disable compilation of Debug statements
+ */
+ public static final boolean debug = false;
+ /**
+ * The current output stream (defaults to System.err)
+ */
+ public static PrintStream debugout = System.err;
+ private static Properties prop = null;
+ private static boolean timing = false;
+ private static boolean ttrace = false;
+ private static boolean lines = false;
+ private static boolean hexdump = false;
+ private static long last = 0;
+ private static int balen = 36;
+ private static int bawidth = 80;
+ private static Class saveclass = null;
+ //TODO: 1.5 private static Map<Class<? extends Object>, FilterCommand> filterMap = new HashMap<Class<? extends Object>, FilterCommand>();
+ private static Map filterMap = new HashMap();
+
+ /**
+ * Set properties to configure debugging.
+ * Format of properties is class => level, e.g.
+ * <pre>
+ * cx.ath.matthew.io.TeeOutputStream = INFO
+ * cx.ath.matthew.io.DOMPrinter = DEBUG
+ * </pre>
+ * The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which
+ * correspond to all messages up to that level. The special words YES, ALL and TRUE
+ * cause all messages to be printed regardless of level. All other terms disable
+ * messages for that class. CRIT and ERR messages are always printed if debugging is enabled
+ * unless explicitly disabled.
+ * The special class name ALL can be used to set the default level for all classes.
+ *
+ * @param prop Properties object to use.
+ */
+ public static void setProperties(Properties prop) {
+ Debug.prop = prop;
+ }
+
+ /**
+ * Read which class to debug on at which level from the given File.
+ * Syntax the same as Java Properties files:
+ * <pre>
+ * <class> = <debuglevel>
+ * </pre>
+ * E.G.
+ * <pre>
+ * cx.ath.matthew.io.TeeOutputStream = INFO
+ * cx.ath.matthew.io.DOMPrinter = DEBUG
+ * </pre>
+ * The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which
+ * correspond to all messages up to that level. The special words YES, ALL and TRUE
+ * cause all messages to be printed regardless of level. All other terms disable
+ * messages for that class. CRIT and ERR messages are always printed if debugging is enabled
+ * unless explicitly disabled.
+ * The special class name ALL can be used to set the default level for all classes.
+ *
+ * @param f File to read from.
+ */
+ public static void loadConfig(File f) throws IOException {
+ prop = new Properties();
+ prop.load(new FileInputStream(f));
+ }
+
+ /**
+ * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in.
+ */
+ //TODO: 1.5 @Deprecated()
+ public static boolean debugging(Class c, int loglevel) {
+ if (debug) {
+ if (null == c) return true;
+ return debugging(c.getName(), loglevel);
+ }
+ return false;
+ }
+
+ public static boolean debugging(String s, int loglevel) {
+ if (debug) {
+ try {
+ if (null == s) return true;
+ if (null == prop) return loglevel <= DEBUG;
+ String d = prop.getProperty(s);
+ if (null == d || "".equals(d)) d = prop.getProperty("ALL");
+ if (null == d) return loglevel <= ERR;
+ if ("".equals(d)) return loglevel <= ERR;
+ d = d.toLowerCase();
+ if ("true".equals(d)) return true;
+ if ("yes".equals(d)) return true;
+ if ("all".equals(d)) return true;
+ if ("verbose".equals(d)) return loglevel <= VERBOSE;
+ if ("debug".equals(d)) return loglevel <= DEBUG;
+ if ("info".equals(d)) return loglevel <= INFO;
+ if ("warn".equals(d)) return loglevel <= WARN;
+ if ("err".equals(d)) return loglevel <= ERR;
+ if ("crit".equals(d)) return loglevel <= CRIT;
+ int i = Integer.parseInt(d);
+ return i >= loglevel;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Output to the given Stream
+ */
+ public static void setOutput(PrintStream p) throws IOException {
+ debugout = p;
+ }
+
+ /**
+ * Output to the given file
+ */
+ public static void setOutput(String filename) throws IOException {
+ debugout = new PrintStream(new FileOutputStream(filename, true));
+ }
+
+ /**
+ * Output to the default debug.log
+ */
+ public static void setOutput() throws IOException {
+ setOutput("./debug.log");
+ }
+
+ /**
+ * Log at DEBUG
+ *
+ * @param d The object to log
+ */
+ public static void print(Object d) {
+ if (debug) {
+ if (d instanceof String)
+ print(DEBUG, (String) d);
+ else if (d instanceof Throwable)
+ print(DEBUG, (Throwable) d);
+ else if (d instanceof byte[])
+ print(DEBUG, (byte[]) d);
+ else if (d instanceof Map)
+ printMap(DEBUG, (Map) d);
+ else print(DEBUG, d);
+ }
+ }
+
+ /**
+ * Log at DEBUG
+ *
+ * @param o The object doing the logging
+ * @param d The object to log
+ * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in.
+ */
+ //TODO: 1.5 @Deprecated()
+ public static void print(Object o, Object d) {
+ if (debug) {
+ if (o instanceof Class)
+ saveclass = (Class) o;
+ else
+ saveclass = o.getClass();
+ print(d);
+ }
+ }
+
+ /**
+ * Log an Object
+ *
+ * @param o The object doing the logging
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param d The object to log with d.toString()
+ * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in.
+ */
+ //TODO: 1.5 @Deprecated()
+ public static void print(Object o, int loglevel, Object d) {
+ if (debug) {
+ if (o instanceof Class)
+ saveclass = (Class) o;
+ else
+ saveclass = o.getClass();
+ print(loglevel, d);
+ }
+ }
+
+ /**
+ * Log a String
+ *
+ * @param o The object doing the logging
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param s The log message
+ * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in.
+ */
+ //TODO: 1.5 @Deprecated()
+ public static void print(Object o, int loglevel, String s) {
+ if (debug) {
+ if (o instanceof Class)
+ saveclass = (Class) o;
+ else
+ saveclass = o.getClass();
+ print(loglevel, s);
+ }
+ }
+
+ /**
+ * Log a Throwable
+ *
+ * @param o The object doing the logging
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param t The throwable to log with .toString and .printStackTrace
+ * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in.
+ */
+ //TODO: 1.5 @Deprecated()
+ public static void print(Object o, int loglevel, Throwable t) {
+ if (debug) {
+ if (o instanceof Class)
+ saveclass = (Class) o;
+ else
+ saveclass = o.getClass();
+ print(loglevel, t);
+ }
+ }
+
+ /**
+ * Log a Throwable
+ *
+ * @param c The class doing the logging
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param t The throwable to log with .toString and .printStackTrace
+ * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in.
+ */
+ //TODO: 1.5 @Deprecated()
+ public static void print(Class c, int loglevel, Throwable t) {
+ if (debug) {
+ saveclass = c;
+ print(loglevel, t);
+ }
+ }
+
+ /**
+ * Log a Throwable
+ *
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param t The throwable to log with .toString and .printStackTrace
+ * @see #setThrowableTraces to turn on stack traces.
+ */
+ public static void print(int loglevel, Throwable t) {
+ if (debug) {
+ String timestr = "";
+ String[] data = getTraceElements();
+ if (debugging(data[0], loglevel)) {
+ if (timing) {
+ long now = System.currentTimeMillis();
+ timestr = "{" + (now - last) + "} ";
+ last = now;
+ }
+ String[] lines = null;
+ if (ttrace) {
+ StackTraceElement[] ste = t.getStackTrace();
+ lines = new String[ste.length];
+ for (int i = 0; i < ste.length; i++)
+ lines[i] = "\tat " + ste[i].toString();
+ }
+ _print(t.getClass(), loglevel, data[0] + "." + data[1] + "()" + data[2], timestr, t.toString(), lines);
+ }
+ }
+ }
+
+ /**
+ * Log a byte array
+ *
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param b The byte array to print.
+ * @see #setHexDump to enable hex dumping.
+ * @see #setByteArrayCount to change how many bytes are printed.
+ * @see #setByteArrayWidth to change the formatting width of hex.
+ */
+ public static void print(int loglevel, byte[] b) {
+ if (debug) {
+ String timestr = "";
+ String[] data = getTraceElements();
+ if (debugging(data[0], loglevel)) {
+ if (timing) {
+ long now = System.currentTimeMillis();
+ timestr = "{" + (now - last) + "} ";
+ last = now;
+ }
+ String[] lines = null;
+ if (hexdump) {
+ if (balen >= b.length)
+ lines = Hexdump.format(b, bawidth).split("\n");
+ else {
+ byte[] buf = new byte[balen];
+ System.arraycopy(b, 0, buf, 0, balen);
+ lines = Hexdump.format(buf, bawidth).split("\n");
+ }
+ }
+ _print(b.getClass(), loglevel, data[0] + "." + data[1] + "()" + data[2], timestr, b.length + " bytes", lines);
+ }
+ }
+ }
+
+ /**
+ * Log a String
+ *
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param s The string to log with d.toString()
+ */
+ public static void print(int loglevel, String s) {
+ if (debug)
+ print(loglevel, (Object) s);
+ }
+
+ /**
+ * Log an Object
+ *
+ * @param c The class doing the logging
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param d The object to log with d.toString()
+ * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in.
+ */
+ //TODO: 1.5 @Deprecated()
+ public static void print(Class c, int loglevel, Object d) {
+ if (debug) {
+ saveclass = c;
+ print(loglevel, d);
+ }
+ }
+
+ /**
+ * Log a String
+ *
+ * @param c The class doing the logging
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param s The log message
+ * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in.
+ */
+ //TODO: 1.5 @Deprecated()
+ public static void print(Class c, int loglevel, String s) {
+ if (debug) {
+ saveclass = c;
+ print(loglevel, s);
+ }
+ }
+
+ private static String[] getTraceElements() {
+ String[] data = new String[]{"", "", ""};
+ try {
+ Method m = Thread.class.getMethod("getStackTrace", new Class[0]);
+ StackTraceElement[] stes = (StackTraceElement[]) m.invoke(Thread.currentThread(), new Object[0]);
+ for (StackTraceElement ste : stes) {
+ if (Debug.class.getName().equals(ste.getClassName())) continue;
+ if (Thread.class.getName().equals(ste.getClassName())) continue;
+ if (Method.class.getName().equals(ste.getClassName())) continue;
+ if (ste.getClassName().startsWith("sun.reflect")) continue;
+ data[0] = ste.getClassName();
+ data[1] = ste.getMethodName();
+ if (lines)
+ data[2] = " " + ste.getFileName() + ":" + ste.getLineNumber();
+ break;
+ }
+ } catch (NoSuchMethodException NSMe) {
+ if (null != saveclass)
+ data[0] = saveclass.getName();
+ } catch (IllegalAccessException IAe) {
+ } catch (InvocationTargetException ITe) {
+ }
+ return data;
+ }
+
+ /**
+ * Log an Object
+ *
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param o The object to log
+ */
+ public static void print(int loglevel, Object o) {
+ if (debug) {
+ String timestr = "";
+ String[] data = getTraceElements();
+ if (debugging(data[0], loglevel)) {
+ if (timing) {
+ long now = System.currentTimeMillis();
+ timestr = "{" + (now - last) + "} ";
+ last = now;
+ }
+ _print(o.getClass(), loglevel, data[0] + "." + data[1] + "()" + data[2], timestr, o.toString(), null);
+ }
+ }
+ }
+
+ /**
+ * Log a Map
+ *
+ * @param o The object doing the logging
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param m The Map to print out
+ * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in.
+ */
+ //TODO: 1.5 @Deprecated()
+ public static void printMap(Object o, int loglevel, Map m) {
+ if (debug) {
+ if (o instanceof Class)
+ saveclass = (Class) o;
+ else
+ saveclass = o.getClass();
+ printMap(loglevel, m);
+ }
+ }
+
+ /**
+ * Log a Map
+ *
+ * @param c The class doing the logging
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param m The Map to print out
+ * @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in.
+ */
+ //TODO: 1.5 @Deprecated()
+ public static void printMap(Class c, int loglevel, Map m) {
+ if (debug) {
+ saveclass = c;
+ printMap(loglevel, m);
+ }
+ }
+
+ /**
+ * Log a Map at DEBUG log level
+ *
+ * @param m The Map to print out
+ */
+ public static void printMap(Map m) {
+ printMap(DEBUG, m);
+ }
+
+ /**
+ * Log a Map
+ *
+ * @param loglevel The level to log at (DEBUG, WARN, etc)
+ * @param m The Map to print out
+ */
+ public static void printMap(int loglevel, Map m) {
+ if (debug) {
+ String timestr = "";
+ String[] data = getTraceElements();
+ if (debugging(data[0], loglevel)) {
+ if (timing) {
+ long now = System.currentTimeMillis();
+ timestr = "{" + (now - last) + "} ";
+ last = now;
+ }
+ Iterator i = m.keySet().iterator();
+ String[] lines = new String[m.size()];
+ int j = 0;
+ while (i.hasNext()) {
+ Object key = i.next();
+ lines[j++] = "\t\t- " + key + " => " + m.get(key);
+ }
+ _print(m.getClass(), loglevel, data[0] + "." + data[1] + "()" + data[2], timestr, "Map:", lines);
+ }
+ }
+ }
+
+ /**
+ * Enable or disable stack traces in Debuging throwables.
+ */
+ public static void setThrowableTraces(boolean ttrace) {
+ Debug.ttrace = ttrace;
+ }
+
+ /**
+ * Enable or disable timing in Debug messages.
+ */
+ public static void setTiming(boolean timing) {
+ Debug.timing = timing;
+ }
+
+ /**
+ * Enable or disable line numbers.
+ */
+ public static void setLineNos(boolean lines) {
+ Debug.lines = lines;
+ }
+
+ /**
+ * Enable or disable hexdumps.
+ */
+ public static void setHexDump(boolean hexdump) {
+ Debug.hexdump = hexdump;
+ }
+
+ /**
+ * Set the size of hexdumps.
+ * (Default: 36)
+ */
+ public static void setByteArrayCount(int count) {
+ Debug.balen = count;
+ }
+
+ /**
+ * Set the formatted width of hexdumps.
+ * (Default: 80 chars)
+ */
+ public static void setByteArrayWidth(int width) {
+ Debug.bawidth = width;
+ }
+
+ /**
+ * Add a filter command for a specific type.
+ * This command will be called with the output stream
+ * and the text to be sent. It should perform any
+ * changes necessary to the text and then print the
+ * result to the output stream.
+ */
+ public static void addFilterCommand(Class c, FilterCommand f)
+ //TODO 1.5: public static void addFilterCommand(Class<? extends Object> c, FilterCommand f)
+ {
+ filterMap.put(c, f);
+ }
+
+ private static void _print(Class c, int level, String loc, String extra, String message, String[] lines) {
+ //TODO 1.5: FilterCommand f = filterMap.get(c);
+ FilterCommand f = (FilterCommand) filterMap.get(c);
+ if (null == f) {
+ debugout.println("[" + loc + "] " + extra + message);
+ if (null != lines)
+ for (String s : lines)
+ debugout.println(s);
+ } else
+ f.filter(debugout, level, loc, extra, message, lines);
+ }
+}
diff --git a/federation/sssd/src/main/java/cx/ath/matthew/LibraryLoader.java b/federation/sssd/src/main/java/cx/ath/matthew/LibraryLoader.java
new file mode 100644
index 0000000..4088d46
--- /dev/null
+++ b/federation/sssd/src/main/java/cx/ath/matthew/LibraryLoader.java
@@ -0,0 +1,46 @@
+/*
+ * 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 cx.ath.matthew;
+
+/**
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
+ */
+public class LibraryLoader {
+
+ private static final String[] PATHS = {"/usr/lib/", "/usr/lib64/", "/usr/local/lib/", "/opt/local/lib/"};
+ private static final String LIBRARY_NAME = "libunix_dbus_java";
+ private static final String VERSION = "0.0.8";
+ private static boolean loadSucceeded;
+
+ public static LibraryLoader load() {
+ for (String path : PATHS) {
+ try {
+ System.load(String.format("%s/%s.so.%s", path, LIBRARY_NAME, VERSION));
+ loadSucceeded = true;
+ break;
+ } catch (UnsatisfiedLinkError e) {
+ loadSucceeded = false;
+ }
+ }
+
+ return new LibraryLoader();
+ }
+
+ public boolean succeed() {
+ return loadSucceeded;
+ }
+}
diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/NotConnectedException.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/NotConnectedException.java
new file mode 100644
index 0000000..836f5d6
--- /dev/null
+++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/NotConnectedException.java
@@ -0,0 +1,35 @@
+/*
+ * Java Unix Sockets Library
+ *
+ * Copyright (c) Matthew Johnson 2004
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * To Contact the author, please email src@matthew.ath.cx
+ *
+ */
+package cx.ath.matthew.unix;
+
+import java.net.SocketException;
+
+public class NotConnectedException extends SocketException {
+ public NotConnectedException() {
+ super("The Socket is Not Connected");
+ }
+}
diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixIOException.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixIOException.java
new file mode 100644
index 0000000..24fd20c
--- /dev/null
+++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixIOException.java
@@ -0,0 +1,43 @@
+/*
+ * Java Unix Sockets Library
+ *
+ * Copyright (c) Matthew Johnson 2004
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * To Contact the author, please email src@matthew.ath.cx
+ *
+ */
+package cx.ath.matthew.unix;
+
+import java.io.IOException;
+
+/**
+ * An IO Exception which occurred during UNIX Socket IO
+ */
+public class UnixIOException extends IOException {
+ private int no;
+ private String message;
+
+ public UnixIOException(int no, String message) {
+ super(message);
+ this.message = message;
+ this.no = no;
+ }
+}
diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocket.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocket.java
new file mode 100644
index 0000000..8851637
--- /dev/null
+++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocket.java
@@ -0,0 +1,350 @@
+/*
+ * Java Unix Sockets Library
+ *
+ * Copyright (c) Matthew Johnson 2004
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * To Contact the author, please email src@matthew.ath.cx
+ *
+ */
+package cx.ath.matthew.unix;
+
+import cx.ath.matthew.LibraryLoader;
+import cx.ath.matthew.debug.Debug;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Represents a UnixSocket.
+ */
+public class UnixSocket {
+ static {
+ LibraryLoader.load();
+ }
+
+ private native void native_set_pass_cred(int sock, boolean passcred) throws IOException;
+
+ private native int native_connect(String address, boolean abs) throws IOException;
+
+ private native void native_close(int sock) throws IOException;
+
+ private native int native_getPID(int sock);
+
+ private native int native_getUID(int sock);
+
+ private native int native_getGID(int sock);
+
+ private native void native_send_creds(int sock, byte data) throws IOException;
+
+ private native byte native_recv_creds(int sock, int[] creds) throws IOException;
+
+ private UnixSocketAddress address = null;
+ private USOutputStream os = null;
+ private USInputStream is = null;
+ private boolean closed = false;
+ private boolean connected = false;
+ private boolean passcred = false;
+ private int sock = 0;
+ private boolean blocking = true;
+ private int uid = -1;
+ private int pid = -1;
+ private int gid = -1;
+
+ UnixSocket(int sock, UnixSocketAddress address) {
+ this.sock = sock;
+ this.address = address;
+ this.connected = true;
+ this.os = new USOutputStream(sock, this);
+ this.is = new USInputStream(sock, this);
+ }
+
+ /**
+ * Create an unconnected socket.
+ */
+ public UnixSocket() {
+ }
+
+ /**
+ * Create a socket connected to the given address.
+ *
+ * @param address The Unix Socket address to connect to
+ */
+ public UnixSocket(UnixSocketAddress address) throws IOException {
+ connect(address);
+ }
+
+ /**
+ * Create a socket connected to the given address.
+ *
+ * @param address The Unix Socket address to connect to
+ */
+ public UnixSocket(String address) throws IOException {
+ this(new UnixSocketAddress(address));
+ }
+
+ /**
+ * Connect the socket to this address.
+ *
+ * @param address The Unix Socket address to connect to
+ */
+ public void connect(UnixSocketAddress address) throws IOException {
+ if (connected) close();
+ this.sock = native_connect(address.path, address.abs);
+ this.os = new USOutputStream(this.sock, this);
+ this.is = new USInputStream(this.sock, this);
+ this.address = address;
+ this.connected = true;
+ this.closed = false;
+ this.is.setBlocking(blocking);
+ }
+
+ /**
+ * Connect the socket to this address.
+ *
+ * @param address The Unix Socket address to connect to
+ */
+ public void connect(String address) throws IOException {
+ connect(new UnixSocketAddress(address));
+ }
+
+ public void finalize() {
+ try {
+ close();
+ } catch (IOException IOe) {
+ }
+ }
+
+ /**
+ * Closes the connection.
+ */
+ public synchronized void close() throws IOException {
+ if (Debug.debug) Debug.print(Debug.INFO, "Closing socket");
+ native_close(sock);
+ sock = 0;
+ this.closed = true;
+ this.connected = false;
+ os = null;
+ is = null;
+ }
+
+ /**
+ * Returns an InputStream for reading from the socket.
+ *
+ * @return An InputStream connected to this socket.
+ */
+ public InputStream getInputStream() {
+ return is;
+ }
+
+ /**
+ * Returns an OutputStream for writing to the socket.
+ *
+ * @return An OutputStream connected to this socket.
+ */
+ public OutputStream getOutputStream() {
+ return os;
+ }
+
+ /**
+ * Returns the address this socket is connected to.
+ * Returns null if the socket is unconnected.
+ *
+ * @return The UnixSocketAddress the socket is connected to
+ */
+ public UnixSocketAddress getAddress() {
+ return address;
+ }
+
+ /**
+ * Send a single byte of data with credentials.
+ * (Works on BSDs)
+ *
+ * @param data The byte of data to send.
+ */
+ public void sendCredentialByte(byte data) throws IOException {
+ if (!connected) throw new NotConnectedException();
+ native_send_creds(sock, data);
+ }
+
+ /**
+ * Receive a single byte of data, with credentials.
+ * (Works on BSDs)
+ *
+ * @param data The byte of data to send.
+ * @see getPeerUID
+ * @see getPeerPID
+ * @see getPeerGID
+ */
+ public byte recvCredentialByte() throws IOException {
+ if (!connected) throw new NotConnectedException();
+ int[] creds = new int[]{-1, -1, -1};
+ byte data = native_recv_creds(sock, creds);
+ pid = creds[0];
+ uid = creds[1];
+ gid = creds[2];
+ return data;
+ }
+
+ /**
+ * Get the credential passing status.
+ * (only effective on linux)
+ *
+ * @return The current status of credential passing.
+ * @see setPassCred
+ */
+ public boolean getPassCred() {
+ return passcred;
+ }
+
+ /**
+ * Return the uid of the remote process.
+ * Some data must have been received on the socket to do this.
+ * Either setPassCred must be called on Linux first, or recvCredentialByte
+ * on BSD.
+ *
+ * @return the UID or -1 if it is not available
+ */
+ public int getPeerUID() {
+ if (-1 == uid)
+ uid = native_getUID(sock);
+ return uid;
+ }
+
+ /**
+ * Return the gid of the remote process.
+ * Some data must have been received on the socket to do this.
+ * Either setPassCred must be called on Linux first, or recvCredentialByte
+ * on BSD.
+ *
+ * @return the GID or -1 if it is not available
+ */
+ public int getPeerGID() {
+ if (-1 == gid)
+ gid = native_getGID(sock);
+ return gid;
+ }
+
+ /**
+ * Return the pid of the remote process.
+ * Some data must have been received on the socket to do this.
+ * Either setPassCred must be called on Linux first, or recvCredentialByte
+ * on BSD.
+ *
+ * @return the PID or -1 if it is not available
+ */
+ public int getPeerPID() {
+ if (-1 == pid)
+ pid = native_getPID(sock);
+ return pid;
+ }
+
+ /**
+ * Set the credential passing status.
+ * (Only does anything on linux, for other OS, you need
+ * to use send/recv credentials)
+ *
+ * @param enable Set to true for credentials to be passed.
+ */
+ public void setPassCred(boolean enable) throws IOException {
+ native_set_pass_cred(sock, enable);
+ passcred = enable;
+ }
+
+ /**
+ * Get the blocking mode.
+ *
+ * @return true if reads are blocking.
+ * @see setBlocking
+ */
+ public boolean getBlocking() {
+ return blocking;
+ }
+
+ /**
+ * Set the blocking mode.
+ *
+ * @param enable Set to false for non-blocking reads.
+ */
+ public void setBlocking(boolean enable) {
+ blocking = enable;
+ if (null != is) is.setBlocking(enable);
+ }
+
+ /**
+ * Check the socket status.
+ *
+ * @return true if closed.
+ */
+ public boolean isClosed() {
+ return closed;
+ }
+
+ /**
+ * Check the socket status.
+ *
+ * @return true if connected.
+ */
+ public boolean isConnected() {
+ return connected;
+ }
+
+ /**
+ * Check the socket status.
+ *
+ * @return true if the input stream has been shutdown
+ */
+ public boolean isInputShutdown() {
+ return is.isClosed();
+ }
+
+ /**
+ * Check the socket status.
+ *
+ * @return true if the output stream has been shutdown
+ */
+ public boolean isOutputShutdown() {
+ return os.isClosed();
+ }
+
+ /**
+ * Shuts down the input stream.
+ * Subsequent reads on the associated InputStream will fail.
+ */
+ public void shutdownInput() {
+ is.closed = true;
+ }
+
+ /**
+ * Shuts down the output stream.
+ * Subsequent writes to the associated OutputStream will fail.
+ */
+ public void shutdownOutput() {
+ os.closed = true;
+ }
+
+ /**
+ * Set timeout of read requests.
+ */
+ public void setSoTimeout(int timeout) {
+ is.setSoTimeout(timeout);
+ }
+}
diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java
new file mode 100644
index 0000000..0baba47
--- /dev/null
+++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java
@@ -0,0 +1,86 @@
+/*
+ * Java Unix Sockets Library
+ *
+ * Copyright (c) Matthew Johnson 2004
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * To Contact the author, please email src@matthew.ath.cx
+ *
+ */
+package cx.ath.matthew.unix;
+
+/**
+ * Represents an address for a Unix Socket
+ */
+public class UnixSocketAddress {
+ String path;
+ boolean abs;
+
+ /**
+ * Create the address.
+ *
+ * @param path The path to the Unix Socket.
+ * @param abs True if this should be an abstract socket.
+ */
+ public UnixSocketAddress(String path, boolean abs) {
+ this.path = path;
+ this.abs = abs;
+ }
+
+ /**
+ * Create the address.
+ *
+ * @param path The path to the Unix Socket.
+ */
+ public UnixSocketAddress(String path) {
+ this.path = path;
+ this.abs = false;
+ }
+
+ /**
+ * Return the path.
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Returns true if this an address for an abstract socket.
+ */
+ public boolean isAbstract() {
+ return abs;
+ }
+
+ /**
+ * Return the Address as a String.
+ */
+ public String toString() {
+ return "unix" + (abs ? ":abstract" : "") + ":path=" + path;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof UnixSocketAddress)) return false;
+ return ((UnixSocketAddress) o).path.equals(this.path);
+ }
+
+ public int hashCode() {
+ return path.hashCode();
+ }
+}
diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/USInputStream.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/USInputStream.java
new file mode 100644
index 0000000..eb143fe
--- /dev/null
+++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/USInputStream.java
@@ -0,0 +1,94 @@
+/*
+ * Java Unix Sockets Library
+ *
+ * Copyright (c) Matthew Johnson 2004
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * To Contact the author, please email src@matthew.ath.cx
+ *
+ */
+package cx.ath.matthew.unix;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class USInputStream extends InputStream {
+ public static final int MSG_DONTWAIT = 0x40;
+
+ private native int native_recv(int sock, byte[] b, int off, int len, int flags, int timeout) throws IOException;
+
+ private int sock;
+ boolean closed = false;
+ private byte[] onebuf = new byte[1];
+ private UnixSocket us;
+ private boolean blocking = true;
+ private int flags = 0;
+ private int timeout = 0;
+
+ public USInputStream(int sock, UnixSocket us) {
+ this.sock = sock;
+ this.us = us;
+ }
+
+ public void close() throws IOException {
+ closed = true;
+ us.close();
+ }
+
+ public boolean markSupported() {
+ return false;
+ }
+
+ public int read() throws IOException {
+ int rv = 0;
+ while (0 >= rv) rv = read(onebuf);
+ if (-1 == rv) return -1;
+ return 0 > onebuf[0] ? -onebuf[0] : onebuf[0];
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (closed) throw new NotConnectedException();
+ int count = native_recv(sock, b, off, len, flags, timeout);
+ /* Yes, I really want to do this. Recv returns 0 for 'connection shut down'.
+ * read() returns -1 for 'end of stream.
+ * Recv returns -1 for 'EAGAIN' (all other errors cause an exception to be raised)
+ * whereas read() returns 0 for '0 bytes read', so yes, I really want to swap them here.
+ */
+ if (0 == count) return -1;
+ else if (-1 == count) return 0;
+ else return count;
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ public UnixSocket getSocket() {
+ return us;
+ }
+
+ public void setBlocking(boolean enable) {
+ flags = enable ? 0 : MSG_DONTWAIT;
+ }
+
+ public void setSoTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+}
diff --git a/federation/sssd/src/main/java/cx/ath/matthew/unix/USOutputStream.java b/federation/sssd/src/main/java/cx/ath/matthew/unix/USOutputStream.java
new file mode 100644
index 0000000..d8c85a7
--- /dev/null
+++ b/federation/sssd/src/main/java/cx/ath/matthew/unix/USOutputStream.java
@@ -0,0 +1,78 @@
+/*
+ * Java Unix Sockets Library
+ *
+ * Copyright (c) Matthew Johnson 2004
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * To Contact the author, please email src@matthew.ath.cx
+ *
+ */
+package cx.ath.matthew.unix;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class USOutputStream extends OutputStream {
+ private native int native_send(int sock, byte[] b, int off, int len) throws IOException;
+
+ private native int native_send(int sock, byte[][] b) throws IOException;
+
+ private int sock;
+ boolean closed = false;
+ private byte[] onebuf = new byte[1];
+ private UnixSocket us;
+
+ public USOutputStream(int sock, UnixSocket us) {
+ this.sock = sock;
+ this.us = us;
+ }
+
+ public void close() throws IOException {
+ closed = true;
+ us.close();
+ }
+
+ public void flush() {
+ } // no-op, we do not buffer
+
+ public void write(byte[][] b) throws IOException {
+ if (closed) throw new NotConnectedException();
+ native_send(sock, b);
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (closed) throw new NotConnectedException();
+ native_send(sock, b, off, len);
+ }
+
+ public void write(int b) throws IOException {
+ onebuf[0] = (byte) (b % 0x7F);
+ if (1 == (b % 0x80)) onebuf[0] = (byte) -onebuf[0];
+ write(onebuf);
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ public UnixSocket getSocket() {
+ return us;
+ }
+}
diff --git a/federation/sssd/src/main/java/cx/ath/matthew/utils/Hexdump.java b/federation/sssd/src/main/java/cx/ath/matthew/utils/Hexdump.java
new file mode 100644
index 0000000..63f3719
--- /dev/null
+++ b/federation/sssd/src/main/java/cx/ath/matthew/utils/Hexdump.java
@@ -0,0 +1,147 @@
+/*
+ * Java Hexdump Library
+ *
+ * Copyright (c) Matthew Johnson 2005
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * To Contact the author, please email src@matthew.ath.cx
+ *
+ */
+
+package cx.ath.matthew.utils;
+
+import java.io.PrintStream;
+
+public class Hexdump {
+ public static final char[] hexchars = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ public static String toHex(byte[] buf) {
+ return toHex(buf, 0, buf.length);
+ }
+
+ public static String toHex(byte[] buf, int ofs, int len) {
+ StringBuffer sb = new StringBuffer();
+ int j = ofs + len;
+ for (int i = ofs; i < j; i++) {
+ if (i < buf.length) {
+ sb.append(hexchars[(buf[i] & 0xF0) >> 4]);
+ sb.append(hexchars[buf[i] & 0x0F]);
+ sb.append(' ');
+ } else {
+ sb.append(' ');
+ sb.append(' ');
+ sb.append(' ');
+ }
+ }
+ return sb.toString();
+ }
+
+ public static String toAscii(byte[] buf) {
+ return toAscii(buf, 0, buf.length);
+ }
+
+ public static String toAscii(byte[] buf, int ofs, int len) {
+ StringBuffer sb = new StringBuffer();
+ int j = ofs + len;
+ for (int i = ofs; i < j; i++) {
+ if (i < buf.length) {
+ if (20 <= buf[i] && 126 >= buf[i])
+ sb.append((char) buf[i]);
+ else
+ sb.append('.');
+ } else
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+ public static String format(byte[] buf) {
+ return format(buf, 80);
+ }
+
+ public static String format(byte[] buf, int width) {
+ int bs = (width - 8) / 4;
+ int i = 0;
+ StringBuffer sb = new StringBuffer();
+ do {
+ for (int j = 0; j < 6; j++) {
+ sb.append(hexchars[(i << (j * 4) & 0xF00000) >> 20]);
+ }
+ sb.append('\t');
+ sb.append(toHex(buf, i, bs));
+ sb.append(' ');
+ sb.append(toAscii(buf, i, bs));
+ sb.append('\n');
+ i += bs;
+ } while (i < buf.length);
+ return sb.toString();
+ }
+
+ public static void print(byte[] buf) {
+ print(buf, System.err);
+ }
+
+ public static void print(byte[] buf, int width) {
+ print(buf, width, System.err);
+ }
+
+ public static void print(byte[] buf, int width, PrintStream out) {
+ out.print(format(buf, width));
+ }
+
+ public static void print(byte[] buf, PrintStream out) {
+ out.print(format(buf));
+ }
+
+ /**
+ * Returns a string which can be written to a Java source file as part
+ * of a static initializer for a byte array.
+ * Returns data in the format 0xAB, 0xCD, ....
+ * use like:
+ * javafile.print("byte[] data = {")
+ * javafile.print(Hexdump.toByteArray(data));
+ * javafile.println("};");
+ */
+ public static String toByteArray(byte[] buf) {
+ return toByteArray(buf, 0, buf.length);
+ }
+
+ /**
+ * Returns a string which can be written to a Java source file as part
+ * of a static initializer for a byte array.
+ * Returns data in the format 0xAB, 0xCD, ....
+ * use like:
+ * javafile.print("byte[] data = {")
+ * javafile.print(Hexdump.toByteArray(data));
+ * javafile.println("};");
+ */
+ public static String toByteArray(byte[] buf, int ofs, int len) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = ofs; i < len && i < buf.length; i++) {
+ sb.append('0');
+ sb.append('x');
+ sb.append(hexchars[(buf[i] & 0xF0) >> 4]);
+ sb.append(hexchars[buf[i] & 0x0F]);
+ if ((i + 1) < len && (i + 1) < buf.length)
+ sb.append(',');
+ }
+ return sb.toString();
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/DBus.java b/federation/sssd/src/main/java/org/freedesktop/DBus.java
new file mode 100644
index 0000000..b7a1687
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/DBus.java
@@ -0,0 +1,534 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop;
+
+import org.freedesktop.dbus.DBusInterface;
+import org.freedesktop.dbus.DBusSignal;
+import org.freedesktop.dbus.Position;
+import org.freedesktop.dbus.Struct;
+import org.freedesktop.dbus.Tuple;
+import org.freedesktop.dbus.UInt16;
+import org.freedesktop.dbus.UInt32;
+import org.freedesktop.dbus.UInt64;
+import org.freedesktop.dbus.Variant;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.Map;
+
+public interface DBus extends DBusInterface {
+
+ String BUSNAME = "org.freedesktop.DBus";
+ String OBJECTPATH = "/org/freedesktop/DBus";
+
+ int DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x01;
+ int DBUS_NAME_FLAG_REPLACE_EXISTING = 0x02;
+ int DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x04;
+ int DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1;
+ int DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 2;
+ int DBUS_REQUEST_NAME_REPLY_EXISTS = 3;
+ int DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 4;
+ int DBUS_RELEASEME_REPLY_RELEASED = 1;
+ int DBUS_RELEASE_NAME_REPLY_NON_EXISTANT = 2;
+ int DBUS_RELEASE_NAME_REPLY_NOT_OWNER = 3;
+ int DBUS_START_REPLY_SUCCESS = 1;
+ int DBUS_START_REPLY_ALREADY_RUNNING = 2;
+
+ /**
+ * All DBus Applications should respond to the Ping method on this interface
+ */
+ public interface Peer extends DBusInterface {
+ public void Ping();
+ }
+
+ /**
+ * Objects can provide introspection data via this interface and method.
+ * See the <a href="http://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format">Introspection Format</a>.
+ */
+ public interface Introspectable extends DBusInterface {
+ /**
+ * @return The XML introspection data for this object
+ */
+ public String Introspect();
+ }
+
+ /**
+ * A standard properties interface.
+ */
+ public interface Properties extends DBusInterface {
+ /**
+ * Get the value for the given property.
+ *
+ * @param interface_name The interface this property is associated with.
+ * @param property_name The name of the property.
+ * @return The value of the property (may be any valid DBus type).
+ */
+ public <A> A Get(String interface_name, String property_name);
+
+ /**
+ * Set the value for the given property.
+ *
+ * @param interface_name The interface this property is associated with.
+ * @param property_name The name of the property.
+ * @param value The new value of the property (may be any valid DBus type).
+ */
+ public <A> void Set(String interface_name, String property_name, A value);
+
+ /**
+ * Get all properties and values.
+ *
+ * @param interface_name The interface the properties is associated with.
+ * @return The properties mapped to their values.
+ */
+ public Map<String, Variant> GetAll(String interface_name);
+ }
+
+ /**
+ * Messages generated locally in the application.
+ */
+ public interface Local extends DBusInterface {
+ public class Disconnected extends DBusSignal {
+ public Disconnected(String path) throws DBusException {
+ super(path);
+ }
+ }
+ }
+
+ /**
+ * Initial message to register ourselves on the Bus.
+ *
+ * @return The unique name of this connection to the Bus.
+ */
+ public String Hello();
+
+ /**
+ * Lists all connected names on the Bus.
+ *
+ * @return An array of all connected names.
+ */
+ public String[] ListNames();
+
+ /**
+ * Determine if a name has an owner.
+ *
+ * @param name The name to query.
+ * @return true if the name has an owner.
+ */
+ public boolean NameHasOwner(String name);
+
+ /**
+ * Get the connection unique name that owns the given name.
+ *
+ * @param name The name to query.
+ * @return The connection which owns the name.
+ */
+ public String GetNameOwner(String name);
+
+ /**
+ * Get the Unix UID that owns a connection name.
+ *
+ * @param connection_name The connection name.
+ * @return The Unix UID that owns it.
+ */
+ public UInt32 GetConnectionUnixUser(String connection_name);
+
+ /**
+ * Start a service. If the given service is not provided
+ * by any application, it will be started according to the .service file
+ * for that service.
+ *
+ * @param name The service name to start.
+ * @param flags Unused.
+ * @return DBUS_START_REPLY constants.
+ */
+ public UInt32 StartServiceByName(String name, UInt32 flags);
+
+ /**
+ * Request a name on the bus.
+ *
+ * @param name The name to request.
+ * @param flags DBUS_NAME flags.
+ * @return DBUS_REQUEST_NAME_REPLY constants.
+ */
+ public UInt32 RequestName(String name, UInt32 flags);
+
+ /**
+ * Release a name on the bus.
+ *
+ * @param name The name to release.
+ * @return DBUS_RELEASE_NAME_REPLY constants.
+ */
+ public UInt32 ReleaseName(String name);
+
+ /**
+ * Add a match rule.
+ * Will cause you to receive messages that aren't directed to you which
+ * match this rule.
+ *
+ * @param matchrule The Match rule as a string. Format Undocumented.
+ */
+ public void AddMatch(String matchrule) throws Error.MatchRuleInvalid;
+
+ /**
+ * Remove a match rule.
+ * Will cause you to stop receiving messages that aren't directed to you which
+ * match this rule.
+ *
+ * @param matchrule The Match rule as a string. Format Undocumented.
+ */
+ public void RemoveMatch(String matchrule) throws Error.MatchRuleInvalid;
+
+ /**
+ * List the connections currently queued for a name.
+ *
+ * @param name The name to query
+ * @return A list of unique connection IDs.
+ */
+ public String[] ListQueuedOwners(String name);
+
+ /**
+ * Returns the proccess ID associated with a connection.
+ *
+ * @param connection_name The name of the connection
+ * @return The PID of the connection.
+ */
+ public UInt32 GetConnectionUnixProcessID(String connection_name);
+
+ /**
+ * Does something undocumented.
+ */
+ public Byte[] GetConnectionSELinuxSecurityContext(String a);
+
+ /**
+ * Does something undocumented.
+ */
+ public void ReloadConfig();
+
+ /**
+ * Signal sent when the owner of a name changes
+ */
+ public class NameOwnerChanged extends DBusSignal {
+ public final String name;
+ public final String old_owner;
+ public final String new_owner;
+
+ public NameOwnerChanged(String path, String name, String old_owner, String new_owner) throws DBusException {
+ super(path, new Object[]{name, old_owner, new_owner});
+ this.name = name;
+ this.old_owner = old_owner;
+ this.new_owner = new_owner;
+ }
+ }
+
+ /**
+ * Signal sent to a connection when it loses a name
+ */
+ public class NameLost extends DBusSignal {
+ public final String name;
+
+ public NameLost(String path, String name) throws DBusException {
+ super(path, name);
+ this.name = name;
+ }
+ }
+
+ /**
+ * Signal sent to a connection when it aquires a name
+ */
+ public class NameAcquired extends DBusSignal {
+ public final String name;
+
+ public NameAcquired(String path, String name) throws DBusException {
+ super(path, name);
+ this.name = name;
+ }
+ }
+
+ /**
+ * Contains standard errors that can be thrown from methods.
+ */
+ public interface Error {
+ /**
+ * Thrown if the method called was unknown on the remote object
+ */
+ @SuppressWarnings("serial")
+ public class UnknownMethod extends DBusExecutionException {
+ public UnknownMethod(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Thrown if the object was unknown on a remote connection
+ */
+ @SuppressWarnings("serial")
+ public class UnknownObject extends DBusExecutionException {
+ public UnknownObject(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Thrown if the requested service was not available
+ */
+ @SuppressWarnings("serial")
+ public class ServiceUnknown extends DBusExecutionException {
+ public ServiceUnknown(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Thrown if the match rule is invalid
+ */
+ @SuppressWarnings("serial")
+ public class MatchRuleInvalid extends DBusExecutionException {
+ public MatchRuleInvalid(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Thrown if there is no reply to a method call
+ */
+ @SuppressWarnings("serial")
+ public class NoReply extends DBusExecutionException {
+ public NoReply(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Thrown if a message is denied due to a security policy
+ */
+ @SuppressWarnings("serial")
+ public class AccessDenied extends DBusExecutionException {
+ public AccessDenied(String message) {
+ super(message);
+ }
+ }
+ }
+
+ /**
+ * Description of the interface or method, returned in the introspection data
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Description {
+ String value();
+ }
+
+ /**
+ * Indicates that a DBus interface or method is deprecated
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Deprecated {
+ }
+
+ /**
+ * Contains method-specific annotations
+ */
+ public interface Method {
+ /**
+ * Methods annotated with this do not send a reply
+ */
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface NoReply {
+ }
+
+ /**
+ * Give an error that the method can return
+ */
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface Error {
+ String value();
+ }
+ }
+
+ /**
+ * Contains GLib-specific annotations
+ */
+ public interface GLib {
+ /**
+ * Define a C symbol to map to this method. Used by GLib only
+ */
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface CSymbol {
+ String value();
+ }
+ }
+
+ /**
+ * Contains Binding-test interfaces
+ */
+ public interface Binding {
+ public interface SingleTests extends DBusInterface {
+ @Description("Returns the sum of the values in the input list")
+ public UInt32 Sum(byte[] a);
+ }
+
+ public interface TestClient extends DBusInterface {
+ @Description("when the trigger signal is received, this method should be called on the sending process/object.")
+ public void Response(UInt16 a, double b);
+
+ @Description("Causes a callback")
+ public static class Trigger extends DBusSignal {
+ public final UInt16 a;
+ public final double b;
+
+ public Trigger(String path, UInt16 a, double b) throws DBusException {
+ super(path, a, b);
+ this.a = a;
+ this.b = b;
+ }
+ }
+
+ }
+
+ public interface Tests extends DBusInterface {
+ @Description("Returns whatever it is passed")
+ public <T> Variant<T> Identity(Variant<T> input);
+
+ @Description("Returns whatever it is passed")
+ public byte IdentityByte(byte input);
+
+ @Description("Returns whatever it is passed")
+ public boolean IdentityBool(boolean input);
+
+ @Description("Returns whatever it is passed")
+ public short IdentityInt16(short input);
+
+ @Description("Returns whatever it is passed")
+ public UInt16 IdentityUInt16(UInt16 input);
+
+ @Description("Returns whatever it is passed")
+ public int IdentityInt32(int input);
+
+ @Description("Returns whatever it is passed")
+ public UInt32 IdentityUInt32(UInt32 input);
+
+ @Description("Returns whatever it is passed")
+ public long IdentityInt64(long input);
+
+ @Description("Returns whatever it is passed")
+ public UInt64 IdentityUInt64(UInt64 input);
+
+ @Description("Returns whatever it is passed")
+ public double IdentityDouble(double input);
+
+ @Description("Returns whatever it is passed")
+ public String IdentityString(String input);
+
+ @Description("Returns whatever it is passed")
+ public <T> Variant<T>[] IdentityArray(Variant<T>[] input);
+
+ @Description("Returns whatever it is passed")
+ public byte[] IdentityByteArray(byte[] input);
+
+ @Description("Returns whatever it is passed")
+ public boolean[] IdentityBoolArray(boolean[] input);
+
+ @Description("Returns whatever it is passed")
+ public short[] IdentityInt16Array(short[] input);
+
+ @Description("Returns whatever it is passed")
+ public UInt16[] IdentityUInt16Array(UInt16[] input);
+
+ @Description("Returns whatever it is passed")
+ public int[] IdentityInt32Array(int[] input);
+
+ @Description("Returns whatever it is passed")
+ public UInt32[] IdentityUInt32Array(UInt32[] input);
+
+ @Description("Returns whatever it is passed")
+ public long[] IdentityInt64Array(long[] input);
+
+ @Description("Returns whatever it is passed")
+ public UInt64[] IdentityUInt64Array(UInt64[] input);
+
+ @Description("Returns whatever it is passed")
+ public double[] IdentityDoubleArray(double[] input);
+
+ @Description("Returns whatever it is passed")
+ public String[] IdentityStringArray(String[] input);
+
+ @Description("Returns the sum of the values in the input list")
+ public long Sum(int[] a);
+
+ @Description("Given a map of A => B, should return a map of B => a list of all the As which mapped to B")
+ public Map<String, List<String>> InvertMapping(Map<String, String> a);
+
+ @Description("This method returns the contents of a struct as separate values")
+ public Triplet<String, UInt32, Short> DeStruct(TestStruct a);
+
+ @Description("Given any compound type as a variant, return all the primitive types recursively contained within as an array of variants")
+ public List<Variant<Object>> Primitize(Variant<Object> a);
+
+ @Description("inverts it's input")
+ public boolean Invert(boolean a);
+
+ @Description("triggers sending of a signal from the supplied object with the given parameter")
+ public void Trigger(String a, UInt64 b);
+
+ @Description("Causes the server to exit")
+ public void Exit();
+ }
+
+ public interface TestSignals extends DBusInterface {
+ @Description("Sent in response to a method call")
+ public static class Triggered extends DBusSignal {
+ public final UInt64 a;
+
+ public Triggered(String path, UInt64 a) throws DBusException {
+ super(path, a);
+ this.a = a;
+ }
+ }
+ }
+
+ public final class Triplet<A, B, C> extends Tuple {
+ @Position(0)
+ public final A a;
+ @Position(1)
+ public final B b;
+ @Position(2)
+ public final C c;
+
+ public Triplet(A a, B b, C c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+ }
+
+ public final class TestStruct extends Struct {
+ @Position(0)
+ public final String a;
+ @Position(1)
+ public final UInt32 b;
+ @Position(2)
+ public final Short c;
+
+ public TestStruct(String a, UInt32 b, Short c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/AbstractConnection.java b/federation/sssd/src/main/java/org/freedesktop/dbus/AbstractConnection.java
new file mode 100644
index 0000000..d1d7f51
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/AbstractConnection.java
@@ -0,0 +1,1059 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import org.freedesktop.DBus;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+import org.freedesktop.dbus.exceptions.FatalDBusException;
+import org.freedesktop.dbus.exceptions.FatalException;
+import org.freedesktop.dbus.exceptions.NotConnected;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Vector;
+import java.util.regex.Pattern;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+
+/**
+ * Handles a connection to DBus.
+ */
+public abstract class AbstractConnection {
+ protected class FallbackContainer {
+ private Map<String[], ExportedObject> fallbacks = new HashMap<String[], ExportedObject>();
+
+ public synchronized void add(String path, ExportedObject eo) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Adding fallback on " + path + " of " + eo);
+ fallbacks.put(path.split("/"), eo);
+ }
+
+ public synchronized void remove(String path) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Removing fallback on " + path);
+ fallbacks.remove(path.split("/"));
+ }
+
+ public synchronized ExportedObject get(String path) {
+ int best = 0;
+ int i = 0;
+ ExportedObject bestobject = null;
+ String[] pathel = path.split("/");
+ for (String[] fbpath : fallbacks.keySet()) {
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Trying fallback path " + Arrays.deepToString(fbpath) + " to match " + Arrays.deepToString(pathel));
+ for (i = 0; i < pathel.length && i < fbpath.length; i++)
+ if (!pathel[i].equals(fbpath[i])) break;
+ if (i > 0 && i == fbpath.length && i > best)
+ bestobject = fallbacks.get(fbpath);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Matches " + i + " bestobject now " + bestobject);
+ }
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Found fallback for " + path + " of " + bestobject);
+ return bestobject;
+ }
+ }
+
+ protected class _thread extends Thread {
+ public _thread() {
+ setName("DBusConnection");
+ }
+
+ public void run() {
+ try {
+ Message m = null;
+ while (_run) {
+ m = null;
+
+ // read from the wire
+ try {
+ // this blocks on outgoing being non-empty or a message being available.
+ m = readIncoming();
+ if (m != null) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Got Incoming Message: " + m);
+ synchronized (this) {
+ notifyAll();
+ }
+
+ if (m instanceof DBusSignal)
+ handleMessage((DBusSignal) m);
+ else if (m instanceof MethodCall)
+ handleMessage((MethodCall) m);
+ else if (m instanceof MethodReturn)
+ handleMessage((MethodReturn) m);
+ else if (m instanceof Error)
+ handleMessage((Error) m);
+
+ m = null;
+ }
+ } catch (Exception e) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ if (e instanceof FatalException) {
+ disconnect();
+ }
+ }
+
+ }
+ synchronized (this) {
+ notifyAll();
+ }
+ } catch (Exception e) {
+ if (Debug.debug && EXCEPTION_DEBUG) Debug.print(Debug.ERR, e);
+ }
+ }
+ }
+
+ private class _globalhandler implements org.freedesktop.DBus.Peer, org.freedesktop.DBus.Introspectable {
+ private String objectpath;
+
+ public _globalhandler() {
+ this.objectpath = null;
+ }
+
+ public _globalhandler(String objectpath) {
+ this.objectpath = objectpath;
+ }
+
+ public boolean isRemote() {
+ return false;
+ }
+
+ public void Ping() {
+ return;
+ }
+
+ public String Introspect() {
+ String intro = objectTree.Introspect(objectpath);
+ if (null == intro) {
+ ExportedObject eo = fallbackcontainer.get(objectpath);
+ if (null != eo) intro = eo.introspectiondata;
+ }
+ if (null == intro)
+ throw new DBus.Error.UnknownObject("Introspecting on non-existant object");
+ else return
+ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" " +
+ "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + intro;
+ }
+ }
+
+ protected class _workerthread extends Thread {
+ private boolean _run = true;
+
+ public void halt() {
+ _run = false;
+ }
+
+ public void run() {
+ while (_run) {
+ Runnable r = null;
+ synchronized (runnables) {
+ while (runnables.size() == 0 && _run)
+ try {
+ runnables.wait();
+ } catch (InterruptedException Ie) {
+ }
+ if (runnables.size() > 0)
+ r = runnables.removeFirst();
+ }
+ if (null != r) r.run();
+ }
+ }
+ }
+
+ private class _sender extends Thread {
+ public _sender() {
+ setName("Sender");
+ }
+
+ public void run() {
+ Message m = null;
+
+ if (Debug.debug) Debug.print(Debug.INFO, "Monitoring outbound queue");
+ // block on the outbound queue and send from it
+ while (_run) {
+ if (null != outgoing) synchronized (outgoing) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking");
+ while (outgoing.size() == 0 && _run)
+ try {
+ outgoing.wait();
+ } catch (InterruptedException Ie) {
+ }
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Notified");
+ if (outgoing.size() > 0)
+ m = outgoing.remove();
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Got message: " + m);
+ }
+ if (null != m)
+ sendMessage(m);
+ m = null;
+ }
+
+ if (Debug.debug) Debug.print(Debug.INFO, "Flushing outbound queue and quitting");
+ // flush the outbound queue before disconnect.
+ if (null != outgoing) do {
+ EfficientQueue ogq = outgoing;
+ synchronized (ogq) {
+ outgoing = null;
+ }
+ if (!ogq.isEmpty())
+ m = ogq.remove();
+ else m = null;
+ sendMessage(m);
+ } while (null != m);
+
+ // close the underlying streams
+ }
+ }
+
+ /**
+ * Timeout in us on checking the BUS for incoming messages and sending outgoing messages
+ */
+ protected static final int TIMEOUT = 100000;
+ /**
+ * Initial size of the pending calls map
+ */
+ private static final int PENDING_MAP_INITIAL_SIZE = 10;
+ static final String BUSNAME_REGEX = "^[-_a-zA-Z][-_a-zA-Z0-9]*(\\.[-_a-zA-Z][-_a-zA-Z0-9]*)*$";
+ static final String CONNID_REGEX = "^:[0-9]*\\.[0-9]*$";
+ static final String OBJECT_REGEX = "^/([-_a-zA-Z0-9]+(/[-_a-zA-Z0-9]+)*)?$";
+ static final byte THREADCOUNT = 4;
+ static final int MAX_ARRAY_LENGTH = 67108864;
+ static final int MAX_NAME_LENGTH = 255;
+ protected Map<String, ExportedObject> exportedObjects;
+ private ObjectTree objectTree;
+ private _globalhandler _globalhandlerreference;
+ protected Map<DBusInterface, RemoteObject> importedObjects;
+ protected Map<SignalTuple, Vector<DBusSigHandler<? extends DBusSignal>>> handledSignals;
+ protected EfficientMap pendingCalls;
+ protected Map<MethodCall, CallbackHandler<? extends Object>> pendingCallbacks;
+ protected Map<MethodCall, DBusAsyncReply<? extends Object>> pendingCallbackReplys;
+ protected LinkedList<Runnable> runnables;
+ protected LinkedList<_workerthread> workers;
+ protected FallbackContainer fallbackcontainer;
+ protected boolean _run;
+ EfficientQueue outgoing;
+ LinkedList<Error> pendingErrors;
+ private static final Map<Thread, DBusCallInfo> infomap = new HashMap<Thread, DBusCallInfo>();
+ protected _thread thread;
+ protected _sender sender;
+ protected Transport transport;
+ protected String addr;
+ protected boolean weakreferences = false;
+ static final Pattern dollar_pattern = Pattern.compile("[$]");
+ public static final boolean EXCEPTION_DEBUG;
+ static final boolean FLOAT_SUPPORT;
+ protected boolean connected = false;
+
+ static {
+ FLOAT_SUPPORT = (null != System.getenv("DBUS_JAVA_FLOATS"));
+ EXCEPTION_DEBUG = (null != System.getenv("DBUS_JAVA_EXCEPTION_DEBUG"));
+ if (EXCEPTION_DEBUG) {
+ Debug.print("Debugging of internal exceptions enabled");
+ Debug.setThrowableTraces(true);
+ }
+ if (Debug.debug) {
+ File f = new File("debug.conf");
+ if (f.exists()) {
+ Debug.print("Loading debug config file: " + f);
+ try {
+ Debug.loadConfig(f);
+ } catch (IOException IOe) {
+ }
+ } else {
+ Properties p = new Properties();
+ p.setProperty("ALL", "INFO");
+ Debug.print("debug config file " + f + " does not exist, not loading.");
+ }
+ Debug.setHexDump(true);
+ }
+ }
+
+ protected AbstractConnection(String address) throws DBusException {
+ exportedObjects = new HashMap<String, ExportedObject>();
+ importedObjects = new HashMap<DBusInterface, RemoteObject>();
+ _globalhandlerreference = new _globalhandler();
+ synchronized (exportedObjects) {
+ exportedObjects.put(null, new ExportedObject(_globalhandlerreference, weakreferences));
+ }
+ handledSignals = new HashMap<SignalTuple, Vector<DBusSigHandler<? extends DBusSignal>>>();
+ pendingCalls = new EfficientMap(PENDING_MAP_INITIAL_SIZE);
+ outgoing = new EfficientQueue(PENDING_MAP_INITIAL_SIZE);
+ pendingCallbacks = new HashMap<MethodCall, CallbackHandler<? extends Object>>();
+ pendingCallbackReplys = new HashMap<MethodCall, DBusAsyncReply<? extends Object>>();
+ pendingErrors = new LinkedList<Error>();
+ runnables = new LinkedList<Runnable>();
+ workers = new LinkedList<_workerthread>();
+ objectTree = new ObjectTree();
+ fallbackcontainer = new FallbackContainer();
+ synchronized (workers) {
+ for (int i = 0; i < THREADCOUNT; i++) {
+ _workerthread t = new _workerthread();
+ t.start();
+ workers.add(t);
+ }
+ }
+ _run = true;
+ addr = address;
+ }
+
+ protected void listen() {
+ // start listening
+ thread = new _thread();
+ thread.start();
+ sender = new _sender();
+ sender.start();
+ }
+
+ /**
+ * Change the number of worker threads to receive method calls and handle signals.
+ * Default is 4 threads
+ *
+ * @param newcount The new number of worker Threads to use.
+ */
+ public void changeThreadCount(byte newcount) {
+ synchronized (workers) {
+ if (workers.size() > newcount) {
+ int n = workers.size() - newcount;
+ for (int i = 0; i < n; i++) {
+ _workerthread t = workers.removeFirst();
+ t.halt();
+ }
+ } else if (workers.size() < newcount) {
+ int n = newcount - workers.size();
+ for (int i = 0; i < n; i++) {
+ _workerthread t = new _workerthread();
+ t.start();
+ workers.add(t);
+ }
+ }
+ }
+ }
+
+ private void addRunnable(Runnable r) {
+ synchronized (runnables) {
+ runnables.add(r);
+ runnables.notifyAll();
+ }
+ }
+
+ String getExportedObject(DBusInterface i) throws DBusException {
+ synchronized (exportedObjects) {
+ for (String s : exportedObjects.keySet())
+ if (i.equals(exportedObjects.get(s).object.get()))
+ return s;
+ }
+
+ String s = importedObjects.get(i).objectpath;
+ if (null != s) return s;
+
+ throw new DBusException("Not an object exported or imported by this connection");
+ }
+
+ abstract DBusInterface getExportedObject(String source, String path) throws DBusException;
+
+ /**
+ * Returns a structure with information on the current method call.
+ *
+ * @return the DBusCallInfo for this method call, or null if we are not in a method call.
+ */
+ public static DBusCallInfo getCallInfo() {
+ DBusCallInfo info;
+ synchronized (infomap) {
+ info = infomap.get(Thread.currentThread());
+ }
+ return info;
+ }
+
+ /**
+ * If set to true the bus will not hold a strong reference to exported objects.
+ * If they go out of scope they will automatically be unexported from the bus.
+ * The default is to hold a strong reference, which means objects must be
+ * explicitly unexported before they will be garbage collected.
+ */
+ public void setWeakReferences(boolean weakreferences) {
+ this.weakreferences = weakreferences;
+ }
+
+ /**
+ * Export an object so that its methods can be called on DBus.
+ *
+ * @param objectpath The path to the object we are exposing. MUST be in slash-notation, like "/org/freedesktop/Local",
+ * and SHOULD end with a capitalised term. Only one object may be exposed on each path at any one time, but an object
+ * may be exposed on several paths at once.
+ * @param object The object to export.
+ * @throws DBusException If the objectpath is already exporting an object.
+ * or if objectpath is incorrectly formatted,
+ */
+ public void exportObject(String objectpath, DBusInterface object) throws DBusException {
+ if (null == objectpath || "".equals(objectpath))
+ throw new DBusException(getString("missingObjectPath"));
+ if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidObjectPath") + objectpath);
+ synchronized (exportedObjects) {
+ if (null != exportedObjects.get(objectpath))
+ throw new DBusException(getString("objectAlreadyExported"));
+ ExportedObject eo = new ExportedObject(object, weakreferences);
+ exportedObjects.put(objectpath, eo);
+ objectTree.add(objectpath, eo, eo.introspectiondata);
+ }
+ }
+
+ /**
+ * Export an object as a fallback object.
+ * This object will have it's methods invoked for all paths starting
+ * with this object path.
+ *
+ * @param objectprefix The path below which the fallback handles calls.
+ * MUST be in slash-notation, like "/org/freedesktop/Local",
+ * @param object The object to export.
+ * @throws DBusException If the objectpath is incorrectly formatted,
+ */
+ public void addFallback(String objectprefix, DBusInterface object) throws DBusException {
+ if (null == objectprefix || "".equals(objectprefix))
+ throw new DBusException(getString("missingObjectPath"));
+ if (!objectprefix.matches(OBJECT_REGEX) || objectprefix.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidObjectPath") + objectprefix);
+ ExportedObject eo = new ExportedObject(object, weakreferences);
+ fallbackcontainer.add(objectprefix, eo);
+ }
+
+ /**
+ * Remove a fallback
+ *
+ * @param objectprefix The prefix to remove the fallback for.
+ */
+ public void removeFallback(String objectprefix) {
+ fallbackcontainer.remove(objectprefix);
+ }
+
+ /**
+ * Stop Exporting an object
+ *
+ * @param objectpath The objectpath to stop exporting.
+ */
+ public void unExportObject(String objectpath) {
+ synchronized (exportedObjects) {
+ exportedObjects.remove(objectpath);
+ objectTree.remove(objectpath);
+ }
+ }
+ /**
+ * Return a reference to a remote object.
+ * This method will resolve the well known name (if given) to a unique bus name when you call it.
+ * This means that if a well known name is released by one process and acquired by another calls to
+ * objects gained from this method will continue to operate on the original process.
+ * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local")
+ * or may be a DBus address such as ":1-16".
+ * @param objectpath The path on which the process is exporting the object.$
+ * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures
+ * as the interface the remote object is exporting.
+ * @return A reference to a remote object.
+ * @throws ClassCastException If type is not a sub-type of DBusInterface
+ * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package.
+ */
+ /**
+ * Send a signal.
+ *
+ * @param signal The signal to send.
+ */
+ public void sendSignal(DBusSignal signal) {
+ queueOutgoing(signal);
+ }
+
+ void queueOutgoing(Message m) {
+ synchronized (outgoing) {
+ if (null == outgoing) return;
+ outgoing.add(m);
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Notifying outgoing thread");
+ outgoing.notifyAll();
+ }
+ }
+
+ /**
+ * Remove a Signal Handler.
+ * Stops listening for this signal.
+ *
+ * @param type The signal to watch for.
+ * @throws DBusException If listening for the signal on the bus failed.
+ * @throws ClassCastException If type is not a sub-type of DBusSignal.
+ */
+ public <T extends DBusSignal> void removeSigHandler(Class<T> type, DBusSigHandler<T> handler) throws DBusException {
+ if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal"));
+ removeSigHandler(new DBusMatchRule(type), handler);
+ }
+
+ /**
+ * Remove a Signal Handler.
+ * Stops listening for this signal.
+ *
+ * @param type The signal to watch for.
+ * @param object The object emitting the signal.
+ * @throws DBusException If listening for the signal on the bus failed.
+ * @throws ClassCastException If type is not a sub-type of DBusSignal.
+ */
+ public <T extends DBusSignal> void removeSigHandler(Class<T> type, DBusInterface object, DBusSigHandler<T> handler) throws DBusException {
+ if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal"));
+ String objectpath = importedObjects.get(object).objectpath;
+ if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidObjectPath") + objectpath);
+ removeSigHandler(new DBusMatchRule(type, null, objectpath), handler);
+ }
+
+ protected abstract <T extends DBusSignal> void removeSigHandler(DBusMatchRule rule, DBusSigHandler<T> handler) throws DBusException;
+
+ /**
+ * Add a Signal Handler.
+ * Adds a signal handler to call when a signal is received which matches the specified type and name.
+ *
+ * @param type The signal to watch for.
+ * @param handler The handler to call when a signal is received.
+ * @throws DBusException If listening for the signal on the bus failed.
+ * @throws ClassCastException If type is not a sub-type of DBusSignal.
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends DBusSignal> void addSigHandler(Class<T> type, DBusSigHandler<T> handler) throws DBusException {
+ if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal"));
+ addSigHandler(new DBusMatchRule(type), (DBusSigHandler<? extends DBusSignal>) handler);
+ }
+
+ /**
+ * Add a Signal Handler.
+ * Adds a signal handler to call when a signal is received which matches the specified type, name and object.
+ *
+ * @param type The signal to watch for.
+ * @param object The object from which the signal will be emitted
+ * @param handler The handler to call when a signal is received.
+ * @throws DBusException If listening for the signal on the bus failed.
+ * @throws ClassCastException If type is not a sub-type of DBusSignal.
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends DBusSignal> void addSigHandler(Class<T> type, DBusInterface object, DBusSigHandler<T> handler) throws DBusException {
+ if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal"));
+ String objectpath = importedObjects.get(object).objectpath;
+ if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidObjectPath") + objectpath);
+ addSigHandler(new DBusMatchRule(type, null, objectpath), (DBusSigHandler<? extends DBusSignal>) handler);
+ }
+
+ protected abstract <T extends DBusSignal> void addSigHandler(DBusMatchRule rule, DBusSigHandler<T> handler) throws DBusException;
+
+ protected <T extends DBusSignal> void addSigHandlerWithoutMatch(Class<? extends DBusSignal> signal, DBusSigHandler<T> handler) throws DBusException {
+ DBusMatchRule rule = new DBusMatchRule(signal);
+ SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource());
+ synchronized (handledSignals) {
+ Vector<DBusSigHandler<? extends DBusSignal>> v = handledSignals.get(key);
+ if (null == v) {
+ v = new Vector<DBusSigHandler<? extends DBusSignal>>();
+ v.add(handler);
+ handledSignals.put(key, v);
+ } else
+ v.add(handler);
+ }
+ }
+
+ /**
+ * Disconnect from the Bus.
+ */
+ public void disconnect() {
+ connected = false;
+ if (Debug.debug) Debug.print(Debug.INFO, "Sending disconnected signal");
+ try {
+ handleMessage(new org.freedesktop.DBus.Local.Disconnected("/"));
+ } catch (Exception ee) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, ee);
+ }
+
+ if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Abstract Connection");
+ // run all pending tasks.
+ while (runnables.size() > 0)
+ synchronized (runnables) {
+ runnables.notifyAll();
+ }
+
+ // stop the main thread
+ _run = false;
+
+ // unblock the sending thread.
+ synchronized (outgoing) {
+ outgoing.notifyAll();
+ }
+
+ // disconnect from the trasport layer
+ try {
+ if (null != transport) {
+ transport.disconnect();
+ transport = null;
+ }
+ } catch (IOException IOe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe);
+ }
+
+ // stop all the workers
+ synchronized (workers) {
+ for (_workerthread t : workers)
+ t.halt();
+ }
+
+ // make sure none are blocking on the runnables queue still
+ synchronized (runnables) {
+ runnables.notifyAll();
+ }
+ }
+
+ public void finalize() {
+ disconnect();
+ }
+
+ /**
+ * Return any DBus error which has been received.
+ *
+ * @return A DBusExecutionException, or null if no error is pending.
+ */
+ public DBusExecutionException getError() {
+ synchronized (pendingErrors) {
+ if (pendingErrors.size() == 0) return null;
+ else
+ return pendingErrors.removeFirst().getException();
+ }
+ }
+
+ /**
+ * Call a method asynchronously and set a callback.
+ * This handler will be called in a separate thread.
+ *
+ * @param object The remote object on which to call the method.
+ * @param m The name of the method on the interface to call.
+ * @param callback The callback handler.
+ * @param parameters The parameters to call the method with.
+ */
+ @SuppressWarnings("unchecked")
+ public <A> void callWithCallback(DBusInterface object, String m, CallbackHandler<A> callback, Object... parameters) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "callWithCallback(" + object + "," + m + ", " + callback);
+ Class[] types = new Class[parameters.length];
+ for (int i = 0; i < parameters.length; i++)
+ types[i] = parameters[i].getClass();
+ RemoteObject ro = importedObjects.get(object);
+
+ try {
+ Method me;
+ if (null == ro.iface)
+ me = object.getClass().getMethod(m, types);
+ else
+ me = ro.iface.getMethod(m, types);
+ RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_CALLBACK, callback, parameters);
+ } catch (DBusExecutionException DBEe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe);
+ throw DBEe;
+ } catch (Exception e) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ throw new DBusExecutionException(e.getMessage());
+ }
+ }
+
+ /**
+ * Call a method asynchronously and get a handle with which to get the reply.
+ *
+ * @param object The remote object on which to call the method.
+ * @param m The name of the method on the interface to call.
+ * @param parameters The parameters to call the method with.
+ * @return A handle to the call.
+ */
+ @SuppressWarnings("unchecked")
+ public DBusAsyncReply callMethodAsync(DBusInterface object, String m, Object... parameters) {
+ Class<?>[] types = new Class[parameters.length];
+ for (int i = 0; i < parameters.length; i++)
+ types[i] = parameters[i].getClass();
+ RemoteObject ro = importedObjects.get(object);
+
+ try {
+ Method me;
+ if (null == ro.iface)
+ me = object.getClass().getMethod(m, types);
+ else
+ me = ro.iface.getMethod(m, types);
+ return (DBusAsyncReply) RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_ASYNC, null, parameters);
+ } catch (DBusExecutionException DBEe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe);
+ throw DBEe;
+ } catch (Exception e) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ throw new DBusExecutionException(e.getMessage());
+ }
+ }
+
+ private void handleMessage(final MethodCall m) throws DBusException {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming method call: " + m);
+
+ ExportedObject eo = null;
+ Method meth = null;
+ Object o = null;
+
+ if (null == m.getInterface() ||
+ m.getInterface().equals("org.freedesktop.DBus.Peer") ||
+ m.getInterface().equals("org.freedesktop.DBus.Introspectable")) {
+ synchronized (exportedObjects) {
+ eo = exportedObjects.get(null);
+ }
+ if (null != eo && null == eo.object.get()) {
+ unExportObject(null);
+ eo = null;
+ }
+ if (null != eo) {
+ meth = eo.methods.get(new MethodTuple(m.getName(), m.getSig()));
+ }
+ if (null != meth)
+ o = new _globalhandler(m.getPath());
+ else
+ eo = null;
+ }
+ if (null == o) {
+ // now check for specific exported functions
+
+ synchronized (exportedObjects) {
+ eo = exportedObjects.get(m.getPath());
+ }
+ if (null != eo && null == eo.object.get()) {
+ if (Debug.debug) Debug.print(Debug.INFO, "Unexporting " + m.getPath() + " implicitly");
+ unExportObject(m.getPath());
+ eo = null;
+ }
+
+ if (null == eo) {
+ eo = fallbackcontainer.get(m.getPath());
+ }
+
+ if (null == eo) {
+ try {
+ queueOutgoing(new Error(m, new DBus.Error.UnknownObject(m.getPath() + getString("notObjectProvidedByProcess"))));
+ } catch (DBusException DBe) {
+ }
+ return;
+ }
+ if (Debug.debug) {
+ Debug.print(Debug.VERBOSE, "Searching for method " + m.getName() + " with signature " + m.getSig());
+ Debug.print(Debug.VERBOSE, "List of methods on " + eo + ":");
+ for (MethodTuple mt : eo.methods.keySet())
+ Debug.print(Debug.VERBOSE, " " + mt + " => " + eo.methods.get(mt));
+ }
+ meth = eo.methods.get(new MethodTuple(m.getName(), m.getSig()));
+ if (null == meth) {
+ try {
+ queueOutgoing(new Error(m, new DBus.Error.UnknownMethod(MessageFormat.format(getString("methodDoesNotExist"), new Object[]{m.getInterface(), m.getName()}))));
+ } catch (DBusException DBe) {
+ }
+ return;
+ }
+ o = eo.object.get();
+ }
+
+ // now execute it
+ final Method me = meth;
+ final Object ob = o;
+ final boolean noreply = (1 == (m.getFlags() & Message.Flags.NO_REPLY_EXPECTED));
+ final DBusCallInfo info = new DBusCallInfo(m);
+ final AbstractConnection conn = this;
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for method " + meth);
+ addRunnable(new Runnable() {
+ private boolean run = false;
+
+ public synchronized void run() {
+ if (run) return;
+ run = true;
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Running method " + me + " for remote call");
+ try {
+ Type[] ts = me.getGenericParameterTypes();
+ m.setArgs(Marshalling.deSerializeParameters(m.getParameters(), ts, conn));
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Deserialised " + Arrays.deepToString(m.getParameters()) + " to types " + Arrays.deepToString(ts));
+ } catch (Exception e) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ try {
+ conn.queueOutgoing(new Error(m, new DBus.Error.UnknownMethod(getString("deSerializationFailure") + e)));
+ } catch (DBusException DBe) {
+ }
+ return;
+ }
+
+ try {
+ synchronized (infomap) {
+ infomap.put(Thread.currentThread(), info);
+ }
+ Object result;
+ try {
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Invoking Method: " + me + " on " + ob + " with parameters " + Arrays.deepToString(m.getParameters()));
+ result = me.invoke(ob, m.getParameters());
+ } catch (InvocationTargetException ITe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, ITe.getCause());
+ throw ITe.getCause();
+ }
+ synchronized (infomap) {
+ infomap.remove(Thread.currentThread());
+ }
+ if (!noreply) {
+ MethodReturn reply;
+ if (Void.TYPE.equals(me.getReturnType()))
+ reply = new MethodReturn(m, null);
+ else {
+ StringBuffer sb = new StringBuffer();
+ for (String s : Marshalling.getDBusType(me.getGenericReturnType()))
+ sb.append(s);
+ Object[] nr = Marshalling.convertParameters(new Object[]{result}, new Type[]{me.getGenericReturnType()}, conn);
+
+ reply = new MethodReturn(m, sb.toString(), nr);
+ }
+ conn.queueOutgoing(reply);
+ }
+ } catch (DBusExecutionException DBEe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe);
+ try {
+ conn.queueOutgoing(new Error(m, DBEe));
+ } catch (DBusException DBe) {
+ }
+ } catch (Throwable e) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ try {
+ conn.queueOutgoing(new Error(m, new DBusExecutionException(MessageFormat.format(getString("errorExecutingMethod"), new Object[]{m.getInterface(), m.getName(), e.getMessage()}))));
+ } catch (DBusException DBe) {
+ }
+ }
+ }
+ });
+ }
+
+ @SuppressWarnings({"unchecked", "deprecation"})
+ private void handleMessage(final DBusSignal s) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming signal: " + s);
+ Vector<DBusSigHandler<? extends DBusSignal>> v = new Vector<DBusSigHandler<? extends DBusSignal>>();
+ synchronized (handledSignals) {
+ Vector<DBusSigHandler<? extends DBusSignal>> t;
+ t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), null, null));
+ if (null != t) v.addAll(t);
+ t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), s.getPath(), null));
+ if (null != t) v.addAll(t);
+ t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), null, s.getSource()));
+ if (null != t) v.addAll(t);
+ t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), s.getPath(), s.getSource()));
+ if (null != t) v.addAll(t);
+ }
+ if (0 == v.size()) return;
+ final AbstractConnection conn = this;
+ for (final DBusSigHandler<? extends DBusSignal> h : v) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for signal " + s + " with handler " + h);
+ addRunnable(new Runnable() {
+ private boolean run = false;
+
+ public synchronized void run() {
+ if (run) return;
+ run = true;
+ try {
+ DBusSignal rs;
+ if (s instanceof DBusSignal.internalsig || s.getClass().equals(DBusSignal.class))
+ rs = s.createReal(conn);
+ else
+ rs = s;
+ ((DBusSigHandler<DBusSignal>) h).handle(rs);
+ } catch (DBusException DBe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe);
+ try {
+ conn.queueOutgoing(new Error(s, new DBusExecutionException("Error handling signal " + s.getInterface() + "." + s.getName() + ": " + DBe.getMessage())));
+ } catch (DBusException DBe2) {
+ }
+ }
+ }
+ });
+ }
+ }
+
+ private void handleMessage(final Error err) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming error: " + err);
+ MethodCall m = null;
+ if (null == pendingCalls) return;
+ synchronized (pendingCalls) {
+ if (pendingCalls.contains(err.getReplySerial()))
+ m = pendingCalls.remove(err.getReplySerial());
+ }
+ if (null != m) {
+ m.setReply(err);
+ CallbackHandler cbh = null;
+ DBusAsyncReply asr = null;
+ synchronized (pendingCallbacks) {
+ cbh = pendingCallbacks.remove(m);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, cbh + " = pendingCallbacks.remove(" + m + ")");
+ asr = pendingCallbackReplys.remove(m);
+ }
+ // queue callback for execution
+ if (null != cbh) {
+ final CallbackHandler fcbh = cbh;
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Error Runnable with callback handler " + fcbh);
+ addRunnable(new Runnable() {
+ private boolean run = false;
+
+ public synchronized void run() {
+ if (run) return;
+ run = true;
+ try {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Running Error Callback for " + err);
+ DBusCallInfo info = new DBusCallInfo(err);
+ synchronized (infomap) {
+ infomap.put(Thread.currentThread(), info);
+ }
+
+ fcbh.handleError(err.getException());
+ synchronized (infomap) {
+ infomap.remove(Thread.currentThread());
+ }
+
+ } catch (Exception e) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ }
+ }
+ });
+ }
+
+ } else
+ synchronized (pendingErrors) {
+ pendingErrors.addLast(err);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void handleMessage(final MethodReturn mr) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming method return: " + mr);
+ MethodCall m = null;
+ if (null == pendingCalls) return;
+ synchronized (pendingCalls) {
+ if (pendingCalls.contains(mr.getReplySerial()))
+ m = pendingCalls.remove(mr.getReplySerial());
+ }
+ if (null != m) {
+ m.setReply(mr);
+ mr.setCall(m);
+ CallbackHandler cbh = null;
+ DBusAsyncReply asr = null;
+ synchronized (pendingCallbacks) {
+ cbh = pendingCallbacks.remove(m);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, cbh + " = pendingCallbacks.remove(" + m + ")");
+ asr = pendingCallbackReplys.remove(m);
+ }
+ // queue callback for execution
+ if (null != cbh) {
+ final CallbackHandler fcbh = cbh;
+ final DBusAsyncReply fasr = asr;
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Adding Runnable for method " + fasr.getMethod() + " with callback handler " + fcbh);
+ addRunnable(new Runnable() {
+ private boolean run = false;
+
+ public synchronized void run() {
+ if (run) return;
+ run = true;
+ try {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Running Callback for " + mr);
+ DBusCallInfo info = new DBusCallInfo(mr);
+ synchronized (infomap) {
+ infomap.put(Thread.currentThread(), info);
+ }
+
+ fcbh.handle(RemoteInvocationHandler.convertRV(mr.getSig(), mr.getParameters(), fasr.getMethod(), fasr.getConnection()));
+ synchronized (infomap) {
+ infomap.remove(Thread.currentThread());
+ }
+
+ } catch (Exception e) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ }
+ }
+ });
+ }
+
+ } else
+ try {
+ queueOutgoing(new Error(mr, new DBusExecutionException(getString("spuriousReply"))));
+ } catch (DBusException DBe) {
+ }
+ }
+
+ protected void sendMessage(Message m) {
+ try {
+ if (!connected) throw new NotConnected(getString("disconnected"));
+ if (m instanceof DBusSignal)
+ ((DBusSignal) m).appendbody(this);
+
+ if (m instanceof MethodCall) {
+ if (0 == (m.getFlags() & Message.Flags.NO_REPLY_EXPECTED))
+ if (null == pendingCalls)
+ ((MethodCall) m).setReply(new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.disconnected", 0, "s", new Object[]{getString("disconnected")}));
+ else synchronized (pendingCalls) {
+ pendingCalls.put(m.getSerial(), (MethodCall) m);
+ }
+ }
+
+ transport.mout.writeMessage(m);
+
+ } catch (Exception e) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ if (m instanceof MethodCall && e instanceof NotConnected)
+ try {
+ ((MethodCall) m).setReply(new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.disconnected", 0, "s", new Object[]{getString("disconnected")}));
+ } catch (DBusException DBe) {
+ }
+ if (m instanceof MethodCall && e instanceof DBusExecutionException)
+ try {
+ ((MethodCall) m).setReply(new Error(m, e));
+ } catch (DBusException DBe) {
+ }
+ else if (m instanceof MethodCall)
+ try {
+ if (Debug.debug) Debug.print(Debug.INFO, "Setting reply to " + m + " as an error");
+ ((MethodCall) m).setReply(new Error(m, new DBusExecutionException(getString("messageFailedSend") + e.getMessage())));
+ } catch (DBusException DBe) {
+ }
+ else if (m instanceof MethodReturn)
+ try {
+ transport.mout.writeMessage(new Error(m, e));
+ } catch (IOException IOe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe);
+ } catch (DBusException IOe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ }
+ if (e instanceof IOException) disconnect();
+ }
+ }
+
+ private Message readIncoming() throws DBusException {
+ if (!connected) throw new NotConnected(getString("missingTransport"));
+ Message m = null;
+ try {
+ m = transport.min.readMessage();
+ } catch (IOException IOe) {
+ throw new FatalDBusException(IOe.getMessage());
+ }
+ return m;
+ }
+
+ /**
+ * Returns the address this connection is connected to.
+ */
+ public BusAddress getAddress() throws ParseException {
+ return new BusAddress(addr);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/ArrayFrob.java b/federation/sssd/src/main/java/org/freedesktop/dbus/ArrayFrob.java
new file mode 100644
index 0000000..86dfa52
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/ArrayFrob.java
@@ -0,0 +1,173 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+
+import java.lang.reflect.Array;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.List;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+class ArrayFrob {
+ static Hashtable<Class<? extends Object>, Class<? extends Object>> primitiveToWrapper = new Hashtable<Class<? extends Object>, Class<? extends Object>>();
+ static Hashtable<Class<? extends Object>, Class<? extends Object>> wrapperToPrimitive = new Hashtable<Class<? extends Object>, Class<? extends Object>>();
+
+ static {
+ primitiveToWrapper.put(Boolean.TYPE, Boolean.class);
+ primitiveToWrapper.put(Byte.TYPE, Byte.class);
+ primitiveToWrapper.put(Short.TYPE, Short.class);
+ primitiveToWrapper.put(Character.TYPE, Character.class);
+ primitiveToWrapper.put(Integer.TYPE, Integer.class);
+ primitiveToWrapper.put(Long.TYPE, Long.class);
+ primitiveToWrapper.put(Float.TYPE, Float.class);
+ primitiveToWrapper.put(Double.TYPE, Double.class);
+ wrapperToPrimitive.put(Boolean.class, Boolean.TYPE);
+ wrapperToPrimitive.put(Byte.class, Byte.TYPE);
+ wrapperToPrimitive.put(Short.class, Short.TYPE);
+ wrapperToPrimitive.put(Character.class, Character.TYPE);
+ wrapperToPrimitive.put(Integer.class, Integer.TYPE);
+ wrapperToPrimitive.put(Long.class, Long.TYPE);
+ wrapperToPrimitive.put(Float.class, Float.TYPE);
+ wrapperToPrimitive.put(Double.class, Double.TYPE);
+
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> T[] wrap(Object o) throws IllegalArgumentException {
+ Class<? extends Object> ac = o.getClass();
+ if (!ac.isArray()) throw new IllegalArgumentException(getString("invalidArray"));
+ Class<? extends Object> cc = ac.getComponentType();
+ Class<? extends Object> ncc = primitiveToWrapper.get(cc);
+ if (null == ncc) throw new IllegalArgumentException(getString("notPrimitiveType"));
+ T[] ns = (T[]) Array.newInstance(ncc, Array.getLength(o));
+ for (int i = 0; i < ns.length; i++)
+ ns[i] = (T) Array.get(o, i);
+ return ns;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> Object unwrap(T[] ns) throws IllegalArgumentException {
+ Class<? extends T[]> ac = (Class<? extends T[]>) ns.getClass();
+ Class<T> cc = (Class<T>) ac.getComponentType();
+ Class<? extends Object> ncc = wrapperToPrimitive.get(cc);
+ if (null == ncc) throw new IllegalArgumentException(getString("invalidWrapperType"));
+ Object o = Array.newInstance(ncc, ns.length);
+ for (int i = 0; i < ns.length; i++)
+ Array.set(o, i, ns[i]);
+ return o;
+ }
+
+ public static <T> List<T> listify(T[] ns) throws IllegalArgumentException {
+ return Arrays.asList(ns);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> List<T> listify(Object o) throws IllegalArgumentException {
+ if (o instanceof Object[]) return listify((T[]) o);
+ if (!o.getClass().isArray()) throw new IllegalArgumentException(getString("invalidArray"));
+ List<T> l = new ArrayList<T>(Array.getLength(o));
+ for (int i = 0; i < Array.getLength(o); i++)
+ l.add((T) Array.get(o, i));
+ return l;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> T[] delist(List<T> l, Class<T> c) throws IllegalArgumentException {
+ return l.toArray((T[]) Array.newInstance(c, 0));
+ }
+
+ public static <T> Object delistprimitive(List<T> l, Class<T> c) throws IllegalArgumentException {
+ Object o = Array.newInstance(c, l.size());
+ for (int i = 0; i < l.size(); i++)
+ Array.set(o, i, l.get(i));
+ return o;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Object convert(Object o, Class<? extends Object> c) throws IllegalArgumentException {
+ /* Possible Conversions:
+ *
+ ** List<Integer> -> List<Integer>
+ ** List<Integer> -> int[]
+ ** List<Integer> -> Integer[]
+ ** int[] -> int[]
+ ** int[] -> List<Integer>
+ ** int[] -> Integer[]
+ ** Integer[] -> Integer[]
+ ** Integer[] -> int[]
+ ** Integer[] -> List<Integer>
+ */
+ try {
+ // List<Integer> -> List<Integer>
+ if (List.class.equals(c)
+ && o instanceof List)
+ return o;
+
+ // int[] -> List<Integer>
+ // Integer[] -> List<Integer>
+ if (List.class.equals(c)
+ && o.getClass().isArray())
+ return listify(o);
+
+ // int[] -> int[]
+ // Integer[] -> Integer[]
+ if (o.getClass().isArray()
+ && c.isArray()
+ && o.getClass().getComponentType().equals(c.getComponentType()))
+ return o;
+
+ // int[] -> Integer[]
+ if (o.getClass().isArray()
+ && c.isArray()
+ && o.getClass().getComponentType().isPrimitive())
+ return wrap(o);
+
+ // Integer[] -> int[]
+ if (o.getClass().isArray()
+ && c.isArray()
+ && c.getComponentType().isPrimitive())
+ return unwrap((Object[]) o);
+
+ // List<Integer> -> int[]
+ if (o instanceof List
+ && c.isArray()
+ && c.getComponentType().isPrimitive())
+ return delistprimitive((List<Object>) o, (Class<Object>) c.getComponentType());
+
+ // List<Integer> -> Integer[]
+ if (o instanceof List
+ && c.isArray())
+ return delist((List<Object>) o, (Class<Object>) c.getComponentType());
+
+ if (o.getClass().isArray()
+ && c.isArray())
+ return type((Object[]) o, (Class<Object>) c.getComponentType());
+
+ } catch (Exception e) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ throw new IllegalArgumentException(e);
+ }
+
+ throw new IllegalArgumentException(MessageFormat.format(getString("convertionTypeNotExpected"), new Object[]{o.getClass(), c}));
+ }
+
+ public static Object[] type(Object[] old, Class<Object> c) {
+ Object[] ns = (Object[]) Array.newInstance(c, old.length);
+ for (int i = 0; i < ns.length; i++)
+ ns[i] = old[i];
+ return ns;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/BusAddress.java b/federation/sssd/src/main/java/org/freedesktop/dbus/BusAddress.java
new file mode 100644
index 0000000..3848951
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/BusAddress.java
@@ -0,0 +1,52 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+public class BusAddress {
+ private String type;
+ private Map<String, String> parameters;
+
+ public BusAddress(String address) throws ParseException {
+ if (null == address || "".equals(address)) throw new ParseException(getString("busAddressBlank"), 0);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Parsing bus address: " + address);
+ String[] ss = address.split(":", 2);
+ if (ss.length < 2) throw new ParseException(getString("busAddressInvalid") + address, 0);
+ type = ss[0];
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Transport type: " + type);
+ String[] ps = ss[1].split(",");
+ parameters = new HashMap<String, String>();
+ for (String p : ps) {
+ String[] kv = p.split("=", 2);
+ parameters.put(kv[0], kv[1]);
+ }
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Transport options: " + parameters);
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getParameter(String key) {
+ return parameters.get(key);
+ }
+
+ public String toString() {
+ return type + ": " + parameters;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/CallbackHandler.java b/federation/sssd/src/main/java/org/freedesktop/dbus/CallbackHandler.java
new file mode 100644
index 0000000..0e2a81e
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/CallbackHandler.java
@@ -0,0 +1,22 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+
+/**
+ * Interface for callbacks in async mode
+ */
+public interface CallbackHandler<ReturnType> {
+ public void handle(ReturnType r);
+
+ public void handleError(DBusExecutionException e);
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Container.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Container.java
new file mode 100644
index 0000000..39fd245
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Container.java
@@ -0,0 +1,93 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class is the super class of both Structs and Tuples
+ * and holds common methods.
+ */
+abstract class Container {
+ private static Map<Type, Type[]> typecache = new HashMap<Type, Type[]>();
+
+ static void putTypeCache(Type k, Type[] v) {
+ typecache.put(k, v);
+ }
+
+ static Type[] getTypeCache(Type k) {
+ return typecache.get(k);
+ }
+
+ private Object[] parameters = null;
+
+ public Container() {
+ }
+
+ private void setup() {
+ Field[] fs = getClass().getDeclaredFields();
+ Object[] args = new Object[fs.length];
+
+ int diff = 0;
+ for (Field f : fs) {
+ Position p = f.getAnnotation(Position.class);
+ if (null == p) {
+ diff++;
+ continue;
+ }
+ try {
+ args[p.value()] = f.get(this);
+ } catch (IllegalAccessException IAe) {
+ }
+ }
+
+ this.parameters = new Object[args.length - diff];
+ System.arraycopy(args, 0, parameters, 0, parameters.length);
+ }
+
+ /**
+ * Returns the struct contents in order.
+ *
+ * @throws DBusException If there is a problem doing this.
+ */
+ public final Object[] getParameters() {
+ if (null != parameters) return parameters;
+ setup();
+ return parameters;
+ }
+
+ /**
+ * Returns this struct as a string.
+ */
+ public final String toString() {
+ String s = getClass().getName() + "<";
+ if (null == parameters)
+ setup();
+ if (0 == parameters.length)
+ return s + ">";
+ for (Object o : parameters)
+ s += o + ", ";
+ return s.replaceAll(", $", ">");
+ }
+
+ public final boolean equals(Object other) {
+ if (other instanceof Container) {
+ Container that = (Container) other;
+ if (this.getClass().equals(that.getClass()))
+ return Arrays.equals(this.getParameters(), that.getParameters());
+ else return false;
+ } else return false;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java
new file mode 100644
index 0000000..51c2bfd
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java
@@ -0,0 +1,117 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import org.freedesktop.DBus.Error.NoReply;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+/**
+ * A handle to an asynchronous method call.
+ */
+public class DBusAsyncReply<ReturnType> {
+ /**
+ * Check if any of a set of asynchronous calls have had a reply.
+ *
+ * @param replies A Collection of handles to replies to check.
+ * @return A Collection only containing those calls which have had replies.
+ */
+ public static Collection<DBusAsyncReply<? extends Object>> hasReply(Collection<DBusAsyncReply<? extends Object>> replies) {
+ Collection<DBusAsyncReply<? extends Object>> c = new ArrayList<DBusAsyncReply<? extends Object>>(replies);
+ Iterator<DBusAsyncReply<? extends Object>> i = c.iterator();
+ while (i.hasNext())
+ if (!i.next().hasReply()) i.remove();
+ return c;
+ }
+
+ private ReturnType rval = null;
+ private DBusExecutionException error = null;
+ private MethodCall mc;
+ private Method me;
+ private AbstractConnection conn;
+
+ DBusAsyncReply(MethodCall mc, Method me, AbstractConnection conn) {
+ this.mc = mc;
+ this.me = me;
+ this.conn = conn;
+ }
+
+ @SuppressWarnings("unchecked")
+ private synchronized void checkReply() {
+ if (mc.hasReply()) {
+ Message m = mc.getReply();
+ if (m instanceof Error)
+ error = ((Error) m).getException();
+ else if (m instanceof MethodReturn) {
+ try {
+ rval = (ReturnType) RemoteInvocationHandler.convertRV(m.getSig(), m.getParameters(), me, conn);
+ } catch (DBusExecutionException DBEe) {
+ error = DBEe;
+ } catch (DBusException DBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe);
+ error = new DBusExecutionException(DBe.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * Check if we've had a reply.
+ *
+ * @return True if we have a reply
+ */
+ public boolean hasReply() {
+ if (null != rval || null != error) return true;
+ checkReply();
+ return null != rval || null != error;
+ }
+
+ /**
+ * Get the reply.
+ *
+ * @return The return value from the method.
+ * @throws DBusExecutionException if the reply to the method was an error.
+ * @throws NoReply if the method hasn't had a reply yet
+ */
+ public ReturnType getReply() throws DBusExecutionException {
+ if (null != rval) return rval;
+ else if (null != error) throw error;
+ checkReply();
+ if (null != rval) return rval;
+ else if (null != error) throw error;
+ else throw new NoReply(getString("asyncCallNoReply"));
+ }
+
+ public String toString() {
+ return getString("waitingFor") + mc;
+ }
+
+ Method getMethod() {
+ return me;
+ }
+
+ AbstractConnection getConnection() {
+ return conn;
+ }
+
+ MethodCall getCall() {
+ return mc;
+ }
+}
+
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusCallInfo.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusCallInfo.java
new file mode 100644
index 0000000..1c9d143
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusCallInfo.java
@@ -0,0 +1,79 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+/**
+ * Holds information on a method call
+ */
+public class DBusCallInfo {
+ /**
+ * Indicates the caller won't wait for a reply (and we won't send one).
+ */
+ public static final int NO_REPLY = Message.Flags.NO_REPLY_EXPECTED;
+ public static final int ASYNC = 0x100;
+ private String source;
+ private String destination;
+ private String objectpath;
+ private String iface;
+ private String method;
+ private int flags;
+
+ DBusCallInfo(Message m) {
+ this.source = m.getSource();
+ this.destination = m.getDestination();
+ this.objectpath = m.getPath();
+ this.iface = m.getInterface();
+ this.method = m.getName();
+ this.flags = m.getFlags();
+ }
+
+ /**
+ * Returns the BusID which called the method
+ */
+ public String getSource() {
+ return source;
+ }
+
+ /**
+ * Returns the name with which we were addressed on the Bus
+ */
+ public String getDestination() {
+ return destination;
+ }
+
+ /**
+ * Returns the object path used to call this method
+ */
+ public String getObjectPath() {
+ return objectpath;
+ }
+
+ /**
+ * Returns the interface this method was called with
+ */
+ public String getInterface() {
+ return iface;
+ }
+
+ /**
+ * Returns the method name used to call this method
+ */
+ public String getMethod() {
+ return method;
+ }
+
+ /**
+ * Returns any flags set on this method call
+ */
+ public int getFlags() {
+ return flags;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusConnection.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusConnection.java
new file mode 100644
index 0000000..45e33a4
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusConnection.java
@@ -0,0 +1,794 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import org.freedesktop.DBus;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+import org.freedesktop.dbus.exceptions.NotConnected;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.lang.reflect.Proxy;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Vector;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+/**
+ * Handles a connection to DBus.
+ * <p>
+ * This is a Singleton class, only 1 connection to the SYSTEM or SESSION busses can be made.
+ * Repeated calls to getConnection will return the same reference.
+ * </p>
+ * <p>
+ * Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues.
+ * </p>
+ */
+public class DBusConnection extends AbstractConnection {
+ /**
+ * Add addresses of peers to a set which will watch for them to
+ * disappear and automatically remove them from the set.
+ */
+ public class PeerSet implements Set<String>, DBusSigHandler<DBus.NameOwnerChanged> {
+ private Set<String> addresses;
+
+ public PeerSet() {
+ addresses = new TreeSet<String>();
+ try {
+ addSigHandler(new DBusMatchRule(DBus.NameOwnerChanged.class, null, null), this);
+ } catch (DBusException DBe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe);
+ }
+ }
+
+ public void handle(DBus.NameOwnerChanged noc) {
+ if (Debug.debug)
+ Debug.print(Debug.DEBUG, "Received NameOwnerChanged(" + noc.name + "," + noc.old_owner + "," + noc.new_owner + ")");
+ if ("".equals(noc.new_owner) && addresses.contains(noc.name))
+ remove(noc.name);
+ }
+
+ public boolean add(String address) {
+ if (Debug.debug)
+ Debug.print(Debug.DEBUG, "Adding " + address);
+ synchronized (addresses) {
+ return addresses.add(address);
+ }
+ }
+
+ public boolean addAll(Collection<? extends String> addresses) {
+ synchronized (this.addresses) {
+ return this.addresses.addAll(addresses);
+ }
+ }
+
+ public void clear() {
+ synchronized (addresses) {
+ addresses.clear();
+ }
+ }
+
+ public boolean contains(Object o) {
+ return addresses.contains(o);
+ }
+
+ public boolean containsAll(Collection<?> os) {
+ return addresses.containsAll(os);
+ }
+
+ public boolean equals(Object o) {
+ if (o instanceof PeerSet)
+ return ((PeerSet) o).addresses.equals(addresses);
+ else return false;
+ }
+
+ public int hashCode() {
+ return addresses.hashCode();
+ }
+
+ public boolean isEmpty() {
+ return addresses.isEmpty();
+ }
+
+ public Iterator<String> iterator() {
+ return addresses.iterator();
+ }
+
+ public boolean remove(Object o) {
+ if (Debug.debug)
+ Debug.print(Debug.DEBUG, "Removing " + o);
+ synchronized (addresses) {
+ return addresses.remove(o);
+ }
+ }
+
+ public boolean removeAll(Collection<?> os) {
+ synchronized (addresses) {
+ return addresses.removeAll(os);
+ }
+ }
+
+ public boolean retainAll(Collection<?> os) {
+ synchronized (addresses) {
+ return addresses.retainAll(os);
+ }
+ }
+
+ public int size() {
+ return addresses.size();
+ }
+
+ public Object[] toArray() {
+ synchronized (addresses) {
+ return addresses.toArray();
+ }
+ }
+
+ public <T> T[] toArray(T[] a) {
+ synchronized (addresses) {
+ return addresses.toArray(a);
+ }
+ }
+ }
+
+ private class _sighandler implements DBusSigHandler<DBusSignal> {
+ public void handle(DBusSignal s) {
+ if (s instanceof org.freedesktop.DBus.Local.Disconnected) {
+ if (Debug.debug) Debug.print(Debug.WARN, "Handling disconnected signal from bus");
+ try {
+ Error err = new Error(
+ "org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.disconnected", 0, "s", new Object[]{getString("disconnected")});
+ if (null != pendingCalls) synchronized (pendingCalls) {
+ long[] set = pendingCalls.getKeys();
+ for (long l : set)
+ if (-1 != l) {
+ MethodCall m = pendingCalls.remove(l);
+ if (null != m)
+ m.setReply(err);
+ }
+ }
+ synchronized (pendingErrors) {
+ pendingErrors.add(err);
+ }
+ } catch (DBusException DBe) {
+ }
+ } else if (s instanceof org.freedesktop.DBus.NameAcquired) {
+ busnames.add(((org.freedesktop.DBus.NameAcquired) s).name);
+ }
+ }
+ }
+
+ /**
+ * System Bus
+ */
+ public static final int SYSTEM = 0;
+ /**
+ * Session Bus
+ */
+ public static final int SESSION = 1;
+
+ public static final String DEFAULT_SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket";
+
+ private List<String> busnames;
+
+ private static final Map<Object, DBusConnection> conn = new HashMap<Object, DBusConnection>();
+ private int _refcount = 0;
+ private Object _reflock = new Object();
+ private DBus _dbus;
+
+ /**
+ * Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned.
+ *
+ * @param address The address of the bus to connect to
+ * @throws DBusException If there is a problem connecting to the Bus.
+ */
+ public static DBusConnection getConnection(String address) throws DBusException {
+ synchronized (conn) {
+ DBusConnection c = conn.get(address);
+ if (null != c) {
+ synchronized (c._reflock) {
+ c._refcount++;
+ }
+ return c;
+ } else {
+ c = new DBusConnection(address);
+ conn.put(address, c);
+ return c;
+ }
+ }
+ }
+
+ /**
+ * Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned.
+ *
+ * @param bustype The Bus to connect to.
+ * @throws DBusException If there is a problem connecting to the Bus.
+ * @see #SYSTEM
+ * @see #SESSION
+ */
+ public static DBusConnection getConnection(int bustype) throws DBusException {
+ synchronized (conn) {
+ String s = null;
+ switch (bustype) {
+ case SYSTEM:
+ s = System.getenv("DBUS_SYSTEM_BUS_ADDRESS");
+ if (null == s) s = DEFAULT_SYSTEM_BUS_ADDRESS;
+ break;
+ case SESSION:
+ s = System.getenv("DBUS_SESSION_BUS_ADDRESS");
+ if (null == s) {
+ // address gets stashed in $HOME/.dbus/session-bus/`dbus-uuidgen --get`-`sed 's/:\(.\)\..*/\1/' <<< $DISPLAY`
+ String display = System.getenv("DISPLAY");
+ if (null == display) throw new DBusException(getString("cannotResolveSessionBusAddress"));
+ File uuidfile = new File("/var/lib/dbus/machine-id");
+ if (!uuidfile.exists()) throw new DBusException(getString("cannotResolveSessionBusAddress"));
+ try {
+ BufferedReader r = new BufferedReader(new FileReader(uuidfile));
+ String uuid = r.readLine();
+ String homedir = System.getProperty("user.home");
+ File addressfile = new File(homedir + "/.dbus/session-bus",
+ uuid + "-" + display.replaceAll(":([0-9]*)\\..*", "$1"));
+ if (!addressfile.exists())
+ throw new DBusException(getString("cannotResolveSessionBusAddress"));
+ r = new BufferedReader(new FileReader(addressfile));
+ String l;
+ while (null != (l = r.readLine())) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading D-Bus session data: " + l);
+ if (l.matches("DBUS_SESSION_BUS_ADDRESS.*")) {
+ s = l.replaceAll("^[^=]*=", "");
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Parsing " + l + " to " + s);
+ }
+ }
+ if (null == s || "".equals(s))
+ throw new DBusException(getString("cannotResolveSessionBusAddress"));
+ if (Debug.debug)
+ Debug.print(Debug.INFO, "Read bus address " + s + " from file " + addressfile.toString());
+ } catch (Exception e) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ throw new DBusException(getString("cannotResolveSessionBusAddress"));
+ }
+ }
+ break;
+ default:
+ throw new DBusException(getString("invalidBusType") + bustype);
+ }
+ DBusConnection c = conn.get(s);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Getting bus connection for " + s + ": " + c);
+ if (null != c) {
+ synchronized (c._reflock) {
+ c._refcount++;
+ }
+ return c;
+ } else {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Creating new bus connection to: " + s);
+ c = new DBusConnection(s);
+ conn.put(s, c);
+ return c;
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private DBusConnection(String address) throws DBusException {
+ super(address);
+ busnames = new Vector<String>();
+
+ synchronized (_reflock) {
+ _refcount = 1;
+ }
+
+ try {
+ transport = new Transport(addr, AbstractConnection.TIMEOUT);
+ connected = true;
+ } catch (IOException IOe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe);
+ disconnect();
+ throw new DBusException(getString("connectionFailure") + IOe.getMessage());
+ } catch (ParseException Pe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Pe);
+ disconnect();
+ throw new DBusException(getString("connectionFailure") + Pe.getMessage());
+ }
+
+ // start listening for calls
+ listen();
+
+ // register disconnect handlers
+ DBusSigHandler h = new _sighandler();
+ addSigHandlerWithoutMatch(org.freedesktop.DBus.Local.Disconnected.class, h);
+ addSigHandlerWithoutMatch(org.freedesktop.DBus.NameAcquired.class, h);
+
+ // register ourselves
+ _dbus = getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class);
+ try {
+ busnames.add(_dbus.Hello());
+ } catch (DBusExecutionException DBEe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe);
+ throw new DBusException(DBEe.getMessage());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ DBusInterface dynamicProxy(String source, String path) throws DBusException {
+ if (Debug.debug)
+ Debug.print(Debug.INFO, "Introspecting " + path + " on " + source + " for dynamic proxy creation");
+ try {
+ DBus.Introspectable intro = getRemoteObject(source, path, DBus.Introspectable.class);
+ String data = intro.Introspect();
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Got introspection data: " + data);
+ String[] tags = data.split("[<>]");
+ Vector<String> ifaces = new Vector<String>();
+ for (String tag : tags) {
+ if (tag.startsWith("interface")) {
+ ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1"));
+ }
+ }
+ Vector<Class<? extends Object>> ifcs = new Vector<Class<? extends Object>>();
+ for (String iface : ifaces) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Trying interface " + iface);
+ int j = 0;
+ while (j >= 0) {
+ try {
+ Class ifclass = Class.forName(iface);
+ if (!ifcs.contains(ifclass))
+ ifcs.add(ifclass);
+ break;
+ } catch (Exception e) {
+ }
+ j = iface.lastIndexOf(".");
+ char[] cs = iface.toCharArray();
+ if (j >= 0) {
+ cs[j] = '$';
+ iface = String.valueOf(cs);
+ }
+ }
+ }
+
+ if (ifcs.size() == 0) throw new DBusException(getString("interfaceToCastNotFound"));
+
+ RemoteObject ro = new RemoteObject(source, path, null, false);
+ DBusInterface newi = (DBusInterface)
+ Proxy.newProxyInstance(ifcs.get(0).getClassLoader(),
+ ifcs.toArray(new Class[0]),
+ new RemoteInvocationHandler(this, ro));
+ importedObjects.put(newi, ro);
+ return newi;
+ } catch (Exception e) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ throw new DBusException(MessageFormat.format(getString("createProxyExportFailure"), new Object[]{path, source, e.getMessage()}));
+ }
+ }
+
+ DBusInterface getExportedObject(String source, String path) throws DBusException {
+ ExportedObject o = null;
+ synchronized (exportedObjects) {
+ o = exportedObjects.get(path);
+ }
+ if (null != o && null == o.object.get()) {
+ unExportObject(path);
+ o = null;
+ }
+ if (null != o) return o.object.get();
+ if (null == source) throw new DBusException(getString("objectNotExportedNoRemoteSpecified"));
+ return dynamicProxy(source, path);
+ }
+
+ /**
+ * Release a bus name.
+ * Releases the name so that other people can use it
+ *
+ * @param busname The name to release. MUST be in dot-notation like "org.freedesktop.local"
+ * @throws DBusException If the busname is incorrectly formatted.
+ */
+ public void releaseBusName(String busname) throws DBusException {
+ if (!busname.matches(BUSNAME_REGEX) || busname.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidBusName"));
+ synchronized (this.busnames) {
+ UInt32 rv;
+ try {
+ rv = _dbus.ReleaseName(busname);
+ } catch (DBusExecutionException DBEe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe);
+ throw new DBusException(DBEe.getMessage());
+ }
+ this.busnames.remove(busname);
+ }
+ }
+
+ /**
+ * Request a bus name.
+ * Request the well known name that this should respond to on the Bus.
+ *
+ * @param busname The name to respond to. MUST be in dot-notation like "org.freedesktop.local"
+ * @throws DBusException If the register name failed, or our name already exists on the bus.
+ * or if busname is incorrectly formatted.
+ */
+ public void requestBusName(String busname) throws DBusException {
+ if (!busname.matches(BUSNAME_REGEX) || busname.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidBusName"));
+ synchronized (this.busnames) {
+ UInt32 rv;
+ try {
+ rv = _dbus.RequestName(busname,
+ new UInt32(DBus.DBUS_NAME_FLAG_REPLACE_EXISTING |
+ DBus.DBUS_NAME_FLAG_DO_NOT_QUEUE));
+ } catch (DBusExecutionException DBEe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe);
+ throw new DBusException(DBEe.getMessage());
+ }
+ switch (rv.intValue()) {
+ case DBus.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
+ break;
+ case DBus.DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
+ throw new DBusException(getString("dbusRegistrationFailure"));
+ case DBus.DBUS_REQUEST_NAME_REPLY_EXISTS:
+ throw new DBusException(getString("dbusRegistrationFailure"));
+ case DBus.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER:
+ break;
+ default:
+ break;
+ }
+ this.busnames.add(busname);
+ }
+ }
+
+ /**
+ * Returns the unique name of this connection.
+ */
+ public String getUniqueName() {
+ return busnames.get(0);
+ }
+
+ /**
+ * Returns all the names owned by this connection.
+ */
+ public String[] getNames() {
+ Set<String> names = new TreeSet<String>();
+ names.addAll(busnames);
+ return names.toArray(new String[0]);
+ }
+
+ public <I extends DBusInterface> I getPeerRemoteObject(String busname, String objectpath, Class<I> type) throws DBusException {
+ return getPeerRemoteObject(busname, objectpath, type, true);
+ }
+
+ /**
+ * Return a reference to a remote object.
+ * This method will resolve the well known name (if given) to a unique bus name when you call it.
+ * This means that if a well known name is released by one process and acquired by another calls to
+ * objects gained from this method will continue to operate on the original process.
+ * <p>
+ * This method will use bus introspection to determine the interfaces on a remote object and so
+ * <b>may block</b> and <b>may fail</b>. The resulting proxy object will, however, be castable
+ * to any interface it implements. It will also autostart the process if applicable. Also note
+ * that the resulting proxy may fail to execute the correct method with overloaded methods
+ * and that complex types may fail in interesting ways. Basically, if something odd happens,
+ * try specifying the interface explicitly.
+ *
+ * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local")
+ * or may be a DBus address such as ":1-16".
+ * @param objectpath The path on which the process is exporting the object.$
+ * @return A reference to a remote object.
+ * @throws ClassCastException If type is not a sub-type of DBusInterface
+ * @throws DBusException If busname or objectpath are incorrectly formatted.
+ */
+ public DBusInterface getPeerRemoteObject(String busname, String objectpath) throws DBusException {
+ if (null == busname) throw new DBusException(getString("nullBusName"));
+
+ if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX))
+ || busname.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidBusName") + busname);
+
+ String unique = _dbus.GetNameOwner(busname);
+
+ return dynamicProxy(unique, objectpath);
+ }
+
+ /**
+ * Return a reference to a remote object.
+ * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name.
+ * In particular this means that if a process providing the well known name disappears and is taken over by another process
+ * proxy objects gained by this method will make calls on the new proccess.
+ * <p>
+ * This method will use bus introspection to determine the interfaces on a remote object and so
+ * <b>may block</b> and <b>may fail</b>. The resulting proxy object will, however, be castable
+ * to any interface it implements. It will also autostart the process if applicable. Also note
+ * that the resulting proxy may fail to execute the correct method with overloaded methods
+ * and that complex types may fail in interesting ways. Basically, if something odd happens,
+ * try specifying the interface explicitly.
+ *
+ * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local")
+ * or may be a DBus address such as ":1-16".
+ * @param objectpath The path on which the process is exporting the object.
+ * @return A reference to a remote object.
+ * @throws ClassCastException If type is not a sub-type of DBusInterface
+ * @throws DBusException If busname or objectpath are incorrectly formatted.
+ */
+ public DBusInterface getRemoteObject(String busname, String objectpath) throws DBusException {
+ if (null == busname) throw new DBusException(getString("nullBusName"));
+ if (null == objectpath) throw new DBusException(getString("nullObjectPath"));
+
+ if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX))
+ || busname.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidBusName") + busname);
+
+ if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidObjectPath") + objectpath);
+
+ return dynamicProxy(busname, objectpath);
+ }
+
+ /**
+ * Return a reference to a remote object.
+ * This method will resolve the well known name (if given) to a unique bus name when you call it.
+ * This means that if a well known name is released by one process and acquired by another calls to
+ * objects gained from this method will continue to operate on the original process.
+ *
+ * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local")
+ * or may be a DBus address such as ":1-16".
+ * @param objectpath The path on which the process is exporting the object.$
+ * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures
+ * as the interface the remote object is exporting.
+ * @param autostart Disable/Enable auto-starting of services in response to calls on this object.
+ * Default is enabled; when calling a method with auto-start enabled, if the destination is a well-known name
+ * and is not owned the bus will attempt to start a process to take the name. When disabled an error is
+ * returned immediately.
+ * @return A reference to a remote object.
+ * @throws ClassCastException If type is not a sub-type of DBusInterface
+ * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package.
+ */
+ public <I extends DBusInterface> I getPeerRemoteObject(String busname, String objectpath, Class<I> type, boolean autostart) throws DBusException {
+ if (null == busname) throw new DBusException(getString("nullBusName"));
+
+ if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX))
+ || busname.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidBusName") + busname);
+
+ String unique = _dbus.GetNameOwner(busname);
+
+ return getRemoteObject(unique, objectpath, type, autostart);
+ }
+
+ /**
+ * Return a reference to a remote object.
+ * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name.
+ * In particular this means that if a process providing the well known name disappears and is taken over by another process
+ * proxy objects gained by this method will make calls on the new proccess.
+ *
+ * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local")
+ * or may be a DBus address such as ":1-16".
+ * @param objectpath The path on which the process is exporting the object.
+ * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures
+ * as the interface the remote object is exporting.
+ * @return A reference to a remote object.
+ * @throws ClassCastException If type is not a sub-type of DBusInterface
+ * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package.
+ */
+ public <I extends DBusInterface> I getRemoteObject(String busname, String objectpath, Class<I> type) throws DBusException {
+ return getRemoteObject(busname, objectpath, type, true);
+ }
+
+ /**
+ * Return a reference to a remote object.
+ * This method will always refer to the well known name (if given) rather than resolving it to a unique bus name.
+ * In particular this means that if a process providing the well known name disappears and is taken over by another process
+ * proxy objects gained by this method will make calls on the new proccess.
+ *
+ * @param busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local")
+ * or may be a DBus address such as ":1-16".
+ * @param objectpath The path on which the process is exporting the object.
+ * @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures
+ * as the interface the remote object is exporting.
+ * @param autostart Disable/Enable auto-starting of services in response to calls on this object.
+ * Default is enabled; when calling a method with auto-start enabled, if the destination is a well-known name
+ * and is not owned the bus will attempt to start a process to take the name. When disabled an error is
+ * returned immediately.
+ * @return A reference to a remote object.
+ * @throws ClassCastException If type is not a sub-type of DBusInterface
+ * @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package.
+ */
+ @SuppressWarnings("unchecked")
+ public <I extends DBusInterface> I getRemoteObject(String busname, String objectpath, Class<I> type, boolean autostart) throws DBusException {
+ if (null == busname) throw new DBusException(getString("nullBusName"));
+ if (null == objectpath) throw new DBusException(getString("nullObjectPath"));
+ if (null == type) throw new ClassCastException(getString("notDBusInterface"));
+
+ if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX))
+ || busname.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidBusName") + busname);
+
+ if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidObjectPath") + objectpath);
+
+ if (!DBusInterface.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusInterface"));
+
+ // don't let people import things which don't have a
+ // valid D-Bus interface name
+ if (type.getName().equals(type.getSimpleName()))
+ throw new DBusException(getString("interfaceNotAllowedOutsidePackage"));
+
+ RemoteObject ro = new RemoteObject(busname, objectpath, type, autostart);
+ I i = (I) Proxy.newProxyInstance(type.getClassLoader(),
+ new Class[]{type}, new RemoteInvocationHandler(this, ro));
+ importedObjects.put(i, ro);
+ return i;
+ }
+
+ /**
+ * Remove a Signal Handler.
+ * Stops listening for this signal.
+ *
+ * @param type The signal to watch for.
+ * @param source The source of the signal.
+ * @throws DBusException If listening for the signal on the bus failed.
+ * @throws ClassCastException If type is not a sub-type of DBusSignal.
+ */
+ public <T extends DBusSignal> void removeSigHandler(Class<T> type, String source, DBusSigHandler<T> handler) throws DBusException {
+ if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal"));
+ if (source.matches(BUSNAME_REGEX)) throw new DBusException(getString("cannotWatchSignalsWellKnownBussName"));
+ if (!source.matches(CONNID_REGEX) || source.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidBusName") + source);
+ removeSigHandler(new DBusMatchRule(type, source, null), handler);
+ }
+
+ /**
+ * Remove a Signal Handler.
+ * Stops listening for this signal.
+ *
+ * @param type The signal to watch for.
+ * @param source The source of the signal.
+ * @param object The object emitting the signal.
+ * @throws DBusException If listening for the signal on the bus failed.
+ * @throws ClassCastException If type is not a sub-type of DBusSignal.
+ */
+ public <T extends DBusSignal> void removeSigHandler(Class<T> type, String source, DBusInterface object, DBusSigHandler<T> handler) throws DBusException {
+ if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal"));
+ if (source.matches(BUSNAME_REGEX)) throw new DBusException(getString("cannotWatchSignalsWellKnownBussName"));
+ if (!source.matches(CONNID_REGEX) || source.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidBusName") + source);
+ String objectpath = importedObjects.get(object).objectpath;
+ if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidObjectPath") + objectpath);
+ removeSigHandler(new DBusMatchRule(type, source, objectpath), handler);
+ }
+
+ protected <T extends DBusSignal> void removeSigHandler(DBusMatchRule rule, DBusSigHandler<T> handler) throws DBusException {
+
+ SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource());
+ synchronized (handledSignals) {
+ Vector<DBusSigHandler<? extends DBusSignal>> v = handledSignals.get(key);
+ if (null != v) {
+ v.remove(handler);
+ if (0 == v.size()) {
+ handledSignals.remove(key);
+ try {
+ _dbus.RemoveMatch(rule.toString());
+ } catch (NotConnected NC) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, NC);
+ } catch (DBusExecutionException DBEe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe);
+ throw new DBusException(DBEe.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Add a Signal Handler.
+ * Adds a signal handler to call when a signal is received which matches the specified type, name and source.
+ *
+ * @param type The signal to watch for.
+ * @param source The process which will send the signal. This <b>MUST</b> be a unique bus name and not a well known name.
+ * @param handler The handler to call when a signal is received.
+ * @throws DBusException If listening for the signal on the bus failed.
+ * @throws ClassCastException If type is not a sub-type of DBusSignal.
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends DBusSignal> void addSigHandler(Class<T> type, String source, DBusSigHandler<T> handler) throws DBusException {
+ if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal"));
+ if (source.matches(BUSNAME_REGEX)) throw new DBusException(getString("cannotWatchSignalsWellKnownBussName"));
+ if (!source.matches(CONNID_REGEX) || source.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidBusName") + source);
+ addSigHandler(new DBusMatchRule(type, source, null), (DBusSigHandler<? extends DBusSignal>) handler);
+ }
+
+ /**
+ * Add a Signal Handler.
+ * Adds a signal handler to call when a signal is received which matches the specified type, name, source and object.
+ *
+ * @param type The signal to watch for.
+ * @param source The process which will send the signal. This <b>MUST</b> be a unique bus name and not a well known name.
+ * @param object The object from which the signal will be emitted
+ * @param handler The handler to call when a signal is received.
+ * @throws DBusException If listening for the signal on the bus failed.
+ * @throws ClassCastException If type is not a sub-type of DBusSignal.
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends DBusSignal> void addSigHandler(Class<T> type, String source, DBusInterface object, DBusSigHandler<T> handler) throws DBusException {
+ if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException(getString("notDBusSignal"));
+ if (source.matches(BUSNAME_REGEX)) throw new DBusException(getString("cannotWatchSignalsWellKnownBussName"));
+ if (!source.matches(CONNID_REGEX) || source.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidBusName") + source);
+ String objectpath = importedObjects.get(object).objectpath;
+ if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH)
+ throw new DBusException(getString("invalidObjectPath") + objectpath);
+ addSigHandler(new DBusMatchRule(type, source, objectpath), (DBusSigHandler<? extends DBusSignal>) handler);
+ }
+
+ protected <T extends DBusSignal> void addSigHandler(DBusMatchRule rule, DBusSigHandler<T> handler) throws DBusException {
+ try {
+ _dbus.AddMatch(rule.toString());
+ } catch (DBusExecutionException DBEe) {
+ if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe);
+ throw new DBusException(DBEe.getMessage());
+ }
+ SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource());
+ synchronized (handledSignals) {
+ Vector<DBusSigHandler<? extends DBusSignal>> v = handledSignals.get(key);
+ if (null == v) {
+ v = new Vector<DBusSigHandler<? extends DBusSignal>>();
+ v.add(handler);
+ handledSignals.put(key, v);
+ } else
+ v.add(handler);
+ }
+ }
+
+ /**
+ * Disconnect from the Bus.
+ * This only disconnects when the last reference to the bus has disconnect called on it
+ * or has been destroyed.
+ */
+ public void disconnect() {
+ synchronized (conn) {
+ synchronized (_reflock) {
+ if (0 == --_refcount) {
+ if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting DBusConnection");
+ // Set all pending messages to have an error.
+ try {
+ Error err = new Error(
+ "org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.disconnected", 0, "s", new Object[]{getString("disconnected")});
+ synchronized (pendingCalls) {
+ long[] set = pendingCalls.getKeys();
+ for (long l : set)
+ if (-1 != l) {
+ MethodCall m = pendingCalls.remove(l);
+ if (null != m)
+ m.setReply(err);
+ }
+ pendingCalls = null;
+ }
+ synchronized (pendingErrors) {
+ pendingErrors.add(err);
+ }
+ } catch (DBusException DBe) {
+ }
+
+ conn.remove(addr);
+ super.disconnect();
+ }
+ }
+ }
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterface.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterface.java
new file mode 100644
index 0000000..b9e404c
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterface.java
@@ -0,0 +1,31 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+/**
+ * Denotes a class as exportable or a remote interface which can be called.
+ * <p>
+ * Any interface which should be exported or imported should extend this
+ * interface. All public methods from that interface are exported/imported
+ * with the given method signatures.
+ * </p>
+ * <p>
+ * All method calls on exported objects are run in their own threads.
+ * Application writers are responsible for any concurrency issues.
+ * </p>
+ */
+public interface DBusInterface {
+ /**
+ * Returns true on remote objects.
+ * Local objects implementing this interface MUST return false.
+ */
+ public boolean isRemote();
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java
new file mode 100644
index 0000000..0fc7056
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java
@@ -0,0 +1,28 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Force the interface name to be different to the Java class name.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DBusInterfaceName {
+ /**
+ * The replacement interface name.
+ */
+ String value();
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMap.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMap.java
new file mode 100644
index 0000000..6590e62
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMap.java
@@ -0,0 +1,150 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Vector;
+
+class DBusMap<K, V> implements Map<K, V> {
+ Object[][] entries;
+
+ public DBusMap(Object[][] entries) {
+ this.entries = entries;
+ }
+
+ class Entry implements Map.Entry<K, V>, Comparable<Entry> {
+ private int entry;
+
+ public Entry(int i) {
+ this.entry = i;
+ }
+
+ public boolean equals(Object o) {
+ if (null == o) return false;
+ if (!(o instanceof DBusMap.Entry)) return false;
+ return this.entry == ((Entry) o).entry;
+ }
+
+ @SuppressWarnings("unchecked")
+ public K getKey() {
+ return (K) entries[entry][0];
+ }
+
+ @SuppressWarnings("unchecked")
+ public V getValue() {
+ return (V) entries[entry][1];
+ }
+
+ public int hashCode() {
+ return entries[entry][0].hashCode();
+ }
+
+ public V setValue(V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int compareTo(Entry e) {
+ return entry - e.entry;
+ }
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean containsKey(Object key) {
+ for (int i = 0; i < entries.length; i++)
+ if (key == entries[i][0] || (key != null && key.equals(entries[i][0])))
+ return true;
+ return false;
+ }
+
+ public boolean containsValue(Object value) {
+ for (int i = 0; i < entries.length; i++)
+ if (value == entries[i][1] || (value != null && value.equals(entries[i][1])))
+ return true;
+ return false;
+ }
+
+ public Set<Map.Entry<K, V>> entrySet() {
+ Set<Map.Entry<K, V>> s = new TreeSet<Map.Entry<K, V>>();
+ for (int i = 0; i < entries.length; i++)
+ s.add(new Entry(i));
+ return s;
+ }
+
+ @SuppressWarnings("unchecked")
+ public V get(Object key) {
+ for (int i = 0; i < entries.length; i++)
+ if (key == entries[i][0] || (key != null && key.equals(entries[i][0])))
+ return (V) entries[i][1];
+ return null;
+ }
+
+ public boolean isEmpty() {
+ return entries.length == 0;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Set<K> keySet() {
+ Set<K> s = new TreeSet<K>();
+ for (Object[] entry : entries)
+ s.add((K) entry[0]);
+ return s;
+ }
+
+ public V put(K key, V value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void putAll(Map<? extends K, ? extends V> t) {
+ throw new UnsupportedOperationException();
+ }
+
+ public V remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public int size() {
+ return entries.length;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Collection<V> values() {
+ List<V> l = new Vector<V>();
+ for (Object[] entry : entries)
+ l.add((V) entry[1]);
+ return l;
+ }
+
+ public int hashCode() {
+ return Arrays.deepHashCode(entries);
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object o) {
+ if (null == o) return false;
+ if (!(o instanceof Map)) return false;
+ return ((Map<K, V>) o).entrySet().equals(entrySet());
+ }
+
+ public String toString() {
+ String s = "{ ";
+ for (int i = 0; i < entries.length; i++)
+ s += entries[i][0] + " => " + entries[i][1] + ",";
+ return s.replaceAll(".$", " }");
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMatchRule.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMatchRule.java
new file mode 100644
index 0000000..fa886c0
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMatchRule.java
@@ -0,0 +1,151 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+
+import java.util.HashMap;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+public class DBusMatchRule {
+ /* signal, error, method_call, method_reply */
+ private String type;
+ private String iface;
+ private String member;
+ private String object;
+ private String source;
+ private static HashMap<String, Class<? extends DBusSignal>> signalTypeMap =
+ new HashMap<String, Class<? extends DBusSignal>>();
+
+ static Class<? extends DBusSignal> getCachedSignalType(String type) {
+ return signalTypeMap.get(type);
+ }
+
+ public DBusMatchRule(String type, String iface, String member) {
+ this.type = type;
+ this.iface = iface;
+ this.member = member;
+ }
+
+ public DBusMatchRule(DBusExecutionException e) throws DBusException {
+ this(e.getClass());
+ member = null;
+ type = "error";
+ }
+
+ public DBusMatchRule(Message m) {
+ iface = m.getInterface();
+ member = m.getName();
+ if (m instanceof DBusSignal)
+ type = "signal";
+ else if (m instanceof Error) {
+ type = "error";
+ member = null;
+ } else if (m instanceof MethodCall)
+ type = "method_call";
+ else if (m instanceof MethodReturn)
+ type = "method_reply";
+ }
+
+ public DBusMatchRule(Class<? extends DBusInterface> c, String method) throws DBusException {
+ this(c);
+ member = method;
+ type = "method_call";
+ }
+
+ public DBusMatchRule(Class<? extends Object> c, String source, String object) throws DBusException {
+ this(c);
+ this.source = source;
+ this.object = object;
+ }
+
+ @SuppressWarnings("unchecked")
+ public DBusMatchRule(Class<? extends Object> c) throws DBusException {
+ if (DBusInterface.class.isAssignableFrom(c)) {
+ if (null != c.getAnnotation(DBusInterfaceName.class))
+ iface = c.getAnnotation(DBusInterfaceName.class).value();
+ else
+ iface = AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll(".");
+ if (!iface.matches(".*\\..*"))
+ throw new DBusException(getString("interfaceMustBeDefinedPackage"));
+ member = null;
+ type = null;
+ } else if (DBusSignal.class.isAssignableFrom(c)) {
+ if (null == c.getEnclosingClass())
+ throw new DBusException(getString("signalsMustBeMemberOfClass"));
+ else if (null != c.getEnclosingClass().getAnnotation(DBusInterfaceName.class))
+ iface = c.getEnclosingClass().getAnnotation(DBusInterfaceName.class).value();
+ else
+ iface = AbstractConnection.dollar_pattern.matcher(c.getEnclosingClass().getName()).replaceAll(".");
+ // Don't export things which are invalid D-Bus interfaces
+ if (!iface.matches(".*\\..*"))
+ throw new DBusException(getString("interfaceMustBeDefinedPackage"));
+ if (c.isAnnotationPresent(DBusMemberName.class))
+ member = c.getAnnotation(DBusMemberName.class).value();
+ else
+ member = c.getSimpleName();
+ signalTypeMap.put(iface + '$' + member, (Class<? extends DBusSignal>) c);
+ type = "signal";
+ } else if (Error.class.isAssignableFrom(c)) {
+ if (null != c.getAnnotation(DBusInterfaceName.class))
+ iface = c.getAnnotation(DBusInterfaceName.class).value();
+ else
+ iface = AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll(".");
+ if (!iface.matches(".*\\..*"))
+ throw new DBusException(getString("interfaceMustBeDefinedPackage"));
+ member = null;
+ type = "error";
+ } else if (DBusExecutionException.class.isAssignableFrom(c)) {
+ if (null != c.getClass().getAnnotation(DBusInterfaceName.class))
+ iface = c.getClass().getAnnotation(DBusInterfaceName.class).value();
+ else
+ iface = AbstractConnection.dollar_pattern.matcher(c.getClass().getName()).replaceAll(".");
+ if (!iface.matches(".*\\..*"))
+ throw new DBusException(getString("interfaceMustBeDefinedPackage"));
+ member = null;
+ type = "error";
+ } else
+ throw new DBusException(getString("invalidTypeMatchRule") + c);
+ }
+
+ public String toString() {
+ String s = null;
+ if (null != type) s = null == s ? "type='" + type + "'" : s + ",type='" + type + "'";
+ if (null != member) s = null == s ? "member='" + member + "'" : s + ",member='" + member + "'";
+ if (null != iface) s = null == s ? "interface='" + iface + "'" : s + ",interface='" + iface + "'";
+ if (null != source) s = null == s ? "sender='" + source + "'" : s + ",sender='" + source + "'";
+ if (null != object) s = null == s ? "path='" + object + "'" : s + ",path='" + object + "'";
+ return s;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getInterface() {
+ return iface;
+ }
+
+ public String getMember() {
+ return member;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public String getObject() {
+ return object;
+ }
+
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMemberName.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMemberName.java
new file mode 100644
index 0000000..25d30d7
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusMemberName.java
@@ -0,0 +1,29 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Force the member (method/signal) name on the bus to be different to the Java name.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface DBusMemberName {
+ /**
+ * The replacement member name.
+ */
+ String value();
+}
+
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSerializable.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSerializable.java
new file mode 100644
index 0000000..8d9fea5
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSerializable.java
@@ -0,0 +1,39 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+
+/**
+ * Custom classes may be sent over DBus if they implement this interface.
+ * <p>
+ * In addition to the serialize method, classes <b>MUST</b> implement
+ * a deserialize method which returns null and takes as it's arguments
+ * all the DBus types the class will be serialied to <i>in order</i> and
+ * <i>with type parameterisation</i>. They <b>MUST</b> also provide a
+ * zero-argument constructor.
+ * </p>
+ * <p>
+ * The serialize method should return the class properties you wish to
+ * serialize, correctly formatted for the wire
+ * (DBusConnection.convertParameters() can help with this), in order in an
+ * Object array.
+ * </p>
+ * <p>
+ * The deserialize method will be called once after the zero-argument
+ * constructor. This should contain all the code to initialise the object
+ * from the types.
+ * </p>
+ */
+public interface DBusSerializable {
+ public Object[] serialize() throws DBusException;
+}
+
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSigHandler.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSigHandler.java
new file mode 100644
index 0000000..6e40a82
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSigHandler.java
@@ -0,0 +1,27 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+/**
+ * Handle a signal on DBus.
+ * All Signal handlers are run in their own Thread.
+ * Application writers are responsible for managing any concurrency issues.
+ */
+public interface DBusSigHandler<T extends DBusSignal> {
+ /**
+ * Handle a signal.
+ *
+ * @param s The signal to handle. If such a class exists, the
+ * signal will be an instance of the class with the correct type signature.
+ * Otherwise it will be an instance of DBusSignal
+ */
+ public void handle(T s);
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSignal.java b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSignal.java
new file mode 100644
index 0000000..96ba6b3
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/DBusSignal.java
@@ -0,0 +1,259 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.MessageFormatException;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+public class DBusSignal extends Message {
+ DBusSignal() {
+ }
+
+ public DBusSignal(String source, String path, String iface, String member, String sig, Object... args) throws DBusException {
+ super(Message.Endian.BIG, Message.MessageType.SIGNAL, (byte) 0);
+
+ if (null == path || null == member || null == iface)
+ throw new MessageFormatException(getString("missingPathInterfaceSignal"));
+ headers.put(Message.HeaderField.PATH, path);
+ headers.put(Message.HeaderField.MEMBER, member);
+ headers.put(Message.HeaderField.INTERFACE, iface);
+
+ Vector<Object> hargs = new Vector<Object>();
+ hargs.add(new Object[]{Message.HeaderField.PATH, new Object[]{ArgumentType.OBJECT_PATH_STRING, path}});
+ hargs.add(new Object[]{Message.HeaderField.INTERFACE, new Object[]{ArgumentType.STRING_STRING, iface}});
+ hargs.add(new Object[]{Message.HeaderField.MEMBER, new Object[]{ArgumentType.STRING_STRING, member}});
+
+ if (null != source) {
+ headers.put(Message.HeaderField.SENDER, source);
+ hargs.add(new Object[]{Message.HeaderField.SENDER, new Object[]{ArgumentType.STRING_STRING, source}});
+ }
+
+ if (null != sig) {
+ hargs.add(new Object[]{Message.HeaderField.SIGNATURE, new Object[]{ArgumentType.SIGNATURE_STRING, sig}});
+ headers.put(Message.HeaderField.SIGNATURE, sig);
+ setArgs(args);
+ }
+
+ blen = new byte[4];
+ appendBytes(blen);
+ append("ua(yv)", ++serial, hargs.toArray());
+ pad((byte) 8);
+
+ long c = bytecounter;
+ if (null != sig) append(sig, args);
+ marshallint(bytecounter - c, blen, 0, 4);
+ bodydone = true;
+ }
+
+ static class internalsig extends DBusSignal {
+ public internalsig(String source, String objectpath, String type, String name, String sig, Object[] parameters, long serial) throws DBusException {
+ super(source, objectpath, type, name, sig, parameters, serial);
+ }
+ }
+
+ private static Map<Class<? extends DBusSignal>, Type[]> typeCache = new HashMap<Class<? extends DBusSignal>, Type[]>();
+ private static Map<String, Class<? extends DBusSignal>> classCache = new HashMap<String, Class<? extends DBusSignal>>();
+ private static Map<Class<? extends DBusSignal>, Constructor<? extends DBusSignal>> conCache = new HashMap<Class<? extends DBusSignal>, Constructor<? extends DBusSignal>>();
+ private static Map<String, String> signames = new HashMap<String, String>();
+ private static Map<String, String> intnames = new HashMap<String, String>();
+ private Class<? extends DBusSignal> c;
+ private boolean bodydone = false;
+ private byte[] blen;
+
+ static void addInterfaceMap(String java, String dbus) {
+ intnames.put(dbus, java);
+ }
+
+ static void addSignalMap(String java, String dbus) {
+ signames.put(dbus, java);
+ }
+
+ static DBusSignal createSignal(Class<? extends DBusSignal> c, String source, String objectpath, String sig, long serial, Object... parameters) throws DBusException {
+ String type = "";
+ if (null != c.getEnclosingClass()) {
+ if (null != c.getEnclosingClass().getAnnotation(DBusInterfaceName.class))
+ type = c.getEnclosingClass().getAnnotation(DBusInterfaceName.class).value();
+ else
+ type = AbstractConnection.dollar_pattern.matcher(c.getEnclosingClass().getName()).replaceAll(".");
+
+ } else
+ throw new DBusException(getString("signalsMustBeMemberOfClass"));
+ DBusSignal s = new internalsig(source, objectpath, type, c.getSimpleName(), sig, parameters, serial);
+ s.c = c;
+ return s;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Class<? extends DBusSignal> createSignalClass(String intname, String signame) throws DBusException {
+ String name = intname + '$' + signame;
+ Class<? extends DBusSignal> c = classCache.get(name);
+ if (null == c) c = DBusMatchRule.getCachedSignalType(name);
+ if (null != c) return c;
+ do {
+ try {
+ c = (Class<? extends DBusSignal>) Class.forName(name);
+ } catch (ClassNotFoundException CNFe) {
+ }
+ name = name.replaceAll("\\.([^\\.]*)$", "\\$$1");
+ } while (null == c && name.matches(".*\\..*"));
+ if (null == c)
+ throw new DBusException(getString("cannotCreateClassFromSignal") + intname + '.' + signame);
+ classCache.put(name, c);
+ return c;
+ }
+
+ @SuppressWarnings("unchecked")
+ DBusSignal createReal(AbstractConnection conn) throws DBusException {
+ String intname = intnames.get(getInterface());
+ String signame = signames.get(getName());
+ if (null == intname) intname = getInterface();
+ if (null == signame) signame = getName();
+ if (null == c)
+ c = createSignalClass(intname, signame);
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Converting signal to type: " + c);
+ Type[] types = typeCache.get(c);
+ Constructor<? extends DBusSignal> con = conCache.get(c);
+ if (null == types) {
+ con = (Constructor<? extends DBusSignal>) c.getDeclaredConstructors()[0];
+ conCache.put(c, con);
+ Type[] ts = con.getGenericParameterTypes();
+ types = new Type[ts.length - 1];
+ for (int i = 1; i < ts.length; i++)
+ if (ts[i] instanceof TypeVariable)
+ for (Type b : ((TypeVariable<GenericDeclaration>) ts[i]).getBounds())
+ types[i - 1] = b;
+ else
+ types[i - 1] = ts[i];
+ typeCache.put(c, types);
+ }
+
+ try {
+ DBusSignal s;
+ Object[] args = Marshalling.deSerializeParameters(getParameters(), types, conn);
+ if (null == args) s = (DBusSignal) con.newInstance(getPath());
+ else {
+ Object[] params = new Object[args.length + 1];
+ params[0] = getPath();
+ System.arraycopy(args, 0, params, 1, args.length);
+
+ if (Debug.debug)
+ Debug.print(Debug.DEBUG, "Creating signal of type " + c + " with parameters " + Arrays.deepToString(params));
+ s = (DBusSignal) con.newInstance(params);
+ }
+ s.headers = headers;
+ s.wiredata = wiredata;
+ s.bytecounter = wiredata.length;
+ return s;
+ } catch (Exception e) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ throw new DBusException(e.getMessage());
+ }
+ }
+
+ /**
+ * Create a new signal.
+ * This contructor MUST be called by all sub classes.
+ *
+ * @param objectpath The path to the object this is emitted from.
+ * @param args The parameters of the signal.
+ * @throws DBusException This is thrown if the subclass is incorrectly defined.
+ */
+ @SuppressWarnings("unchecked")
+ protected DBusSignal(String objectpath, Object... args) throws DBusException {
+ super(Message.Endian.BIG, Message.MessageType.SIGNAL, (byte) 0);
+
+ if (!objectpath.matches(AbstractConnection.OBJECT_REGEX))
+ throw new DBusException(getString("invalidObjectPath") + objectpath);
+
+ Class<? extends DBusSignal> tc = getClass();
+ String member;
+ if (tc.isAnnotationPresent(DBusMemberName.class))
+ member = tc.getAnnotation(DBusMemberName.class).value();
+ else
+ member = tc.getSimpleName();
+ String iface = null;
+ Class<? extends Object> enc = tc.getEnclosingClass();
+ if (null == enc ||
+ !DBusInterface.class.isAssignableFrom(enc) ||
+ enc.getName().equals(enc.getSimpleName()))
+ throw new DBusException(getString("signalsMustBeMemberOfClass"));
+ else if (null != enc.getAnnotation(DBusInterfaceName.class))
+ iface = enc.getAnnotation(DBusInterfaceName.class).value();
+ else
+ iface = AbstractConnection.dollar_pattern.matcher(enc.getName()).replaceAll(".");
+
+ headers.put(Message.HeaderField.PATH, objectpath);
+ headers.put(Message.HeaderField.MEMBER, member);
+ headers.put(Message.HeaderField.INTERFACE, iface);
+
+ Vector<Object> hargs = new Vector<Object>();
+ hargs.add(new Object[]{Message.HeaderField.PATH, new Object[]{ArgumentType.OBJECT_PATH_STRING, objectpath}});
+ hargs.add(new Object[]{Message.HeaderField.INTERFACE, new Object[]{ArgumentType.STRING_STRING, iface}});
+ hargs.add(new Object[]{Message.HeaderField.MEMBER, new Object[]{ArgumentType.STRING_STRING, member}});
+
+ String sig = null;
+ if (0 < args.length) {
+ try {
+ Type[] types = typeCache.get(tc);
+ if (null == types) {
+ Constructor<? extends DBusSignal> con = (Constructor<? extends DBusSignal>) tc.getDeclaredConstructors()[0];
+ conCache.put(tc, con);
+ Type[] ts = con.getGenericParameterTypes();
+ types = new Type[ts.length - 1];
+ for (int i = 1; i <= types.length; i++)
+ if (ts[i] instanceof TypeVariable)
+ types[i - 1] = ((TypeVariable<GenericDeclaration>) ts[i]).getBounds()[0];
+ else
+ types[i - 1] = ts[i];
+ typeCache.put(tc, types);
+ }
+ sig = Marshalling.getDBusType(types);
+ hargs.add(new Object[]{Message.HeaderField.SIGNATURE, new Object[]{ArgumentType.SIGNATURE_STRING, sig}});
+ headers.put(Message.HeaderField.SIGNATURE, sig);
+ setArgs(args);
+ } catch (Exception e) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ throw new DBusException(getString("errorAddSignalParameters") + e.getMessage());
+ }
+ }
+
+ blen = new byte[4];
+ appendBytes(blen);
+ append("ua(yv)", ++serial, hargs.toArray());
+ pad((byte) 8);
+ }
+
+ void appendbody(AbstractConnection conn) throws DBusException {
+ if (bodydone) return;
+
+ Type[] types = typeCache.get(getClass());
+ Object[] args = Marshalling.convertParameters(getParameters(), types, conn);
+ setArgs(args);
+ String sig = getSig();
+
+ long c = bytecounter;
+ if (null != args && 0 < args.length) append(sig, args);
+ marshallint(bytecounter - c, blen, 0, 4);
+ bodydone = true;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientMap.java b/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientMap.java
new file mode 100644
index 0000000..32cc7bb
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientMap.java
@@ -0,0 +1,122 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+/**
+ * Provides a long => MethodCall map which doesn't allocate objects
+ * on insertion/removal. Keys must be inserted in ascending order.
+ */
+class EfficientMap {
+ private long[] kv;
+ private MethodCall[] vv;
+ private int start;
+ private int end;
+ private int init_size;
+
+ public EfficientMap(int initial_size) {
+ init_size = initial_size;
+ shrink();
+ }
+
+ private void grow() {
+ // create new vectors twice as long
+ long[] oldkv = kv;
+ kv = new long[oldkv.length * 2];
+ MethodCall[] oldvv = vv;
+ vv = new MethodCall[oldvv.length * 2];
+
+ // copy start->length to the start of the new vector
+ System.arraycopy(oldkv, start, kv, 0, oldkv.length - start);
+ System.arraycopy(oldvv, start, vv, 0, oldvv.length - start);
+ // copy 0->end to the next part of the new vector
+ if (end != (oldkv.length - 1)) {
+ System.arraycopy(oldkv, 0, kv, oldkv.length - start, end + 1);
+ System.arraycopy(oldvv, 0, vv, oldvv.length - start, end + 1);
+ }
+ // reposition pointers
+ start = 0;
+ end = oldkv.length;
+ }
+
+ // create a new vector with just the valid keys in and return it
+ public long[] getKeys() {
+ int size;
+ if (start < end) size = end - start;
+ else size = kv.length - (start - end);
+ long[] lv = new long[size];
+ int copya;
+ if (size > kv.length - start) copya = kv.length - start;
+ else copya = size;
+ System.arraycopy(kv, start, lv, 0, copya);
+ if (copya < size) {
+ System.arraycopy(kv, 0, lv, copya, size - copya);
+ }
+ return lv;
+ }
+
+ private void shrink() {
+ if (null != kv && kv.length == init_size) return;
+ // reset to original size
+ kv = new long[init_size];
+ vv = new MethodCall[init_size];
+ start = 0;
+ end = 0;
+ }
+
+ public void put(long l, MethodCall m) {
+ // put this at the end
+ kv[end] = l;
+ vv[end] = m;
+ // move the end
+ if (end == (kv.length - 1)) end = 0;
+ else end++;
+ // if we are out of space, grow.
+ if (end == start) grow();
+ }
+
+ public MethodCall remove(long l) {
+ // find the item
+ int pos = find(l);
+ // if we don't have it return null
+ if (-1 == pos) return null;
+ // get the value
+ MethodCall m = vv[pos];
+ // set it as unused
+ vv[pos] = null;
+ kv[pos] = -1;
+ // move the pointer to the first full element
+ while (-1 == kv[start]) {
+ if (start == (kv.length - 1)) start = 0;
+ else start++;
+ // if we have emptied the list, shrink it
+ if (start == end) {
+ shrink();
+ break;
+ }
+ }
+ return m;
+ }
+
+ public boolean contains(long l) {
+ // check if find succeeds
+ return -1 != find(l);
+ }
+
+ /* could binary search, but it's probably the first one */
+ private int find(long l) {
+ int i = start;
+ while (i != end && kv[i] != l)
+ if (i == (kv.length - 1)) i = 0;
+ else i++;
+ if (i == end) return -1;
+ return i;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientQueue.java b/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientQueue.java
new file mode 100644
index 0000000..a60c887
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/EfficientQueue.java
@@ -0,0 +1,109 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+
+/**
+ * Provides a Message queue which doesn't allocate objects
+ * on insertion/removal.
+ */
+class EfficientQueue {
+ private Message[] mv;
+ private int start;
+ private int end;
+ private int init_size;
+
+ public EfficientQueue(int initial_size) {
+ init_size = initial_size;
+ shrink();
+ }
+
+ private void grow() {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Growing");
+ // create new vectors twice as long
+ Message[] oldmv = mv;
+ mv = new Message[oldmv.length * 2];
+
+ // copy start->length to the start of the new vector
+ System.arraycopy(oldmv, start, mv, 0, oldmv.length - start);
+ // copy 0->end to the next part of the new vector
+ if (end != (oldmv.length - 1)) {
+ System.arraycopy(oldmv, 0, mv, oldmv.length - start, end + 1);
+ }
+ // reposition pointers
+ start = 0;
+ end = oldmv.length;
+ }
+
+ // create a new vector with just the valid keys in and return it
+ public Message[] getKeys() {
+ if (start == end) return new Message[0];
+ Message[] lv;
+ if (start < end) {
+ int size = end - start;
+ lv = new Message[size];
+ System.arraycopy(mv, start, lv, 0, size);
+ } else {
+ int size = mv.length - start + end;
+ lv = new Message[size];
+ System.arraycopy(mv, start, lv, 0, mv.length - start);
+ System.arraycopy(mv, 0, lv, mv.length - start, end);
+ }
+ return lv;
+ }
+
+ private void shrink() {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Shrinking");
+ if (null != mv && mv.length == init_size) return;
+ // reset to original size
+ mv = new Message[init_size];
+ start = 0;
+ end = 0;
+ }
+
+ public void add(Message m) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Enqueueing Message " + m);
+ // put this at the end
+ mv[end] = m;
+ // move the end
+ if (end == (mv.length - 1)) end = 0;
+ else end++;
+ // if we are out of space, grow.
+ if (end == start) grow();
+ }
+
+ public Message remove() {
+ if (start == end) return null;
+ // find the item
+ int pos = start;
+ // get the value
+ Message m = mv[pos];
+ // set it as unused
+ mv[pos] = null;
+ if (start == (mv.length - 1)) start = 0;
+ else start++;
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Dequeueing " + m);
+ return m;
+ }
+
+ public boolean isEmpty() {
+ // check if find succeeds
+ return start == end;
+ }
+
+ public int size() {
+ if (end >= start)
+ return end - start;
+ else
+ return mv.length - start + end;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Error.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Error.java
new file mode 100644
index 0000000..3350e95
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Error.java
@@ -0,0 +1,144 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+import org.freedesktop.dbus.exceptions.MessageFormatException;
+import org.freedesktop.dbus.exceptions.NotConnected;
+
+import java.lang.reflect.Constructor;
+import java.util.Vector;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+/**
+ * Error messages which can be sent over the bus.
+ */
+public class Error extends Message {
+ Error() {
+ }
+
+ public Error(String dest, String errorName, long replyserial, String sig, Object... args) throws DBusException {
+ this(null, dest, errorName, replyserial, sig, args);
+ }
+
+ public Error(String source, String dest, String errorName, long replyserial, String sig, Object... args) throws DBusException {
+ super(Message.Endian.BIG, Message.MessageType.ERROR, (byte) 0);
+
+ if (null == errorName)
+ throw new MessageFormatException(getString("missingErrorName"));
+ headers.put(Message.HeaderField.REPLY_SERIAL, replyserial);
+ headers.put(Message.HeaderField.ERROR_NAME, errorName);
+
+ Vector<Object> hargs = new Vector<Object>();
+ hargs.add(new Object[]{Message.HeaderField.ERROR_NAME, new Object[]{ArgumentType.STRING_STRING, errorName}});
+ hargs.add(new Object[]{Message.HeaderField.REPLY_SERIAL, new Object[]{ArgumentType.UINT32_STRING, replyserial}});
+
+ if (null != source) {
+ headers.put(Message.HeaderField.SENDER, source);
+ hargs.add(new Object[]{Message.HeaderField.SENDER, new Object[]{ArgumentType.STRING_STRING, source}});
+ }
+
+ if (null != dest) {
+ headers.put(Message.HeaderField.DESTINATION, dest);
+ hargs.add(new Object[]{Message.HeaderField.DESTINATION, new Object[]{ArgumentType.STRING_STRING, dest}});
+ }
+
+ if (null != sig) {
+ hargs.add(new Object[]{Message.HeaderField.SIGNATURE, new Object[]{ArgumentType.SIGNATURE_STRING, sig}});
+ headers.put(Message.HeaderField.SIGNATURE, sig);
+ setArgs(args);
+ }
+
+ byte[] blen = new byte[4];
+ appendBytes(blen);
+ append("ua(yv)", serial, hargs.toArray());
+ pad((byte) 8);
+
+ long c = bytecounter;
+ if (null != sig) append(sig, args);
+ marshallint(bytecounter - c, blen, 0, 4);
+ }
+
+ public Error(String source, Message m, Throwable e) throws DBusException {
+ this(source, m.getSource(), AbstractConnection.dollar_pattern.matcher(e.getClass().getName()).replaceAll("."), m.getSerial(), "s", e.getMessage());
+ }
+
+ public Error(Message m, Throwable e) throws DBusException {
+ this(m.getSource(), AbstractConnection.dollar_pattern.matcher(e.getClass().getName()).replaceAll("."), m.getSerial(), "s", e.getMessage());
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Class<? extends DBusExecutionException> createExceptionClass(String name) {
+ if (name == "org.freedesktop.DBus.Local.disconnected") return NotConnected.class;
+ Class<? extends DBusExecutionException> c = null;
+ do {
+ try {
+ c = (Class<? extends org.freedesktop.dbus.exceptions.DBusExecutionException>) Class.forName(name);
+ } catch (ClassNotFoundException CNFe) {
+ }
+ name = name.replaceAll("\\.([^\\.]*)$", "\\$$1");
+ } while (null == c && name.matches(".*\\..*"));
+ return c;
+ }
+
+ /**
+ * Turns this into an exception of the correct type
+ */
+ public DBusExecutionException getException() {
+ try {
+ Class<? extends DBusExecutionException> c = createExceptionClass(getName());
+ if (null == c || !DBusExecutionException.class.isAssignableFrom(c)) c = DBusExecutionException.class;
+ Constructor<? extends DBusExecutionException> con = c.getConstructor(String.class);
+ DBusExecutionException ex;
+ Object[] args = getParameters();
+ if (null == args || 0 == args.length)
+ ex = con.newInstance("");
+ else {
+ String s = "";
+ for (Object o : args)
+ s += o + " ";
+ ex = con.newInstance(s.trim());
+ }
+ ex.setType(getName());
+ return ex;
+ } catch (Exception e) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug && null != e.getCause())
+ Debug.print(Debug.ERR, e.getCause());
+ DBusExecutionException ex;
+ Object[] args = null;
+ try {
+ args = getParameters();
+ } catch (Exception ee) {
+ }
+ if (null == args || 0 == args.length)
+ ex = new DBusExecutionException("");
+ else {
+ String s = "";
+ for (Object o : args)
+ s += o + " ";
+ ex = new DBusExecutionException(s.trim());
+ }
+ ex.setType(getName());
+ return ex;
+ }
+ }
+
+ /**
+ * Throw this as an exception of the correct type
+ */
+ public void throwException() throws DBusExecutionException {
+ throw getException();
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java
new file mode 100644
index 0000000..d9c7e93
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java
@@ -0,0 +1,24 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+/**
+ * An exception within DBus.
+ */
+@SuppressWarnings("serial")
+public class DBusException extends Exception {
+ /**
+ * Create an exception with the specified message
+ */
+ public DBusException(String message) {
+ super(message);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java
new file mode 100644
index 0000000..641a967
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java
@@ -0,0 +1,39 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+/**
+ * An exception while running a remote method within DBus.
+ */
+@SuppressWarnings("serial")
+public class DBusExecutionException extends RuntimeException {
+ private String type;
+
+ /**
+ * Create an exception with the specified message
+ */
+ public DBusExecutionException(String message) {
+ super(message);
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * Get the DBus type of this exception. Use if this
+ * was an exception we don't have a class file for.
+ */
+ public String getType() {
+ if (null == type) return getClass().getName();
+ else return type;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java
new file mode 100644
index 0000000..90002de
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java
@@ -0,0 +1,18 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+@SuppressWarnings("serial")
+public class FatalDBusException extends DBusException implements FatalException {
+ public FatalDBusException(String message) {
+ super(message);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java
new file mode 100644
index 0000000..58e3220
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java
@@ -0,0 +1,14 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+public interface FatalException {
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java
new file mode 100644
index 0000000..ab9ecc1
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java
@@ -0,0 +1,18 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+@SuppressWarnings("serial")
+public class InternalMessageException extends DBusExecutionException implements NonFatalException {
+ public InternalMessageException(String message) {
+ super(message);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java
new file mode 100644
index 0000000..3635456
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java
@@ -0,0 +1,18 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+@SuppressWarnings("serial")
+public class MarshallingException extends DBusException implements NonFatalException {
+ public MarshallingException(String message) {
+ super(message);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java
new file mode 100644
index 0000000..e8a1938
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java
@@ -0,0 +1,21 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+/**
+ * Thrown if a message is formatted incorrectly.
+ */
+@SuppressWarnings("serial")
+public class MessageFormatException extends DBusException implements NonFatalException {
+ public MessageFormatException(String message) {
+ super(message);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java
new file mode 100644
index 0000000..c093b41
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java
@@ -0,0 +1,20 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+import java.io.IOException;
+
+@SuppressWarnings("serial")
+public class MessageProtocolVersionException extends IOException implements FatalException {
+ public MessageProtocolVersionException(String message) {
+ super(message);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java
new file mode 100644
index 0000000..21028e1
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java
@@ -0,0 +1,20 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+import java.io.IOException;
+
+@SuppressWarnings("serial")
+public class MessageTypeException extends IOException implements NonFatalException {
+ public MessageTypeException(String message) {
+ super(message);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java
new file mode 100644
index 0000000..e6a0f36
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java
@@ -0,0 +1,14 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+public interface NonFatalException {
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java
new file mode 100644
index 0000000..0610c20
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java
@@ -0,0 +1,21 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+/**
+ * Thrown if a DBus action is called when not connected to the Bus.
+ */
+@SuppressWarnings("serial")
+public class NotConnected extends DBusExecutionException implements FatalException {
+ public NotConnected(String message) {
+ super(message);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java
new file mode 100644
index 0000000..af35075
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java
@@ -0,0 +1,20 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.exceptions;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+@SuppressWarnings("serial")
+public class UnknownTypeCodeException extends DBusException implements NonFatalException {
+ public UnknownTypeCodeException(byte code) {
+ super(getString("invalidDBusCode") + code);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/ExportedObject.java b/federation/sssd/src/main/java/org/freedesktop/dbus/ExportedObject.java
new file mode 100644
index 0000000..b9719a1
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/ExportedObject.java
@@ -0,0 +1,165 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+
+import java.lang.annotation.Annotation;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+class ExportedObject {
+ @SuppressWarnings("unchecked")
+ private String getAnnotations(AnnotatedElement c) {
+ String ans = "";
+ for (Annotation a : c.getDeclaredAnnotations()) {
+ Class t = a.annotationType();
+ String value = "";
+ try {
+ Method m = t.getMethod("value");
+ value = m.invoke(a).toString();
+ } catch (NoSuchMethodException NSMe) {
+ } catch (InvocationTargetException ITe) {
+ } catch (IllegalAccessException IAe) {
+ }
+
+ ans += " <annotation name=\"" + AbstractConnection.dollar_pattern.matcher(t.getName()).replaceAll(".") + "\" value=\"" + value + "\" />\n";
+ }
+ return ans;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<MethodTuple, Method> getExportedMethods(Class c) throws DBusException {
+ if (DBusInterface.class.equals(c)) return new HashMap<MethodTuple, Method>();
+ Map<MethodTuple, Method> m = new HashMap<MethodTuple, Method>();
+ for (Class i : c.getInterfaces())
+ if (DBusInterface.class.equals(i)) {
+ // add this class's public methods
+ if (null != c.getAnnotation(DBusInterfaceName.class)) {
+ String name = ((DBusInterfaceName) c.getAnnotation(DBusInterfaceName.class)).value();
+ introspectiondata += " <interface name=\"" + name + "\">\n";
+ DBusSignal.addInterfaceMap(c.getName(), name);
+ } else {
+ // don't let people export things which don't have a
+ // valid D-Bus interface name
+ if (c.getName().equals(c.getSimpleName()))
+ throw new DBusException(getString("interfaceNotAllowedOutsidePackage"));
+ if (c.getName().length() > DBusConnection.MAX_NAME_LENGTH)
+ throw new DBusException(getString("introspectInterfaceExceedCharacters") + c.getName());
+ else
+ introspectiondata += " <interface name=\"" + AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll(".") + "\">\n";
+ }
+ introspectiondata += getAnnotations(c);
+ for (Method meth : c.getDeclaredMethods())
+ if (Modifier.isPublic(meth.getModifiers())) {
+ String ms = "";
+ String name;
+ if (meth.isAnnotationPresent(DBusMemberName.class))
+ name = meth.getAnnotation(DBusMemberName.class).value();
+ else
+ name = meth.getName();
+ if (name.length() > DBusConnection.MAX_NAME_LENGTH)
+ throw new DBusException(getString("introspectMethodExceedCharacters") + name);
+ introspectiondata += " <method name=\"" + name + "\" >\n";
+ introspectiondata += getAnnotations(meth);
+ for (Class ex : meth.getExceptionTypes())
+ if (DBusExecutionException.class.isAssignableFrom(ex))
+ introspectiondata +=
+ " <annotation name=\"org.freedesktop.DBus.Method.Error\" value=\"" + AbstractConnection.dollar_pattern.matcher(ex.getName()).replaceAll(".") + "\" />\n";
+ for (Type pt : meth.getGenericParameterTypes())
+ for (String s : Marshalling.getDBusType(pt)) {
+ introspectiondata += " <arg type=\"" + s + "\" direction=\"in\"/>\n";
+ ms += s;
+ }
+ if (!Void.TYPE.equals(meth.getGenericReturnType())) {
+ if (Tuple.class.isAssignableFrom((Class) meth.getReturnType())) {
+ ParameterizedType tc = (ParameterizedType) meth.getGenericReturnType();
+ Type[] ts = tc.getActualTypeArguments();
+
+ for (Type t : ts)
+ if (t != null)
+ for (String s : Marshalling.getDBusType(t))
+ introspectiondata += " <arg type=\"" + s + "\" direction=\"out\"/>\n";
+ } else if (Object[].class.equals(meth.getGenericReturnType())) {
+ throw new DBusException(getString("cannotIntrospectReturnType"));
+ } else
+ for (String s : Marshalling.getDBusType(meth.getGenericReturnType()))
+ introspectiondata += " <arg type=\"" + s + "\" direction=\"out\"/>\n";
+ }
+ introspectiondata += " </method>\n";
+ m.put(new MethodTuple(name, ms), meth);
+ }
+ for (Class sig : c.getDeclaredClasses())
+ if (DBusSignal.class.isAssignableFrom(sig)) {
+ String name;
+ if (sig.isAnnotationPresent(DBusMemberName.class)) {
+ name = ((DBusMemberName) sig.getAnnotation(DBusMemberName.class)).value();
+ DBusSignal.addSignalMap(sig.getSimpleName(), name);
+ } else
+ name = sig.getSimpleName();
+ if (name.length() > DBusConnection.MAX_NAME_LENGTH)
+ throw new DBusException(getString("introspectSignalExceedCharacters") + name);
+ introspectiondata += " <signal name=\"" + name + "\">\n";
+ Constructor con = sig.getConstructors()[0];
+ Type[] ts = con.getGenericParameterTypes();
+ for (int j = 1; j < ts.length; j++)
+ for (String s : Marshalling.getDBusType(ts[j]))
+ introspectiondata += " <arg type=\"" + s + "\" direction=\"out\" />\n";
+ introspectiondata += getAnnotations(sig);
+ introspectiondata += " </signal>\n";
+
+ }
+ introspectiondata += " </interface>\n";
+ } else {
+ // recurse
+ m.putAll(getExportedMethods(i));
+ }
+ return m;
+ }
+
+ Map<MethodTuple, Method> methods;
+ Reference<DBusInterface> object;
+ String introspectiondata;
+
+ public ExportedObject(DBusInterface object, boolean weakreferences) throws DBusException {
+ if (weakreferences)
+ this.object = new WeakReference<DBusInterface>(object);
+ else
+ this.object = new StrongReference<DBusInterface>(object);
+ introspectiondata = "";
+ methods = getExportedMethods(object.getClass());
+ introspectiondata +=
+ " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" +
+ " <method name=\"Introspect\">\n" +
+ " <arg type=\"s\" direction=\"out\"/>\n" +
+ " </method>\n" +
+ " </interface>\n";
+ introspectiondata +=
+ " <interface name=\"org.freedesktop.DBus.Peer\">\n" +
+ " <method name=\"Ping\">\n" +
+ " </method>\n" +
+ " </interface>\n";
+ }
+}
+
+
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Gettext.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Gettext.java
new file mode 100644
index 0000000..a519a6f
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Gettext.java
@@ -0,0 +1,30 @@
+/*
+ * Pescetti Pseudo-Duplimate Generator
+ *
+ * Copyright (C) 2007 Matthew Johnson
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License Version 2 as published by
+ * the Free Software Foundation. This program is distributed in the hope that
+ * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details. You should have received a
+ * copy of the GNU Lesser General Public License along with this program; if not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * To Contact me, please email src@matthew.ath.cx
+ *
+ */
+package org.freedesktop.dbus;
+
+import java.util.ResourceBundle;
+
+public class Gettext {
+ private static ResourceBundle myResources =
+ ResourceBundle.getBundle("en_US");
+
+ public static String getString(String s) {
+ return myResources.getString(s);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/InternalSignal.java b/federation/sssd/src/main/java/org/freedesktop/dbus/InternalSignal.java
new file mode 100644
index 0000000..0a3072d
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/InternalSignal.java
@@ -0,0 +1,20 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+
+class InternalSignal extends DBusSignal {
+ public InternalSignal(String source, String objectpath, String name, String iface, String sig, long serial, Object... parameters) throws DBusException {
+ super(objectpath, iface, name, sig, parameters);
+ this.serial = serial;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Marshalling.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Marshalling.java
new file mode 100644
index 0000000..9e88309
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Marshalling.java
@@ -0,0 +1,629 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.types.DBusListType;
+import org.freedesktop.dbus.types.DBusMapType;
+import org.freedesktop.dbus.types.DBusStructType;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+/**
+ * Contains static methods for marshalling values.
+ */
+public class Marshalling {
+ private static Map<Type, String[]> typeCache = new HashMap<Type, String[]>();
+
+ /**
+ * Will return the DBus type corresponding to the given Java type.
+ * Note, container type should have their ParameterizedType not their
+ * Class passed in here.
+ *
+ * @param c The Java types.
+ * @return The DBus types.
+ * @throws DBusException If the given type cannot be converted to a DBus type.
+ */
+ public static String getDBusType(Type[] c) throws DBusException {
+ StringBuffer sb = new StringBuffer();
+ for (Type t : c)
+ for (String s : getDBusType(t))
+ sb.append(s);
+ return sb.toString();
+ }
+
+ /**
+ * Will return the DBus type corresponding to the given Java type.
+ * Note, container type should have their ParameterizedType not their
+ * Class passed in here.
+ *
+ * @param c The Java type.
+ * @return The DBus type.
+ * @throws DBusException If the given type cannot be converted to a DBus type.
+ */
+ public static String[] getDBusType(Type c) throws DBusException {
+ String[] cached = typeCache.get(c);
+ if (null != cached) return cached;
+ cached = getDBusType(c, false);
+ typeCache.put(c, cached);
+ return cached;
+ }
+
+ /**
+ * Will return the DBus type corresponding to the given Java type.
+ * Note, container type should have their ParameterizedType not their
+ * Class passed in here.
+ *
+ * @param c The Java type.
+ * @param basic If true enforces this to be a non-compound type. (compound types are Maps, Structs and Lists/arrays).
+ * @return The DBus type.
+ * @throws DBusException If the given type cannot be converted to a DBus type.
+ */
+ public static String[] getDBusType(Type c, boolean basic) throws DBusException {
+ return recursiveGetDBusType(c, basic, 0);
+ }
+
+ private static StringBuffer[] out = new StringBuffer[10];
+
+ @SuppressWarnings("unchecked")
+ public static String[] recursiveGetDBusType(Type c, boolean basic, int level) throws DBusException {
+ if (out.length <= level) {
+ StringBuffer[] newout = new StringBuffer[out.length];
+ System.arraycopy(out, 0, newout, 0, out.length);
+ out = newout;
+ }
+ if (null == out[level]) out[level] = new StringBuffer();
+ else out[level].delete(0, out[level].length());
+
+ if (basic && !(c instanceof Class))
+ throw new DBusException(c + getString("notBasicType"));
+
+ if (c instanceof TypeVariable) out[level].append((char) Message.ArgumentType.VARIANT);
+ else if (c instanceof GenericArrayType) {
+ out[level].append((char) Message.ArgumentType.ARRAY);
+ String[] s = recursiveGetDBusType(((GenericArrayType) c).getGenericComponentType(), false, level + 1);
+ if (s.length != 1) throw new DBusException(getString("multiValuedArrayNotPermitted"));
+ out[level].append(s[0]);
+ } else if ((c instanceof Class &&
+ DBusSerializable.class.isAssignableFrom((Class<? extends Object>) c)) ||
+ (c instanceof ParameterizedType &&
+ DBusSerializable.class.isAssignableFrom((Class<? extends Object>) ((ParameterizedType) c).getRawType()))) {
+ // it's a custom serializable type
+ Type[] newtypes = null;
+ if (c instanceof Class) {
+ for (Method m : ((Class<? extends Object>) c).getDeclaredMethods())
+ if (m.getName().equals("deserialize"))
+ newtypes = m.getGenericParameterTypes();
+ } else
+ for (Method m : ((Class<? extends Object>) ((ParameterizedType) c).getRawType()).getDeclaredMethods())
+ if (m.getName().equals("deserialize"))
+ newtypes = m.getGenericParameterTypes();
+
+ if (null == newtypes) throw new DBusException(getString("mustImplementDeserializeMethod"));
+
+ String[] sigs = new String[newtypes.length];
+ for (int j = 0; j < sigs.length; j++) {
+ String[] ss = recursiveGetDBusType(newtypes[j], false, level + 1);
+ if (1 != ss.length) throw new DBusException(getString("mustSerializeNativeDBusTypes"));
+ sigs[j] = ss[0];
+ }
+ return sigs;
+ } else if (c instanceof ParameterizedType) {
+ ParameterizedType p = (ParameterizedType) c;
+ if (p.getRawType().equals(Map.class)) {
+ out[level].append("a{");
+ Type[] t = p.getActualTypeArguments();
+ try {
+ String[] s = recursiveGetDBusType(t[0], true, level + 1);
+ if (s.length != 1) throw new DBusException(getString("multiValuedArrayNotPermitted"));
+ out[level].append(s[0]);
+ s = recursiveGetDBusType(t[1], false, level + 1);
+ if (s.length != 1) throw new DBusException(getString("multiValuedArrayNotPermitted"));
+ out[level].append(s[0]);
+ } catch (ArrayIndexOutOfBoundsException AIOOBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe);
+ throw new DBusException(getString("mapParameters"));
+ }
+ out[level].append('}');
+ } else if (List.class.isAssignableFrom((Class<? extends Object>) p.getRawType())) {
+ for (Type t : p.getActualTypeArguments()) {
+ if (Type.class.equals(t))
+ out[level].append((char) Message.ArgumentType.SIGNATURE);
+ else {
+ String[] s = recursiveGetDBusType(t, false, level + 1);
+ if (s.length != 1) throw new DBusException(getString("multiValuedArrayNotPermitted"));
+ out[level].append((char) Message.ArgumentType.ARRAY);
+ out[level].append(s[0]);
+ }
+ }
+ } else if (p.getRawType().equals(Variant.class)) {
+ out[level].append((char) Message.ArgumentType.VARIANT);
+ } else if (DBusInterface.class.isAssignableFrom((Class<? extends Object>) p.getRawType())) {
+ out[level].append((char) Message.ArgumentType.OBJECT_PATH);
+ } else if (Tuple.class.isAssignableFrom((Class<? extends Object>) p.getRawType())) {
+ Type[] ts = p.getActualTypeArguments();
+ Vector<String> vs = new Vector<String>();
+ for (Type t : ts)
+ for (String s : recursiveGetDBusType(t, false, level + 1))
+ vs.add(s);
+ return vs.toArray(new String[0]);
+ } else
+ throw new DBusException(getString("nonExportableParameterizedType") + c);
+ } else if (c.equals(Byte.class)) out[level].append((char) Message.ArgumentType.BYTE);
+ else if (c.equals(Byte.TYPE)) out[level].append((char) Message.ArgumentType.BYTE);
+ else if (c.equals(Boolean.class)) out[level].append((char) Message.ArgumentType.BOOLEAN);
+ else if (c.equals(Boolean.TYPE)) out[level].append((char) Message.ArgumentType.BOOLEAN);
+ else if (c.equals(Short.class)) out[level].append((char) Message.ArgumentType.INT16);
+ else if (c.equals(Short.TYPE)) out[level].append((char) Message.ArgumentType.INT16);
+ else if (c.equals(UInt16.class)) out[level].append((char) Message.ArgumentType.UINT16);
+ else if (c.equals(Integer.class)) out[level].append((char) Message.ArgumentType.INT32);
+ else if (c.equals(Integer.TYPE)) out[level].append((char) Message.ArgumentType.INT32);
+ else if (c.equals(UInt32.class)) out[level].append((char) Message.ArgumentType.UINT32);
+ else if (c.equals(Long.class)) out[level].append((char) Message.ArgumentType.INT64);
+ else if (c.equals(Long.TYPE)) out[level].append((char) Message.ArgumentType.INT64);
+ else if (c.equals(UInt64.class)) out[level].append((char) Message.ArgumentType.UINT64);
+ else if (c.equals(Double.class)) out[level].append((char) Message.ArgumentType.DOUBLE);
+ else if (c.equals(Double.TYPE)) out[level].append((char) Message.ArgumentType.DOUBLE);
+ else if (c.equals(Float.class) && AbstractConnection.FLOAT_SUPPORT)
+ out[level].append((char) Message.ArgumentType.FLOAT);
+ else if (c.equals(Float.class)) out[level].append((char) Message.ArgumentType.DOUBLE);
+ else if (c.equals(Float.TYPE) && AbstractConnection.FLOAT_SUPPORT)
+ out[level].append((char) Message.ArgumentType.FLOAT);
+ else if (c.equals(Float.TYPE)) out[level].append((char) Message.ArgumentType.DOUBLE);
+ else if (c.equals(String.class)) out[level].append((char) Message.ArgumentType.STRING);
+ else if (c.equals(Variant.class)) out[level].append((char) Message.ArgumentType.VARIANT);
+ else if (c instanceof Class &&
+ DBusInterface.class.isAssignableFrom((Class<? extends Object>) c))
+ out[level].append((char) Message.ArgumentType.OBJECT_PATH);
+ else if (c instanceof Class &&
+ Path.class.equals((Class<? extends Object>) c))
+ out[level].append((char) Message.ArgumentType.OBJECT_PATH);
+ else if (c instanceof Class &&
+ ObjectPath.class.equals((Class<? extends Object>) c))
+ out[level].append((char) Message.ArgumentType.OBJECT_PATH);
+ else if (c instanceof Class &&
+ ((Class<? extends Object>) c).isArray()) {
+ if (Type.class.equals(((Class<? extends Object>) c).getComponentType()))
+ out[level].append((char) Message.ArgumentType.SIGNATURE);
+ else {
+ out[level].append((char) Message.ArgumentType.ARRAY);
+ String[] s = recursiveGetDBusType(((Class<? extends Object>) c).getComponentType(), false, level + 1);
+ if (s.length != 1) throw new DBusException(getString("multiValuedArrayNotPermitted"));
+ out[level].append(s[0]);
+ }
+ } else if (c instanceof Class &&
+ Struct.class.isAssignableFrom((Class<? extends Object>) c)) {
+ out[level].append((char) Message.ArgumentType.STRUCT1);
+ Type[] ts = Container.getTypeCache(c);
+ if (null == ts) {
+ Field[] fs = ((Class<? extends Object>) c).getDeclaredFields();
+ ts = new Type[fs.length];
+ for (Field f : fs) {
+ Position p = f.getAnnotation(Position.class);
+ if (null == p) continue;
+ ts[p.value()] = f.getGenericType();
+ }
+ Container.putTypeCache(c, ts);
+ }
+
+ for (Type t : ts)
+ if (t != null)
+ for (String s : recursiveGetDBusType(t, false, level + 1))
+ out[level].append(s);
+ out[level].append(')');
+ } else {
+ throw new DBusException(getString("nonExportableType") + c);
+ }
+
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Converted Java type: " + c + " to D-Bus Type: " + out[level]);
+
+ return new String[]{out[level].toString()};
+ }
+
+ /**
+ * Converts a dbus type string into Java Type objects,
+ *
+ * @param dbus The DBus type or types.
+ * @param rv Vector to return the types in.
+ * @param limit Maximum number of types to parse (-1 == nolimit).
+ * @return number of characters parsed from the type string.
+ */
+ public static int getJavaType(String dbus, List<Type> rv, int limit) throws DBusException {
+ if (null == dbus || "".equals(dbus) || 0 == limit) return 0;
+
+ try {
+ int i = 0;
+ for (; i < dbus.length() && (-1 == limit || limit > rv.size()); i++)
+ switch (dbus.charAt(i)) {
+ case Message.ArgumentType.STRUCT1:
+ int j = i + 1;
+ for (int c = 1; c > 0; j++) {
+ if (')' == dbus.charAt(j)) c--;
+ else if (Message.ArgumentType.STRUCT1 == dbus.charAt(j)) c++;
+ }
+
+ Vector<Type> contained = new Vector<Type>();
+ int c = getJavaType(dbus.substring(i + 1, j - 1), contained, -1);
+ rv.add(new DBusStructType(contained.toArray(new Type[0])));
+ i = j;
+ break;
+ case Message.ArgumentType.ARRAY:
+ if (Message.ArgumentType.DICT_ENTRY1 == dbus.charAt(i + 1)) {
+ contained = new Vector<Type>();
+ c = getJavaType(dbus.substring(i + 2), contained, 2);
+ rv.add(new DBusMapType(contained.get(0), contained.get(1)));
+ i += (c + 2);
+ } else {
+ contained = new Vector<Type>();
+ c = getJavaType(dbus.substring(i + 1), contained, 1);
+ rv.add(new DBusListType(contained.get(0)));
+ i += c;
+ }
+ break;
+ case Message.ArgumentType.VARIANT:
+ rv.add(Variant.class);
+ break;
+ case Message.ArgumentType.BOOLEAN:
+ rv.add(Boolean.class);
+ break;
+ case Message.ArgumentType.INT16:
+ rv.add(Short.class);
+ break;
+ case Message.ArgumentType.BYTE:
+ rv.add(Byte.class);
+ break;
+ case Message.ArgumentType.OBJECT_PATH:
+ rv.add(DBusInterface.class);
+ break;
+ case Message.ArgumentType.UINT16:
+ rv.add(UInt16.class);
+ break;
+ case Message.ArgumentType.INT32:
+ rv.add(Integer.class);
+ break;
+ case Message.ArgumentType.UINT32:
+ rv.add(UInt32.class);
+ break;
+ case Message.ArgumentType.INT64:
+ rv.add(Long.class);
+ break;
+ case Message.ArgumentType.UINT64:
+ rv.add(UInt64.class);
+ break;
+ case Message.ArgumentType.DOUBLE:
+ rv.add(Double.class);
+ break;
+ case Message.ArgumentType.FLOAT:
+ rv.add(Float.class);
+ break;
+ case Message.ArgumentType.STRING:
+ rv.add(String.class);
+ break;
+ case Message.ArgumentType.SIGNATURE:
+ rv.add(Type[].class);
+ break;
+ case Message.ArgumentType.DICT_ENTRY1:
+ rv.add(Map.Entry.class);
+ contained = new Vector<Type>();
+ c = getJavaType(dbus.substring(i + 1), contained, 2);
+ i += c + 1;
+ break;
+ default:
+ throw new DBusException(MessageFormat.format(getString("parseDBusSignatureFailure"), new Object[]{dbus, dbus.charAt(i)}));
+ }
+ return i;
+ } catch (IndexOutOfBoundsException IOOBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOOBe);
+ throw new DBusException(getString("parseDBusTypeSignatureFailure") + dbus);
+ }
+ }
+
+ /**
+ * Recursively converts types for serialization onto DBus.
+ *
+ * @param parameters The parameters to convert.
+ * @param types The (possibly generic) types of the parameters.
+ * @return The converted parameters.
+ * @throws DBusException Thrown if there is an error in converting the objects.
+ */
+ @SuppressWarnings("unchecked")
+ public static Object[] convertParameters(Object[] parameters, Type[] types, AbstractConnection conn) throws DBusException {
+ if (null == parameters) return null;
+ for (int i = 0; i < parameters.length; i++) {
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Converting " + i + " from " + parameters[i] + " to " + types[i]);
+ if (null == parameters[i]) continue;
+
+ if (parameters[i] instanceof DBusSerializable) {
+ for (Method m : parameters[i].getClass().getDeclaredMethods())
+ if (m.getName().equals("deserialize")) {
+ Type[] newtypes = m.getParameterTypes();
+ Type[] expand = new Type[types.length + newtypes.length - 1];
+ System.arraycopy(types, 0, expand, 0, i);
+ System.arraycopy(newtypes, 0, expand, i, newtypes.length);
+ System.arraycopy(types, i + 1, expand, i + newtypes.length, types.length - i - 1);
+ types = expand;
+ Object[] newparams = ((DBusSerializable) parameters[i]).serialize();
+ Object[] exparams = new Object[parameters.length + newparams.length - 1];
+ System.arraycopy(parameters, 0, exparams, 0, i);
+ System.arraycopy(newparams, 0, exparams, i, newparams.length);
+ System.arraycopy(parameters, i + 1, exparams, i + newparams.length, parameters.length - i - 1);
+ parameters = exparams;
+ }
+ i--;
+ } else if (parameters[i] instanceof Tuple) {
+ Type[] newtypes = ((ParameterizedType) types[i]).getActualTypeArguments();
+ Type[] expand = new Type[types.length + newtypes.length - 1];
+ System.arraycopy(types, 0, expand, 0, i);
+ System.arraycopy(newtypes, 0, expand, i, newtypes.length);
+ System.arraycopy(types, i + 1, expand, i + newtypes.length, types.length - i - 1);
+ types = expand;
+ Object[] newparams = ((Tuple) parameters[i]).getParameters();
+ Object[] exparams = new Object[parameters.length + newparams.length - 1];
+ System.arraycopy(parameters, 0, exparams, 0, i);
+ System.arraycopy(newparams, 0, exparams, i, newparams.length);
+ System.arraycopy(parameters, i + 1, exparams, i + newparams.length, parameters.length - i - 1);
+ parameters = exparams;
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "New params: " + Arrays.deepToString(parameters) + " new types: " + Arrays.deepToString(types));
+ i--;
+ } else if (types[i] instanceof TypeVariable &&
+ !(parameters[i] instanceof Variant))
+ // its an unwrapped variant, wrap it
+ parameters[i] = new Variant<Object>(parameters[i]);
+ else if (parameters[i] instanceof DBusInterface)
+ parameters[i] = conn.getExportedObject((DBusInterface) parameters[i]);
+ }
+ return parameters;
+ }
+
+ @SuppressWarnings("unchecked")
+ static Object deSerializeParameter(Object parameter, Type type, AbstractConnection conn) throws Exception {
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Deserializing from " + parameter.getClass() + " to " + type.getClass());
+ if (null == parameter)
+ return null;
+
+ // its a wrapped variant, unwrap it
+ if (type instanceof TypeVariable
+ && parameter instanceof Variant) {
+ parameter = ((Variant) parameter).getValue();
+ }
+
+ // Turn a signature into a Type[]
+ if (type instanceof Class
+ && ((Class) type).isArray()
+ && ((Class) type).getComponentType().equals(Type.class)
+ && parameter instanceof String) {
+ Vector<Type> rv = new Vector<Type>();
+ getJavaType((String) parameter, rv, -1);
+ parameter = rv.toArray(new Type[0]);
+ }
+
+ // its an object path, get/create the proxy
+ if (parameter instanceof ObjectPath) {
+ if (type instanceof Class && DBusInterface.class.isAssignableFrom((Class) type))
+ parameter = conn.getExportedObject(
+ ((ObjectPath) parameter).source,
+ ((ObjectPath) parameter).path);
+ else
+ parameter = new Path(((ObjectPath) parameter).path);
+ }
+
+ // it should be a struct. create it
+ if (parameter instanceof Object[] &&
+ type instanceof Class &&
+ Struct.class.isAssignableFrom((Class) type)) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating Struct " + type + " from " + parameter);
+ Type[] ts = Container.getTypeCache(type);
+ if (null == ts) {
+ Field[] fs = ((Class) type).getDeclaredFields();
+ ts = new Type[fs.length];
+ for (Field f : fs) {
+ Position p = f.getAnnotation(Position.class);
+ if (null == p) continue;
+ ts[p.value()] = f.getGenericType();
+ }
+ Container.putTypeCache(type, ts);
+ }
+
+ // recurse over struct contents
+ parameter = deSerializeParameters((Object[]) parameter, ts, conn);
+ for (Constructor con : ((Class) type).getDeclaredConstructors()) {
+ try {
+ parameter = con.newInstance((Object[]) parameter);
+ break;
+ } catch (IllegalArgumentException IAe) {
+ }
+ }
+ }
+
+ // recurse over arrays
+ if (parameter instanceof Object[]) {
+ Type[] ts = new Type[((Object[]) parameter).length];
+ Arrays.fill(ts, parameter.getClass().getComponentType());
+ parameter = deSerializeParameters((Object[]) parameter,
+ ts, conn);
+ }
+ if (parameter instanceof List) {
+ Type type2;
+ if (type instanceof ParameterizedType)
+ type2 = ((ParameterizedType) type).getActualTypeArguments()[0];
+ else if (type instanceof GenericArrayType)
+ type2 = ((GenericArrayType) type).getGenericComponentType();
+ else if (type instanceof Class && ((Class) type).isArray())
+ type2 = ((Class) type).getComponentType();
+ else
+ type2 = null;
+ if (null != type2)
+ parameter = deSerializeParameters((List) parameter, type2, conn);
+ }
+
+ // correct floats if appropriate
+ if (type.equals(Float.class) || type.equals(Float.TYPE))
+ if (!(parameter instanceof Float))
+ parameter = ((Number) parameter).floatValue();
+
+ // make sure arrays are in the correct format
+ if (parameter instanceof Object[] ||
+ parameter instanceof List ||
+ parameter.getClass().isArray()) {
+ if (type instanceof ParameterizedType)
+ parameter = ArrayFrob.convert(parameter,
+ (Class<? extends Object>) ((ParameterizedType) type).getRawType());
+ else if (type instanceof GenericArrayType) {
+ Type ct = ((GenericArrayType) type).getGenericComponentType();
+ Class cc = null;
+ if (ct instanceof Class)
+ cc = (Class) ct;
+ if (ct instanceof ParameterizedType)
+ cc = (Class) ((ParameterizedType) ct).getRawType();
+ Object o = Array.newInstance(cc, 0);
+ parameter = ArrayFrob.convert(parameter,
+ o.getClass());
+ } else if (type instanceof Class &&
+ ((Class) type).isArray()) {
+ Class cc = ((Class) type).getComponentType();
+ if ((cc.equals(Float.class) || cc.equals(Float.TYPE))
+ && (parameter instanceof double[])) {
+ double[] tmp1 = (double[]) parameter;
+ float[] tmp2 = new float[tmp1.length];
+ for (int i = 0; i < tmp1.length; i++)
+ tmp2[i] = (float) tmp1[i];
+ parameter = tmp2;
+ }
+ Object o = Array.newInstance(cc, 0);
+ parameter = ArrayFrob.convert(parameter,
+ o.getClass());
+ }
+ }
+ if (parameter instanceof DBusMap) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing a Map");
+ DBusMap dmap = (DBusMap) parameter;
+ Type[] maptypes = ((ParameterizedType) type).getActualTypeArguments();
+ for (int i = 0; i < dmap.entries.length; i++) {
+ dmap.entries[i][0] = deSerializeParameter(dmap.entries[i][0], maptypes[0], conn);
+ dmap.entries[i][1] = deSerializeParameter(dmap.entries[i][1], maptypes[1], conn);
+ }
+ }
+ return parameter;
+ }
+
+ static List<Object> deSerializeParameters(List<Object> parameters, Type type, AbstractConnection conn) throws Exception {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from " + parameters + " to " + type);
+ if (null == parameters) return null;
+ for (int i = 0; i < parameters.size(); i++) {
+ if (null == parameters.get(i)) continue;
+
+ /* DO NOT DO THIS! IT'S REALLY NOT SUPPORTED!
+ * if (type instanceof Class &&
+ DBusSerializable.class.isAssignableFrom((Class) types[i])) {
+ for (Method m: ((Class) types[i]).getDeclaredMethods())
+ if (m.getName().equals("deserialize")) {
+ Type[] newtypes = m.getGenericParameterTypes();
+ try {
+ Object[] sub = new Object[newtypes.length];
+ System.arraycopy(parameters, i, sub, 0, newtypes.length);
+ sub = deSerializeParameters(sub, newtypes, conn);
+ DBusSerializable sz = (DBusSerializable) ((Class) types[i]).newInstance();
+ m.invoke(sz, sub);
+ Object[] compress = new Object[parameters.length - newtypes.length + 1];
+ System.arraycopy(parameters, 0, compress, 0, i);
+ compress[i] = sz;
+ System.arraycopy(parameters, i + newtypes.length, compress, i+1, parameters.length - i - newtypes.length);
+ parameters = compress;
+ } catch (ArrayIndexOutOfBoundsException AIOOBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe);
+ throw new DBusException("Not enough elements to create custom object from serialized data ("+(parameters.size()-i)+" < "+(newtypes.length)+")");
+ }
+ }
+ } else*/
+ parameters.set(i, deSerializeParameter(parameters.get(i), type, conn));
+ }
+ return parameters;
+ }
+
+ @SuppressWarnings("unchecked")
+ static Object[] deSerializeParameters(Object[] parameters, Type[] types, AbstractConnection conn) throws Exception {
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Deserializing from " + Arrays.deepToString(parameters) + " to " + Arrays.deepToString(types));
+ if (null == parameters) return null;
+
+ if (types.length == 1 && types[0] instanceof ParameterizedType
+ && Tuple.class.isAssignableFrom((Class) ((ParameterizedType) types[0]).getRawType())) {
+ types = ((ParameterizedType) types[0]).getActualTypeArguments();
+ }
+
+ for (int i = 0; i < parameters.length; i++) {
+ // CHECK IF ARRAYS HAVE THE SAME LENGTH <-- has to happen after expanding parameters
+ if (i >= types.length) {
+ if (Debug.debug) {
+ for (int j = 0; j < parameters.length; j++) {
+ Debug.print(Debug.ERR, String.format("Error, Parameters difference (%1d, '%2s')", j, parameters[j].toString()));
+ }
+ }
+ throw new DBusException(getString("errorDeserializingMessage"));
+ }
+ if (null == parameters[i]) continue;
+
+ if ((types[i] instanceof Class &&
+ DBusSerializable.class.isAssignableFrom((Class<? extends Object>) types[i])) ||
+ (types[i] instanceof ParameterizedType &&
+ DBusSerializable.class.isAssignableFrom((Class<? extends Object>) ((ParameterizedType) types[i]).getRawType()))) {
+ Class<? extends DBusSerializable> dsc;
+ if (types[i] instanceof Class)
+ dsc = (Class<? extends DBusSerializable>) types[i];
+ else
+ dsc = (Class<? extends DBusSerializable>) ((ParameterizedType) types[i]).getRawType();
+ for (Method m : dsc.getDeclaredMethods())
+ if (m.getName().equals("deserialize")) {
+ Type[] newtypes = m.getGenericParameterTypes();
+ try {
+ Object[] sub = new Object[newtypes.length];
+ System.arraycopy(parameters, i, sub, 0, newtypes.length);
+ sub = deSerializeParameters(sub, newtypes, conn);
+ DBusSerializable sz = dsc.newInstance();
+ m.invoke(sz, sub);
+ Object[] compress = new Object[parameters.length - newtypes.length + 1];
+ System.arraycopy(parameters, 0, compress, 0, i);
+ compress[i] = sz;
+ System.arraycopy(parameters, i + newtypes.length, compress, i + 1, parameters.length - i - newtypes.length);
+ parameters = compress;
+ } catch (ArrayIndexOutOfBoundsException AIOOBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe);
+ throw new DBusException(MessageFormat.format(getString("notEnoughElementsToCreateCustomObject"),
+ new Object[]{parameters.length - i, newtypes.length}));
+ }
+ }
+ } else
+ parameters[i] = deSerializeParameter(parameters[i], types[i], conn);
+ }
+ return parameters;
+ }
+}
+
+
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Message.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Message.java
new file mode 100644
index 0000000..01f00e8
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Message.java
@@ -0,0 +1,1216 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import cx.ath.matthew.utils.Hexdump;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.MarshallingException;
+import org.freedesktop.dbus.exceptions.UnknownTypeCodeException;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.lang.reflect.Type;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+/**
+ * Superclass of all messages which are sent over the Bus.
+ * This class deals with all the marshalling to/from the wire format.
+ */
+public class Message {
+ /**
+ * Defines constants representing the endianness of the message.
+ */
+ public static interface Endian {
+ public static final byte BIG = 'B';
+ public static final byte LITTLE = 'l';
+ }
+
+ /**
+ * Defines constants representing the flags which can be set on a message.
+ */
+ public static interface Flags {
+ public static final byte NO_REPLY_EXPECTED = 0x01;
+ public static final byte NO_AUTO_START = 0x02;
+ public static final byte ASYNC = 0x40;
+ }
+
+ /**
+ * Defines constants for each message type.
+ */
+ public static interface MessageType {
+ public static final byte METHOD_CALL = 1;
+ public static final byte METHOD_RETURN = 2;
+ public static final byte ERROR = 3;
+ public static final byte SIGNAL = 4;
+ }
+
+ /**
+ * The current protocol major version.
+ */
+ public static final byte PROTOCOL = 1;
+
+ /**
+ * Defines constants for each valid header field type.
+ */
+ public static interface HeaderField {
+ public static final byte PATH = 1;
+ public static final byte INTERFACE = 2;
+ public static final byte MEMBER = 3;
+ public static final byte ERROR_NAME = 4;
+ public static final byte REPLY_SERIAL = 5;
+ public static final byte DESTINATION = 6;
+ public static final byte SENDER = 7;
+ public static final byte SIGNATURE = 8;
+ }
+
+ /**
+ * Defines constants for each argument type.
+ * There are two constants for each argument type,
+ * as a byte or as a String (the _STRING version)
+ */
+ public static interface ArgumentType {
+ public static final String BYTE_STRING = "y";
+ public static final String BOOLEAN_STRING = "b";
+ public static final String INT16_STRING = "n";
+ public static final String UINT16_STRING = "q";
+ public static final String INT32_STRING = "i";
+ public static final String UINT32_STRING = "u";
+ public static final String INT64_STRING = "x";
+ public static final String UINT64_STRING = "t";
+ public static final String DOUBLE_STRING = "d";
+ public static final String FLOAT_STRING = "f";
+ public static final String STRING_STRING = "s";
+ public static final String OBJECT_PATH_STRING = "o";
+ public static final String SIGNATURE_STRING = "g";
+ public static final String ARRAY_STRING = "a";
+ public static final String VARIANT_STRING = "v";
+ public static final String STRUCT_STRING = "r";
+ public static final String STRUCT1_STRING = "(";
+ public static final String STRUCT2_STRING = ")";
+ public static final String DICT_ENTRY_STRING = "e";
+ public static final String DICT_ENTRY1_STRING = "{";
+ public static final String DICT_ENTRY2_STRING = "}";
+
+ public static final byte BYTE = 'y';
+ public static final byte BOOLEAN = 'b';
+ public static final byte INT16 = 'n';
+ public static final byte UINT16 = 'q';
+ public static final byte INT32 = 'i';
+ public static final byte UINT32 = 'u';
+ public static final byte INT64 = 'x';
+ public static final byte UINT64 = 't';
+ public static final byte DOUBLE = 'd';
+ public static final byte FLOAT = 'f';
+ public static final byte STRING = 's';
+ public static final byte OBJECT_PATH = 'o';
+ public static final byte SIGNATURE = 'g';
+ public static final byte ARRAY = 'a';
+ public static final byte VARIANT = 'v';
+ public static final byte STRUCT = 'r';
+ public static final byte STRUCT1 = '(';
+ public static final byte STRUCT2 = ')';
+ public static final byte DICT_ENTRY = 'e';
+ public static final byte DICT_ENTRY1 = '{';
+ public static final byte DICT_ENTRY2 = '}';
+ }
+
+ /**
+ * Keep a static reference to each size of padding array to prevent allocation.
+ */
+ private static byte[][] padding;
+
+ static {
+ padding = new byte[][]{
+ null,
+ new byte[1],
+ new byte[2],
+ new byte[3],
+ new byte[4],
+ new byte[5],
+ new byte[6],
+ new byte[7]};
+ }
+
+ /**
+ * Steps to increment the buffer array.
+ */
+ private static final int BUFFERINCREMENT = 20;
+
+ private boolean big;
+ protected byte[][] wiredata;
+ protected long bytecounter;
+ protected Map<Byte, Object> headers;
+ protected static long globalserial = 0;
+ protected long serial;
+ protected byte type;
+ protected byte flags;
+ protected byte protover;
+ private Object[] args;
+ private byte[] body;
+ private long bodylen = 0;
+ private int preallocated = 0;
+ private int paofs = 0;
+ private byte[] pabuf;
+ private int bufferuse = 0;
+
+ /**
+ * Returns the name of the given header field.
+ */
+ public static String getHeaderFieldName(byte field) {
+ switch (field) {
+ case HeaderField.PATH:
+ return "Path";
+ case HeaderField.INTERFACE:
+ return "Interface";
+ case HeaderField.MEMBER:
+ return "Member";
+ case HeaderField.ERROR_NAME:
+ return "Error Name";
+ case HeaderField.REPLY_SERIAL:
+ return "Reply Serial";
+ case HeaderField.DESTINATION:
+ return "Destination";
+ case HeaderField.SENDER:
+ return "Sender";
+ case HeaderField.SIGNATURE:
+ return "Signature";
+ default:
+ return "Invalid";
+ }
+ }
+
+ /**
+ * Create a message; only to be called by sub-classes.
+ *
+ * @param endian The endianness to create the message.
+ * @param type The message type.
+ * @param flags Any message flags.
+ */
+ protected Message(byte endian, byte type, byte flags) throws DBusException {
+ wiredata = new byte[BUFFERINCREMENT][];
+ headers = new HashMap<Byte, Object>();
+ big = (Endian.BIG == endian);
+ bytecounter = 0;
+ synchronized (Message.class) {
+ serial = ++globalserial;
+ }
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Creating message with serial " + serial);
+ this.type = type;
+ this.flags = flags;
+ preallocate(4);
+ append("yyyy", endian, type, flags, Message.PROTOCOL);
+ }
+
+ /**
+ * Create a blank message. Only to be used when calling populate.
+ */
+ protected Message() {
+ wiredata = new byte[BUFFERINCREMENT][];
+ headers = new HashMap<Byte, Object>();
+ bytecounter = 0;
+ }
+
+ /**
+ * Create a message from wire-format data.
+ *
+ * @param msg D-Bus serialized data of type yyyuu
+ * @param headers D-Bus serialized data of type a(yv)
+ * @param body D-Bus serialized data of the signature defined in headers.
+ */
+ @SuppressWarnings("unchecked")
+ void populate(byte[] msg, byte[] headers, byte[] body) throws DBusException {
+ big = (msg[0] == Endian.BIG);
+ type = msg[1];
+ flags = msg[2];
+ protover = msg[3];
+ wiredata[0] = msg;
+ wiredata[1] = headers;
+ wiredata[2] = body;
+ this.body = body;
+ bufferuse = 3;
+ bodylen = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 4)[0]).longValue();
+ serial = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 8)[0]).longValue();
+ bytecounter = msg.length + headers.length + body.length;
+ if (Debug.debug) Debug.print(Debug.VERBOSE, headers);
+ Object[] hs = extract("a(yv)", headers, 0);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.deepToString(hs));
+ for (Object o : (Vector<Object>) hs[0]) {
+ this.headers.put((Byte) ((Object[]) o)[0], ((Variant<Object>) ((Object[]) o)[1]).getValue());
+ }
+ }
+
+ /**
+ * Create a buffer of num bytes.
+ * Data is copied to this rather than added to the buffer list.
+ */
+ private void preallocate(int num) {
+ preallocated = 0;
+ pabuf = new byte[num];
+ appendBytes(pabuf);
+ preallocated = num;
+ paofs = 0;
+ }
+
+ /**
+ * Ensures there are enough free buffers.
+ *
+ * @param num number of free buffers to create.
+ */
+ private void ensureBuffers(int num) {
+ int increase = num - wiredata.length + bufferuse;
+ if (increase > 0) {
+ if (increase < BUFFERINCREMENT) increase = BUFFERINCREMENT;
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing " + bufferuse);
+ byte[][] temp = new byte[wiredata.length + increase][];
+ System.arraycopy(wiredata, 0, temp, 0, wiredata.length);
+ wiredata = temp;
+ }
+ }
+
+ /**
+ * Appends a buffer to the buffer list.
+ */
+ protected void appendBytes(byte[] buf) {
+ if (null == buf) return;
+ if (preallocated > 0) {
+ if (paofs + buf.length > pabuf.length)
+ throw new ArrayIndexOutOfBoundsException(MessageFormat.format(getString("arrayOutOfBounds"), new Object[]{paofs, pabuf.length, buf.length}));
+ System.arraycopy(buf, 0, pabuf, paofs, buf.length);
+ paofs += buf.length;
+ preallocated -= buf.length;
+ } else {
+ if (bufferuse == wiredata.length) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing " + bufferuse);
+ byte[][] temp = new byte[wiredata.length + BUFFERINCREMENT][];
+ System.arraycopy(wiredata, 0, temp, 0, wiredata.length);
+ wiredata = temp;
+ }
+ wiredata[bufferuse++] = buf;
+ bytecounter += buf.length;
+ }
+ }
+
+ /**
+ * Appends a byte to the buffer list.
+ */
+ protected void appendByte(byte b) {
+ if (preallocated > 0) {
+ pabuf[paofs++] = b;
+ preallocated--;
+ } else {
+ if (bufferuse == wiredata.length) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing " + bufferuse);
+ byte[][] temp = new byte[wiredata.length + BUFFERINCREMENT][];
+ System.arraycopy(wiredata, 0, temp, 0, wiredata.length);
+ wiredata = temp;
+ }
+ wiredata[bufferuse++] = new byte[]{b};
+ bytecounter++;
+ }
+ }
+
+ /**
+ * Demarshalls an integer of a given width from a buffer.
+ * Endianness is determined from the format of the message.
+ *
+ * @param buf The buffer to demarshall from.
+ * @param ofs The offset to demarshall from.
+ * @param width The byte-width of the int.
+ */
+ public long demarshallint(byte[] buf, int ofs, int width) {
+ return big ? demarshallintBig(buf, ofs, width) : demarshallintLittle(buf, ofs, width);
+ }
+
+ /**
+ * Demarshalls an integer of a given width from a buffer.
+ *
+ * @param buf The buffer to demarshall from.
+ * @param ofs The offset to demarshall from.
+ * @param endian The endianness to use in demarshalling.
+ * @param width The byte-width of the int.
+ */
+ public static long demarshallint(byte[] buf, int ofs, byte endian, int width) {
+ return endian == Endian.BIG ? demarshallintBig(buf, ofs, width) : demarshallintLittle(buf, ofs, width);
+ }
+
+ /**
+ * Demarshalls an integer of a given width from a buffer using big-endian format.
+ *
+ * @param buf The buffer to demarshall from.
+ * @param ofs The offset to demarshall from.
+ * @param width The byte-width of the int.
+ */
+ public static long demarshallintBig(byte[] buf, int ofs, int width) {
+ long l = 0;
+ for (int i = 0; i < width; i++) {
+ l <<= 8;
+ l |= (buf[ofs + i] & 0xFF);
+ }
+ return l;
+ }
+
+ /**
+ * Demarshalls an integer of a given width from a buffer using little-endian format.
+ *
+ * @param buf The buffer to demarshall from.
+ * @param ofs The offset to demarshall from.
+ * @param width The byte-width of the int.
+ */
+ public static long demarshallintLittle(byte[] buf, int ofs, int width) {
+ long l = 0;
+ for (int i = (width - 1); i >= 0; i--) {
+ l <<= 8;
+ l |= (buf[ofs + i] & 0xFF);
+ }
+ return l;
+ }
+
+ /**
+ * Marshalls an integer of a given width and appends it to the message.
+ * Endianness is determined from the message.
+ *
+ * @param l The integer to marshall.
+ * @param width The byte-width of the int.
+ */
+ public void appendint(long l, int width) {
+ byte[] buf = new byte[width];
+ marshallint(l, buf, 0, width);
+ appendBytes(buf);
+ }
+
+ /**
+ * Marshalls an integer of a given width into a buffer.
+ * Endianness is determined from the message.
+ *
+ * @param l The integer to marshall.
+ * @param buf The buffer to marshall to.
+ * @param ofs The offset to marshall to.
+ * @param width The byte-width of the int.
+ */
+ public void marshallint(long l, byte[] buf, int ofs, int width) {
+ if (big) marshallintBig(l, buf, ofs, width);
+ else marshallintLittle(l, buf, ofs, width);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Marshalled int " + l + " to " + Hexdump.toHex(buf, ofs, width));
+ }
+
+ /**
+ * Marshalls an integer of a given width into a buffer using big-endian format.
+ *
+ * @param l The integer to marshall.
+ * @param buf The buffer to marshall to.
+ * @param ofs The offset to marshall to.
+ * @param width The byte-width of the int.
+ */
+ public static void marshallintBig(long l, byte[] buf, int ofs, int width) {
+ for (int i = (width - 1); i >= 0; i--) {
+ buf[i + ofs] = (byte) (l & 0xFF);
+ l >>= 8;
+ }
+ }
+
+ /**
+ * Marshalls an integer of a given width into a buffer using little-endian format.
+ *
+ * @param l The integer to marshall.
+ * @param buf The buffer to demarshall to.
+ * @param ofs The offset to demarshall to.
+ * @param width The byte-width of the int.
+ */
+ public static void marshallintLittle(long l, byte[] buf, int ofs, int width) {
+ for (int i = 0; i < width; i++) {
+ buf[i + ofs] = (byte) (l & 0xFF);
+ l >>= 8;
+ }
+ }
+
+ public byte[][] getWireData() {
+ return wiredata;
+ }
+
+ /**
+ * Formats the message in a human-readable format.
+ */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(getClass().getSimpleName());
+ sb.append('(');
+ sb.append(flags);
+ sb.append(',');
+ sb.append(serial);
+ sb.append(')');
+ sb.append(' ');
+ sb.append('{');
+ sb.append(' ');
+ if (headers.size() == 0)
+ sb.append('}');
+ else {
+ for (Byte field : headers.keySet()) {
+ sb.append(getHeaderFieldName(field));
+ sb.append('=');
+ sb.append('>');
+ sb.append(headers.get(field).toString());
+ sb.append(',');
+ sb.append(' ');
+ }
+ sb.setCharAt(sb.length() - 2, ' ');
+ sb.setCharAt(sb.length() - 1, '}');
+ }
+ sb.append(' ');
+ sb.append('{');
+ sb.append(' ');
+ Object[] args = null;
+ try {
+ args = getParameters();
+ } catch (DBusException DBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe);
+ }
+ if (null == args || 0 == args.length)
+ sb.append('}');
+ else {
+ for (Object o : args) {
+ if (o instanceof Object[])
+ sb.append(Arrays.deepToString((Object[]) o));
+ else if (o instanceof byte[])
+ sb.append(Arrays.toString((byte[]) o));
+ else if (o instanceof int[])
+ sb.append(Arrays.toString((int[]) o));
+ else if (o instanceof short[])
+ sb.append(Arrays.toString((short[]) o));
+ else if (o instanceof long[])
+ sb.append(Arrays.toString((long[]) o));
+ else if (o instanceof boolean[])
+ sb.append(Arrays.toString((boolean[]) o));
+ else if (o instanceof double[])
+ sb.append(Arrays.toString((double[]) o));
+ else if (o instanceof float[])
+ sb.append(Arrays.toString((float[]) o));
+ else
+ sb.append(o.toString());
+ sb.append(',');
+ sb.append(' ');
+ }
+ sb.setCharAt(sb.length() - 2, ' ');
+ sb.setCharAt(sb.length() - 1, '}');
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns the value of the header field of a given field.
+ *
+ * @param type The field to return.
+ * @return The value of the field or null if unset.
+ */
+ public Object getHeader(byte type) {
+ return headers.get(type);
+ }
+
+ /**
+ * Appends a value to the message.
+ * The type of the value is read from a D-Bus signature and used to marshall
+ * the value.
+ *
+ * @param sigb A buffer of the D-Bus signature.
+ * @param sigofs The offset into the signature corresponding to this value.
+ * @param data The value to marshall.
+ * @return The offset into the signature of the end of this value's type.
+ */
+ @SuppressWarnings("unchecked")
+ private int appendone(byte[] sigb, int sigofs, Object data) throws DBusException {
+ try {
+ int i = sigofs;
+ if (Debug.debug) Debug.print(Debug.VERBOSE, (Object) bytecounter);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending type: " + ((char) sigb[i]) + " value: " + data);
+
+ // pad to the alignment of this type.
+ pad(sigb[i]);
+ switch (sigb[i]) {
+ case ArgumentType.BYTE:
+ appendByte(((Number) data).byteValue());
+ break;
+ case ArgumentType.BOOLEAN:
+ appendint(((Boolean) data).booleanValue() ? 1 : 0, 4);
+ break;
+ case ArgumentType.DOUBLE:
+ long l = Double.doubleToLongBits(((Number) data).doubleValue());
+ appendint(l, 8);
+ break;
+ case ArgumentType.FLOAT:
+ int rf = Float.floatToIntBits(((Number) data).floatValue());
+ appendint(rf, 4);
+ break;
+ case ArgumentType.UINT32:
+ appendint(((Number) data).longValue(), 4);
+ break;
+ case ArgumentType.INT64:
+ appendint(((Number) data).longValue(), 8);
+ break;
+ case ArgumentType.UINT64:
+ if (big) {
+ appendint(((UInt64) data).top(), 4);
+ appendint(((UInt64) data).bottom(), 4);
+ } else {
+ appendint(((UInt64) data).bottom(), 4);
+ appendint(((UInt64) data).top(), 4);
+ }
+ break;
+ case ArgumentType.INT32:
+ appendint(((Number) data).intValue(), 4);
+ break;
+ case ArgumentType.UINT16:
+ appendint(((Number) data).intValue(), 2);
+ break;
+ case ArgumentType.INT16:
+ appendint(((Number) data).shortValue(), 2);
+ break;
+ case ArgumentType.STRING:
+ case ArgumentType.OBJECT_PATH:
+ // Strings are marshalled as a UInt32 with the length,
+ // followed by the String, followed by a null byte.
+ String payload = data.toString();
+ byte[] payloadbytes = null;
+ try {
+ payloadbytes = payload.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException UEe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe);
+ throw new DBusException(getString("utf8NotSupported"));
+ }
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending String of length " + payloadbytes.length);
+ appendint(payloadbytes.length, 4);
+ appendBytes(payloadbytes);
+ appendBytes(padding[1]);
+ //pad(ArgumentType.STRING);? do we need this?
+ break;
+ case ArgumentType.SIGNATURE:
+ // Signatures are marshalled as a byte with the length,
+ // followed by the String, followed by a null byte.
+ // Signatures are generally short, so preallocate the array
+ // for the string, length and null byte.
+ if (data instanceof Type[])
+ payload = Marshalling.getDBusType((Type[]) data);
+ else
+ payload = (String) data;
+ byte[] pbytes = payload.getBytes();
+ preallocate(2 + pbytes.length);
+ appendByte((byte) pbytes.length);
+ appendBytes(pbytes);
+ appendByte((byte) 0);
+ break;
+ case ArgumentType.ARRAY:
+ // Arrays are given as a UInt32 for the length in bytes,
+ // padding to the element alignment, then elements in
+ // order. The length is the length from the end of the
+ // initial padding to the end of the last element.
+ if (Debug.debug) {
+ if (data instanceof Object[])
+ Debug.print(Debug.VERBOSE, "Appending array: " + Arrays.deepToString((Object[]) data));
+ }
+
+ byte[] alen = new byte[4];
+ appendBytes(alen);
+ pad(sigb[++i]);
+ long c = bytecounter;
+
+ // optimise primatives
+ if (data.getClass().isArray() &&
+ data.getClass().getComponentType().isPrimitive()) {
+ byte[] primbuf;
+ int algn = getAlignment(sigb[i]);
+ int len = Array.getLength(data);
+ switch (sigb[i]) {
+ case ArgumentType.BYTE:
+ primbuf = (byte[]) data;
+ break;
+ case ArgumentType.INT16:
+ case ArgumentType.INT32:
+ case ArgumentType.INT64:
+ primbuf = new byte[len * algn];
+ for (int j = 0, k = 0; j < len; j++, k += algn)
+ marshallint(Array.getLong(data, j), primbuf, k, algn);
+ break;
+ case ArgumentType.BOOLEAN:
+ primbuf = new byte[len * algn];
+ for (int j = 0, k = 0; j < len; j++, k += algn)
+ marshallint(Array.getBoolean(data, j) ? 1 : 0, primbuf, k, algn);
+ break;
+ case ArgumentType.DOUBLE:
+ primbuf = new byte[len * algn];
+ if (data instanceof float[])
+ for (int j = 0, k = 0; j < len; j++, k += algn)
+ marshallint(Double.doubleToRawLongBits(((float[]) data)[j]),
+ primbuf, k, algn);
+ else
+ for (int j = 0, k = 0; j < len; j++, k += algn)
+ marshallint(Double.doubleToRawLongBits(((double[]) data)[j]),
+ primbuf, k, algn);
+ break;
+ case ArgumentType.FLOAT:
+ primbuf = new byte[len * algn];
+ for (int j = 0, k = 0; j < len; j++, k += algn)
+ marshallint(
+ Float.floatToRawIntBits(((float[]) data)[j]),
+ primbuf, k, algn);
+ break;
+ default:
+ throw new MarshallingException(getString("arraySentAsNonPrimitive"));
+ }
+ appendBytes(primbuf);
+ } else if (data instanceof List) {
+ Object[] contents = ((List) data).toArray();
+ int diff = i;
+ ensureBuffers(contents.length * 4);
+ for (Object o : contents)
+ diff = appendone(sigb, i, o);
+ i = diff;
+ } else if (data instanceof Map) {
+ int diff = i;
+ ensureBuffers(((Map) data).size() * 6);
+ for (Map.Entry<Object, Object> o : ((Map<Object, Object>) data).entrySet())
+ diff = appendone(sigb, i, o);
+ if (i == diff) {
+ // advance the type parser even on 0-size arrays.
+ Vector<Type> temp = new Vector<Type>();
+ byte[] temp2 = new byte[sigb.length - diff];
+ System.arraycopy(sigb, diff, temp2, 0, temp2.length);
+ String temp3 = new String(temp2);
+ int temp4 = Marshalling.getJavaType(temp3, temp, 1);
+ diff += temp4;
+ }
+ i = diff;
+ } else {
+ Object[] contents = (Object[]) data;
+ ensureBuffers(contents.length * 4);
+ int diff = i;
+ for (Object o : contents)
+ diff = appendone(sigb, i, o);
+ i = diff;
+ }
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "start: " + c + " end: " + bytecounter + " length: " + (bytecounter - c));
+ marshallint(bytecounter - c, alen, 0, 4);
+ break;
+ case ArgumentType.STRUCT1:
+ // Structs are aligned to 8 bytes
+ // and simply contain each element marshalled in order
+ Object[] contents;
+ if (data instanceof Container)
+ contents = ((Container) data).getParameters();
+ else
+ contents = (Object[]) data;
+ ensureBuffers(contents.length * 4);
+ int j = 0;
+ for (i++; sigb[i] != ArgumentType.STRUCT2; i++)
+ i = appendone(sigb, i, contents[j++]);
+ break;
+ case ArgumentType.DICT_ENTRY1:
+ // Dict entries are the same as structs.
+ if (data instanceof Map.Entry) {
+ i++;
+ i = appendone(sigb, i, ((Map.Entry) data).getKey());
+ i++;
+ i = appendone(sigb, i, ((Map.Entry) data).getValue());
+ i++;
+ } else {
+ contents = (Object[]) data;
+ j = 0;
+ for (i++; sigb[i] != ArgumentType.DICT_ENTRY2; i++)
+ i = appendone(sigb, i, contents[j++]);
+ }
+ break;
+ case ArgumentType.VARIANT:
+ // Variants are marshalled as a signature
+ // followed by the value.
+ if (data instanceof Variant) {
+ Variant var = (Variant) data;
+ appendone(new byte[]{ArgumentType.SIGNATURE}, 0, var.getSig());
+ appendone((var.getSig()).getBytes(), 0, var.getValue());
+ } else if (data instanceof Object[]) {
+ contents = (Object[]) data;
+ appendone(new byte[]{ArgumentType.SIGNATURE}, 0, contents[0]);
+ appendone(((String) contents[0]).getBytes(), 0, contents[1]);
+ } else {
+ String sig = Marshalling.getDBusType(data.getClass())[0];
+ appendone(new byte[]{ArgumentType.SIGNATURE}, 0, sig);
+ appendone((sig).getBytes(), 0, data);
+ }
+ break;
+ }
+ return i;
+ } catch (ClassCastException CCe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, CCe);
+ throw new MarshallingException(MessageFormat.format(getString("unconvertableType"), new Object[]{data.getClass().getName(), sigb[sigofs]}));
+ }
+ }
+
+ /**
+ * Pad the message to the proper alignment for the given type.
+ */
+ public void pad(byte type) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "padding for " + (char) type);
+ int a = getAlignment(type);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated + " " + paofs + " " + bytecounter + " " + a);
+ int b = (int) ((bytecounter - preallocated) % a);
+ if (0 == b) return;
+ a = (a - b);
+ if (preallocated > 0) {
+ paofs += a;
+ preallocated -= a;
+ } else
+ appendBytes(padding[a]);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated + " " + paofs + " " + bytecounter + " " + a);
+ }
+
+ /**
+ * Return the alignment for a given type.
+ */
+ public static int getAlignment(byte type) {
+ switch (type) {
+ case 2:
+ case ArgumentType.INT16:
+ case ArgumentType.UINT16:
+ return 2;
+ case 4:
+ case ArgumentType.BOOLEAN:
+ case ArgumentType.FLOAT:
+ case ArgumentType.INT32:
+ case ArgumentType.UINT32:
+ case ArgumentType.STRING:
+ case ArgumentType.OBJECT_PATH:
+ case ArgumentType.ARRAY:
+ return 4;
+ case 8:
+ case ArgumentType.INT64:
+ case ArgumentType.UINT64:
+ case ArgumentType.DOUBLE:
+ case ArgumentType.STRUCT:
+ case ArgumentType.DICT_ENTRY:
+ case ArgumentType.STRUCT1:
+ case ArgumentType.DICT_ENTRY1:
+ case ArgumentType.STRUCT2:
+ case ArgumentType.DICT_ENTRY2:
+ return 8;
+ case 1:
+ case ArgumentType.BYTE:
+ case ArgumentType.SIGNATURE:
+ case ArgumentType.VARIANT:
+ default:
+ return 1;
+ }
+ }
+
+ /**
+ * Append a series of values to the message.
+ *
+ * @param sig The signature(s) of the value(s).
+ * @param data The value(s).
+ */
+ public void append(String sig, Object... data) throws DBusException {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Appending sig: " + sig + " data: " + Arrays.deepToString(data));
+ byte[] sigb = sig.getBytes();
+ int j = 0;
+ for (int i = 0; i < sigb.length; i++) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending item: " + i + " " + ((char) sigb[i]) + " " + j);
+ i = appendone(sigb, i, data[j++]);
+ }
+ }
+
+ /**
+ * Align a counter to the given type.
+ *
+ * @param current The current counter.
+ * @param type The type to align to.
+ * @return The new, aligned, counter.
+ */
+ public int align(int current, byte type) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "aligning to " + (char) type);
+ int a = getAlignment(type);
+ if (0 == (current % a)) return current;
+ return current + (a - (current % a));
+ }
+
+ /**
+ * Demarshall one value from a buffer.
+ *
+ * @param sigb A buffer of the D-Bus signature.
+ * @param buf The buffer to demarshall from.
+ * @param ofs An array of two ints, the offset into the signature buffer
+ * and the offset into the data buffer. These values will be
+ * updated to the start of the next value ofter demarshalling.
+ * @param contained converts nested arrays to Lists
+ * @return The demarshalled value.
+ */
+ private Object extractone(byte[] sigb, byte[] buf, int[] ofs, boolean contained) throws DBusException {
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Extracting type: " + ((char) sigb[ofs[0]]) + " from offset " + ofs[1]);
+ Object rv = null;
+ ofs[1] = align(ofs[1], sigb[ofs[0]]);
+ switch (sigb[ofs[0]]) {
+ case ArgumentType.BYTE:
+ rv = buf[ofs[1]++];
+ break;
+ case ArgumentType.UINT32:
+ rv = new UInt32(demarshallint(buf, ofs[1], 4));
+ ofs[1] += 4;
+ break;
+ case ArgumentType.INT32:
+ rv = (int) demarshallint(buf, ofs[1], 4);
+ ofs[1] += 4;
+ break;
+ case ArgumentType.INT16:
+ rv = (short) demarshallint(buf, ofs[1], 2);
+ ofs[1] += 2;
+ break;
+ case ArgumentType.UINT16:
+ rv = new UInt16((int) demarshallint(buf, ofs[1], 2));
+ ofs[1] += 2;
+ break;
+ case ArgumentType.INT64:
+ rv = demarshallint(buf, ofs[1], 8);
+ ofs[1] += 8;
+ break;
+ case ArgumentType.UINT64:
+ long top;
+ long bottom;
+ if (big) {
+ top = demarshallint(buf, ofs[1], 4);
+ ofs[1] += 4;
+ bottom = demarshallint(buf, ofs[1], 4);
+ } else {
+ bottom = demarshallint(buf, ofs[1], 4);
+ ofs[1] += 4;
+ top = demarshallint(buf, ofs[1], 4);
+ }
+ rv = new UInt64(top, bottom);
+ ofs[1] += 4;
+ break;
+ case ArgumentType.DOUBLE:
+ long l = demarshallint(buf, ofs[1], 8);
+ ofs[1] += 8;
+ rv = Double.longBitsToDouble(l);
+ break;
+ case ArgumentType.FLOAT:
+ int rf = (int) demarshallint(buf, ofs[1], 4);
+ ofs[1] += 4;
+ rv = Float.intBitsToFloat(rf);
+ break;
+ case ArgumentType.BOOLEAN:
+ rf = (int) demarshallint(buf, ofs[1], 4);
+ ofs[1] += 4;
+ rv = (1 == rf) ? Boolean.TRUE : Boolean.FALSE;
+ break;
+ case ArgumentType.ARRAY:
+ long size = demarshallint(buf, ofs[1], 4);
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading array of size: " + size);
+ ofs[1] += 4;
+ byte algn = (byte) getAlignment(sigb[++ofs[0]]);
+ ofs[1] = align(ofs[1], sigb[ofs[0]]);
+ int length = (int) (size / algn);
+ if (length > DBusConnection.MAX_ARRAY_LENGTH)
+ throw new MarshallingException(getString("arrayMustNotExceed") + DBusConnection.MAX_ARRAY_LENGTH);
+ // optimise primatives
+ switch (sigb[ofs[0]]) {
+ case ArgumentType.BYTE:
+ rv = new byte[length];
+ System.arraycopy(buf, ofs[1], rv, 0, length);
+ ofs[1] += size;
+ break;
+ case ArgumentType.INT16:
+ rv = new short[length];
+ for (int j = 0; j < length; j++, ofs[1] += algn)
+ ((short[]) rv)[j] = (short) demarshallint(buf, ofs[1], algn);
+ break;
+ case ArgumentType.INT32:
+ rv = new int[length];
+ for (int j = 0; j < length; j++, ofs[1] += algn)
+ ((int[]) rv)[j] = (int) demarshallint(buf, ofs[1], algn);
+ break;
+ case ArgumentType.INT64:
+ rv = new long[length];
+ for (int j = 0; j < length; j++, ofs[1] += algn)
+ ((long[]) rv)[j] = demarshallint(buf, ofs[1], algn);
+ break;
+ case ArgumentType.BOOLEAN:
+ rv = new boolean[length];
+ for (int j = 0; j < length; j++, ofs[1] += algn)
+ ((boolean[]) rv)[j] = (1 == demarshallint(buf, ofs[1], algn));
+ break;
+ case ArgumentType.FLOAT:
+ rv = new float[length];
+ for (int j = 0; j < length; j++, ofs[1] += algn)
+ ((float[]) rv)[j] =
+ Float.intBitsToFloat((int) demarshallint(buf, ofs[1], algn));
+ break;
+ case ArgumentType.DOUBLE:
+ rv = new double[length];
+ for (int j = 0; j < length; j++, ofs[1] += algn)
+ ((double[]) rv)[j] =
+ Double.longBitsToDouble(demarshallint(buf, ofs[1], algn));
+ break;
+ case ArgumentType.DICT_ENTRY1:
+ if (0 == size) {
+ // advance the type parser even on 0-size arrays.
+ Vector<Type> temp = new Vector<Type>();
+ byte[] temp2 = new byte[sigb.length - ofs[0]];
+ System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length);
+ String temp3 = new String(temp2);
+ // ofs[0] gets incremented anyway. Leave one character on the stack
+ int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1;
+ ofs[0] += temp4;
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Aligned type: " + temp3 + " " + temp4 + " " + ofs[0]);
+ }
+ int ofssave = ofs[0];
+ long end = ofs[1] + size;
+ Vector<Object[]> entries = new Vector<Object[]>();
+ while (ofs[1] < end) {
+ ofs[0] = ofssave;
+ entries.add((Object[]) extractone(sigb, buf, ofs, true));
+ }
+ rv = new DBusMap<Object, Object>(entries.toArray(new Object[0][]));
+ break;
+ default:
+ if (0 == size) {
+ // advance the type parser even on 0-size arrays.
+ Vector<Type> temp = new Vector<Type>();
+ byte[] temp2 = new byte[sigb.length - ofs[0]];
+ System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length);
+ String temp3 = new String(temp2);
+ // ofs[0] gets incremented anyway. Leave one character on the stack
+ int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1;
+ ofs[0] += temp4;
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Aligned type: " + temp3 + " " + temp4 + " " + ofs[0]);
+ }
+ ofssave = ofs[0];
+ end = ofs[1] + size;
+ Vector<Object> contents = new Vector<Object>();
+ while (ofs[1] < end) {
+ ofs[0] = ofssave;
+ contents.add(extractone(sigb, buf, ofs, true));
+ }
+ rv = contents;
+ }
+ if (contained && !(rv instanceof List) && !(rv instanceof Map))
+ rv = ArrayFrob.listify(rv);
+ break;
+ case ArgumentType.STRUCT1:
+ Vector<Object> contents = new Vector<Object>();
+ while (sigb[++ofs[0]] != ArgumentType.STRUCT2)
+ contents.add(extractone(sigb, buf, ofs, true));
+ rv = contents.toArray();
+ break;
+ case ArgumentType.DICT_ENTRY1:
+ Object[] decontents = new Object[2];
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Extracting Dict Entry (" + Hexdump.toAscii(sigb, ofs[0], sigb.length - ofs[0]) + ") from: " + Hexdump.toHex(buf, ofs[1], buf.length - ofs[1]));
+ ofs[0]++;
+ decontents[0] = extractone(sigb, buf, ofs, true);
+ ofs[0]++;
+ decontents[1] = extractone(sigb, buf, ofs, true);
+ ofs[0]++;
+ rv = decontents;
+ break;
+ case ArgumentType.VARIANT:
+ int[] newofs = new int[]{0, ofs[1]};
+ String sig = (String) extract(ArgumentType.SIGNATURE_STRING, buf, newofs)[0];
+ newofs[0] = 0;
+ rv = new Variant<Object>(extract(sig, buf, newofs)[0], sig);
+ ofs[1] = newofs[1];
+ break;
+ case ArgumentType.STRING:
+ length = (int) demarshallint(buf, ofs[1], 4);
+ ofs[1] += 4;
+ try {
+ rv = new String(buf, ofs[1], length, "UTF-8");
+ } catch (UnsupportedEncodingException UEe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe);
+ throw new DBusException(getString("utf8NotSupported"));
+ }
+ ofs[1] += length + 1;
+ break;
+ case ArgumentType.OBJECT_PATH:
+ length = (int) demarshallint(buf, ofs[1], 4);
+ ofs[1] += 4;
+ rv = new ObjectPath(getSource(), new String(buf, ofs[1], length));
+ ofs[1] += length + 1;
+ break;
+ case ArgumentType.SIGNATURE:
+ length = (buf[ofs[1]++] & 0xFF);
+ rv = new String(buf, ofs[1], length);
+ ofs[1] += length + 1;
+ break;
+ default:
+ throw new UnknownTypeCodeException(sigb[ofs[0]]);
+ }
+ if (Debug.debug) if (rv instanceof Object[])
+ Debug.print(Debug.VERBOSE, "Extracted: " + Arrays.deepToString((Object[]) rv) + " (now at " + ofs[1] + ")");
+ else
+ Debug.print(Debug.VERBOSE, "Extracted: " + rv + " (now at " + ofs[1] + ")");
+ return rv;
+ }
+
+ /**
+ * Demarshall values from a buffer.
+ *
+ * @param sig The D-Bus signature(s) of the value(s).
+ * @param buf The buffer to demarshall from.
+ * @param ofs The offset into the data buffer to start.
+ * @return The demarshalled value(s).
+ */
+ public Object[] extract(String sig, byte[] buf, int ofs) throws DBusException {
+ return extract(sig, buf, new int[]{0, ofs});
+ }
+
+ /**
+ * Demarshall values from a buffer.
+ *
+ * @param sig The D-Bus signature(s) of the value(s).
+ * @param buf The buffer to demarshall from.
+ * @param ofs An array of two ints, the offset into the signature
+ * and the offset into the data buffer. These values will be
+ * updated to the start of the next value ofter demarshalling.
+ * @return The demarshalled value(s).
+ */
+ public Object[] extract(String sig, byte[] buf, int[] ofs) throws DBusException {
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "extract(" + sig + ",#" + buf.length + ", {" + ofs[0] + "," + ofs[1] + "}");
+ Vector<Object> rv = new Vector<Object>();
+ byte[] sigb = sig.getBytes();
+ for (int[] i = ofs; i[0] < sigb.length; i[0]++) {
+ rv.add(extractone(sigb, buf, i, false));
+ }
+ return rv.toArray();
+ }
+
+ /**
+ * Returns the Bus ID that sent the message.
+ */
+ public String getSource() {
+ return (String) headers.get(HeaderField.SENDER);
+ }
+
+ /**
+ * Returns the destination of the message.
+ */
+ public String getDestination() {
+ return (String) headers.get(HeaderField.DESTINATION);
+ }
+
+ /**
+ * Returns the interface of the message.
+ */
+ public String getInterface() {
+ return (String) headers.get(HeaderField.INTERFACE);
+ }
+
+ /**
+ * Returns the object path of the message.
+ */
+ public String getPath() {
+ Object o = headers.get(HeaderField.PATH);
+ if (null == o) return null;
+ return o.toString();
+ }
+
+ /**
+ * Returns the member name or error name this message represents.
+ */
+ public String getName() {
+ if (this instanceof Error)
+ return (String) headers.get(HeaderField.ERROR_NAME);
+ else
+ return (String) headers.get(HeaderField.MEMBER);
+ }
+
+ /**
+ * Returns the dbus signature of the parameters.
+ */
+ public String getSig() {
+ return (String) headers.get(HeaderField.SIGNATURE);
+ }
+
+ /**
+ * Returns the message flags.
+ */
+ public int getFlags() {
+ return flags;
+ }
+
+ /**
+ * Returns the message serial ID (unique for this connection)
+ *
+ * @return the message serial.
+ */
+ public long getSerial() {
+ return serial;
+ }
+
+ /**
+ * If this is a reply to a message, this returns its serial.
+ *
+ * @return The reply serial, or 0 if it is not a reply.
+ */
+ public long getReplySerial() {
+ Number l = (Number) headers.get(HeaderField.REPLY_SERIAL);
+ if (null == l) return 0;
+ return l.longValue();
+ }
+
+ /**
+ * Parses and returns the parameters to this message as an Object array.
+ */
+ public Object[] getParameters() throws DBusException {
+ if (null == args && null != body) {
+ String sig = (String) headers.get(HeaderField.SIGNATURE);
+ if (null != sig && 0 != body.length) {
+ args = extract(sig, body, 0);
+ } else args = new Object[0];
+ }
+ return args;
+ }
+
+ protected void setArgs(Object[] args) {
+ this.args = args;
+ }
+
+ /**
+ * Warning, do not use this method unless you really know what you are doing.
+ */
+ public void setSource(String source) throws DBusException {
+ if (null != body) {
+ wiredata = new byte[BUFFERINCREMENT][];
+ bufferuse = 0;
+ bytecounter = 0;
+ preallocate(12);
+ append("yyyyuu", big ? Endian.BIG : Endian.LITTLE, type, flags, protover, bodylen, serial);
+ headers.put(HeaderField.SENDER, source);
+ Object[][] newhead = new Object[headers.size()][];
+ int i = 0;
+ for (Byte b : headers.keySet()) {
+ newhead[i] = new Object[2];
+ newhead[i][0] = b;
+ newhead[i][1] = headers.get(b);
+ i++;
+ }
+ append("a(yv)", (Object) newhead);
+ pad((byte) 8);
+ appendBytes(body);
+ }
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/MessageReader.java b/federation/sssd/src/main/java/org/freedesktop/dbus/MessageReader.java
new file mode 100644
index 0000000..4dbea00
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/MessageReader.java
@@ -0,0 +1,194 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import cx.ath.matthew.utils.Hexdump;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.MessageProtocolVersionException;
+import org.freedesktop.dbus.exceptions.MessageTypeException;
+
+import java.io.BufferedInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketTimeoutException;
+import java.text.MessageFormat;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+public class MessageReader {
+ private InputStream in;
+ private byte[] buf = null;
+ private byte[] tbuf = null;
+ private byte[] header = null;
+ private byte[] body = null;
+ private int[] len = new int[4];
+
+ public MessageReader(InputStream in) {
+ this.in = new BufferedInputStream(in);
+ }
+
+ public Message readMessage() throws IOException, DBusException {
+ int rv;
+ /* Read the 12 byte fixed header, retrying as neccessary */
+ if (null == buf) {
+ buf = new byte[12];
+ len[0] = 0;
+ }
+ if (len[0] < 12) {
+ try {
+ rv = in.read(buf, len[0], 12 - len[0]);
+ } catch (SocketTimeoutException STe) {
+ return null;
+ }
+ if (-1 == rv) throw new EOFException(getString("transportReturnedEOF"));
+ len[0] += rv;
+ }
+ if (len[0] == 0) return null;
+ if (len[0] < 12) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Only got " + len[0] + " of 12 bytes of header");
+ return null;
+ }
+
+ /* Parse the details from the header */
+ byte endian = buf[0];
+ byte type = buf[1];
+ byte protover = buf[3];
+ if (protover > Message.PROTOCOL) {
+ buf = null;
+ throw new MessageProtocolVersionException(MessageFormat.format(getString("protocolVersionUnsupported"), new Object[]{protover}));
+ }
+
+ /* Read the length of the variable header */
+ if (null == tbuf) {
+ tbuf = new byte[4];
+ len[1] = 0;
+ }
+ if (len[1] < 4) {
+ try {
+ rv = in.read(tbuf, len[1], 4 - len[1]);
+ } catch (SocketTimeoutException STe) {
+ return null;
+ }
+ if (-1 == rv) throw new EOFException(getString("transportReturnedEOF"));
+ len[1] += rv;
+ }
+ if (len[1] < 4) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Only got " + len[1] + " of 4 bytes of header");
+ return null;
+ }
+
+ /* Parse the variable header length */
+ int headerlen = 0;
+ if (null == header) {
+ headerlen = (int) Message.demarshallint(tbuf, 0, endian, 4);
+ if (0 != headerlen % 8)
+ headerlen += 8 - (headerlen % 8);
+ } else
+ headerlen = header.length - 8;
+
+ /* Read the variable header */
+ if (null == header) {
+ header = new byte[headerlen + 8];
+ System.arraycopy(tbuf, 0, header, 0, 4);
+ len[2] = 0;
+ }
+ if (len[2] < headerlen) {
+ try {
+ rv = in.read(header, 8 + len[2], headerlen - len[2]);
+ } catch (SocketTimeoutException STe) {
+ return null;
+ }
+ if (-1 == rv) throw new EOFException(getString("transportReturnedEOF"));
+ len[2] += rv;
+ }
+ if (len[2] < headerlen) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Only got " + len[2] + " of " + headerlen + " bytes of header");
+ return null;
+ }
+
+ /* Read the body */
+ int bodylen = 0;
+ if (null == body) bodylen = (int) Message.demarshallint(buf, 4, endian, 4);
+ if (null == body) {
+ body = new byte[bodylen];
+ len[3] = 0;
+ }
+ if (len[3] < body.length) {
+ try {
+ rv = in.read(body, len[3], body.length - len[3]);
+ } catch (SocketTimeoutException STe) {
+ return null;
+ }
+ if (-1 == rv) throw new EOFException(getString("transportReturnedEOF"));
+ len[3] += rv;
+ }
+ if (len[3] < body.length) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Only got " + len[3] + " of " + body.length + " bytes of body");
+ return null;
+ }
+
+ Message m;
+ switch (type) {
+ case Message.MessageType.METHOD_CALL:
+ m = new MethodCall();
+ break;
+ case Message.MessageType.METHOD_RETURN:
+ m = new MethodReturn();
+ break;
+ case Message.MessageType.SIGNAL:
+ m = new DBusSignal();
+ break;
+ case Message.MessageType.ERROR:
+ m = new Error();
+ break;
+ default:
+ throw new MessageTypeException(MessageFormat.format(getString("messageTypeUnsupported"), new Object[]{type}));
+ }
+ if (Debug.debug) {
+ Debug.print(Debug.VERBOSE, Hexdump.format(buf));
+ Debug.print(Debug.VERBOSE, Hexdump.format(tbuf));
+ Debug.print(Debug.VERBOSE, Hexdump.format(header));
+ Debug.print(Debug.VERBOSE, Hexdump.format(body));
+ }
+ try {
+ m.populate(buf, header, body);
+ } catch (DBusException DBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe);
+ buf = null;
+ tbuf = null;
+ body = null;
+ header = null;
+ throw DBe;
+ } catch (RuntimeException Re) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Re);
+ buf = null;
+ tbuf = null;
+ body = null;
+ header = null;
+ throw Re;
+ }
+ if (Debug.debug) {
+ Debug.print(Debug.INFO, "=> " + m);
+ }
+ buf = null;
+ tbuf = null;
+ body = null;
+ header = null;
+ return m;
+ }
+
+ public void close() throws IOException {
+ if (Debug.debug) Debug.print(Debug.INFO, "Closing Message Reader");
+ in.close();
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/MessageWriter.java b/federation/sssd/src/main/java/org/freedesktop/dbus/MessageWriter.java
new file mode 100644
index 0000000..45e8cb7
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/MessageWriter.java
@@ -0,0 +1,67 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import cx.ath.matthew.unix.USOutputStream;
+import cx.ath.matthew.utils.Hexdump;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class MessageWriter {
+ private OutputStream out;
+ private boolean isunix;
+
+ public MessageWriter(OutputStream out) {
+ this.out = out;
+ this.isunix = false;
+ try {
+ if (out instanceof USOutputStream)
+ this.isunix = true;
+ } catch (Throwable t) {
+ }
+ if (!this.isunix)
+ this.out = new BufferedOutputStream(this.out);
+ }
+
+ public void writeMessage(Message m) throws IOException {
+ if (Debug.debug) {
+ Debug.print(Debug.INFO, "<= " + m);
+ }
+ if (null == m) return;
+ if (null == m.getWireData()) {
+ if (Debug.debug) Debug.print(Debug.WARN, "Message " + m + " wire-data was null!");
+ return;
+ }
+ if (isunix) {
+ if (Debug.debug) {
+ Debug.print(Debug.DEBUG, "Writing all " + m.getWireData().length + " buffers simultaneously to Unix Socket");
+ for (byte[] buf : m.getWireData())
+ Debug.print(Debug.VERBOSE, "(" + buf + "):" + (null == buf ? "" : Hexdump.format(buf)));
+ }
+ ((USOutputStream) out).write(m.getWireData());
+ } else
+ for (byte[] buf : m.getWireData()) {
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "(" + buf + "):" + (null == buf ? "" : Hexdump.format(buf)));
+ if (null == buf) break;
+ out.write(buf);
+ }
+ out.flush();
+ }
+
+ public void close() throws IOException {
+ if (Debug.debug) Debug.print(Debug.INFO, "Closing Message Writer");
+ out.close();
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/MethodCall.java b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodCall.java
new file mode 100644
index 0000000..72242cc
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodCall.java
@@ -0,0 +1,137 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import cx.ath.matthew.utils.Hexdump;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.MessageFormatException;
+
+import java.util.Vector;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+public class MethodCall extends Message {
+ MethodCall() {
+ }
+
+ public MethodCall(String dest, String path, String iface, String member, byte flags, String sig, Object... args) throws DBusException {
+ this(null, dest, path, iface, member, flags, sig, args);
+ }
+
+ public MethodCall(String source, String dest, String path, String iface, String member, byte flags, String sig, Object... args) throws DBusException {
+ super(Message.Endian.BIG, Message.MessageType.METHOD_CALL, flags);
+
+ if (null == member || null == path)
+ throw new MessageFormatException(getString("missingDestinationPathFunction"));
+ headers.put(Message.HeaderField.PATH, path);
+ headers.put(Message.HeaderField.MEMBER, member);
+
+ Vector<Object> hargs = new Vector<Object>();
+
+ hargs.add(new Object[]{Message.HeaderField.PATH, new Object[]{ArgumentType.OBJECT_PATH_STRING, path}});
+
+ if (null != source) {
+ headers.put(Message.HeaderField.SENDER, source);
+ hargs.add(new Object[]{Message.HeaderField.SENDER, new Object[]{ArgumentType.STRING_STRING, source}});
+ }
+
+ if (null != dest) {
+ headers.put(Message.HeaderField.DESTINATION, dest);
+ hargs.add(new Object[]{Message.HeaderField.DESTINATION, new Object[]{ArgumentType.STRING_STRING, dest}});
+ }
+
+ if (null != iface) {
+ hargs.add(new Object[]{Message.HeaderField.INTERFACE, new Object[]{ArgumentType.STRING_STRING, iface}});
+ headers.put(Message.HeaderField.INTERFACE, iface);
+ }
+
+ hargs.add(new Object[]{Message.HeaderField.MEMBER, new Object[]{ArgumentType.STRING_STRING, member}});
+
+ if (null != sig) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Appending arguments with signature: " + sig);
+ hargs.add(new Object[]{Message.HeaderField.SIGNATURE, new Object[]{ArgumentType.SIGNATURE_STRING, sig}});
+ headers.put(Message.HeaderField.SIGNATURE, sig);
+ setArgs(args);
+ }
+
+ byte[] blen = new byte[4];
+ appendBytes(blen);
+ append("ua(yv)", serial, hargs.toArray());
+ pad((byte) 8);
+
+ long c = bytecounter;
+ if (null != sig) append(sig, args);
+ if (Debug.debug)
+ Debug.print(Debug.DEBUG, "Appended body, type: " + sig + " start: " + c + " end: " + bytecounter + " size: " + (bytecounter - c));
+ marshallint(bytecounter - c, blen, 0, 4);
+ if (Debug.debug) Debug.print("marshalled size (" + blen + "): " + Hexdump.format(blen));
+ }
+
+ private static long REPLY_WAIT_TIMEOUT = 20000;
+
+ /**
+ * Set the default timeout for method calls.
+ * Default is 20s.
+ *
+ * @param timeout New timeout in ms.
+ */
+ public static void setDefaultTimeout(long timeout) {
+ REPLY_WAIT_TIMEOUT = timeout;
+ }
+
+ Message reply = null;
+
+ public synchronized boolean hasReply() {
+ return null != reply;
+ }
+
+ /**
+ * Block (if neccessary) for a reply.
+ *
+ * @param timeout The length of time to block before timing out (ms).
+ * @return The reply to this MethodCall, or null if a timeout happens.
+ */
+ public synchronized Message getReply(long timeout) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking on " + this);
+ if (null != reply) return reply;
+ try {
+ wait(timeout);
+ return reply;
+ } catch (InterruptedException Ie) {
+ return reply;
+ }
+ }
+
+ /**
+ * Block (if neccessary) for a reply.
+ * Default timeout is 20s, or can be configured with setDefaultTimeout()
+ *
+ * @return The reply to this MethodCall, or null if a timeout happens.
+ */
+ public synchronized Message getReply() {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking on " + this);
+ if (null != reply) return reply;
+ try {
+ wait(REPLY_WAIT_TIMEOUT);
+ return reply;
+ } catch (InterruptedException Ie) {
+ return reply;
+ }
+ }
+
+ protected synchronized void setReply(Message reply) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting reply to " + this + " to " + reply);
+ this.reply = reply;
+ notifyAll();
+ }
+
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/MethodReturn.java b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodReturn.java
new file mode 100644
index 0000000..35f4d0d
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodReturn.java
@@ -0,0 +1,77 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+
+import java.util.Vector;
+
+public class MethodReturn extends Message {
+ MethodReturn() {
+ }
+
+ public MethodReturn(String dest, long replyserial, String sig, Object... args) throws DBusException {
+ this(null, dest, replyserial, sig, args);
+ }
+
+ public MethodReturn(String source, String dest, long replyserial, String sig, Object... args) throws DBusException {
+ super(Message.Endian.BIG, Message.MessageType.METHOD_RETURN, (byte) 0);
+
+ headers.put(Message.HeaderField.REPLY_SERIAL, replyserial);
+
+ Vector<Object> hargs = new Vector<Object>();
+ hargs.add(new Object[]{Message.HeaderField.REPLY_SERIAL, new Object[]{ArgumentType.UINT32_STRING, replyserial}});
+
+ if (null != source) {
+ headers.put(Message.HeaderField.SENDER, source);
+ hargs.add(new Object[]{Message.HeaderField.SENDER, new Object[]{ArgumentType.STRING_STRING, source}});
+ }
+
+ if (null != dest) {
+ headers.put(Message.HeaderField.DESTINATION, dest);
+ hargs.add(new Object[]{Message.HeaderField.DESTINATION, new Object[]{ArgumentType.STRING_STRING, dest}});
+ }
+
+ if (null != sig) {
+ hargs.add(new Object[]{Message.HeaderField.SIGNATURE, new Object[]{ArgumentType.SIGNATURE_STRING, sig}});
+ headers.put(Message.HeaderField.SIGNATURE, sig);
+ setArgs(args);
+ }
+
+ byte[] blen = new byte[4];
+ appendBytes(blen);
+ append("ua(yv)", serial, hargs.toArray());
+ pad((byte) 8);
+
+ long c = bytecounter;
+ if (null != sig) append(sig, args);
+ marshallint(bytecounter - c, blen, 0, 4);
+ }
+
+ public MethodReturn(MethodCall mc, String sig, Object... args) throws DBusException {
+ this(null, mc, sig, args);
+ }
+
+ public MethodReturn(String source, MethodCall mc, String sig, Object... args) throws DBusException {
+ this(source, mc.getSource(), mc.getSerial(), sig, args);
+ this.call = mc;
+ }
+
+ MethodCall call;
+
+ public MethodCall getCall() {
+ return call;
+ }
+
+ protected void setCall(MethodCall call) {
+ this.call = call;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/MethodTuple.java b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodTuple.java
new file mode 100644
index 0000000..d135714
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/MethodTuple.java
@@ -0,0 +1,37 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+
+class MethodTuple {
+ String name;
+ String sig;
+
+ public MethodTuple(String name, String sig) {
+ this.name = name;
+ if (null != sig)
+ this.sig = sig;
+ else
+ this.sig = "";
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "new MethodTuple(" + this.name + ", " + this.sig + ")");
+ }
+
+ public boolean equals(Object o) {
+ return o.getClass().equals(MethodTuple.class)
+ && ((MethodTuple) o).name.equals(this.name)
+ && ((MethodTuple) o).sig.equals(this.sig);
+ }
+
+ public int hashCode() {
+ return name.hashCode() + sig.hashCode();
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectPath.java b/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectPath.java
new file mode 100644
index 0000000..5ea1158
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectPath.java
@@ -0,0 +1,22 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+class ObjectPath extends Path {
+ public String source;
+
+ // public DBusConnection conn;
+ public ObjectPath(String source, String path/*, DBusConnection conn*/) {
+ super(path);
+ this.source = source;
+ // this.conn = conn;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectTree.java b/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectTree.java
new file mode 100644
index 0000000..0332e6a
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/ObjectTree.java
@@ -0,0 +1,158 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+
+import java.util.regex.Pattern;
+
+/**
+ * Keeps track of the exported objects for introspection data
+ */
+class ObjectTree {
+ class TreeNode {
+ String name;
+ ExportedObject object;
+ String data;
+ TreeNode right;
+ TreeNode down;
+
+ public TreeNode(String name) {
+ this.name = name;
+ }
+
+ public TreeNode(String name, ExportedObject object, String data) {
+ this.name = name;
+ this.object = object;
+ this.data = data;
+ }
+ }
+
+ private TreeNode root;
+
+ public ObjectTree() {
+ root = new TreeNode("");
+ }
+
+ public static final Pattern slashpattern = Pattern.compile("/");
+
+ private TreeNode recursiveFind(TreeNode current, String path) {
+ if ("/".equals(path)) return current;
+ String[] elements = path.split("/", 2);
+ // this is us or a parent node
+ if (path.startsWith(current.name)) {
+ // this is us
+ if (path.equals(current.name)) {
+ return current;
+ }
+ // recurse down
+ else {
+ if (current.down == null)
+ return null;
+ else return recursiveFind(current.down, elements[1]);
+ }
+ } else if (current.right == null) {
+ return null;
+ } else if (0 > current.right.name.compareTo(elements[0])) {
+ return null;
+ }
+ // recurse right
+ else {
+ return recursiveFind(current.right, path);
+ }
+ }
+
+ private TreeNode recursiveAdd(TreeNode current, String path, ExportedObject object, String data) {
+ String[] elements = slashpattern.split(path, 2);
+ // this is us or a parent node
+ if (path.startsWith(current.name)) {
+ // this is us
+ if (1 == elements.length || "".equals(elements[1])) {
+ current.object = object;
+ current.data = data;
+ }
+ // recurse down
+ else {
+ if (current.down == null) {
+ String[] el = elements[1].split("/", 2);
+ current.down = new TreeNode(el[0]);
+ }
+ current.down = recursiveAdd(current.down, elements[1], object, data);
+ }
+ }
+ // need to create a new sub-tree on the end
+ else if (current.right == null) {
+ current.right = new TreeNode(elements[0]);
+ current.right = recursiveAdd(current.right, path, object, data);
+ }
+ // need to insert here
+ else if (0 > current.right.name.compareTo(elements[0])) {
+ TreeNode t = new TreeNode(elements[0]);
+ t.right = current.right;
+ current.right = t;
+ current.right = recursiveAdd(current.right, path, object, data);
+ }
+ // recurse right
+ else {
+ current.right = recursiveAdd(current.right, path, object, data);
+ }
+ return current;
+ }
+
+ public void add(String path, ExportedObject object, String data) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Adding " + path + " to object tree");
+ root = recursiveAdd(root, path, object, data);
+ }
+
+ public void remove(String path) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Removing " + path + " from object tree");
+ TreeNode t = recursiveFind(root, path);
+ t.object = null;
+ t.data = null;
+ }
+
+ public String Introspect(String path) {
+ TreeNode t = recursiveFind(root, path);
+ if (null == t) return null;
+ StringBuilder sb = new StringBuilder();
+ sb.append("<node name=\"");
+ sb.append(path);
+ sb.append("\">\n");
+ if (null != t.data) sb.append(t.data);
+ t = t.down;
+ while (null != t) {
+ sb.append("<node name=\"");
+ sb.append(t.name);
+ sb.append("\"/>\n");
+ t = t.right;
+ }
+ sb.append("</node>");
+ return sb.toString();
+ }
+
+ private String recursivePrint(TreeNode current) {
+ String s = "";
+ if (null != current) {
+ s += current.name;
+ if (null != current.object)
+ s += "*";
+ if (null != current.down)
+ s += "/{" + recursivePrint(current.down) + "}";
+ if (null != current.right)
+ s += ", " + recursivePrint(current.right);
+ }
+ return s;
+ }
+
+ public String toString() {
+ return recursivePrint(root);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Path.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Path.java
new file mode 100644
index 0000000..91a5a6d
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Path.java
@@ -0,0 +1,39 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+public class Path implements Comparable<Path> {
+ protected String path;
+
+ public Path(String path) {
+ this.path = path;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String toString() {
+ return path;
+ }
+
+ public boolean equals(Object other) {
+ return (other instanceof Path) && path.equals(((Path) other).path);
+ }
+
+ public int hashCode() {
+ return path.hashCode();
+ }
+
+ public int compareTo(Path that) {
+ return path.compareTo(that.path);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Position.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Position.java
new file mode 100644
index 0000000..ed0bb11
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Position.java
@@ -0,0 +1,29 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Position annotation, to annotate Struct fields
+ * to be sent over DBus.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Position {
+ /**
+ * The order of this field in the Struct.
+ */
+ int value();
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java b/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java
new file mode 100644
index 0000000..f6b7867
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java
@@ -0,0 +1,187 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import org.freedesktop.DBus;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+import org.freedesktop.dbus.exceptions.NotConnected;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.text.MessageFormat;
+import java.util.Arrays;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+class RemoteInvocationHandler implements InvocationHandler {
+ public static final int CALL_TYPE_SYNC = 0;
+ public static final int CALL_TYPE_ASYNC = 1;
+ public static final int CALL_TYPE_CALLBACK = 2;
+
+ public static Object convertRV(String sig, Object[] rp, Method m, AbstractConnection conn) throws DBusException {
+ Class<? extends Object> c = m.getReturnType();
+
+ if (null == rp) {
+ if (null == c || Void.TYPE.equals(c)) return null;
+ else throw new DBusExecutionException(getString("voidReturnType"));
+ } else {
+ try {
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Converting return parameters from " + Arrays.deepToString(rp) + " to type " + m.getGenericReturnType());
+ rp = Marshalling.deSerializeParameters(rp,
+ new Type[]{m.getGenericReturnType()}, conn);
+ } catch (Exception e) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ throw new DBusExecutionException(MessageFormat.format(getString("invalidReturnType"), new Object[]{e.getMessage()}));
+ }
+ }
+
+ switch (rp.length) {
+ case 0:
+ if (null == c || Void.TYPE.equals(c))
+ return null;
+ else throw new DBusExecutionException(getString("voidReturnType"));
+ case 1:
+ return rp[0];
+ default:
+
+ // check we are meant to return multiple values
+ if (!Tuple.class.isAssignableFrom(c))
+ throw new DBusExecutionException(getString("tupleReturnType"));
+
+ Constructor<? extends Object> cons = c.getConstructors()[0];
+ try {
+ return cons.newInstance(rp);
+ } catch (Exception e) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ throw new DBusException(e.getMessage());
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Object executeRemoteMethod(RemoteObject ro, Method m, AbstractConnection conn, int syncmethod, CallbackHandler callback, Object... args) throws DBusExecutionException {
+ Type[] ts = m.getGenericParameterTypes();
+ String sig = null;
+ if (ts.length > 0) try {
+ sig = Marshalling.getDBusType(ts);
+ args = Marshalling.convertParameters(args, ts, conn);
+ } catch (DBusException DBe) {
+ throw new DBusExecutionException(getString("contructDBusTypeFailure") + DBe.getMessage());
+ }
+ MethodCall call;
+ byte flags = 0;
+ if (!ro.autostart) flags |= Message.Flags.NO_AUTO_START;
+ if (syncmethod == CALL_TYPE_ASYNC) flags |= Message.Flags.ASYNC;
+ if (m.isAnnotationPresent(DBus.Method.NoReply.class)) flags |= Message.Flags.NO_REPLY_EXPECTED;
+ try {
+ String name;
+ if (m.isAnnotationPresent(DBusMemberName.class))
+ name = m.getAnnotation(DBusMemberName.class).value();
+ else
+ name = m.getName();
+ if (null == ro.iface)
+ call = new MethodCall(ro.busname, ro.objectpath, null, name, flags, sig, args);
+ else {
+ if (null != ro.iface.getAnnotation(DBusInterfaceName.class)) {
+ call = new MethodCall(ro.busname, ro.objectpath, ro.iface.getAnnotation(DBusInterfaceName.class).value(), name, flags, sig, args);
+ } else
+ call = new MethodCall(ro.busname, ro.objectpath, AbstractConnection.dollar_pattern.matcher(ro.iface.getName()).replaceAll("."), name, flags, sig, args);
+ }
+ } catch (DBusException DBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe);
+ throw new DBusExecutionException(getString("constructOutgoingMethodCallFailure") + DBe.getMessage());
+ }
+ if (null == conn.outgoing) throw new NotConnected(getString("notConnected"));
+
+ switch (syncmethod) {
+ case CALL_TYPE_ASYNC:
+ conn.queueOutgoing(call);
+ return new DBusAsyncReply(call, m, conn);
+ case CALL_TYPE_CALLBACK:
+ synchronized (conn.pendingCallbacks) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Queueing Callback " + callback + " for " + call);
+ conn.pendingCallbacks.put(call, callback);
+ conn.pendingCallbackReplys.put(call, new DBusAsyncReply(call, m, conn));
+ }
+ conn.queueOutgoing(call);
+ return null;
+ case CALL_TYPE_SYNC:
+ conn.queueOutgoing(call);
+ break;
+ }
+
+ // get reply
+ if (m.isAnnotationPresent(DBus.Method.NoReply.class)) return null;
+
+ Message reply = call.getReply();
+ if (null == reply) throw new DBus.Error.NoReply(getString("notReplyWithSpecifiedTime"));
+
+ if (reply instanceof Error)
+ ((Error) reply).throwException();
+
+ try {
+ return convertRV(reply.getSig(), reply.getParameters(), m, conn);
+ } catch (DBusException e) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
+ throw new DBusExecutionException(e.getMessage());
+ }
+ }
+
+ AbstractConnection conn;
+ RemoteObject remote;
+
+ public RemoteInvocationHandler(AbstractConnection conn, RemoteObject remote) {
+ this.remote = remote;
+ this.conn = conn;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getName().equals("isRemote")) return true;
+ else if (method.getName().equals("clone")) return null;
+ else if (method.getName().equals("equals")) {
+ try {
+ if (1 == args.length)
+ return new Boolean(remote.equals(((RemoteInvocationHandler) Proxy.getInvocationHandler(args[0])).remote));
+ } catch (IllegalArgumentException IAe) {
+ return Boolean.FALSE;
+ }
+ } else if (method.getName().equals("finalize")) return null;
+ else if (method.getName().equals("getClass")) return DBusInterface.class;
+ else if (method.getName().equals("hashCode")) return remote.hashCode();
+ else if (method.getName().equals("notify")) {
+ remote.notify();
+ return null;
+ } else if (method.getName().equals("notifyAll")) {
+ remote.notifyAll();
+ return null;
+ } else if (method.getName().equals("wait")) {
+ if (0 == args.length) remote.wait();
+ else if (1 == args.length
+ && args[0] instanceof Long) remote.wait((Long) args[0]);
+ else if (2 == args.length
+ && args[0] instanceof Long
+ && args[1] instanceof Integer)
+ remote.wait((Long) args[0], (Integer) args[1]);
+ if (args.length <= 2)
+ return null;
+ } else if (method.getName().equals("toString"))
+ return remote.toString();
+
+ return executeRemoteMethod(remote, method, conn, CALL_TYPE_SYNC, null, args);
+ }
+}
+
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteObject.java b/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteObject.java
new file mode 100644
index 0000000..c157d12
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/RemoteObject.java
@@ -0,0 +1,67 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+class RemoteObject {
+ String busname;
+ String objectpath;
+ Class<? extends DBusInterface> iface;
+ boolean autostart;
+
+ public RemoteObject(String busname, String objectpath, Class<? extends DBusInterface> iface, boolean autostart) {
+ this.busname = busname;
+ this.objectpath = objectpath;
+ this.iface = iface;
+ this.autostart = autostart;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof RemoteObject)) return false;
+ RemoteObject them = (RemoteObject) o;
+
+ if (!them.objectpath.equals(this.objectpath)) return false;
+
+ if (null == this.busname && null != them.busname) return false;
+ if (null != this.busname && null == them.busname) return false;
+ if (null != them.busname && !them.busname.equals(this.busname)) return false;
+
+ if (null == this.iface && null != them.iface) return false;
+ if (null != this.iface && null == them.iface) return false;
+ if (null != them.iface && !them.iface.equals(this.iface)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ return (null == busname ? 0 : busname.hashCode()) + objectpath.hashCode() +
+ (null == iface ? 0 : iface.hashCode());
+ }
+
+ public boolean autoStarting() {
+ return autostart;
+ }
+
+ public String getBusName() {
+ return busname;
+ }
+
+ public String getObjectPath() {
+ return objectpath;
+ }
+
+ public Class<? extends DBusInterface> getInterface() {
+ return iface;
+ }
+
+ public String toString() {
+ return busname + ":" + objectpath + ":" + iface;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/SignalTuple.java b/federation/sssd/src/main/java/org/freedesktop/dbus/SignalTuple.java
new file mode 100644
index 0000000..29ec987
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/SignalTuple.java
@@ -0,0 +1,51 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+class SignalTuple {
+ String type;
+ String name;
+ String object;
+ String source;
+
+ public SignalTuple(String type, String name, String object, String source) {
+ this.type = type;
+ this.name = name;
+ this.object = object;
+ this.source = source;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof SignalTuple)) return false;
+ SignalTuple other = (SignalTuple) o;
+ if (null == this.type && null != other.type) return false;
+ if (null != this.type && !this.type.equals(other.type)) return false;
+ if (null == this.name && null != other.name) return false;
+ if (null != this.name && !this.name.equals(other.name)) return false;
+ if (null == this.object && null != other.object) return false;
+ if (null != this.object && !this.object.equals(other.object)) return false;
+ if (null == this.source && null != other.source) return false;
+ if (null != this.source && !this.source.equals(other.source)) return false;
+ return true;
+ }
+
+ public int hashCode() {
+ return (null == type ? 0 : type.hashCode())
+ + (null == name ? 0 : name.hashCode())
+ + (null == source ? 0 : source.hashCode())
+ + (null == object ? 0 : object.hashCode());
+ }
+
+ public String toString() {
+ return "SignalTuple(" + type + "," + name + "," + object + "," + source + ")";
+ }
+}
+
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/StrongReference.java b/federation/sssd/src/main/java/org/freedesktop/dbus/StrongReference.java
new file mode 100644
index 0000000..d5358ec
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/StrongReference.java
@@ -0,0 +1,42 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * An alternative to a WeakReference when you don't want
+ * that behaviour.
+ */
+public class StrongReference<T> extends WeakReference<T> {
+ T referant;
+
+ public StrongReference(T referant) {
+ super(referant);
+ this.referant = referant;
+ }
+
+ public void clear() {
+ referant = null;
+ }
+
+ public boolean enqueue() {
+ return false;
+ }
+
+ public T get() {
+ return referant;
+ }
+
+ public boolean isEnqueued() {
+ return false;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Struct.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Struct.java
new file mode 100644
index 0000000..18667e5
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Struct.java
@@ -0,0 +1,23 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+/**
+ * This class should be extended to create Structs.
+ * Any such class may be sent over DBus to a method which takes a Struct.
+ * All fields in the Struct which you wish to be serialized and sent to the
+ * remote method should be annotated with the org.freedesktop.dbus.Position
+ * annotation, in the order they should appear in to Struct to DBus.
+ */
+public abstract class Struct extends Container {
+ public Struct() {
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Transport.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Transport.java
new file mode 100644
index 0000000..1745bcf
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Transport.java
@@ -0,0 +1,835 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import cx.ath.matthew.unix.UnixSocket;
+import cx.ath.matthew.unix.UnixSocketAddress;
+import cx.ath.matthew.utils.Hexdump;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.Collator;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.Vector;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+public class Transport {
+ public static class SASL {
+ public static class Command {
+ private int command;
+ private int mechs;
+ private String data;
+ private String response;
+
+ public Command() {
+ }
+
+ public Command(String s) throws IOException {
+ String[] ss = s.split(" ");
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating command from: " + Arrays.toString(ss));
+ if (0 == col.compare(ss[0], "OK")) {
+ command = COMMAND_OK;
+ data = ss[1];
+ } else if (0 == col.compare(ss[0], "AUTH")) {
+ command = COMMAND_AUTH;
+ if (ss.length > 1) {
+ if (0 == col.compare(ss[1], "EXTERNAL"))
+ mechs = AUTH_EXTERNAL;
+ else if (0 == col.compare(ss[1], "DBUS_COOKIE_SHA1"))
+ mechs = AUTH_SHA;
+ else if (0 == col.compare(ss[1], "ANONYMOUS"))
+ mechs = AUTH_ANON;
+ }
+ if (ss.length > 2)
+ data = ss[2];
+ } else if (0 == col.compare(ss[0], "DATA")) {
+ command = COMMAND_DATA;
+ data = ss[1];
+ } else if (0 == col.compare(ss[0], "REJECTED")) {
+ command = COMMAND_REJECTED;
+ for (int i = 1; i < ss.length; i++)
+ if (0 == col.compare(ss[i], "EXTERNAL"))
+ mechs |= AUTH_EXTERNAL;
+ else if (0 == col.compare(ss[i], "DBUS_COOKIE_SHA1"))
+ mechs |= AUTH_SHA;
+ else if (0 == col.compare(ss[i], "ANONYMOUS"))
+ mechs |= AUTH_ANON;
+ } else if (0 == col.compare(ss[0], "BEGIN")) {
+ command = COMMAND_BEGIN;
+ } else if (0 == col.compare(ss[0], "CANCEL")) {
+ command = COMMAND_CANCEL;
+ } else if (0 == col.compare(ss[0], "ERROR")) {
+ command = COMMAND_ERROR;
+ data = ss[1];
+ } else {
+ throw new IOException(getString("invalidCommand") + ss[0]);
+ }
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Created command: " + this);
+ }
+
+ public int getCommand() {
+ return command;
+ }
+
+ public int getMechs() {
+ return mechs;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ public String getResponse() {
+ return response;
+ }
+
+ public void setResponse(String s) {
+ response = s;
+ }
+
+ public String toString() {
+ return "Command(" + command + ", " + mechs + ", " + data + ", " + null + ")";
+ }
+ }
+
+ private static Collator col = Collator.getInstance();
+
+ static {
+ col.setDecomposition(Collator.FULL_DECOMPOSITION);
+ col.setStrength(Collator.PRIMARY);
+ }
+
+ public static final int LOCK_TIMEOUT = 1000;
+ public static final int NEW_KEY_TIMEOUT_SECONDS = 60 * 5;
+ public static final int EXPIRE_KEYS_TIMEOUT_SECONDS = NEW_KEY_TIMEOUT_SECONDS + (60 * 2);
+ public static final int MAX_TIME_TRAVEL_SECONDS = 60 * 5;
+ public static final int COOKIE_TIMEOUT = 240;
+ public static final String COOKIE_CONTEXT = "org_freedesktop_java";
+
+ private String findCookie(String context, String ID) throws IOException {
+ String homedir = System.getProperty("user.home");
+ File f = new File(homedir + "/.dbus-keyrings/" + context);
+ BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
+ String s = null;
+ String cookie = null;
+ long now = System.currentTimeMillis() / 1000;
+ while (null != (s = r.readLine())) {
+ String[] line = s.split(" ");
+ long timestamp = Long.parseLong(line[1]);
+ if (line[0].equals(ID) && (!(timestamp < 0 ||
+ (now + MAX_TIME_TRAVEL_SECONDS) < timestamp ||
+ (now - EXPIRE_KEYS_TIMEOUT_SECONDS) > timestamp))) {
+ cookie = line[2];
+ break;
+ }
+ }
+ r.close();
+ return cookie;
+ }
+
+ private void addCookie(String context, String ID, long timestamp, String cookie) throws IOException {
+ String homedir = System.getProperty("user.home");
+ File keydir = new File(homedir + "/.dbus-keyrings/");
+ File cookiefile = new File(homedir + "/.dbus-keyrings/" + context);
+ File lock = new File(homedir + "/.dbus-keyrings/" + context + ".lock");
+ File temp = new File(homedir + "/.dbus-keyrings/" + context + ".temp");
+
+ // ensure directory exists
+ if (!keydir.exists()) keydir.mkdirs();
+
+ // acquire lock
+ long start = System.currentTimeMillis();
+ while (!lock.createNewFile() && LOCK_TIMEOUT > (System.currentTimeMillis() - start)) ;
+
+ // read old file
+ Vector<String> lines = new Vector<String>();
+ if (cookiefile.exists()) {
+ BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(cookiefile)));
+ String s = null;
+ while (null != (s = r.readLine())) {
+ String[] line = s.split(" ");
+ long time = Long.parseLong(line[1]);
+ // expire stale cookies
+ if ((timestamp - time) < COOKIE_TIMEOUT)
+ lines.add(s);
+ }
+ r.close();
+ }
+
+ // add cookie
+ lines.add(ID + " " + timestamp + " " + cookie);
+
+ // write temp file
+ PrintWriter w = new PrintWriter(new FileOutputStream(temp));
+ for (String l : lines)
+ w.println(l);
+ w.close();
+
+ // atomically move to old file
+ if (!temp.renameTo(cookiefile)) {
+ cookiefile.delete();
+ temp.renameTo(cookiefile);
+ }
+
+ // remove lock
+ lock.delete();
+ }
+
+ /**
+ * Takes the string, encodes it as hex and then turns it into a string again.
+ * No, I don't know why either.
+ */
+ private String stupidlyEncode(String data) {
+ return Hexdump.toHex(data.getBytes()).replaceAll(" ", "");
+ }
+
+ private String stupidlyEncode(byte[] data) {
+ return Hexdump.toHex(data).replaceAll(" ", "");
+ }
+
+ private byte getNibble(char c) {
+ switch (c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return (byte) (c - '0');
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ return (byte) (c - 'A' + 10);
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ return (byte) (c - 'a' + 10);
+ default:
+ return 0;
+ }
+ }
+
+ private String stupidlyDecode(String data) {
+ char[] cs = new char[data.length()];
+ char[] res = new char[cs.length / 2];
+ data.getChars(0, data.length(), cs, 0);
+ for (int i = 0, j = 0; j < res.length; i += 2, j++) {
+ int b = 0;
+ b |= getNibble(cs[i]) << 4;
+ b |= getNibble(cs[i + 1]);
+ res[j] = (char) b;
+ }
+ return new String(res);
+ }
+
+ public static final int MODE_SERVER = 1;
+ public static final int MODE_CLIENT = 2;
+
+ public static final int AUTH_NONE = 0;
+ public static final int AUTH_EXTERNAL = 1;
+ public static final int AUTH_SHA = 2;
+ public static final int AUTH_ANON = 4;
+
+ public static final int COMMAND_AUTH = 1;
+ public static final int COMMAND_DATA = 2;
+ public static final int COMMAND_REJECTED = 3;
+ public static final int COMMAND_OK = 4;
+ public static final int COMMAND_BEGIN = 5;
+ public static final int COMMAND_CANCEL = 6;
+ public static final int COMMAND_ERROR = 7;
+
+ public static final int INITIAL_STATE = 0;
+ public static final int WAIT_DATA = 1;
+ public static final int WAIT_OK = 2;
+ public static final int WAIT_REJECT = 3;
+ public static final int WAIT_AUTH = 4;
+ public static final int WAIT_BEGIN = 5;
+ public static final int AUTHENTICATED = 6;
+ public static final int FAILED = 7;
+
+ public static final int OK = 1;
+ public static final int CONTINUE = 2;
+ public static final int ERROR = 3;
+ public static final int REJECT = 4;
+
+ public Command receive(InputStream s) throws IOException {
+ StringBuffer sb = new StringBuffer();
+ top:
+ while (true) {
+ int c = s.read();
+ switch (c) {
+ case -1:
+ throw new IOException("Stream unexpectedly short (broken pipe)");
+ case 0:
+ case '\r':
+ continue;
+ case '\n':
+ break top;
+ default:
+ sb.append((char) c);
+ }
+ }
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "received: " + sb);
+ try {
+ return new Command(sb.toString());
+ } catch (Exception e) {
+ if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, e);
+ return new Command();
+ }
+ }
+
+ public void send(OutputStream out, int command, String... data) throws IOException {
+ StringBuffer sb = new StringBuffer();
+ switch (command) {
+ case COMMAND_AUTH:
+ sb.append("AUTH");
+ break;
+ case COMMAND_DATA:
+ sb.append("DATA");
+ break;
+ case COMMAND_REJECTED:
+ sb.append("REJECTED");
+ break;
+ case COMMAND_OK:
+ sb.append("OK");
+ break;
+ case COMMAND_BEGIN:
+ sb.append("BEGIN");
+ break;
+ case COMMAND_CANCEL:
+ sb.append("CANCEL");
+ break;
+ case COMMAND_ERROR:
+ sb.append("ERROR");
+ break;
+ default:
+ return;
+ }
+ for (String s : data) {
+ sb.append(' ');
+ sb.append(s);
+ }
+ sb.append('\r');
+ sb.append('\n');
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "sending: " + sb);
+ out.write(sb.toString().getBytes());
+ }
+
+ public int do_challenge(int auth, Command c) throws IOException {
+ switch (auth) {
+ case AUTH_SHA:
+ String[] reply = stupidlyDecode(c.getData()).split(" ");
+ if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.toString(reply));
+ if (3 != reply.length) {
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Reply is not length 3");
+ return ERROR;
+ }
+ String context = reply[0];
+ String ID = reply[1];
+ String serverchallenge = reply[2];
+ MessageDigest md = null;
+ try {
+ md = MessageDigest.getInstance("SHA");
+ } catch (NoSuchAlgorithmException NSAe) {
+ if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe);
+ return ERROR;
+ }
+ byte[] buf = new byte[8];
+ Message.marshallintBig(System.currentTimeMillis(), buf, 0, 8);
+ String clientchallenge = stupidlyEncode(md.digest(buf));
+ md.reset();
+ long start = System.currentTimeMillis();
+ String cookie = null;
+ while (null == cookie && (System.currentTimeMillis() - start) < LOCK_TIMEOUT)
+ cookie = findCookie(context, ID);
+ if (null == cookie) {
+ if (Debug.debug)
+ Debug.print(Debug.DEBUG, "Did not find a cookie in context " + context + " with ID " + ID);
+ return ERROR;
+ }
+ String response = serverchallenge + ":" + clientchallenge + ":" + cookie;
+ buf = md.digest(response.getBytes());
+ if (Debug.debug)
+ Debug.print(Debug.VERBOSE, "Response: " + response + " hash: " + Hexdump.format(buf));
+ response = stupidlyEncode(buf);
+ c.setResponse(stupidlyEncode(clientchallenge + " " + response));
+ return OK;
+ default:
+ if (Debug.debug) Debug.print(Debug.DEBUG, "Not DBUS_COOKIE_SHA1 authtype.");
+ return ERROR;
+ }
+ }
+
+ public String challenge = "";
+ public String cookie = "";
+
+ public int do_response(int auth, String Uid, String kernelUid, Command c) {
+ MessageDigest md = null;
+ try {
+ md = MessageDigest.getInstance("SHA");
+ } catch (NoSuchAlgorithmException NSAe) {
+ if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe);
+ return ERROR;
+ }
+ switch (auth) {
+ case AUTH_NONE:
+ switch (c.getMechs()) {
+ case AUTH_ANON:
+ return OK;
+ case AUTH_EXTERNAL:
+ if (0 == col.compare(Uid, c.getData()) &&
+ (null == kernelUid || 0 == col.compare(Uid, kernelUid)))
+ return OK;
+ else
+ return ERROR;
+ case AUTH_SHA:
+ String context = COOKIE_CONTEXT;
+ long id = System.currentTimeMillis();
+ byte[] buf = new byte[8];
+ Message.marshallintBig(id, buf, 0, 8);
+ challenge = stupidlyEncode(md.digest(buf));
+ Random r = new Random();
+ r.nextBytes(buf);
+ cookie = stupidlyEncode(md.digest(buf));
+ try {
+ addCookie(context, "" + id, id / 1000, cookie);
+ } catch (IOException IOe) {
+ if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe);
+ }
+ if (Debug.debug)
+ Debug.print(Debug.DEBUG, "Sending challenge: " + context + ' ' + id + ' ' + challenge);
+ c.setResponse(stupidlyEncode(context + ' ' + id + ' ' + challenge));
+ return CONTINUE;
+ default:
+ return ERROR;
+ }
+ case AUTH_SHA:
+ String[] response = stupidlyDecode(c.getData()).split(" ");
+ if (response.length < 2) return ERROR;
+ String cchal = response[0];
+ String hash = response[1];
+ String prehash = challenge + ":" + cchal + ":" + cookie;
+ byte[] buf = md.digest(prehash.getBytes());
+ String posthash = stupidlyEncode(buf);
+ if (Debug.debug)
+ Debug.print(Debug.DEBUG, "Authenticating Hash; data=" + prehash + " remote hash=" + hash + " local hash=" + posthash);
+ if (0 == col.compare(posthash, hash))
+ return OK;
+ else
+ return ERROR;
+ default:
+ return ERROR;
+ }
+ }
+
+ public String[] getTypes(int types) {
+ switch (types) {
+ case AUTH_EXTERNAL:
+ return new String[]{"EXTERNAL"};
+ case AUTH_SHA:
+ return new String[]{"DBUS_COOKIE_SHA1"};
+ case AUTH_ANON:
+ return new String[]{"ANONYMOUS"};
+ case AUTH_SHA + AUTH_EXTERNAL:
+ return new String[]{"EXTERNAL", "DBUS_COOKIE_SHA1"};
+ case AUTH_SHA + AUTH_ANON:
+ return new String[]{"ANONYMOUS", "DBUS_COOKIE_SHA1"};
+ case AUTH_EXTERNAL + AUTH_ANON:
+ return new String[]{"ANONYMOUS", "EXTERNAL"};
+ case AUTH_EXTERNAL + AUTH_ANON + AUTH_SHA:
+ return new String[]{"ANONYMOUS", "EXTERNAL", "DBUS_COOKIE_SHA1"};
+ default:
+ return new String[]{};
+ }
+ }
+
+ /**
+ * performs SASL auth on the given streams.
+ * Mode selects whether to run as a SASL server or client.
+ * Types is a bitmask of the available auth types.
+ * Returns true if the auth was successful and false if it failed.
+ */
+ @SuppressWarnings("unchecked")
+ public boolean auth(int mode, int types, String guid, OutputStream out, InputStream in, UnixSocket us) throws IOException {
+ String username = System.getProperty("user.name");
+ String Uid = null;
+ String kernelUid = null;
+ try {
+ Class c = Class.forName("com.sun.security.auth.module.UnixSystem");
+ Method m = c.getMethod("getUid");
+ Object o = c.newInstance();
+ long uid = (Long) m.invoke(o);
+ Uid = stupidlyEncode("" + uid);
+ } catch (Exception e) {
+ Uid = stupidlyEncode(username);
+ }
+ Command c;
+ int failed = 0;
+ int current = 0;
+ int state = INITIAL_STATE;
+
+ while (state != AUTHENTICATED && state != FAILED) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "AUTH state: " + state);
+ switch (mode) {
+ case MODE_CLIENT:
+ switch (state) {
+ case INITIAL_STATE:
+ if (null == us)
+ out.write(new byte[]{0});
+ else
+ us.sendCredentialByte((byte) 0);
+ send(out, COMMAND_AUTH);
+ state = WAIT_DATA;
+ break;
+ case WAIT_DATA:
+ c = receive(in);
+ switch (c.getCommand()) {
+ case COMMAND_DATA:
+ switch (do_challenge(current, c)) {
+ case CONTINUE:
+ send(out, COMMAND_DATA, c.getResponse());
+ break;
+ case OK:
+ send(out, COMMAND_DATA, c.getResponse());
+ state = WAIT_OK;
+ break;
+ case ERROR:
+ send(out, COMMAND_ERROR, c.getResponse());
+ break;
+ }
+ break;
+ case COMMAND_REJECTED:
+ failed |= current;
+ int available = c.getMechs() & (~failed);
+ if (0 != (available & AUTH_EXTERNAL)) {
+ send(out, COMMAND_AUTH, "EXTERNAL", Uid);
+ current = AUTH_EXTERNAL;
+ } else if (0 != (available & AUTH_SHA)) {
+ send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid);
+ current = AUTH_SHA;
+ } else if (0 != (available & AUTH_ANON)) {
+ send(out, COMMAND_AUTH, "ANONYMOUS");
+ current = AUTH_ANON;
+ } else state = FAILED;
+ break;
+ case COMMAND_ERROR:
+ send(out, COMMAND_CANCEL);
+ state = WAIT_REJECT;
+ break;
+ case COMMAND_OK:
+ send(out, COMMAND_BEGIN);
+ state = AUTHENTICATED;
+ break;
+ default:
+ send(out, COMMAND_ERROR, "Got invalid command");
+ break;
+ }
+ break;
+ case WAIT_OK:
+ c = receive(in);
+ switch (c.getCommand()) {
+ case COMMAND_OK:
+ send(out, COMMAND_BEGIN);
+ state = AUTHENTICATED;
+ break;
+ case COMMAND_ERROR:
+ case COMMAND_DATA:
+ send(out, COMMAND_CANCEL);
+ state = WAIT_REJECT;
+ break;
+ case COMMAND_REJECTED:
+ failed |= current;
+ int available = c.getMechs() & (~failed);
+ state = WAIT_DATA;
+ if (0 != (available & AUTH_EXTERNAL)) {
+ send(out, COMMAND_AUTH, "EXTERNAL", Uid);
+ current = AUTH_EXTERNAL;
+ } else if (0 != (available & AUTH_SHA)) {
+ send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid);
+ current = AUTH_SHA;
+ } else if (0 != (available & AUTH_ANON)) {
+ send(out, COMMAND_AUTH, "ANONYMOUS");
+ current = AUTH_ANON;
+ } else state = FAILED;
+ break;
+ default:
+ send(out, COMMAND_ERROR, "Got invalid command");
+ break;
+ }
+ break;
+ case WAIT_REJECT:
+ c = receive(in);
+ switch (c.getCommand()) {
+ case COMMAND_REJECTED:
+ failed |= current;
+ int available = c.getMechs() & (~failed);
+ if (0 != (available & AUTH_EXTERNAL)) {
+ send(out, COMMAND_AUTH, "EXTERNAL", Uid);
+ current = AUTH_EXTERNAL;
+ } else if (0 != (available & AUTH_SHA)) {
+ send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid);
+ current = AUTH_SHA;
+ } else if (0 != (available & AUTH_ANON)) {
+ send(out, COMMAND_AUTH, "ANONYMOUS");
+ current = AUTH_ANON;
+ } else state = FAILED;
+ break;
+ default:
+ state = FAILED;
+ break;
+ }
+ break;
+ default:
+ state = FAILED;
+ }
+ break;
+ case MODE_SERVER:
+ switch (state) {
+ case INITIAL_STATE:
+ byte[] buf = new byte[1];
+ if (null == us) {
+ in.read(buf);
+ } else {
+ buf[0] = us.recvCredentialByte();
+ int kuid = us.getPeerUID();
+ if (kuid >= 0)
+ kernelUid = stupidlyEncode("" + kuid);
+ }
+ if (0 != buf[0]) state = FAILED;
+ else state = WAIT_AUTH;
+ break;
+ case WAIT_AUTH:
+ c = receive(in);
+ switch (c.getCommand()) {
+ case COMMAND_AUTH:
+ if (null == c.getData()) {
+ send(out, COMMAND_REJECTED, getTypes(types));
+ } else {
+ switch (do_response(current, Uid, kernelUid, c)) {
+ case CONTINUE:
+ send(out, COMMAND_DATA, c.getResponse());
+ current = c.getMechs();
+ state = WAIT_DATA;
+ break;
+ case OK:
+ send(out, COMMAND_OK, guid);
+ state = WAIT_BEGIN;
+ current = 0;
+ break;
+ case REJECT:
+ send(out, COMMAND_REJECTED, getTypes(types));
+ current = 0;
+ break;
+ }
+ }
+ break;
+ case COMMAND_ERROR:
+ send(out, COMMAND_REJECTED, getTypes(types));
+ break;
+ case COMMAND_BEGIN:
+ state = FAILED;
+ break;
+ default:
+ send(out, COMMAND_ERROR, "Got invalid command");
+ break;
+ }
+ break;
+ case WAIT_DATA:
+ c = receive(in);
+ switch (c.getCommand()) {
+ case COMMAND_DATA:
+ switch (do_response(current, Uid, kernelUid, c)) {
+ case CONTINUE:
+ send(out, COMMAND_DATA, c.getResponse());
+ state = WAIT_DATA;
+ break;
+ case OK:
+ send(out, COMMAND_OK, guid);
+ state = WAIT_BEGIN;
+ current = 0;
+ break;
+ case REJECT:
+ send(out, COMMAND_REJECTED, getTypes(types));
+ current = 0;
+ break;
+ }
+ break;
+ case COMMAND_ERROR:
+ case COMMAND_CANCEL:
+ send(out, COMMAND_REJECTED, getTypes(types));
+ state = WAIT_AUTH;
+ break;
+ case COMMAND_BEGIN:
+ state = FAILED;
+ break;
+ default:
+ send(out, COMMAND_ERROR, "Got invalid command");
+ break;
+ }
+ break;
+ case WAIT_BEGIN:
+ c = receive(in);
+ switch (c.getCommand()) {
+ case COMMAND_ERROR:
+ case COMMAND_CANCEL:
+ send(out, COMMAND_REJECTED, getTypes(types));
+ state = WAIT_AUTH;
+ break;
+ case COMMAND_BEGIN:
+ state = AUTHENTICATED;
+ break;
+ default:
+ send(out, COMMAND_ERROR, "Got invalid command");
+ break;
+ }
+ break;
+ default:
+ state = FAILED;
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return state == AUTHENTICATED;
+ }
+ }
+
+ public MessageReader min;
+ public MessageWriter mout;
+
+ public Transport() {
+ }
+
+ public static String genGUID() {
+ Random r = new Random();
+ byte[] buf = new byte[16];
+ r.nextBytes(buf);
+ String guid = Hexdump.toHex(buf);
+ return guid.replaceAll(" ", "");
+ }
+
+ public Transport(BusAddress address) throws IOException {
+ connect(address);
+ }
+
+ public Transport(String address) throws IOException, ParseException {
+ connect(new BusAddress(address));
+ }
+
+ public Transport(String address, int timeout) throws IOException, ParseException {
+ connect(new BusAddress(address), timeout);
+ }
+
+ public void connect(String address) throws IOException, ParseException {
+ connect(new BusAddress(address), 0);
+ }
+
+ public void connect(String address, int timeout) throws IOException, ParseException {
+ connect(new BusAddress(address), timeout);
+ }
+
+ public void connect(BusAddress address) throws IOException {
+ connect(address, 0);
+ }
+
+ public void connect(BusAddress address, int timeout) throws IOException {
+ if (Debug.debug) Debug.print(Debug.INFO, "Connecting to " + address);
+ OutputStream out = null;
+ InputStream in = null;
+ UnixSocket us = null;
+ Socket s = null;
+ int mode = 0;
+ int types = 0;
+ if ("unix".equals(address.getType())) {
+ types = SASL.AUTH_EXTERNAL;
+ mode = SASL.MODE_CLIENT;
+ us = new UnixSocket();
+ if (null != address.getParameter("abstract"))
+ us.connect(new UnixSocketAddress(address.getParameter("abstract"), true));
+ else if (null != address.getParameter("path"))
+ us.connect(new UnixSocketAddress(address.getParameter("path"), false));
+ us.setPassCred(true);
+ in = us.getInputStream();
+ out = us.getOutputStream();
+ } else if ("tcp".equals(address.getType())) {
+ types = SASL.AUTH_SHA;
+ if (null != address.getParameter("listen")) {
+ mode = SASL.MODE_SERVER;
+ ServerSocket ss = new ServerSocket();
+ ss.bind(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port"))));
+ s = ss.accept();
+ } else {
+ mode = SASL.MODE_CLIENT;
+ s = new Socket();
+ s.connect(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port"))));
+ }
+ in = s.getInputStream();
+ out = s.getOutputStream();
+ } else {
+ throw new IOException(getString("unknownAddress") + address.getType());
+ }
+
+ if (!(new SASL()).auth(mode, types, address.getParameter("guid"), out, in, us)) {
+ out.close();
+ throw new IOException(getString("errorAuth"));
+ }
+ if (null != us) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to " + timeout + " on Socket");
+ if (timeout == 1)
+ us.setBlocking(false);
+ else
+ us.setSoTimeout(timeout);
+ }
+ if (null != s) {
+ if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to " + timeout + " on Socket");
+ s.setSoTimeout(timeout);
+ }
+ mout = new MessageWriter(out);
+ min = new MessageReader(in);
+ }
+
+ public void disconnect() throws IOException {
+ if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Transport");
+ min.close();
+ mout.close();
+ }
+}
+
+
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Tuple.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Tuple.java
new file mode 100644
index 0000000..5fc13d5
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Tuple.java
@@ -0,0 +1,24 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+/**
+ * This class should be extended to create Tuples.
+ * Any such class may be used as the return type for a method
+ * which returns multiple values.
+ * All fields in the Tuple which you wish to be serialized and sent to the
+ * remote method should be annotated with the org.freedesktop.dbus.Position
+ * annotation, in the order they should appear to DBus.
+ */
+public abstract class Tuple extends Container {
+ public Tuple() {
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusListType.java b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusListType.java
new file mode 100644
index 0000000..a00c307
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusListType.java
@@ -0,0 +1,44 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.types;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * The type of a list.
+ * Should be used whenever you need a Type variable for a list.
+ */
+public class DBusListType implements ParameterizedType {
+ private Type v;
+
+ /**
+ * Create a List type.
+ *
+ * @param v Type of the list contents.
+ */
+ public DBusListType(Type v) {
+ this.v = v;
+ }
+
+ public Type[] getActualTypeArguments() {
+ return new Type[]{v};
+ }
+
+ public Type getRawType() {
+ return List.class;
+ }
+
+ public Type getOwnerType() {
+ return null;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusMapType.java b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusMapType.java
new file mode 100644
index 0000000..754e0a2
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusMapType.java
@@ -0,0 +1,47 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.types;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+/**
+ * The type of a map.
+ * Should be used whenever you need a Type variable for a map.
+ */
+public class DBusMapType implements ParameterizedType {
+ private Type k;
+ private Type v;
+
+ /**
+ * Create a map type.
+ *
+ * @param k The type of the keys.
+ * @param v The type of the values.
+ */
+ public DBusMapType(Type k, Type v) {
+ this.k = k;
+ this.v = v;
+ }
+
+ public Type[] getActualTypeArguments() {
+ return new Type[]{k, v};
+ }
+
+ public Type getRawType() {
+ return Map.class;
+ }
+
+ public Type getOwnerType() {
+ return null;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusStructType.java b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusStructType.java
new file mode 100644
index 0000000..fda2065
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/types/DBusStructType.java
@@ -0,0 +1,45 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus.types;
+
+import org.freedesktop.dbus.Struct;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * The type of a struct.
+ * Should be used whenever you need a Type variable for a struct.
+ */
+public class DBusStructType implements ParameterizedType {
+ private Type[] contents;
+
+ /**
+ * Create a struct type.
+ *
+ * @param contents The types contained in this struct.
+ */
+ public DBusStructType(Type... contents) {
+ this.contents = contents;
+ }
+
+ public Type[] getActualTypeArguments() {
+ return contents;
+ }
+
+ public Type getRawType() {
+ return Struct.class;
+ }
+
+ public Type getOwnerType() {
+ return null;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/TypeSignature.java b/federation/sssd/src/main/java/org/freedesktop/dbus/TypeSignature.java
new file mode 100644
index 0000000..eaad166
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/TypeSignature.java
@@ -0,0 +1,37 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+
+import java.lang.reflect.Type;
+
+public class TypeSignature {
+ String sig;
+
+ public TypeSignature(String sig) {
+ this.sig = sig;
+ }
+
+ public TypeSignature(Type[] types) throws DBusException {
+ StringBuffer sb = new StringBuffer();
+ for (Type t : types) {
+ String[] ts = Marshalling.getDBusType(t);
+ for (String s : ts)
+ sb.append(s);
+ }
+ this.sig = sb.toString();
+ }
+
+ public String getSig() {
+ return sig;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/UInt16.java b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt16.java
new file mode 100644
index 0000000..a8f0600
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt16.java
@@ -0,0 +1,122 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import java.text.MessageFormat;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+/**
+ * Class to represent 16-bit unsigned integers.
+ */
+@SuppressWarnings("serial")
+public class UInt16 extends Number implements Comparable<UInt16> {
+ /**
+ * Maximum possible value.
+ */
+ public static final int MAX_VALUE = 65535;
+ /**
+ * Minimum possible value.
+ */
+ public static final int MIN_VALUE = 0;
+ private int value;
+
+ /**
+ * Create a UInt16 from an int.
+ *
+ * @param value Must be within MIN_VALUE–MAX_VALUE
+ * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE
+ */
+ public UInt16(int value) {
+ if (value < MIN_VALUE || value > MAX_VALUE)
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_VALUE}));
+ this.value = value;
+ }
+
+ /**
+ * Create a UInt16 from a String.
+ *
+ * @param value Must parse to a valid integer within MIN_VALUE–MAX_VALUE
+ * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_VALUE
+ */
+ public UInt16(String value) {
+ this(Integer.parseInt(value));
+ }
+
+ /**
+ * The value of this as a byte.
+ */
+ public byte byteValue() {
+ return (byte) value;
+ }
+
+ /**
+ * The value of this as a double.
+ */
+ public double doubleValue() {
+ return (double) value;
+ }
+
+ /**
+ * The value of this as a float.
+ */
+ public float floatValue() {
+ return (float) value;
+ }
+
+ /**
+ * The value of this as a int.
+ */
+ public int intValue() {
+ return /*(int)*/ value;
+ }
+
+ /**
+ * The value of this as a long.
+ */
+ public long longValue() {
+ return (long) value;
+ }
+
+ /**
+ * The value of this as a short.
+ */
+ public short shortValue() {
+ return (short) value;
+ }
+
+ /**
+ * Test two UInt16s for equality.
+ */
+ public boolean equals(Object o) {
+ return o instanceof UInt16 && ((UInt16) o).value == this.value;
+ }
+
+ public int hashCode() {
+ return /*(int)*/ value;
+ }
+
+ /**
+ * Compare two UInt16s.
+ *
+ * @return 0 if equal, -ve or +ve if they are different.
+ */
+ public int compareTo(UInt16 other) {
+ return /*(int)*/ (this.value - other.value);
+ }
+
+ /**
+ * The value of this as a string.
+ */
+ public String toString() {
+ return "" + value;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/UInt32.java b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt32.java
new file mode 100644
index 0000000..4651919
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt32.java
@@ -0,0 +1,122 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import java.text.MessageFormat;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+/**
+ * Class to represent unsigned 32-bit numbers.
+ */
+@SuppressWarnings("serial")
+public class UInt32 extends Number implements Comparable<UInt32> {
+ /**
+ * Maximum allowed value
+ */
+ public static final long MAX_VALUE = 4294967295L;
+ /**
+ * Minimum allowed value
+ */
+ public static final long MIN_VALUE = 0;
+ private long value;
+
+ /**
+ * Create a UInt32 from a long.
+ *
+ * @param value Must be a valid integer within MIN_VALUE–MAX_VALUE
+ * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE
+ */
+ public UInt32(long value) {
+ if (value < MIN_VALUE || value > MAX_VALUE)
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_VALUE}));
+ this.value = value;
+ }
+
+ /**
+ * Create a UInt32 from a String.
+ *
+ * @param value Must parse to a valid integer within MIN_VALUE–MAX_VALUE
+ * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_VALUE
+ */
+ public UInt32(String value) {
+ this(Long.parseLong(value));
+ }
+
+ /**
+ * The value of this as a byte.
+ */
+ public byte byteValue() {
+ return (byte) value;
+ }
+
+ /**
+ * The value of this as a double.
+ */
+ public double doubleValue() {
+ return (double) value;
+ }
+
+ /**
+ * The value of this as a float.
+ */
+ public float floatValue() {
+ return (float) value;
+ }
+
+ /**
+ * The value of this as a int.
+ */
+ public int intValue() {
+ return (int) value;
+ }
+
+ /**
+ * The value of this as a long.
+ */
+ public long longValue() {
+ return /*(long)*/ value;
+ }
+
+ /**
+ * The value of this as a short.
+ */
+ public short shortValue() {
+ return (short) value;
+ }
+
+ /**
+ * Test two UInt32s for equality.
+ */
+ public boolean equals(Object o) {
+ return o instanceof UInt32 && ((UInt32) o).value == this.value;
+ }
+
+ public int hashCode() {
+ return (int) value;
+ }
+
+ /**
+ * Compare two UInt32s.
+ *
+ * @return 0 if equal, -ve or +ve if they are different.
+ */
+ public int compareTo(UInt32 other) {
+ return (int) (this.value - other.value);
+ }
+
+ /**
+ * The value of this as a string
+ */
+ public String toString() {
+ return "" + value;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/UInt64.java b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt64.java
new file mode 100644
index 0000000..fde2cf0
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/UInt64.java
@@ -0,0 +1,203 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import java.math.BigInteger;
+import java.text.MessageFormat;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+/**
+ * Class to represent unsigned 64-bit numbers.
+ * Warning: Any functions which take or return a <tt>long</tt>
+ * are restricted to the range of a signed 64bit number.
+ * Use the BigInteger methods if you wish access to the full
+ * range.
+ */
+@SuppressWarnings("serial")
+public class UInt64 extends Number implements Comparable<UInt64> {
+ /**
+ * Maximum allowed value (when accessed as a long)
+ */
+ public static final long MAX_LONG_VALUE = Long.MAX_VALUE;
+ /**
+ * Maximum allowed value (when accessed as a BigInteger)
+ */
+ public static final BigInteger MAX_BIG_VALUE = new BigInteger("18446744073709551615");
+ /**
+ * Minimum allowed value
+ */
+ public static final long MIN_VALUE = 0;
+ private BigInteger value;
+ private long top;
+ private long bottom;
+
+ /**
+ * Create a UInt64 from a long.
+ *
+ * @param value Must be a valid integer within MIN_VALUE–MAX_VALUE
+ * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE
+ */
+ public UInt64(long value) {
+ if (value < MIN_VALUE || value > MAX_LONG_VALUE)
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_LONG_VALUE}));
+ this.value = new BigInteger("" + value);
+ this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue();
+ this.bottom = this.value.and(new BigInteger("4294967295")).longValue();
+ }
+
+ /**
+ * Create a UInt64 from two longs.
+ *
+ * @param top Most significant 4 bytes.
+ * @param bottom Least significant 4 bytes.
+ */
+ public UInt64(long top, long bottom) {
+ BigInteger a = new BigInteger("" + top);
+ a = a.shiftLeft(32);
+ a = a.add(new BigInteger("" + bottom));
+ if (0 > a.compareTo(BigInteger.ZERO))
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{a, MIN_VALUE, MAX_BIG_VALUE}));
+ if (0 < a.compareTo(MAX_BIG_VALUE))
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{a, MIN_VALUE, MAX_BIG_VALUE}));
+ this.value = a;
+ this.top = top;
+ this.bottom = bottom;
+ }
+
+ /**
+ * Create a UInt64 from a BigInteger
+ *
+ * @param value Must be a valid BigInteger between MIN_VALUE–MAX_BIG_VALUE
+ * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_BIG_VALUE
+ */
+ public UInt64(BigInteger value) {
+ if (null == value)
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE}));
+ if (0 > value.compareTo(BigInteger.ZERO))
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE}));
+ if (0 < value.compareTo(MAX_BIG_VALUE))
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE}));
+ this.value = value;
+ this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue();
+ this.bottom = this.value.and(new BigInteger("4294967295")).longValue();
+ }
+
+ /**
+ * Create a UInt64 from a String.
+ *
+ * @param value Must parse to a valid integer within MIN_VALUE–MAX_BIG_VALUE
+ * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_BIG_VALUE
+ */
+ public UInt64(String value) {
+ if (null == value)
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE}));
+ BigInteger a = new BigInteger(value);
+ if (0 > a.compareTo(BigInteger.ZERO))
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE}));
+ if (0 < a.compareTo(MAX_BIG_VALUE))
+ throw new NumberFormatException(MessageFormat.format(getString("isNotBetween"), new Object[]{value, MIN_VALUE, MAX_BIG_VALUE}));
+ this.value = a;
+ this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue();
+ this.bottom = this.value.and(new BigInteger("4294967295")).longValue();
+ }
+
+ /**
+ * The value of this as a BigInteger.
+ */
+ public BigInteger value() {
+ return value;
+ }
+
+ /**
+ * The value of this as a byte.
+ */
+ public byte byteValue() {
+ return value.byteValue();
+ }
+
+ /**
+ * The value of this as a double.
+ */
+ public double doubleValue() {
+ return value.doubleValue();
+ }
+
+ /**
+ * The value of this as a float.
+ */
+ public float floatValue() {
+ return value.floatValue();
+ }
+
+ /**
+ * The value of this as a int.
+ */
+ public int intValue() {
+ return value.intValue();
+ }
+
+ /**
+ * The value of this as a long.
+ */
+ public long longValue() {
+ return value.longValue();
+ }
+
+ /**
+ * The value of this as a short.
+ */
+ public short shortValue() {
+ return value.shortValue();
+ }
+
+ /**
+ * Test two UInt64s for equality.
+ */
+ public boolean equals(Object o) {
+ return o instanceof UInt64 && this.value.equals(((UInt64) o).value);
+ }
+
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ /**
+ * Compare two UInt32s.
+ *
+ * @return 0 if equal, -ve or +ve if they are different.
+ */
+ public int compareTo(UInt64 other) {
+ return this.value.compareTo(other.value);
+ }
+
+ /**
+ * The value of this as a string.
+ */
+ public String toString() {
+ return value.toString();
+ }
+
+ /**
+ * Most significant 4 bytes.
+ */
+ public long top() {
+ return top;
+ }
+
+ /**
+ * Least significant 4 bytes.
+ */
+ public long bottom() {
+ return bottom;
+ }
+}
+
diff --git a/federation/sssd/src/main/java/org/freedesktop/dbus/Variant.java b/federation/sssd/src/main/java/org/freedesktop/dbus/Variant.java
new file mode 100644
index 0000000..8fe21a5
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/dbus/Variant.java
@@ -0,0 +1,136 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import org.freedesktop.dbus.exceptions.DBusException;
+
+import java.lang.reflect.Type;
+import java.text.MessageFormat;
+import java.util.Vector;
+
+import static org.freedesktop.dbus.Gettext.getString;
+
+/**
+ * A Wrapper class for Variant values.
+ * A method on DBus can send or receive a Variant.
+ * This will wrap another value whose type is determined at runtime.
+ * The Variant may be parameterized to restrict the types it may accept.
+ */
+public class Variant<T> {
+ private final T o;
+ private final Type type;
+ private final String sig;
+
+ /**
+ * Create a Variant from a basic type object.
+ *
+ * @param o The wrapped value.
+ * @throws IllegalArugmentException If you try and wrap Null or an object of a non-basic type.
+ */
+ public Variant(T o) throws IllegalArgumentException {
+ if (null == o) throw new IllegalArgumentException(getString("cannotWrapNullInVariant"));
+ type = o.getClass();
+ try {
+ String[] ss = Marshalling.getDBusType(o.getClass(), true);
+ if (ss.length != 1)
+ throw new IllegalArgumentException(getString("cannotWrapMultiValuedInVariant") + type);
+ this.sig = ss[0];
+ } catch (DBusException DBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe);
+ throw new IllegalArgumentException(MessageFormat.format(getString("cannotWrapUnqualifiedVariant"), new Object[]{o.getClass(), DBe.getMessage()}));
+ }
+ this.o = o;
+ }
+
+ /**
+ * Create a Variant.
+ *
+ * @param o The wrapped value.
+ * @param type The explicit type of the value.
+ * @throws IllegalArugmentException If you try and wrap Null or an object which cannot be sent over DBus.
+ */
+ public Variant(T o, Type type) throws IllegalArgumentException {
+ if (null == o) throw new IllegalArgumentException(getString("cannotWrapNullInVariant"));
+ this.type = type;
+ try {
+ String[] ss = Marshalling.getDBusType(type);
+ if (ss.length != 1)
+ throw new IllegalArgumentException(getString("cannotWrapMultiValuedInVariant") + type);
+ this.sig = ss[0];
+ } catch (DBusException DBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe);
+ throw new IllegalArgumentException(MessageFormat.format(getString("cannotWrapUnqualifiedVariant"), new Object[]{type, DBe.getMessage()}));
+ }
+ this.o = o;
+ }
+
+ /**
+ * Create a Variant.
+ *
+ * @param o The wrapped value.
+ * @param sig The explicit type of the value, as a dbus type string.
+ * @throws IllegalArugmentException If you try and wrap Null or an object which cannot be sent over DBus.
+ */
+ public Variant(T o, String sig) throws IllegalArgumentException {
+ if (null == o) throw new IllegalArgumentException(getString("cannotWrapNullInVariant"));
+ this.sig = sig;
+ try {
+ Vector<Type> ts = new Vector<Type>();
+ Marshalling.getJavaType(sig, ts, 1);
+ if (ts.size() != 1)
+ throw new IllegalArgumentException(getString("cannotWrapNoTypesInVariant") + sig);
+ this.type = ts.get(0);
+ } catch (DBusException DBe) {
+ if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe);
+ throw new IllegalArgumentException(MessageFormat.format(getString("cannotWrapUnqualifiedVariant"), new Object[]{sig, DBe.getMessage()}));
+ }
+ this.o = o;
+ }
+
+ /**
+ * Return the wrapped value.
+ */
+ public T getValue() {
+ return o;
+ }
+
+ /**
+ * Return the type of the wrapped value.
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * Return the dbus signature of the wrapped value.
+ */
+ public String getSig() {
+ return sig;
+ }
+
+ /**
+ * Format the Variant as a string.
+ */
+ public String toString() {
+ return "[" + o + "]";
+ }
+
+ /**
+ * Compare this Variant with another by comparing contents
+ */
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object other) {
+ if (null == other) return false;
+ if (!(other instanceof Variant)) return false;
+ return this.o.equals(((Variant<? extends Object>) other).o);
+ }
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/Cache.java b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/Cache.java
new file mode 100644
index 0000000..1a45839
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/Cache.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.freedesktop.sssd.infopipe;
+
+import org.freedesktop.dbus.DBusInterface;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
+ */
+public interface Cache extends DBusInterface {
+
+ List<DBusInterface> List();
+
+ List<DBusInterface> ListByDomain(String domain_name);
+
+}
diff --git a/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/InfoPipe.java b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/InfoPipe.java
new file mode 100644
index 0000000..6152d26
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/InfoPipe.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.freedesktop.sssd.infopipe;
+
+import org.freedesktop.dbus.DBusInterface;
+import org.freedesktop.dbus.DBusInterfaceName;
+import org.freedesktop.dbus.DBusMemberName;
+import org.freedesktop.dbus.Variant;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
+ */
+@DBusInterfaceName("org.freedesktop.sssd.infopipe")
+public interface InfoPipe extends DBusInterface {
+
+ String OBJECTPATH = "/org/freedesktop/sssd/infopipe";
+ String BUSNAME = "org.freedesktop.sssd.infopipe";
+
+
+ @DBusMemberName("GetUserAttr")
+ Map<String, Variant> getUserAttributes(String user, List<String> attr);
+
+ @DBusMemberName("GetUserGroups")
+ List<String> getUserGroups(String user);
+
+}
\ No newline at end of file
diff --git a/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/User.java b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/User.java
new file mode 100644
index 0000000..896b80a
--- /dev/null
+++ b/federation/sssd/src/main/java/org/freedesktop/sssd/infopipe/User.java
@@ -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.
+ */
+
+package org.freedesktop.sssd.infopipe;
+
+import org.freedesktop.dbus.DBusInterface;
+import org.freedesktop.dbus.DBusInterfaceName;
+import org.freedesktop.dbus.DBusMemberName;
+
+/**
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>.
+ */
+@DBusInterfaceName("org.freedesktop.sssd.infopipe.Users")
+public interface User extends DBusInterface {
+
+ String OBJECTPATH = "/org/freedesktop/sssd/infopipe/Users";
+
+ @DBusMemberName("FindByCertificate")
+ DBusInterface findByCertificate(String pem_cert);
+
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDCLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDCLibrary.java
new file mode 100644
index 0000000..0de8a2a
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDCLibrary.java
@@ -0,0 +1,34 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2011, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.jvnet.libpam.impl;
+
+/**
+ * @author Sebastian Sdorra
+ */
+public interface BSDCLibrary extends CLibrary {
+
+ BSDPasswd getpwnam(String username);
+
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDPasswd.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDPasswd.java
new file mode 100644
index 0000000..2e437bb
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/BSDPasswd.java
@@ -0,0 +1,93 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2011, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.jvnet.libpam.impl;
+
+import org.jvnet.libpam.impl.CLibrary.passwd;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * FreeeBSD, OpenBSD and MacOS passwd
+ * <p>
+ * struct passwd {
+ * char *pw_name;
+ * char *pw_passwd;
+ * uid_t pw_uid;
+ * gid_t pw_gid;
+ * time_t pw_change;
+ * char *pw_class;
+ * char *pw_gecos;
+ * char *pw_dir;
+ * char *pw_shell;
+ * time_t pw_expire;
+ * };
+ *
+ * @author Sebastian Sdorra
+ */
+public class BSDPasswd extends passwd {
+ /* password change time */
+ public long pw_change;
+
+ /* user access class */
+ public String pw_class;
+
+ /* Honeywell login info */
+ public String pw_gecos;
+
+ /* home directory */
+ public String pw_dir;
+
+ /* default shell */
+ public String pw_shell;
+
+ /* account expiration */
+ public long pw_expire;
+
+ @Override
+ public String getPwGecos() {
+ return pw_gecos;
+ }
+
+ @Override
+ public String getPwDir() {
+ return pw_dir;
+ }
+
+ @Override
+ public String getPwShell() {
+ return pw_shell;
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ List fieldOrder = new ArrayList(super.getFieldOrder());
+ fieldOrder.addAll(Arrays.asList("pw_change", "pw_class", "pw_gecos",
+ "pw_dir", "pw_shell", "pw_expire"));
+ return fieldOrder;
+ }
+
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/CLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/CLibrary.java
new file mode 100644
index 0000000..e3b6105
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/CLibrary.java
@@ -0,0 +1,154 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.jvnet.libpam.impl;
+
+import com.sun.jna.Library;
+import com.sun.jna.Memory;
+import com.sun.jna.Native;
+import com.sun.jna.Platform;
+import com.sun.jna.Pointer;
+import com.sun.jna.Structure;
+import com.sun.jna.ptr.IntByReference;
+import org.jvnet.libpam.PAMException;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+public interface CLibrary extends Library {
+ /**
+ * Comparing http://linux.die.net/man/3/getpwnam
+ * and my Mac OS X reveals that the structure of this field isn't very portable.
+ * In particular, we cannot read the real name reliably.
+ */
+ public class passwd extends Structure {
+ /**
+ * User name.
+ */
+ public String pw_name;
+ /**
+ * Encrypted password.
+ */
+ public String pw_passwd;
+ public int pw_uid;
+ public int pw_gid;
+
+ // ... there are a lot more fields
+
+ public static passwd loadPasswd(String userName) throws PAMException {
+ passwd pwd = libc.getpwnam(userName);
+ if (pwd == null) {
+ throw new PAMException("No user information is available");
+ }
+ return pwd;
+ }
+
+ public String getPwName() {
+ return pw_name;
+ }
+
+ public String getPwPasswd() {
+ return pw_passwd;
+ }
+
+ public int getPwUid() {
+ return pw_uid;
+ }
+
+ public int getPwGid() {
+ return pw_gid;
+ }
+
+ public String getPwGecos() {
+ return null;
+ }
+
+ public String getPwDir() {
+ return null;
+ }
+
+ public String getPwShell() {
+ return null;
+ }
+
+ protected List getFieldOrder() {
+ return Arrays.asList("pw_name", "pw_passwd", "pw_uid", "pw_gid");
+ }
+ }
+
+ public class group extends Structure {
+ public String gr_name;
+ // ... the rest of the field is not interesting for us
+
+ protected List getFieldOrder() {
+ return Arrays.asList("gr_name");
+ }
+ }
+
+ Pointer calloc(int count, int size);
+
+ Pointer strdup(String s);
+
+ passwd getpwnam(String username);
+
+ /**
+ * Lists up group IDs of the given user. On Linux and most BSDs, but not on Solaris.
+ * See http://www.gnu.org/software/hello/manual/gnulib/getgrouplist.html
+ */
+ int getgrouplist(String user, int/*gid_t*/ group, Memory groups, IntByReference ngroups);
+
+ /**
+ * getgrouplist equivalent on Solaris.
+ * See http://mail.opensolaris.org/pipermail/sparks-discuss/2008-September/000528.html
+ */
+ int _getgroupsbymember(String user, Memory groups, int maxgids, int numgids);
+
+ group getgrgid(int/*gid_t*/ gid);
+
+ group getgrnam(String name);
+
+ // other user/group related functions that are likely useful
+ // see http://www.gnu.org/software/libc/manual/html_node/Users-and-Groups.html#Users-and-Groups
+
+
+ public static final CLibrary libc = Instance.init();
+
+ static class Instance {
+ private static CLibrary init() {
+ if (Platform.isMac() || Platform.isOpenBSD()) {
+ return (CLibrary) Native.loadLibrary("c", BSDCLibrary.class);
+ } else if (Platform.isFreeBSD()) {
+ return (CLibrary) Native.loadLibrary("c", FreeBSDCLibrary.class);
+ } else if (Platform.isSolaris()) {
+ return (CLibrary) Native.loadLibrary("c", SolarisCLibrary.class);
+ } else if (Platform.isLinux()) {
+ return (CLibrary) Native.loadLibrary("c", LinuxCLibrary.class);
+ } else {
+ return (CLibrary) Native.loadLibrary("c", CLibrary.class);
+ }
+ }
+ }
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDCLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDCLibrary.java
new file mode 100644
index 0000000..638aec2
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDCLibrary.java
@@ -0,0 +1,34 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2014, R. Tyler Croy, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.jvnet.libpam.impl;
+
+/**
+ * @author R. Tyler Croy
+ */
+public interface FreeBSDCLibrary extends CLibrary {
+
+ FreeBSDPasswd getpwnam(String username);
+
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDPasswd.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDPasswd.java
new file mode 100644
index 0000000..28e519b
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/FreeBSDPasswd.java
@@ -0,0 +1,98 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2014, R. Tyler Croy, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.jvnet.libpam.impl;
+
+import org.jvnet.libpam.impl.CLibrary.passwd;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * FreeeBSD
+ * <p>
+ * struct passwd {
+ * char *pw_name;
+ * char *pw_passwd;
+ * uid_t pw_uid;
+ * gid_t pw_gid;
+ * time_t pw_change;
+ * char *pw_class;
+ * char *pw_gecos;
+ * char *pw_dir;
+ * char *pw_shell;
+ * time_t pw_expire;
+ * int pw_fields;
+ * };
+ *
+ * @author R. Tyler Croy
+ */
+
+public class FreeBSDPasswd extends passwd {
+ /* password change time */
+ public long pw_change;
+
+ /* user access class */
+ public String pw_class;
+
+ /* Honeywell login info */
+ public String pw_gecos;
+
+ /* home directory */
+ public String pw_dir;
+
+ /* default shell */
+ public String pw_shell;
+
+ /* account expiration */
+ public long pw_expire;
+
+ /* internal on FreeBSD? */
+ public int pw_fields;
+
+ @Override
+ public String getPwGecos() {
+ return pw_gecos;
+ }
+
+ @Override
+ public String getPwDir() {
+ return pw_dir;
+ }
+
+ @Override
+ public String getPwShell() {
+ return pw_shell;
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ List fieldOrder = new ArrayList(super.getFieldOrder());
+ fieldOrder.addAll(Arrays.asList("pw_change", "pw_class", "pw_gecos",
+ "pw_dir", "pw_shell", "pw_expire", "pw_fields"));
+ return fieldOrder;
+ }
+
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxCLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxCLibrary.java
new file mode 100644
index 0000000..2525bbe
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxCLibrary.java
@@ -0,0 +1,34 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2011, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.jvnet.libpam.impl;
+
+/**
+ * @author Sebastian Sdorra
+ */
+public interface LinuxCLibrary extends CLibrary {
+
+ LinuxPasswd getpwnam(String username);
+
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxPasswd.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxPasswd.java
new file mode 100644
index 0000000..c1b1678
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/LinuxPasswd.java
@@ -0,0 +1,81 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2011, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.jvnet.libpam.impl;
+
+import org.jvnet.libpam.impl.CLibrary.passwd;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Linux passwd
+ * <p>
+ * ?struct passwd
+ * {
+ * char *pw_name;
+ * char *pw_passwd;
+ * __uid_t pw_uid;
+ * __gid_t pw_gid;
+ * char *pw_gecos;
+ * char *pw_dir;
+ * char *pw_shell;
+ * };
+ *
+ * @author Sebastian Sdorra
+ */
+public class LinuxPasswd extends passwd {
+ /* Honeywell login info */
+ public String pw_gecos;
+
+ /* home directory */
+ public String pw_dir;
+
+ /* default shell */
+ public String pw_shell;
+
+
+ public String getPwGecos() {
+ return pw_gecos;
+ }
+
+ @Override
+ public String getPwDir() {
+ return pw_dir;
+ }
+
+ @Override
+ public String getPwShell() {
+ return pw_shell;
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ List fieldOrder = new ArrayList(super.getFieldOrder());
+ fieldOrder.addAll(Arrays.asList("pw_gecos", "pw_dir", "pw_shell"));
+ return fieldOrder;
+ }
+
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/PAMLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/PAMLibrary.java
new file mode 100644
index 0000000..a34ec31
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/PAMLibrary.java
@@ -0,0 +1,163 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.jvnet.libpam.impl;
+
+import com.sun.jna.Callback;
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+import com.sun.jna.PointerType;
+import com.sun.jna.Structure;
+import com.sun.jna.ptr.PointerByReference;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.jvnet.libpam.impl.CLibrary.libc;
+
+/**
+ * libpam.so binding.
+ * <p>
+ * See http://www.opengroup.org/onlinepubs/008329799/apdxa.htm
+ * for the online reference of pam_appl.h
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public interface PAMLibrary extends Library {
+ class pam_handle_t extends PointerType {
+ public pam_handle_t() {
+ }
+
+ public pam_handle_t(Pointer pointer) {
+ super(pointer);
+ }
+ }
+
+ class pam_message extends Structure {
+ public int msg_style;
+ public String msg;
+
+ /**
+ * Attach to the memory region pointed by the given pointer.
+ */
+ public pam_message(Pointer src) {
+ useMemory(src);
+ read();
+ }
+
+ protected List getFieldOrder() {
+ return Arrays.asList("msg_style", "msg");
+ }
+ }
+
+ class pam_response extends Structure {
+ /**
+ * This is really a string, but this field needs to be malloc-ed by the conversation
+ * method, and to be freed by the caler, so I bind it to {@link Pointer} here.
+ * <p>
+ * The man page doesn't say that, but see
+ * http://www.netbsd.org/docs/guide/en/chap-pam.html#pam-sample-conv
+ * This behavior is confirmed with a test, too; if I don't do strdup,
+ * libpam crashes.
+ */
+ public Pointer resp;
+ public int resp_retcode;
+
+ /**
+ * Attach to the memory region pointed by the given memory.
+ */
+ public pam_response(Pointer src) {
+ useMemory(src);
+ read();
+ }
+
+ public pam_response() {
+ }
+
+ /**
+ * Sets the response code.
+ */
+ public void setResp(String msg) {
+ this.resp = libc.strdup(msg);
+ }
+
+ protected List getFieldOrder() {
+ return Arrays.asList("resp", "resp_retcode");
+ }
+
+ public static final int SIZE = new pam_response().size();
+ }
+
+ class pam_conv extends Structure {
+ public interface PamCallback extends Callback {
+ /**
+ * According to http://www.netbsd.org/docs/guide/en/chap-pam.html#pam-sample-conv,
+ * resp and its member string both needs to be allocated by malloc,
+ * to be freed by the caller.
+ */
+ int callback(int num_msg, Pointer msg, Pointer resp, Pointer _);
+ }
+
+ public PamCallback conv;
+ public Pointer _;
+
+ public pam_conv(PamCallback conv) {
+ this.conv = conv;
+ }
+
+ protected List getFieldOrder() {
+ return Arrays.asList("conv", "_");
+ }
+ }
+
+ int pam_start(String service, String user, pam_conv conv, PointerByReference/* pam_handle_t** */ pamh_p);
+
+ int pam_end(pam_handle_t handle, int pam_status);
+
+ int pam_set_item(pam_handle_t handle, int item_type, String item);
+
+ int pam_get_item(pam_handle_t handle, int item_type, PointerByReference item);
+
+ int pam_authenticate(pam_handle_t handle, int flags);
+
+ int pam_setcred(pam_handle_t handle, int flags);
+
+ int pam_acct_mgmt(pam_handle_t handle, int flags);
+
+ String pam_strerror(pam_handle_t handle, int pam_error);
+
+ final int PAM_USER = 2;
+
+ // error code
+ final int PAM_SUCCESS = 0;
+ final int PAM_CONV_ERR = 6;
+
+
+ final int PAM_PROMPT_ECHO_OFF = 1; /* Echo off when getting response */
+ final int PAM_PROMPT_ECHO_ON = 2; /* Echo on when getting response */
+ final int PAM_ERROR_MSG = 3; /* Error message */
+ final int PAM_TEXT_INFO = 4; /* Textual information */
+
+ public static final PAMLibrary libpam = (PAMLibrary) Native.loadLibrary("pam", PAMLibrary.class);
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisCLibrary.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisCLibrary.java
new file mode 100644
index 0000000..6b538a2
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisCLibrary.java
@@ -0,0 +1,35 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+package org.jvnet.libpam.impl;
+
+/**
+ * @author Sebastian Sdorra
+ */
+public interface SolarisCLibrary extends CLibrary {
+
+ SolarisPasswd getpwnam(String username);
+
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisPasswd.java b/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisPasswd.java
new file mode 100644
index 0000000..37ee749
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/impl/SolarisPasswd.java
@@ -0,0 +1,86 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+package org.jvnet.libpam.impl;
+
+import org.jvnet.libpam.impl.CLibrary.passwd;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Solaris passwd
+ * <p>
+ * struct passwd {
+ * char *pw_name;
+ * char *pw_passwd;
+ * uid_t pw_uid;
+ * gid_t pw_gid;
+ * char *pw_age;
+ * char *pw_comment;
+ * char *pw_gecos;
+ * char *pw_dir;
+ * char *pw_shell;
+ * };
+ *
+ * @author Sebastian Sdorra
+ */
+public class SolarisPasswd extends passwd {
+ public String pw_age;
+
+ public String pw_comment;
+
+ public String pw_gecos;
+
+ public String pw_dir;
+
+ public String pw_shell;
+
+
+ @Override
+ public String getPwGecos() {
+ return pw_gecos;
+ }
+
+ @Override
+ public String getPwDir() {
+ return pw_dir;
+ }
+
+ @Override
+ public String getPwShell() {
+ return pw_shell;
+ }
+
+ @Override
+ protected List getFieldOrder() {
+ List fieldOrder = new ArrayList(super.getFieldOrder());
+ fieldOrder.addAll(Arrays.asList("pw_age", "pw_comment", "pw_gecos",
+ "pw_dir", "pw_shell"));
+ return fieldOrder;
+ }
+
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/PAM.java b/federation/sssd/src/main/java/org/jvnet/libpam/PAM.java
new file mode 100644
index 0000000..7de5f90
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/PAM.java
@@ -0,0 +1,188 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.jvnet.libpam;
+
+import com.sun.jna.Pointer;
+import com.sun.jna.ptr.PointerByReference;
+import org.jboss.logging.Logger;
+import org.jvnet.libpam.impl.CLibrary.passwd;
+import org.jvnet.libpam.impl.PAMLibrary.pam_conv;
+import org.jvnet.libpam.impl.PAMLibrary.pam_conv.PamCallback;
+import org.jvnet.libpam.impl.PAMLibrary.pam_handle_t;
+import org.jvnet.libpam.impl.PAMLibrary.pam_message;
+import org.jvnet.libpam.impl.PAMLibrary.pam_response;
+
+import java.util.Set;
+
+import static com.sun.jna.Native.POINTER_SIZE;
+import static org.jvnet.libpam.impl.CLibrary.libc;
+import static org.jvnet.libpam.impl.PAMLibrary.PAM_CONV_ERR;
+import static org.jvnet.libpam.impl.PAMLibrary.PAM_PROMPT_ECHO_OFF;
+import static org.jvnet.libpam.impl.PAMLibrary.PAM_SUCCESS;
+import static org.jvnet.libpam.impl.PAMLibrary.PAM_USER;
+import static org.jvnet.libpam.impl.PAMLibrary.libpam;
+
+/**
+ * PAM authenticator.
+ * <p>
+ * <p>
+ * Instances are thread unsafe and non reentrant. An instace cannot be reused
+ * to authenticate multiple users.
+ * <p>
+ * <p>
+ * For an overview of PAM programming, refer to the following resources:
+ * <p>
+ * <ul>
+ * <li><a href="http://www.netbsd.org/docs/guide/en/chap-pam.html">NetBSD PAM programming guide</a>
+ * <li><a href="http://www.kernel.org/pub/linux/libs/pam/">Linux PAM</a>
+ * </ul>
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public class PAM {
+ private pam_handle_t pht;
+ private int ret;
+
+ /**
+ * Temporarily stored to pass a value from {@link #authenticate(String, String...)}
+ * to {@link pam_conv}.
+ */
+ private String[] factors;
+
+ /**
+ * Creates a new authenticator.
+ *
+ * @param serviceName PAM service name. This corresponds to the service name that shows up
+ * in the PAM configuration,
+ */
+ public PAM(String serviceName) throws PAMException {
+ pam_conv conv = new pam_conv(new PamCallback() {
+ public int callback(int num_msg, Pointer msg, Pointer resp, Pointer _) {
+ LOGGER.debug("pam_conv num_msg=" + num_msg);
+ if (factors == null)
+ return PAM_CONV_ERR;
+
+ // allocates pam_response[num_msg]. the caller will free this
+ Pointer m = libc.calloc(pam_response.SIZE, num_msg);
+ resp.setPointer(0, m);
+
+ for (int i = 0; i < factors.length; i++) {
+ pam_message pm = new pam_message(msg.getPointer(POINTER_SIZE * i));
+ LOGGER.debug(pm.msg_style + ":" + pm.msg);
+ if (pm.msg_style == PAM_PROMPT_ECHO_OFF) {
+ pam_response r = new pam_response(m.share(pam_response.SIZE * i));
+ r.setResp(factors[i]);
+ r.write(); // write to (*resp)[i]
+ }
+ }
+
+ return PAM_SUCCESS;
+ }
+ });
+
+ PointerByReference phtr = new PointerByReference();
+ check(libpam.pam_start(serviceName, null, conv, phtr), "pam_start failed");
+ pht = new pam_handle_t(phtr.getValue());
+ }
+
+ private void check(int ret, String msg) throws PAMException {
+ this.ret = ret;
+ if (ret != 0) {
+ if (pht != null)
+ throw new PAMException(msg + " : " + libpam.pam_strerror(pht, ret));
+ else
+ throw new PAMException(msg);
+ }
+ }
+
+ /**
+ * Authenticate the user with a password.
+ *
+ * @return Upon a successful authentication, return information about the user.
+ * @throws PAMException If the authentication fails.
+ */
+ public UnixUser authenticate(String username, String... factors) throws PAMException {
+ this.factors = factors;
+ try {
+ check(libpam.pam_set_item(pht, PAM_USER, username), "pam_set_item failed");
+ check(libpam.pam_authenticate(pht, 0), "pam_authenticate failed");
+ check(libpam.pam_setcred(pht, 0), "pam_setcred failed");
+ // several different error code seem to be used to represent authentication failures
+// check(libpam.pam_acct_mgmt(pht,0),"pam_acct_mgmt failed");
+
+ PointerByReference r = new PointerByReference();
+ check(libpam.pam_get_item(pht, PAM_USER, r), "pam_get_item failed");
+ String userName = r.getValue().getString(0);
+ passwd pwd = libc.getpwnam(userName);
+ if (pwd == null)
+ throw new PAMException("Authentication succeeded but no user information is available");
+ return new UnixUser(userName, pwd);
+ } finally {
+ this.factors = null;
+ }
+ }
+
+ /**
+ * Returns the groups a user belongs to
+ *
+ * @param username
+ * @return Set of group names
+ * @throws PAMException
+ * @deprecated Pointless and ugly convenience method.
+ */
+ public Set<String> getGroupsOfUser(String username) throws PAMException {
+ return new UnixUser(username).getGroups();
+ }
+
+ /**
+ * After a successful authentication, call this method to obtain the effective user name.
+ * This can be different from the user name that you passed to the {@link #authenticate(String, String)}
+ * method.
+ */
+
+ /**
+ * Performs an early disposal of the object, instead of letting this GC-ed.
+ * Since PAM may hold on to native resources that don't put pressure on Java GC,
+ * doing this is a good idea.
+ * <p>
+ * <p>
+ * This method is called by {@link #finalize()}, too, so it's not required
+ * to call this method explicitly, however.
+ */
+ public void dispose() {
+ if (pht != null) {
+ libpam.pam_end(pht, ret);
+ pht = null;
+ }
+ }
+
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ dispose();
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(PAM.class.getName());
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/PAMException.java b/federation/sssd/src/main/java/org/jvnet/libpam/PAMException.java
new file mode 100644
index 0000000..d26faf7
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/PAMException.java
@@ -0,0 +1,48 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.jvnet.libpam;
+
+/**
+ * Exception in PAM invoactions.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public class PAMException extends Exception {
+ public PAMException() {
+ }
+
+ public PAMException(String message) {
+ super(message);
+ }
+
+ public PAMException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public PAMException(Throwable cause) {
+ super(cause);
+ }
+
+ private static final long serialVersionUID = 1L;
+}
diff --git a/federation/sssd/src/main/java/org/jvnet/libpam/UnixUser.java b/federation/sssd/src/main/java/org/jvnet/libpam/UnixUser.java
new file mode 100644
index 0000000..bc1ceab
--- /dev/null
+++ b/federation/sssd/src/main/java/org/jvnet/libpam/UnixUser.java
@@ -0,0 +1,159 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.jvnet.libpam;
+
+import com.sun.jna.Memory;
+import com.sun.jna.ptr.IntByReference;
+import org.jvnet.libpam.impl.CLibrary.group;
+import org.jvnet.libpam.impl.CLibrary.passwd;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.jvnet.libpam.impl.CLibrary.libc;
+
+/**
+ * Represents an Unix user. Immutable.
+ *
+ * @author Kohsuke Kawaguchi
+ */
+public class UnixUser {
+ private final String userName, gecos, dir, shell;
+ private final int uid, gid;
+ private final Set<String> groups;
+
+ /*package*/ UnixUser(String userName, passwd pwd) throws PAMException {
+ this.userName = userName;
+ this.gecos = pwd.getPwGecos();
+ this.dir = pwd.getPwDir();
+ this.shell = pwd.getPwShell();
+ this.uid = pwd.getPwUid();
+ this.gid = pwd.getPwGid();
+
+ int sz = 4; /*sizeof(gid_t)*/
+
+ int ngroups = 64;
+ Memory m = new Memory(ngroups * sz);
+ IntByReference pngroups = new IntByReference(ngroups);
+ try {
+ if (libc.getgrouplist(userName, pwd.getPwGid(), m, pngroups) < 0) {
+ // allocate a bigger memory
+ m = new Memory(pngroups.getValue() * sz);
+ if (libc.getgrouplist(userName, pwd.getPwGid(), m, pngroups) < 0)
+ // shouldn't happen, but just in case.
+ throw new PAMException("getgrouplist failed");
+ }
+ ngroups = pngroups.getValue();
+ } catch (LinkageError e) {
+ // some platform, notably Solaris, doesn't have the getgrouplist function
+ ngroups = libc._getgroupsbymember(userName, m, ngroups, 0);
+ if (ngroups < 0)
+ throw new PAMException("_getgroupsbymember failed");
+ }
+
+ groups = new HashSet<String>();
+ for (int i = 0; i < ngroups; i++) {
+ int gid = m.getInt(i * sz);
+ group grp = libc.getgrgid(gid);
+ if (grp == null) {
+ continue;
+ }
+ groups.add(grp.gr_name);
+ }
+ }
+
+ public UnixUser(String userName) throws PAMException {
+ this(userName, passwd.loadPasswd(userName));
+ }
+
+ /**
+ * Copy constructor for mocking. Not intended for regular use. Only for testing.
+ * This signature may change in the future.
+ */
+ protected UnixUser(String userName, String gecos, String dir, String shell, int uid, int gid, Set<String> groups) {
+ this.userName = userName;
+ this.gecos = gecos;
+ this.dir = dir;
+ this.shell = shell;
+ this.uid = uid;
+ this.gid = gid;
+ this.groups = groups;
+ }
+
+ /**
+ * Gets the unix account name. Never null.
+ */
+ public String getUserName() {
+ return userName;
+ }
+
+ /**
+ * Gets the UID of this user.
+ */
+ public int getUID() {
+ return uid;
+ }
+
+ /**
+ * Gets the GID of this user.
+ */
+ public int getGID() {
+ return gid;
+ }
+
+ /**
+ * Gets the gecos (the real name) of this user.
+ */
+ public String getGecos() {
+ return gecos;
+ }
+
+ /**
+ * Gets the home directory of this user.
+ */
+ public String getDir() {
+ return dir;
+ }
+
+ /**
+ * Gets the shell of this user.
+ */
+ public String getShell() {
+ return shell;
+ }
+
+ /**
+ * Gets the groups that this user belongs to.
+ *
+ * @return never null.
+ */
+ public Set<String> getGroups() {
+ return Collections.unmodifiableSet(groups);
+ }
+
+ public static boolean exists(String name) {
+ return libc.getpwnam(name) != null;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/api/Sssd.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/api/Sssd.java
new file mode 100644
index 0000000..065eb98
--- /dev/null
+++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/api/Sssd.java
@@ -0,0 +1,134 @@
+/*
+ * 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.sssd.api;
+
+import cx.ath.matthew.LibraryLoader;
+import org.freedesktop.DBus;
+import org.freedesktop.dbus.DBusConnection;
+import org.freedesktop.dbus.Variant;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.sssd.infopipe.InfoPipe;
+import org.freedesktop.sssd.infopipe.User;
+import org.jboss.logging.Logger;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+/**
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
+ * @version $Revision: 1 $
+ */
+public class Sssd {
+
+ public static User user() {
+ return SingletonHolder.USER_OBJECT;
+ }
+
+ public static InfoPipe infopipe() {
+ return SingletonHolder.INFOPIPE_OBJECT;
+ }
+
+
+ public static void disconnect() {
+ SingletonHolder.DBUS_CONNECTION.disconnect();
+ }
+
+ private String username;
+ private static final Logger logger = Logger.getLogger(Sssd.class);
+
+ private Sssd() {
+ }
+
+ public Sssd(String username) {
+ this.username = username;
+ }
+
+ private static final class SingletonHolder {
+ private static InfoPipe INFOPIPE_OBJECT;
+ private static User USER_OBJECT;
+ private static DBusConnection DBUS_CONNECTION;
+
+ static {
+ try {
+ DBUS_CONNECTION = DBusConnection.getConnection(DBusConnection.SYSTEM);
+ INFOPIPE_OBJECT = DBUS_CONNECTION.getRemoteObject(InfoPipe.BUSNAME, InfoPipe.OBJECTPATH, InfoPipe.class);
+ USER_OBJECT = DBUS_CONNECTION.getRemoteObject(InfoPipe.BUSNAME, User.OBJECTPATH, User.class);
+ } catch (DBusException e) {
+ logger.error("Failed to obtain D-Bus connection", e);
+ }
+ }
+ }
+
+ public static String getRawAttribute(Variant variant) {
+ if (variant != null) {
+ Vector value = (Vector) variant.getValue();
+ if (value.size() >= 1) {
+ return value.get(0).toString();
+ }
+ }
+ return null;
+ }
+
+ public Map<String, Variant> getUserAttributes() {
+ String[] attr = {"mail", "givenname", "sn", "telephoneNumber"};
+ Map<String, Variant> attributes = null;
+ try {
+ InfoPipe infoPipe = infopipe();
+ attributes = infoPipe.getUserAttributes(username, Arrays.asList(attr));
+ } catch (Exception e) {
+ logger.error("Failed to retrieve user's attributes from SSSD", e);
+ }
+
+ return attributes;
+ }
+
+ public List<String> getUserGroups() {
+ List<String> userGroups = null;
+ try {
+ InfoPipe infoPipe = Sssd.infopipe();
+ userGroups = infoPipe.getUserGroups(username);
+ } catch (Exception e) {
+ logger.error("Failed to retrieve user's groups from SSSD", e);
+ }
+ return userGroups;
+ }
+
+ public static boolean isAvailable(){
+ boolean sssdAvailable = false;
+ try {
+ if (LibraryLoader.load().succeed()) {
+ DBusConnection connection = DBusConnection.getConnection(DBusConnection.SYSTEM);
+ DBus dbus = connection.getRemoteObject(DBus.BUSNAME, DBus.OBJECTPATH, DBus.class);
+ sssdAvailable = Arrays.asList(dbus.ListNames()).contains(InfoPipe.BUSNAME);
+ if (!sssdAvailable) {
+ logger.debugv("SSSD is not available in your system. Federation provider will be disabled.");
+ } else {
+ sssdAvailable = true;
+ }
+ connection.disconnect();
+ } else {
+ logger.debugv("libunix_dbus_java not found. Federation provider will be disabled.");
+ }
+ } catch (DBusException e) {
+ logger.error("Failed to check the status of SSSD", e);
+ }
+ return sssdAvailable;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/impl/PAMAuthenticator.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/impl/PAMAuthenticator.java
new file mode 100644
index 0000000..f982201
--- /dev/null
+++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/impl/PAMAuthenticator.java
@@ -0,0 +1,62 @@
+/*
+ * 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.sssd.impl;
+
+import org.jboss.logging.Logger;
+import org.jvnet.libpam.PAM;
+import org.jvnet.libpam.PAMException;
+import org.jvnet.libpam.UnixUser;
+
+/**
+ * PAMAuthenticator for Unix users
+ *
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
+ * @version $Revision: 1 $
+ */
+public class PAMAuthenticator {
+
+ private static final String PAM_SERVICE = "keycloak";
+ private static final Logger logger = Logger.getLogger(PAMAuthenticator.class);
+ private final String username;
+ private final String[] factors;
+
+ public PAMAuthenticator(String username, String... factors) {
+ this.username = username;
+ this.factors = factors;
+ }
+
+ /**
+ * Returns true if user was successfully authenticated against PAM
+ *
+ * @return UnixUser object if user was successfully authenticated
+ */
+ public UnixUser authenticate() {
+ PAM pam = null;
+ UnixUser user = null;
+ try {
+ pam = new PAM(PAM_SERVICE);
+ user = pam.authenticate(username, factors);
+ } catch (PAMException e) {
+ logger.error("Authentication failed", e);
+ e.printStackTrace();
+ } finally {
+ pam.dispose();
+ }
+ return user;
+ }
+}
diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/ReadonlySSSDUserModelDelegate.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/ReadonlySSSDUserModelDelegate.java
new file mode 100755
index 0000000..52061c9
--- /dev/null
+++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/ReadonlySSSDUserModelDelegate.java
@@ -0,0 +1,77 @@
+/*
+ * 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.sssd;
+
+import org.keycloak.models.ModelReadOnlyException;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.UserModelDelegate;
+
+/**
+ * Readonly proxy for a SSSD UserModel that prevents attributes from being updated.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
+ * @version $Revision: 1 $
+ */
+public class ReadonlySSSDUserModelDelegate extends UserModelDelegate implements UserModel {
+
+ private final SSSDFederationProvider provider;
+
+ public ReadonlySSSDUserModelDelegate(UserModel delegate, SSSDFederationProvider provider) {
+ super(delegate);
+ this.provider = provider;
+ }
+
+ @Override
+ public void setUsername(String username) {
+ throw new ModelReadOnlyException("Federated storage is not writable");
+ }
+
+ @Override
+ public void setLastName(String lastName) {
+ throw new ModelReadOnlyException("Federated storage is not writable");
+ }
+
+ @Override
+ public void setFirstName(String first) {
+ throw new ModelReadOnlyException("Federated storage is not writable");
+ }
+
+ @Override
+ public void updateCredentialDirectly(UserCredentialValueModel cred) {
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ throw new IllegalStateException("Federated storage is not writable");
+ }
+ super.updateCredentialDirectly(cred);
+ }
+
+ @Override
+ public void updateCredential(UserCredentialModel cred) {
+ if (provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
+ throw new ModelReadOnlyException("Federated storage is not writable");
+ }
+ delegate.updateCredential(cred);
+ }
+
+ @Override
+ public void setEmail(String email) {
+ throw new ModelReadOnlyException("Federated storage is not writable");
+ }
+}
diff --git a/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java
new file mode 100755
index 0000000..a9089ec
--- /dev/null
+++ b/federation/sssd/src/main/java/org/keycloak/federation/sssd/SSSDFederationProvider.java
@@ -0,0 +1,224 @@
+/*
+ * 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.sssd;
+
+import org.freedesktop.dbus.Variant;
+import org.jboss.logging.Logger;
+import org.keycloak.federation.sssd.api.Sssd;
+import org.keycloak.federation.sssd.impl.PAMAuthenticator;
+import org.keycloak.models.CredentialValidationOutput;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.services.managers.UserManager;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * SPI provider implementation to retrieve data from SSSD and authenticate
+ * against PAM
+ *
+ * @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
+ * @version $Revision: 1 $
+ */
+public class SSSDFederationProvider implements UserFederationProvider {
+
+ private static final Logger logger = Logger.getLogger(SSSDFederationProvider.class);
+
+ protected static final Set<String> supportedCredentialTypes = new HashSet<>();
+ private final SSSDFederationProviderFactory factory;
+ protected KeycloakSession session;
+ protected UserFederationProviderModel model;
+
+ public SSSDFederationProvider(KeycloakSession session, UserFederationProviderModel model, SSSDFederationProviderFactory sssdFederationProviderFactory) {
+ this.session = session;
+ this.model = model;
+ this.factory = sssdFederationProviderFactory;
+ }
+
+ static {
+ supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
+ }
+
+
+ @Override
+ public UserModel getUserByUsername(RealmModel realm, String username) {
+ return findOrCreateAuthenticatedUser(realm, username);
+ }
+
+ /**
+ * Called after successful authentication
+ *
+ * @param realm realm
+ * @param username username without realm prefix
+ * @return user if found or successfully created. Null if user with same username already exists, but is not linked to this provider
+ */
+ protected UserModel findOrCreateAuthenticatedUser(RealmModel realm, String username) {
+ UserModel user = session.userStorage().getUserByUsername(username, realm);
+ if (user != null) {
+ logger.debug("SSSD authenticated user " + username + " found in Keycloak storage");
+
+ if (!model.getId().equals(user.getFederationLink())) {
+ logger.warn("User with username " + username + " already exists, but is not linked to provider [" + model.getDisplayName() + "]");
+ return null;
+ } else {
+ UserModel proxied = validateAndProxy(realm, user);
+ if (proxied != null) {
+ return proxied;
+ } else {
+ logger.warn("User with username " + username + " already exists and is linked to provider [" + model.getDisplayName() +
+ "] but principal is not correct.");
+ logger.warn("Will re-create user");
+ new UserManager(session).removeUser(realm, user, session.userStorage());
+ }
+ }
+ }
+
+ logger.debug("SSSD authenticated user " + username + " not in Keycloak storage. Creating...");
+ return importUserToKeycloak(realm, username);
+ }
+
+ protected UserModel importUserToKeycloak(RealmModel realm, String username) {
+ Sssd sssd = new Sssd(username);
+ Map<String, Variant> sssdUser = sssd.getUserAttributes();
+ logger.debugf("Creating SSSD user: %s to local Keycloak storage", username);
+ UserModel user = session.userStorage().addUser(realm, username);
+ user.setEnabled(true);
+ user.setEmail(Sssd.getRawAttribute(sssdUser.get("mail")));
+ user.setFirstName(Sssd.getRawAttribute(sssdUser.get("givenname")));
+ user.setLastName(Sssd.getRawAttribute(sssdUser.get("sn")));
+ for (String s : sssd.getUserGroups()) {
+ GroupModel group = KeycloakModelUtils.findGroupByPath(realm, "/" + s);
+ if (group == null) {
+ group = session.realms().createGroup(realm, s);
+ }
+ user.joinGroup(group);
+ }
+ user.setFederationLink(model.getId());
+ return validateAndProxy(realm, user);
+ }
+
+ @Override
+ public UserModel getUserByEmail(RealmModel realm, String email) {
+ return null;
+ }
+
+ @Override
+ public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void preRemove(RealmModel realm) {
+ // complete We don't care about the realm being removed
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, RoleModel role) {
+ // complete we dont'care if a role is removed
+
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, GroupModel group) {
+ // complete we dont'care if a role is removed
+
+ }
+
+ @Override
+ public boolean isValid(RealmModel realm, UserModel local) {
+ Map<String, Variant> attributes = new Sssd(local.getUsername()).getUserAttributes();
+ return Sssd.getRawAttribute(attributes.get("mail")).equalsIgnoreCase(local.getEmail());
+ }
+
+ @Override
+ public Set<String> getSupportedCredentialTypes(UserModel user) {
+ return supportedCredentialTypes;
+ }
+
+ @Override
+ public Set<String> getSupportedCredentialTypes() {
+ return supportedCredentialTypes;
+ }
+
+ @Override
+ public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
+ for (UserCredentialModel cred : input) {
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ PAMAuthenticator pam = factory.createPAMAuthenticator(user.getUsername(), cred.getValue());
+ return (pam.authenticate() != null);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
+ return validCredentials(realm, user, Arrays.asList(input));
+ }
+
+ @Override
+ public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) {
+ return CredentialValidationOutput.failed();
+ }
+
+ @Override
+ public UserModel validateAndProxy(RealmModel realm, UserModel local) {
+ if (isValid(realm, local)) {
+ return new ReadonlySSSDUserModelDelegate(local, this);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean synchronizeRegistrations() {
+ return false;
+ }
+
+ @Override
+ public UserModel register(RealmModel realm, UserModel user) {
+ throw new IllegalStateException("Registration not supported");
+ }
+
+ @Override
+ public boolean removeUser(RealmModel realm, UserModel user) {
+ return true;
+ }
+
+ @Override
+ public void close() {
+ Sssd.disconnect();
+ }
+}
diff --git a/federation/sssd/src/main/resources/DBUS-JAVA-AUTHORS b/federation/sssd/src/main/resources/DBUS-JAVA-AUTHORS
new file mode 100644
index 0000000..9b7699e
--- /dev/null
+++ b/federation/sssd/src/main/resources/DBUS-JAVA-AUTHORS
@@ -0,0 +1,37 @@
+The D-Bus Java implementation was written by:
+
+Matthew Johnson <dbus -at matthew -dot- ath -dot- cx>
+
+Bug fixes/reports and other suggestions from:
+
+Remi Emonet <remi.emonet -at- inrialpes -dot- fr>
+Simon McVittie <simon -dot- mcvittie -at- collabora -dot- co -dot- uk>
+Dick Hollenbeck <dick -at- softplc -dot- com>
+Joshua Nichols <nichoj -at- gentoo -dot- org>
+Ralf Kistner <ralf.kistner -at- gmail -dot- com>
+Henrik Petander <henrik -dot- petander -at- iki -dot- fi>
+Luigi Paioro <luigi -at- lambrate -dot- it>
+Roberto Francisco Arroyo Moreno <robfram -at- ugr -dot- es>
+Steve Crane <Steve -dot Crane -at- rococosoft -dot- com>
+Philippe Marschall <philippe -dot- marschall -at- gmail -dot- com>
+Daniel Machado <cdanielmachado -at- gmail -dot- com>
+Anibal Sanchez <anibal -dot- sanchez -at- sunya -dot- com -dot- ar>
+Jan Kümmel <freedesktop -at- snorc -dot- org>
+Johannes Felten <johannesfelten -at- googlemail -dot- com>
+Tom Walsh <walshtc -at- gmail -dot- com>
+Ed Wei <Edward.Wei.03 -at- alum -dot- dartmouth -dot- org>
+Sveinung Kvilhaugsvik <sveinung84 -at- users -dot- sourceforge -dot- net>
+Hugues Moreau <hmoreau -at- gmail -dot- com>
+Viktar Vauchkevich <vctr -at- yandex -dot- ru>
+Serkan Kaba <serkan_kaba -at- yahoo -dot- com>
+Adam Bennett <cruxic -at- gmail -dot- com>
+Frank Benoit <benoit -at- tionex -dot- de>
+Gunnar Aastrand Grimnes <gunnar -dot- grimnes -at- dfki -dot- de>
+
+The included Viewer application was originally written by:
+
+Peter Cox <petercox -at- gawab -dot- com>
+
+with patches from:
+
+Zsombor Gegesy <gzsombor -at- gmail -dot- com>
federation/sssd/src/main/resources/DBUS-JAVA-LICENSE 680(+680 -0)
diff --git a/federation/sssd/src/main/resources/DBUS-JAVA-LICENSE b/federation/sssd/src/main/resources/DBUS-JAVA-LICENSE
new file mode 100644
index 0000000..d651143
--- /dev/null
+++ b/federation/sssd/src/main/resources/DBUS-JAVA-LICENSE
@@ -0,0 +1,680 @@
+The D-Bus Java implementation is licensed to you under your choice of the
+Academic Free License version 2.1, or the GNU Lesser/Library General Public License
+version 2. Both licenses are included here. Each source code file is marked
+with the proper copyright information.
+
+The Academic Free License
+v. 2.1
+
+This Academic Free License (the "License") applies to any original work of
+authorship (the "Original Work") whose owner (the "Licensor") has placed the
+following notice immediately following the copyright notice for the Original
+Work:
+
+Licensed under the Academic Free License version 2.1
+
+1) Grant of Copyright License. Licensor hereby grants You a world-wide,
+royalty-free, non-exclusive, perpetual, sublicenseable license to do the
+following:
+
+a) to reproduce the Original Work in copies;
+
+b) to prepare derivative works ("Derivative Works") based upon the Original
+Work;
+
+c) to distribute copies of the Original Work and Derivative Works to the
+public;
+
+d) to perform the Original Work publicly; and
+
+e) to display the Original Work publicly.
+
+2) Grant of Patent License. Licensor hereby grants You a world-wide,
+royalty-free, non-exclusive, perpetual, sublicenseable license, under patent
+claims owned or controlled by the Licensor that are embodied in the Original
+Work as furnished by the Licensor, to make, use, sell and offer for sale the
+Original Work and Derivative Works.
+
+3) Grant of Source Code License. The term "Source Code" means the preferred
+form of the Original Work for making modifications to it and all available
+documentation describing how to modify the Original Work. Licensor hereby
+agrees to provide a machine-readable copy of the Source Code of the Original
+Work along with each copy of the Original Work that Licensor distributes.
+Licensor reserves the right to satisfy this obligation by placing a
+machine-readable copy of the Source Code in an information repository
+reasonably calculated to permit inexpensive and convenient access by You for as
+long as Licensor continues to distribute the Original Work, and by publishing
+the address of that information repository in a notice immediately following
+the copyright notice that applies to the Original Work.
+
+4) Exclusions From License Grant. Neither the names of Licensor, nor the names
+of any contributors to the Original Work, nor any of their trademarks or
+service marks, may be used to endorse or promote products derived from this
+Original Work without express prior written permission of the Licensor. Nothing
+in this License shall be deemed to grant any rights to trademarks, copyrights,
+patents, trade secrets or any other intellectual property of Licensor except as
+expressly stated herein. No patent license is granted to make, use, sell or
+offer to sell embodiments of any patent claims other than the licensed claims
+defined in Section 2. No right is granted to the trademarks of Licensor even if
+such marks are included in the Original Work. Nothing in this License shall be
+interpreted to prohibit Licensor from licensing under different terms from this
+License any Original Work that Licensor otherwise would have a right to
+license.
+
+5) This section intentionally omitted.
+
+6) Attribution Rights. You must retain, in the Source Code of any Derivative
+Works that You create, all copyright, patent or trademark notices from the
+Source Code of the Original Work, as well as any notices of licensing and any
+descriptive text identified therein as an "Attribution Notice." You must cause
+the Source Code for any Derivative Works that You create to carry a prominent
+Attribution Notice reasonably calculated to inform recipients that You have
+modified the Original Work.
+
+7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that
+the copyright in and to the Original Work and the patent rights granted herein
+by Licensor are owned by the Licensor or are sublicensed to You under the terms
+of this License with the permission of the contributor(s) of those copyrights
+and patent rights. Except as expressly stated in the immediately proceeding
+sentence, the Original Work is provided under this License on an "AS IS" BASIS
+and WITHOUT WARRANTY, either express or implied, including, without limitation,
+the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU.
+This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No
+license to Original Work is granted hereunder except under this disclaimer.
+
+8) Limitation of Liability. Under no circumstances and under no legal theory,
+whether in tort (including negligence), contract, or otherwise, shall the
+Licensor be liable to any person for any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License
+or the use of the Original Work including, without limitation, damages for loss
+of goodwill, work stoppage, computer failure or malfunction, or any and all
+other commercial damages or losses. This limitation of liability shall not
+apply to liability for death or personal injury resulting from Licensor's
+negligence to the extent applicable law prohibits such limitation. Some
+jurisdictions do not allow the exclusion or limitation of incidental or
+consequential damages, so this exclusion and limitation may not apply to You.
+
+9) Acceptance and Termination. If You distribute copies of the Original Work or
+a Derivative Work, You must make a reasonable effort under the circumstances to
+obtain the express assent of recipients to the terms of this License. Nothing
+else but this License (or another written agreement between Licensor and You)
+grants You permission to create Derivative Works based upon the Original Work
+or to exercise any of the rights granted in Section 1 herein, and any attempt
+to do so except under the terms of this License (or another written agreement
+between Licensor and You) is expressly prohibited by U.S. copyright law, the
+equivalent laws of other countries, and by international treaty. Therefore, by
+exercising any of the rights granted to You in Section 1 herein, You indicate
+Your acceptance of this License and all of its terms and conditions.
+
+10) Termination for Patent Action. This License shall terminate automatically
+and You may no longer exercise any of the rights granted to You by this License
+as of the date You commence an action, including a cross-claim or counterclaim,
+against Licensor or any licensee alleging that the Original Work infringes a
+patent. This termination provision shall not apply for an action alleging
+patent infringement by combinations of the Original Work with other software or
+hardware.
+
+11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this
+License may be brought only in the courts of a jurisdiction wherein the
+Licensor resides or in which Licensor conducts its primary business, and under
+the laws of that jurisdiction excluding its conflict-of-law provisions. The
+application of the United Nations Convention on Contracts for the International
+Sale of Goods is expressly excluded. Any use of the Original Work outside the
+scope of this License or after its termination shall be subject to the
+requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et seq.,
+the equivalent laws of other countries, and international treaty. This section
+shall survive the termination of this License.
+
+12) Attorneys Fees. In any action to enforce the terms of this License or
+seeking damages relating thereto, the prevailing party shall be entitled to
+recover its costs and expenses, including, without limitation, reasonable
+attorneys' fees and costs incurred in connection with such action, including
+any appeal of such action. This section shall survive the termination of this
+License.
+
+13) Miscellaneous. This License represents the complete agreement concerning
+the subject matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent necessary to
+make it enforceable.
+
+14) Definition of "You" in This License. "You" throughout this License, whether
+in upper or lower case, means an individual or a legal entity exercising rights
+under, and complying with all of the terms of, this License. For legal
+entities, "You" includes any entity that controls, is controlled by, or is
+under common control with you. For purposes of this definition, "control" means
+(i) the power, direct or indirect, to cause the direction or management of such
+entity, whether by contract or otherwise, or (ii) ownership of fifty percent
+(50%) or more of the outstanding shares, or (iii) beneficial ownership of such
+entity.
+
+15) Right to Use. You may use the Original Work in all ways not otherwise
+restricted or conditioned by this License or by law, and Licensor promises not
+to interfere with or be responsible for such uses by You.
+
+This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved.
+Permission is hereby granted to copy and distribute this license without
+modification. This license may not be modified without the express written
+permission of its copyright owner.
+
+
+--
+END OF ACADEMIC FREE LICENSE. The following is intended to describe the
+essential differences between the Academic Free License (AFL) version 1.0 and
+other open source licenses:
+
+The Academic Free License is similar to the BSD, MIT, UoI/NCSA and Apache
+licenses in many respects but it is intended to solve a few problems with those
+licenses.
+
+* The AFL is written so as to make it clear what software is being
+licensed (by the inclusion of a statement following the copyright notice in the
+software). This way, the license functions better than a template license. The
+BSD, MIT and UoI/NCSA licenses apply to unidentified software.
+
+* The AFL contains a complete copyright grant to the software. The BSD
+and Apache licenses are vague and incomplete in that respect.
+
+* The AFL contains a complete patent grant to the software. The BSD, MIT,
+UoI/NCSA and Apache licenses rely on an implied patent license and contain no
+explicit patent grant.
+
+* The AFL makes it clear that no trademark rights are granted to the
+licensor's trademarks. The Apache license contains such a provision, but the
+BSD, MIT and UoI/NCSA licenses do not.
+
+* The AFL includes the warranty by the licensor that it either owns the
+copyright or that it is distributing the software under a license. None of the
+other licenses contain that warranty. All other warranties are disclaimed, as
+is the case for the other licenses.
+
+* The AFL is itself copyrighted (with the right granted to copy and distribute
+without modification). This ensures that the owner of the copyright to the
+license will control changes. The Apache license contains a copyright notice,
+but the BSD, MIT and UoI/NCSA licenses do not.
+
+--
+START OF GNU LIBRARY GENERAL PUBLIC LICENSE
+--
+
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL. It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it. You can use it for
+your libraries, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the library, or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library. If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software. To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+ Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs. This
+license, the GNU Library General Public License, applies to certain
+designated libraries. This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+ The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it. Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program. However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+ Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries. We
+concluded that weaker conditions might promote sharing better.
+
+ However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves. This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them. (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.) The hope is that this
+will lead to faster development of free libraries.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+ Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License"). Each licensee is
+addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ c) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ d) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Library General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/federation/sssd/src/main/resources/en_US.properties b/federation/sssd/src/main/resources/en_US.properties
new file mode 100644
index 0000000..31bc06c
--- /dev/null
+++ b/federation/sssd/src/main/resources/en_US.properties
@@ -0,0 +1,82 @@
+#java-format
+notBasicType=" is not a basic type"
+notObjectProvidedByProcess=" is not an object provided by this process."
+arrayOutOfBounds="Array index out of bounds, paofs={0}, pabuf.length={1}, buf.length={2}."
+arrayMustNotExceed="Arrays must not exceed "
+asyncCallNoReply="Async call has not had a reply"
+busAddressBlank="Bus address is blank"
+busAddressInvalid="Bus address is invalid: "
+cannotWrapNullInVariant="Can't wrap Null in a Variant"
+cannotWrapMultiValuedInVariant="Can't wrap a multi-valued type in a Variant: "
+cannotWrapNoTypesInVariant="Can't wrap multiple or no types in a Variant: "
+cannotWrapUnqualifiedVariant="Can't wrap {0} in an unqualified Variant ({1})."
+cannotResolveSessionBusAddress="Cannot Resolve Session Bus Address"
+cannotWatchSignalsWellKnownBussName="Cannot watch for signals based on well known bus name as source, only unique names."
+cannotCreateClassFromSignal="Could not create class from signal "
+interfaceToCastNotFound="Could not find an interface to cast to"
+interfaceNotAllowedOutsidePackage="DBusInterfaces cannot be declared outside a package"
+interfaceMustBeDefinedPackage="DBusInterfaces must be defined in a package."
+disconnected="Disconnected"
+errorExecutingMethod="Error Executing Method {0}.{1}: {2}"
+errorDeserializingMessage="Error deserializing message: number of parameters didn't match receiving signature"
+nonExportableParameterizedType="Exporting non-exportable parameterized type "
+nonExportableType "Exporting non-exportable type "
+errorAddSignalParameters="Failed to add signal parameters: "
+errorAuth="Failed to auth"
+connectionFailure="Failed to connect to bus "
+contructDBusTypeFailure="Failed to construct D-Bus type: "
+constructOutgoingMethodCallFailure="Failed to construct outgoing method call: "
+createProxyExportFailure="Failed to create proxy object for {0} exported by {1}. Reason: {2}"
+createProxyFailure="Failed to create proxy object for {0}; reason: {1}."
+parseDBusTypeSignatureFailure="Failed to parse DBus type signature: "
+parseDBusSignatureFailure="Failed to parse DBus type signature: {0} ({1})."
+dbusRegistrationFailure="Failed to register bus name"
+deSerializationFailure="Failure in de-serializing message: "
+introspectInterfaceExceedCharacters="Introspected interface name exceeds 255 characters. Cannot export objects of type "
+introspectMethodExceedCharacters="Introspected method name exceeds 255 characters. Cannot export objects with method "
+introspectSignalExceedCharacters="Introspected signal name exceeds 255 characters. Cannot export objects with signals of type "
+invalidBusType="Invalid Bus Type: "
+invalidCommand="Invalid Command "
+invalidBusName="Invalid bus name"
+nullBusName="Invalid bus name: null"
+invalidObjectPath="Invalid object path: "
+nullObjectPath="Invalid object path: null"
+invalidTypeMatchRule="Invalid type for match rule: "
+mapParameters="Map must have 2 parameters"
+messageFailedSend="Message Failed to Send: "
+messageTypeUnsupported="Message type {0} unsupported"
+multiValuedArrayNotPermitted="Multi-valued array types not permitted"
+missingObjectPath="Must Specify an Object Path"
+missingDestinationPathFunction="Must specify destination, path and function name to MethodCalls."
+missingErrorName="Must specify error name to Errors."
+missingPathInterfaceSignal="Must specify object path, interface and signal name to Signals."
+notReplyWithSpecifiedTime="No reply within specified time"
+missingTransport="No transport present"
+notDBusInterface="Not A DBus Interface"
+notDBusSignal="Not A DBus Signal"
+convertionTypeNotExpected="Not An Expected Convertion type from {0} to {1}"
+notConnected="Not Connected"
+notPrimitiveType="Not a primitive type"
+invalidDBusCode="Not a valid D-Bus type code: "
+invalidWrapperType="Not a wrapper type"
+invalidArray="Not an array"
+objectNotExportedNoRemoteSpecified="Not an object exported by this connection and no remote specified"
+notEnoughElementsToCreateCustomObject="Not enough elements to create custom object from serialized data ({0} < {1})."
+objectAlreadyExported="Object already exported"
+arraySentAsNonPrimitive="Primative array being sent as non-primative array."
+protocolVersionUnsupported="Protocol version {0} is unsupported"
+cannotIntrospectReturnType="Return type of Object[] cannot be introspected properly"
+mustImplementDeserializeMethod="Serializable classes must implement a deserialize method"
+mustSerializeNativeDBusTypes="Serializable classes must serialize to native DBus types"
+signalsMustBeMemberOfClass="Signals must be declared as a member of a class implementing DBusInterface which is the member of a package."
+spuriousReply="Spurious reply. No message with the given serial id was awaiting a reply."
+utf8NotSupported="System does not support UTF-8 encoding"
+methodDoesNotExist="The method `{0}.{1}' does not exist on this object."
+unconvertableType="Trying to marshall to unconvertable type (from {0} to {1})."
+transportReturnedEOF="Underlying transport returned EOF"
+waitingFor="Waiting for: "
+invalidReturnType="Wrong return type (failed to de-serialize correct types: {0} )"
+voidReturnType="Wrong return type (got void, expected a value)"
+tupleReturnType="Wrong return type (not expecting Tuple)"
+unknownAddress="unknown address type "
+isNotBetween="{0} is not between {1} and {2}."
diff --git a/federation/sssd/src/test/java/cx/ath/matthew/unix/testclient.java b/federation/sssd/src/test/java/cx/ath/matthew/unix/testclient.java
new file mode 100644
index 0000000..2acef18
--- /dev/null
+++ b/federation/sssd/src/test/java/cx/ath/matthew/unix/testclient.java
@@ -0,0 +1,49 @@
+/*
+ * Java Unix Sockets Library
+ *
+ * Copyright (c) Matthew Johnson 2005
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * To Contact the author, please email src@matthew.ath.cx
+ *
+ */
+
+package cx.ath.matthew.unix;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+public class testclient {
+ public static void main(String args[]) throws IOException {
+ UnixSocket s = new UnixSocket(new UnixSocketAddress("testsock", true));
+ OutputStream os = s.getOutputStream();
+ PrintWriter o = new PrintWriter(os);
+ BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
+ String l;
+ while (null != (l = r.readLine())) {
+ byte[] buf = (l + "\n").getBytes();
+ os.write(buf, 0, buf.length);
+ }
+ s.close();
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_client.java b/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_client.java
new file mode 100644
index 0000000..b71033d
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_client.java
@@ -0,0 +1,511 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+import org.freedesktop.DBus;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+import org.freedesktop.dbus.types.DBusMapType;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Vector;
+
+public class cross_test_client implements DBus.Binding.TestClient, DBusSigHandler<DBus.Binding.TestSignals.Triggered> {
+ private DBusConnection conn;
+ private static Set<String> passed = new TreeSet<String>();
+ private static Map<String, List<String>> failed = new HashMap<String, List<String>>();
+ private static cross_test_client ctc;
+
+ static {
+ List<String> l = new Vector<String>();
+ l.add("Signal never arrived");
+ failed.put("org.freedesktop.DBus.Binding.TestSignals.Triggered", l);
+ l = new Vector<String>();
+ l.add("Method never called");
+ failed.put("org.freedesktop.DBus.Binding.TestClient.Response", l);
+ }
+
+ public cross_test_client(DBusConnection conn) {
+ this.conn = conn;
+ }
+
+ public boolean isRemote() {
+ return false;
+ }
+
+ public void handle(DBus.Binding.TestSignals.Triggered t) {
+ failed.remove("org.freedesktop.DBus.Binding.TestSignals.Triggered");
+ if (new UInt64(21389479283L).equals(t.a) && "/Test".equals(t.getPath()))
+ pass("org.freedesktop.DBus.Binding.TestSignals.Triggered");
+ else if (!new UInt64(21389479283L).equals(t.a))
+ fail("org.freedesktop.DBus.Binding.TestSignals.Triggered", "Incorrect signal content; expected 21389479283 got " + t.a);
+ else if (!"/Test".equals(t.getPath()))
+ fail("org.freedesktop.DBus.Binding.TestSignals.Triggered", "Incorrect signal source object; expected /Test got " + t.getPath());
+ }
+
+ public void Response(UInt16 a, double b) {
+ failed.remove("org.freedesktop.DBus.Binding.TestClient.Response");
+ if (a.equals(new UInt16(15)) && (b == 12.5))
+ pass("org.freedesktop.DBus.Binding.TestClient.Response");
+ else
+ fail("org.freedesktop.DBus.Binding.TestClient.Response", "Incorrect parameters; expected 15, 12.5 got " + a + ", " + b);
+ }
+
+ public static void pass(String test) {
+ passed.add(test.replaceAll("[$]", ""));
+ }
+
+ public static void fail(String test, String reason) {
+ test = test.replaceAll("[$]", "");
+ List<String> reasons = failed.get(test);
+ if (null == reasons) {
+ reasons = new Vector<String>();
+ failed.put(test, reasons);
+ }
+ reasons.add(reason);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void test(Class<? extends DBusInterface> iface, Object proxy, String method, Object rv, Object... parameters) {
+ try {
+ Method[] ms = iface.getMethods();
+ Method m = null;
+ for (Method t : ms) {
+ if (t.getName().equals(method))
+ m = t;
+ }
+ Object o = m.invoke(proxy, parameters);
+
+ String msg = "Incorrect return value; sent ( ";
+ if (null != parameters)
+ for (Object po : parameters)
+ if (null != po)
+ msg += collapseArray(po) + ",";
+ msg = msg.replaceAll(".$", ");");
+ msg += " expected " + collapseArray(rv) + " got " + collapseArray(o);
+
+ if (null != rv && rv.getClass().isArray()) {
+ compareArray(iface.getName() + "" + method, rv, o);
+ } else if (rv instanceof Map) {
+ if (o instanceof Map) {
+ Map<Object, Object> a = (Map<Object, Object>) o;
+ Map<Object, Object> b = (Map<Object, Object>) rv;
+ if (a.keySet().size() != b.keySet().size()) {
+ fail(iface.getName() + "" + method, msg);
+ } else for (Object k : a.keySet())
+ if (a.get(k) instanceof List) {
+ if (b.get(k) instanceof List)
+ if (setCompareLists((List<Object>) a.get(k), (List<Object>) b.get(k)))
+ ;
+ else
+ fail(iface.getName() + "" + method, msg);
+ else
+ fail(iface.getName() + "" + method, msg);
+ } else if (!a.get(k).equals(b.get(k))) {
+ fail(iface.getName() + "" + method, msg);
+ return;
+ }
+ pass(iface.getName() + "" + method);
+ } else
+ fail(iface.getName() + "" + method, msg);
+ } else {
+ if (o == rv || (o != null && o.equals(rv)))
+ pass(iface.getName() + "" + method);
+ else
+ fail(iface.getName() + "" + method, msg);
+ }
+ } catch (DBusExecutionException DBEe) {
+ DBEe.printStackTrace();
+ fail(iface.getName() + "" + method, "Error occurred during execution: " + DBEe.getClass().getName() + " " + DBEe.getMessage());
+ } catch (InvocationTargetException ITe) {
+ ITe.printStackTrace();
+ fail(iface.getName() + "" + method, "Error occurred during execution: " + ITe.getCause().getClass().getName() + " " + ITe.getCause().getMessage());
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail(iface.getName() + "" + method, "Error occurred during execution: " + e.getClass().getName() + " " + e.getMessage());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static String collapseArray(Object array) {
+ if (null == array) return "null";
+ if (array.getClass().isArray()) {
+ String s = "{ ";
+ for (int i = 0; i < Array.getLength(array); i++)
+ s += collapseArray(Array.get(array, i)) + ",";
+ s = s.replaceAll(".$", " }");
+ return s;
+ } else if (array instanceof List) {
+ String s = "{ ";
+ for (Object o : (List<Object>) array)
+ s += collapseArray(o) + ",";
+ s = s.replaceAll(".$", " }");
+ return s;
+ } else if (array instanceof Map) {
+ String s = "{ ";
+ for (Object o : ((Map<Object, Object>) array).keySet())
+ s += collapseArray(o) + " => " + collapseArray(((Map<Object, Object>) array).get(o)) + ",";
+ s = s.replaceAll(".$", " }");
+ return s;
+ } else return array.toString();
+ }
+
+ public static <T> boolean setCompareLists(List<T> a, List<T> b) {
+ if (a.size() != b.size()) return false;
+ for (Object v : a)
+ if (!b.contains(v)) return false;
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static List<Variant<Object>> PrimitizeRecurse(Object a, Type t) {
+ List<Variant<Object>> vs = new Vector<Variant<Object>>();
+ if (t instanceof ParameterizedType) {
+ Class<Object> c = (Class<Object>) ((ParameterizedType) t).getRawType();
+ if (List.class.isAssignableFrom(c)) {
+ Object os;
+ if (a instanceof List)
+ os = ((List<Object>) a).toArray();
+ else
+ os = a;
+ Type[] ts = ((ParameterizedType) t).getActualTypeArguments();
+ for (int i = 0; i < Array.getLength(os); i++)
+ vs.addAll(PrimitizeRecurse(Array.get(os, i), ts[0]));
+ } else if (Map.class.isAssignableFrom(c)) {
+ Object[] os = ((Map) a).keySet().toArray();
+ Object[] ks = ((Map) a).values().toArray();
+ Type[] ts = ((ParameterizedType) t).getActualTypeArguments();
+ for (int i = 0; i < ks.length; i++)
+ vs.addAll(PrimitizeRecurse(ks[i], ts[0]));
+ for (int i = 0; i < os.length; i++)
+ vs.addAll(PrimitizeRecurse(os[i], ts[1]));
+ } else if (Struct.class.isAssignableFrom(c)) {
+ Object[] os = ((Struct) a).getParameters();
+ Type[] ts = ((ParameterizedType) t).getActualTypeArguments();
+ for (int i = 0; i < os.length; i++)
+ vs.addAll(PrimitizeRecurse(os[i], ts[i]));
+
+ } else if (Variant.class.isAssignableFrom(c)) {
+ vs.addAll(PrimitizeRecurse(((Variant) a).getValue(), ((Variant) a).getType()));
+ }
+ } else if (Variant.class.isAssignableFrom((Class) t))
+ vs.addAll(PrimitizeRecurse(((Variant) a).getValue(), ((Variant) a).getType()));
+ else if (t instanceof Class && ((Class) t).isArray()) {
+ Type t2 = ((Class) t).getComponentType();
+ for (int i = 0; i < Array.getLength(a); i++)
+ vs.addAll(PrimitizeRecurse(Array.get(a, i), t2));
+ } else vs.add(new Variant(a));
+
+ return vs;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static List<Variant<Object>> Primitize(Variant<Object> a) {
+ return PrimitizeRecurse(a.getValue(), a.getType());
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void primitizeTest(DBus.Binding.Tests tests, Object input) {
+ Variant<Object> in = new Variant<Object>(input);
+ List<Variant<Object>> vs = Primitize(in);
+ List<Variant<Object>> res;
+
+ try {
+
+ res = tests.Primitize(in);
+ if (setCompareLists(res, vs))
+ pass("org.freedesktop.DBus.Binding.Tests.Primitize");
+ else
+ fail("org.freedesktop.DBus.Binding.Tests.Primitize", "Wrong Return Value; expected " + collapseArray(vs) + " got " + collapseArray(res));
+
+ } catch (Exception e) {
+ if (Debug.debug) Debug.print(e);
+ fail("org.freedesktop.DBus.Binding.Tests.Primitize", "Exception occurred during test: (" + e.getClass().getName() + ") " + e.getMessage());
+ }
+ }
+
+ public static void doTests(DBus.Peer peer, DBus.Introspectable intro, DBus.Introspectable rootintro, DBus.Binding.Tests tests, DBus.Binding.SingleTests singletests) {
+ Random r = new Random();
+ int i;
+ test(DBus.Peer.class, peer, "Ping", null);
+
+ try {
+ if (intro.Introspect().startsWith("<!DOCTYPE")) pass("org.freedesktop.DBus.Introspectable.Introspect");
+ else
+ fail("org.freedesktop.DBus.Introspectable.Introspect", "Didn't get valid xml data back when introspecting /Test");
+ } catch (DBusExecutionException DBEe) {
+ if (Debug.debug) Debug.print(DBEe);
+ fail("org.freedesktop.DBus.Introspectable.Introspect", "Got exception during introspection on /Test (" + DBEe.getClass().getName() + "): " + DBEe.getMessage());
+ }
+
+ try {
+ if (rootintro.Introspect().startsWith("<!DOCTYPE")) pass("org.freedesktop.DBus.Introspectable.Introspect");
+ else
+ fail("org.freedesktop.DBus.Introspectable.Introspect", "Didn't get valid xml data back when introspecting /");
+ } catch (DBusExecutionException DBEe) {
+ if (Debug.debug) Debug.print(DBEe);
+ fail("org.freedesktop.DBus.Introspectable.Introspect", "Got exception during introspection on / (" + DBEe.getClass().getName() + "): " + DBEe.getMessage());
+ }
+
+ test(DBus.Binding.Tests.class, tests, "Identity", new Variant<Integer>(new Integer(1)), new Variant<Integer>(new Integer(1)));
+ test(DBus.Binding.Tests.class, tests, "Identity", new Variant<String>("Hello"), new Variant<String>("Hello"));
+
+ test(DBus.Binding.Tests.class, tests, "IdentityBool", false, false);
+ test(DBus.Binding.Tests.class, tests, "IdentityBool", true, true);
+
+ test(DBus.Binding.Tests.class, tests, "Invert", false, true);
+ test(DBus.Binding.Tests.class, tests, "Invert", true, false);
+
+ test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) 0, (byte) 0);
+ test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) 1, (byte) 1);
+ test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) -1, (byte) -1);
+ test(DBus.Binding.Tests.class, tests, "IdentityByte", Byte.MAX_VALUE, Byte.MAX_VALUE);
+ test(DBus.Binding.Tests.class, tests, "IdentityByte", Byte.MIN_VALUE, Byte.MIN_VALUE);
+ i = r.nextInt();
+ test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) i, (byte) i);
+
+ test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) 0, (short) 0);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) 1, (short) 1);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) -1, (short) -1);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt16", Short.MAX_VALUE, Short.MAX_VALUE);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt16", Short.MIN_VALUE, Short.MIN_VALUE);
+ i = r.nextInt();
+ test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) i, (short) i);
+
+ test(DBus.Binding.Tests.class, tests, "IdentityInt32", 0, 0);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt32", 1, 1);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt32", -1, -1);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt32", Integer.MAX_VALUE, Integer.MAX_VALUE);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt32", Integer.MIN_VALUE, Integer.MIN_VALUE);
+ i = r.nextInt();
+ test(DBus.Binding.Tests.class, tests, "IdentityInt32", i, i);
+
+
+ test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) 0, (long) 0);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) 1, (long) 1);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) -1, (long) -1);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt64", Long.MAX_VALUE, Long.MAX_VALUE);
+ test(DBus.Binding.Tests.class, tests, "IdentityInt64", Long.MIN_VALUE, Long.MIN_VALUE);
+ i = r.nextInt();
+ test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) i, (long) i);
+
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(0), new UInt16(0));
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(1), new UInt16(1));
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(UInt16.MAX_VALUE), new UInt16(UInt16.MAX_VALUE));
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(UInt16.MIN_VALUE), new UInt16(UInt16.MIN_VALUE));
+ i = r.nextInt();
+ i = i > 0 ? i : -i;
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(i % UInt16.MAX_VALUE), new UInt16(i % UInt16.MAX_VALUE));
+
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(0), new UInt32(0));
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(1), new UInt32(1));
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(UInt32.MAX_VALUE), new UInt32(UInt32.MAX_VALUE));
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(UInt32.MIN_VALUE), new UInt32(UInt32.MIN_VALUE));
+ i = r.nextInt();
+ i = i > 0 ? i : -i;
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(i % UInt32.MAX_VALUE), new UInt32(i % UInt32.MAX_VALUE));
+
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(0), new UInt64(0));
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(1), new UInt64(1));
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MAX_LONG_VALUE), new UInt64(UInt64.MAX_LONG_VALUE));
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MAX_BIG_VALUE), new UInt64(UInt64.MAX_BIG_VALUE));
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MIN_VALUE), new UInt64(UInt64.MIN_VALUE));
+ i = r.nextInt();
+ i = i > 0 ? i : -i;
+ test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(i % UInt64.MAX_LONG_VALUE), new UInt64(i % UInt64.MAX_LONG_VALUE));
+
+ test(DBus.Binding.Tests.class, tests, "IdentityDouble", 0.0, 0.0);
+ test(DBus.Binding.Tests.class, tests, "IdentityDouble", 1.0, 1.0);
+ test(DBus.Binding.Tests.class, tests, "IdentityDouble", -1.0, -1.0);
+ test(DBus.Binding.Tests.class, tests, "IdentityDouble", Double.MAX_VALUE, Double.MAX_VALUE);
+ test(DBus.Binding.Tests.class, tests, "IdentityDouble", Double.MIN_VALUE, Double.MIN_VALUE);
+ i = r.nextInt();
+ test(DBus.Binding.Tests.class, tests, "IdentityDouble", (double) i, (double) i);
+
+ test(DBus.Binding.Tests.class, tests, "IdentityString", "", "");
+ test(DBus.Binding.Tests.class, tests, "IdentityString", "The Quick Brown Fox Jumped Over The Lazy Dog", "The Quick Brown Fox Jumped Over The Lazy Dog");
+ test(DBus.Binding.Tests.class, tests, "IdentityString", "ひらがなゲーム - かなぶん", "ひらがなゲーム - かなぶん");
+
+ testArray(DBus.Binding.Tests.class, tests, "IdentityBoolArray", Boolean.TYPE, null);
+ testArray(DBus.Binding.Tests.class, tests, "IdentityByteArray", Byte.TYPE, null);
+ testArray(DBus.Binding.Tests.class, tests, "IdentityInt16Array", Short.TYPE, null);
+ testArray(DBus.Binding.Tests.class, tests, "IdentityInt32Array", Integer.TYPE, null);
+ testArray(DBus.Binding.Tests.class, tests, "IdentityInt64Array", Long.TYPE, null);
+ testArray(DBus.Binding.Tests.class, tests, "IdentityDoubleArray", Double.TYPE, null);
+
+ testArray(DBus.Binding.Tests.class, tests, "IdentityArray", Variant.class, new Variant<String>("aoeu"));
+ testArray(DBus.Binding.Tests.class, tests, "IdentityUInt16Array", UInt16.class, new UInt16(12));
+ testArray(DBus.Binding.Tests.class, tests, "IdentityUInt32Array", UInt32.class, new UInt32(190));
+ testArray(DBus.Binding.Tests.class, tests, "IdentityUInt64Array", UInt64.class, new UInt64(103948));
+ testArray(DBus.Binding.Tests.class, tests, "IdentityStringArray", String.class, "asdf");
+
+ int[] is = new int[0];
+ test(DBus.Binding.Tests.class, tests, "Sum", 0L, is);
+ r = new Random();
+ int len = (r.nextInt() % 100) + 15;
+ len = (len < 0 ? -len : len) + 15;
+ is = new int[len];
+ long result = 0;
+ for (i = 0; i < len; i++) {
+ is[i] = r.nextInt();
+ result += is[i];
+ }
+ test(DBus.Binding.Tests.class, tests, "Sum", result, is);
+
+ byte[] bs = new byte[0];
+ test(DBus.Binding.SingleTests.class, singletests, "Sum", new UInt32(0), bs);
+ len = (r.nextInt() % 100);
+ len = (len < 0 ? -len : len) + 15;
+ bs = new byte[len];
+ int res = 0;
+ for (i = 0; i < len; i++) {
+ bs[i] = (byte) r.nextInt();
+ res += (bs[i] < 0 ? bs[i] + 256 : bs[i]);
+ }
+ test(DBus.Binding.SingleTests.class, singletests, "Sum", new UInt32(res % (UInt32.MAX_VALUE + 1)), bs);
+
+ test(DBus.Binding.Tests.class, tests, "DeStruct", new DBus.Binding.Triplet<String, UInt32, Short>("hi", new UInt32(12), new Short((short) 99)), new DBus.Binding.TestStruct("hi", new UInt32(12), new Short((short) 99)));
+
+ Map<String, String> in = new HashMap<String, String>();
+ Map<String, List<String>> out = new HashMap<String, List<String>>();
+ test(DBus.Binding.Tests.class, tests, "InvertMapping", out, in);
+
+ in.put("hi", "there");
+ in.put("to", "there");
+ in.put("from", "here");
+ in.put("in", "out");
+ List<String> l = new Vector<String>();
+ l.add("hi");
+ l.add("to");
+ out.put("there", l);
+ l = new Vector<String>();
+ l.add("from");
+ out.put("here", l);
+ l = new Vector<String>();
+ l.add("in");
+ out.put("out", l);
+ test(DBus.Binding.Tests.class, tests, "InvertMapping", out, in);
+
+ primitizeTest(tests, new Integer(1));
+ primitizeTest(tests,
+ new Variant<Variant<Variant<Variant<String>>>>(
+ new Variant<Variant<Variant<String>>>(
+ new Variant<Variant<String>>(
+ new Variant<String>("Hi")))));
+ primitizeTest(tests, new Variant<Map<String, String>>(in, new DBusMapType(String.class, String.class)));
+
+ test(DBus.Binding.Tests.class, tests, "Trigger", null, "/Test", new UInt64(21389479283L));
+
+ try {
+ ctc.conn.sendSignal(new DBus.Binding.TestClient.Trigger("/Test", new UInt16(15), 12.5));
+ } catch (DBusException DBe) {
+ if (Debug.debug) Debug.print(DBe);
+ throw new DBusExecutionException(DBe.getMessage());
+ }
+
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException Ie) {
+ }
+
+ test(DBus.Binding.Tests.class, tests, "Exit", null);
+ }
+
+ public static void testArray(Class<? extends DBusInterface> iface, Object proxy, String method, Class<? extends Object> arrayType, Object content) {
+ Object array = Array.newInstance(arrayType, 0);
+ test(iface, proxy, method, array, array);
+ Random r = new Random();
+ int l = (r.nextInt() % 100);
+ array = Array.newInstance(arrayType, (l < 0 ? -l : l) + 15);
+ if (null != content)
+ Arrays.fill((Object[]) array, content);
+ test(iface, proxy, method, array, array);
+ }
+
+ public static void compareArray(String test, Object a, Object b) {
+ if (!a.getClass().equals(b.getClass())) {
+ fail(test, "Incorrect return type; expected " + a.getClass() + " got " + b.getClass());
+ return;
+ }
+ boolean pass = false;
+
+ if (a instanceof Object[])
+ pass = Arrays.equals((Object[]) a, (Object[]) b);
+ else if (a instanceof byte[])
+ pass = Arrays.equals((byte[]) a, (byte[]) b);
+ else if (a instanceof boolean[])
+ pass = Arrays.equals((boolean[]) a, (boolean[]) b);
+ else if (a instanceof int[])
+ pass = Arrays.equals((int[]) a, (int[]) b);
+ else if (a instanceof short[])
+ pass = Arrays.equals((short[]) a, (short[]) b);
+ else if (a instanceof long[])
+ pass = Arrays.equals((long[]) a, (long[]) b);
+ else if (a instanceof double[])
+ pass = Arrays.equals((double[]) a, (double[]) b);
+
+ if (pass)
+ pass(test);
+ else {
+ String s = "Incorrect return value; expected ";
+ s += collapseArray(a);
+ s += " got ";
+ s += collapseArray(b);
+ fail(test, s);
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ /* init */
+ DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION);
+ ctc = new cross_test_client(conn);
+ conn.exportObject("/Test", ctc);
+ conn.addSigHandler(DBus.Binding.TestSignals.Triggered.class, ctc);
+ DBus.Binding.Tests tests = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Binding.Tests.class);
+ DBus.Binding.SingleTests singletests = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Binding.SingleTests.class);
+ DBus.Peer peer = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Peer.class);
+ DBus.Introspectable intro = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Introspectable.class);
+
+ DBus.Introspectable rootintro = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/", DBus.Introspectable.class);
+
+ doTests(peer, intro, rootintro, tests, singletests);
+
+ /* report results */
+ for (String s : passed)
+ System.out.println(s + " pass");
+ int i = 1;
+ for (String s : failed.keySet())
+ for (String r : failed.get(s)) {
+ System.out.println(s + " fail " + i);
+ System.out.println("report " + i + ": " + r);
+ i++;
+ }
+
+ conn.disconnect();
+ } catch (DBusException DBe) {
+ DBe.printStackTrace();
+ System.exit(1);
+ }
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_server.java b/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_server.java
new file mode 100644
index 0000000..61bba4a
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/cross_test_server.java
@@ -0,0 +1,342 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.DBus;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Vector;
+
+public class cross_test_server implements DBus.Binding.Tests, DBus.Binding.SingleTests, DBusSigHandler<DBus.Binding.TestClient.Trigger> {
+ private DBusConnection conn;
+ boolean run = true;
+ private Set<String> done = new TreeSet<String>();
+ private Set<String> notdone = new TreeSet<String>();
+
+ {
+ notdone.add("org.freedesktop.DBus.Binding.Tests.Identity");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityByte");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityBool");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityDouble");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityString");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityArray");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityByteArray");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityStringArray");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.Sum");
+ notdone.add("org.freedesktop.DBus.Binding.SingleTests.Sum");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.InvertMapping");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.DeStruct");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.Primitize");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.Invert");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.Trigger");
+ notdone.add("org.freedesktop.DBus.Binding.Tests.Exit");
+ notdone.add("org.freedesktop.DBus.Binding.TestClient.Trigger");
+ }
+
+ public cross_test_server(DBusConnection conn) {
+ this.conn = conn;
+ }
+
+ public boolean isRemote() {
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ @DBus.Description("Returns whatever it is passed")
+ public <T> Variant<T> Identity(Variant<T> input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.Identity");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.Identity");
+ return new Variant(input.getValue());
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public byte IdentityByte(byte input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityByte");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityByte");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public boolean IdentityBool(boolean input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityBool");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityBool");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public short IdentityInt16(short input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt16");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public UInt16 IdentityUInt16(UInt16 input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt16");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public int IdentityInt32(int input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt32");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public UInt32 IdentityUInt32(UInt32 input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt32");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public long IdentityInt64(long input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt64");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public UInt64 IdentityUInt64(UInt64 input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt64");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public double IdentityDouble(double input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityDouble");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityDouble");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public String IdentityString(String input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityString");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityString");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public <T> Variant<T>[] IdentityArray(Variant<T>[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityArray");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityArray");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public byte[] IdentityByteArray(byte[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityByteArray");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityByteArray");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public boolean[] IdentityBoolArray(boolean[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public short[] IdentityInt16Array(short[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public UInt16[] IdentityUInt16Array(UInt16[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public int[] IdentityInt32Array(int[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public UInt32[] IdentityUInt32Array(UInt32[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public long[] IdentityInt64Array(long[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public UInt64[] IdentityUInt64Array(UInt64[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public double[] IdentityDoubleArray(double[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray");
+ return input;
+ }
+
+ @DBus.Description("Returns whatever it is passed")
+ public String[] IdentityStringArray(String[] input) {
+ done.add("org.freedesktop.DBus.Binding.Tests.IdentityStringArray");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityStringArray");
+ return input;
+ }
+
+ @DBus.Description("Returns the sum of the values in the input list")
+ public long Sum(int[] a) {
+ done.add("org.freedesktop.DBus.Binding.Tests.Sum");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.Sum");
+ long sum = 0;
+ for (int b : a) sum += b;
+ return sum;
+ }
+
+ @DBus.Description("Returns the sum of the values in the input list")
+ public UInt32 Sum(byte[] a) {
+ done.add("org.freedesktop.DBus.Binding.SingleTests.Sum");
+ notdone.remove("org.freedesktop.DBus.Binding.SingleTests.Sum");
+ int sum = 0;
+ for (byte b : a) sum += (b < 0 ? b + 256 : b);
+ return new UInt32(sum % (UInt32.MAX_VALUE + 1));
+ }
+
+ @DBus.Description("Given a map of A => B, should return a map of B => a list of all the As which mapped to B")
+ public Map<String, List<String>> InvertMapping(Map<String, String> a) {
+ done.add("org.freedesktop.DBus.Binding.Tests.InvertMapping");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.InvertMapping");
+ HashMap<String, List<String>> m = new HashMap<String, List<String>>();
+ for (String s : a.keySet()) {
+ String b = a.get(s);
+ List<String> l = m.get(b);
+ if (null == l) {
+ l = new Vector<String>();
+ m.put(b, l);
+ }
+ l.add(s);
+ }
+ return m;
+ }
+
+ @DBus.Description("This method returns the contents of a struct as separate values")
+ public DBus.Binding.Triplet<String, UInt32, Short> DeStruct(DBus.Binding.TestStruct a) {
+ done.add("org.freedesktop.DBus.Binding.Tests.DeStruct");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.DeStruct");
+ return new DBus.Binding.Triplet<String, UInt32, Short>(a.a, a.b, a.c);
+ }
+
+ @DBus.Description("Given any compound type as a variant, return all the primitive types recursively contained within as an array of variants")
+ @SuppressWarnings("unchecked")
+ public List<Variant<Object>> Primitize(Variant<Object> a) {
+ done.add("org.freedesktop.DBus.Binding.Tests.Primitize");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.Primitize");
+ return cross_test_client.PrimitizeRecurse(a.getValue(), a.getType());
+ }
+
+ @DBus.Description("inverts it's input")
+ public boolean Invert(boolean a) {
+ done.add("org.freedesktop.DBus.Binding.Tests.Invert");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.Invert");
+ return !a;
+ }
+
+ @DBus.Description("triggers sending of a signal from the supplied object with the given parameter")
+ public void Trigger(String a, UInt64 b) {
+ done.add("org.freedesktop.DBus.Binding.Tests.Trigger");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.Trigger");
+ try {
+ conn.sendSignal(new DBus.Binding.TestSignals.Triggered(a, b));
+ } catch (DBusException DBe) {
+ throw new DBusExecutionException(DBe.getMessage());
+ }
+ }
+
+ public void Exit() {
+ done.add("org.freedesktop.DBus.Binding.Tests.Exit");
+ notdone.remove("org.freedesktop.DBus.Binding.Tests.Exit");
+ run = false;
+ synchronized (this) {
+ notifyAll();
+ }
+ }
+
+ public void handle(DBus.Binding.TestClient.Trigger t) {
+ done.add("org.freedesktop.DBus.Binding.TestClient.Trigger");
+ notdone.remove("org.freedesktop.DBus.Binding.TestClient.Trigger");
+ try {
+ DBus.Binding.TestClient cb = conn.getRemoteObject(t.getSource(), "/Test", DBus.Binding.TestClient.class);
+ cb.Response(t.a, t.b);
+ } catch (DBusException DBe) {
+ throw new DBusExecutionException(DBe.getMessage());
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION);
+ conn.requestBusName("org.freedesktop.DBus.Binding.TestServer");
+ cross_test_server cts = new cross_test_server(conn);
+ conn.addSigHandler(DBus.Binding.TestClient.Trigger.class, cts);
+ conn.exportObject("/Test", cts);
+ synchronized (cts) {
+ while (cts.run) {
+ try {
+ cts.wait();
+ } catch (InterruptedException Ie) {
+ }
+ }
+ }
+ for (String s : cts.done)
+ System.out.println(s + " ok");
+ for (String s : cts.notdone)
+ System.out.println(s + " untested");
+ conn.disconnect();
+ System.exit(0);
+ } catch (DBusException DBe) {
+ DBe.printStackTrace();
+ System.exit(1);
+ }
+ }
+}
+
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/profile.java b/federation/sssd/src/test/java/org/freedesktop/dbus/profile.java
new file mode 100644
index 0000000..e6866a7
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/profile.java
@@ -0,0 +1,378 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.DBus.Introspectable;
+import org.freedesktop.DBus.Peer;
+
+import java.util.HashMap;
+import java.util.Random;
+import java.util.Vector;
+
+class ProfileHandler implements DBusSigHandler<Profiler.ProfileSignal> {
+ public int c = 0;
+
+ public void handle(Profiler.ProfileSignal s) {
+ if (0 == (c++ % profile.SIGNAL_INNER)) System.out.print("-");
+ }
+}
+
+/**
+ * Profiling tests.
+ */
+public class profile {
+ public static final int SIGNAL_INNER = 100;
+ public static final int SIGNAL_OUTER = 100;
+ public static final int PING_INNER = 100;
+ public static final int PING_OUTER = 100;
+ public static final int BYTES = 2000000;
+ public static final int INTROSPECTION_OUTER = 100;
+ public static final int INTROSPECTION_INNER = 10;
+ public static final int STRUCT_OUTER = 100;
+ public static final int STRUCT_INNER = 10;
+ public static final int LIST_OUTER = 100;
+ public static final int LIST_INNER = 10;
+ public static final int LIST_LENGTH = 100;
+ public static final int MAP_OUTER = 100;
+ public static final int MAP_INNER = 10;
+ public static final int MAP_LENGTH = 100;
+ public static final int ARRAY_OUTER = 100;
+ public static final int ARRAY_INNER = 10;
+ public static final int ARRAY_LENGTH = 1000;
+ public static final int STRING_ARRAY_OUTER = 10;
+ public static final int STRING_ARRAY_INNER = 1;
+ public static final int STRING_ARRAY_LENGTH = 20000;
+
+ public static class Log {
+ private long last;
+ private int[] deltas;
+ private int current = 0;
+
+ public Log(int size) {
+ deltas = new int[size];
+ }
+
+ public void start() {
+ last = System.currentTimeMillis();
+ }
+
+ public void stop() {
+ deltas[current] = (int) (System.currentTimeMillis() - last);
+ current++;
+ }
+
+ public double mean() {
+ if (0 == current) return 0;
+ long sum = 0;
+ for (int i = 0; i < current; i++)
+ sum += deltas[i];
+ return sum /= current;
+ }
+
+ public long min() {
+ int m = Integer.MAX_VALUE;
+ for (int i = 0; i < current; i++)
+ if (deltas[i] < m) m = deltas[i];
+ return m;
+ }
+
+ public long max() {
+ int m = 0;
+ for (int i = 0; i < current; i++)
+ if (deltas[i] > m) m = deltas[i];
+ return m;
+ }
+
+ public double stddev() {
+ double mean = mean();
+ double sum = 0;
+ for (int i = 0; i < current; i++)
+ sum += (deltas[i] - mean) * (deltas[i] - mean);
+ return Math.sqrt(sum / (current - 1));
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ if (0 == args.length) {
+ System.out.println("You must specify a profile type.");
+ System.out.println("Syntax: profile <pings|arrays|introspect|maps|bytes|lists|structs|signals|rate|strings>");
+ System.exit(1);
+ }
+ DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION);
+ conn.requestBusName("org.freedesktop.DBus.java.profiler");
+ if ("pings".equals(args[0])) {
+ int count = PING_INNER * PING_OUTER;
+ System.out.print("Sending " + count + " pings...");
+ Peer p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Peer.class);
+ Log l = new Log(count);
+ long t = System.currentTimeMillis();
+ for (int i = 0; i < PING_OUTER; i++) {
+ for (int j = 0; j < PING_INNER; j++) {
+ l.start();
+ p.Ping();
+ l.stop();
+ }
+ System.out.print("");
+ }
+ t = System.currentTimeMillis() - t;
+ System.out.println(" done.");
+ System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean());
+ System.out.println("deviation: " + l.stddev());
+ System.out.println("Total time: " + t + "ms");
+ } else if ("strings".equals(args[0])) {
+ int count = STRING_ARRAY_INNER * STRING_ARRAY_OUTER;
+ System.out.print("Sending array of " + STRING_ARRAY_LENGTH + " strings " + count + " times.");
+ ProfilerInstance pi = new ProfilerInstance();
+ conn.exportObject("/Profiler", pi);
+ Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class);
+ String[] v = new String[STRING_ARRAY_LENGTH];
+ Random r = new Random();
+ for (int i = 0; i < STRING_ARRAY_LENGTH; i++) v[i] = "" + r.nextInt();
+ Log l = new Log(count);
+ long t = System.currentTimeMillis();
+ for (int i = 0; i < STRING_ARRAY_OUTER; i++) {
+ for (int j = 0; j < STRING_ARRAY_INNER; j++) {
+ l.start();
+ p.stringarray(v);
+ l.stop();
+ }
+ System.out.print("");
+ }
+ t = System.currentTimeMillis() - t;
+ System.out.println(" done.");
+ System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean());
+ System.out.println("deviation: " + l.stddev());
+ System.out.println("Total time: " + t + "ms");
+ } else if ("arrays".equals(args[0])) {
+ int count = ARRAY_INNER * ARRAY_OUTER;
+ System.out.print("Sending array of " + ARRAY_LENGTH + " ints " + count + " times.");
+ ProfilerInstance pi = new ProfilerInstance();
+ conn.exportObject("/Profiler", pi);
+ Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class);
+ int[] v = new int[ARRAY_LENGTH];
+ Random r = new Random();
+ for (int i = 0; i < ARRAY_LENGTH; i++) v[i] = r.nextInt();
+ Log l = new Log(count);
+ long t = System.currentTimeMillis();
+ for (int i = 0; i < ARRAY_OUTER; i++) {
+ for (int j = 0; j < ARRAY_INNER; j++) {
+ l.start();
+ p.array(v);
+ l.stop();
+ }
+ System.out.print("");
+ }
+ t = System.currentTimeMillis() - t;
+ System.out.println(" done.");
+ System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean());
+ System.out.println("deviation: " + l.stddev());
+ System.out.println("Total time: " + t + "ms");
+ } else if ("maps".equals(args[0])) {
+ int count = MAP_INNER * MAP_OUTER;
+ System.out.print("Sending map of " + MAP_LENGTH + " string=>strings " + count + " times.");
+ ProfilerInstance pi = new ProfilerInstance();
+ conn.exportObject("/Profiler", pi);
+ Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class);
+ HashMap<String, String> m = new HashMap<String, String>();
+ for (int i = 0; i < MAP_LENGTH; i++)
+ m.put("" + i, "hello");
+ Log l = new Log(count);
+ long t = System.currentTimeMillis();
+ for (int i = 0; i < MAP_OUTER; i++) {
+ for (int j = 0; j < MAP_INNER; j++) {
+ l.start();
+ p.map(m);
+ l.stop();
+ }
+ System.out.print("");
+ }
+ t = System.currentTimeMillis() - t;
+ System.out.println(" done.");
+ System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean());
+ System.out.println("deviation: " + l.stddev());
+ System.out.println("Total time: " + t + "ms");
+ } else if ("lists".equals(args[0])) {
+ int count = LIST_OUTER * LIST_INNER;
+ System.out.print("Sending list of " + LIST_LENGTH + " strings " + count + " times.");
+ ProfilerInstance pi = new ProfilerInstance();
+ conn.exportObject("/Profiler", pi);
+ Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class);
+ Vector<String> v = new Vector<String>();
+ for (int i = 0; i < LIST_LENGTH; i++)
+ v.add("hello " + i);
+ Log l = new Log(count);
+ long t = System.currentTimeMillis();
+ for (int i = 0; i < LIST_OUTER; i++) {
+ for (int j = 0; j < LIST_INNER; j++) {
+ l.start();
+ p.list(v);
+ l.stop();
+ }
+ System.out.print("");
+ }
+ t = System.currentTimeMillis() - t;
+ System.out.println(" done.");
+ System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean());
+ System.out.println("deviation: " + l.stddev());
+ System.out.println("Total time: " + t + "ms");
+ } else if ("structs".equals(args[0])) {
+ int count = STRUCT_OUTER * STRUCT_INNER;
+ System.out.print("Sending a struct " + count + " times.");
+ ProfilerInstance pi = new ProfilerInstance();
+ conn.exportObject("/Profiler", pi);
+ Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class);
+ ProfileStruct ps = new ProfileStruct("hello", new UInt32(18), 500L);
+ Log l = new Log(count);
+ long t = System.currentTimeMillis();
+ for (int i = 0; i < STRUCT_OUTER; i++) {
+ for (int j = 0; j < STRUCT_INNER; j++) {
+ l.start();
+ p.struct(ps);
+ l.stop();
+ }
+ System.out.print("");
+ }
+ t = System.currentTimeMillis() - t;
+ System.out.println(" done.");
+ System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean());
+ System.out.println("deviation: " + l.stddev());
+ System.out.println("Total time: " + t + "ms");
+ } else if ("introspect".equals(args[0])) {
+ int count = INTROSPECTION_OUTER * INTROSPECTION_INNER;
+ System.out.print("Recieving introspection data " + count + " times.");
+ ProfilerInstance pi = new ProfilerInstance();
+ conn.exportObject("/Profiler", pi);
+ Introspectable is = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Introspectable.class);
+ Log l = new Log(count);
+ long t = System.currentTimeMillis();
+ String s = null;
+ for (int i = 0; i < INTROSPECTION_OUTER; i++) {
+ for (int j = 0; j < INTROSPECTION_INNER; j++) {
+ l.start();
+ s = is.Introspect();
+ l.stop();
+ }
+ System.out.print("");
+ }
+ t = System.currentTimeMillis() - t;
+ System.out.println(" done.");
+ System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean());
+ System.out.println("deviation: " + l.stddev());
+ System.out.println("Total time: " + t + "ms");
+ System.out.println("Introspect data: " + s);
+ } else if ("bytes".equals(args[0])) {
+ System.out.print("Sending " + BYTES + " bytes");
+ ProfilerInstance pi = new ProfilerInstance();
+ conn.exportObject("/Profiler", pi);
+ Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class);
+ byte[] bs = new byte[BYTES];
+ for (int i = 0; i < BYTES; i++)
+ bs[i] = (byte) i;
+ long t = System.currentTimeMillis();
+ p.bytes(bs);
+ System.out.println(" done in " + (System.currentTimeMillis() - t) + "ms.");
+ } else if ("rate".equals(args[0])) {
+ ProfilerInstance pi = new ProfilerInstance();
+ conn.exportObject("/Profiler", pi);
+ Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class);
+ Peer peer = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Peer.class);
+ conn.changeThreadCount((byte) 1);
+
+ long start = System.currentTimeMillis();
+ int count = 0;
+ do {
+ p.Pong();
+ count++;
+ } while (count < 10000);
+ long end = System.currentTimeMillis();
+ System.out.println("No payload: " + ((count * 1000) / (end - start)) + " RT/second");
+ start = System.currentTimeMillis();
+ count = 0;
+ do {
+ p.Pong();
+ count++;
+ } while (count < 10000);
+ peer.Ping();
+ end = System.currentTimeMillis();
+ System.out.println("No payload, One way: " + ((count * 1000) / (end - start)) + " /second");
+ int len = 256;
+ while (len <= 32768) {
+ byte[] bs = new byte[len];
+ count = 0;
+ start = System.currentTimeMillis();
+ do {
+ p.bytes(bs);
+ count++;
+ } while (count < 1000);
+ end = System.currentTimeMillis();
+ long ms = end - start;
+ double cps = (count * 1000) / ms;
+ double rate = (len * cps) / (1024.0 * 1024.0);
+ System.out.println(len + " byte array) " + (count * len) + " bytes in " + ms + "ms (in " + count + " calls / " + (int) cps + " CPS): " + rate + "MB/s");
+ len <<= 1;
+ }
+ len = 256;
+ while (len <= 32768) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < len; i++) sb.append('a');
+ String s = sb.toString();
+ end = System.currentTimeMillis() + 500;
+ count = 0;
+ do {
+ p.string(s);
+ count++;
+ } while (count < 1000);
+ long ms = end - start;
+ double cps = (count * 1000) / ms;
+ double rate = (len * cps) / (1024.0 * 1024.0);
+ System.out.println(len + " string) " + (count * len) + " bytes in " + ms + "ms (in " + count + " calls / " + (int) cps + " CPS): " + rate + "MB/s");
+ len <<= 1;
+ }
+ } else if ("signals".equals(args[0])) {
+ int count = SIGNAL_OUTER * SIGNAL_INNER;
+ System.out.print("Sending " + count + " signals");
+ ProfileHandler ph = new ProfileHandler();
+ conn.addSigHandler(Profiler.ProfileSignal.class, ph);
+ Log l = new Log(count);
+ Profiler.ProfileSignal ps = new Profiler.ProfileSignal("/");
+ long t = System.currentTimeMillis();
+ for (int i = 0; i < SIGNAL_OUTER; i++) {
+ for (int j = 0; j < SIGNAL_INNER; j++) {
+ l.start();
+ conn.sendSignal(ps);
+ l.stop();
+ }
+ System.out.print("");
+ }
+ t = System.currentTimeMillis() - t;
+ System.out.println(" done.");
+ System.out.println("min/max/avg (ms): " + l.min() + "/" + l.max() + "/" + l.mean());
+ System.out.println("deviation: " + l.stddev());
+ System.out.println("Total time: " + t + "ms");
+ while (ph.c < count) try {
+ Thread.sleep(100);
+ } catch (InterruptedException Ie) {
+ }
+ ;
+ } else {
+ conn.disconnect();
+ System.out.println("Invalid profile ``" + args[0] + "''.");
+ System.out.println("Syntax: profile <pings|arrays|introspect|maps|bytes|lists|structs|signals>");
+ System.exit(1);
+ }
+ conn.disconnect();
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/Profiler.java b/federation/sssd/src/test/java/org/freedesktop/dbus/Profiler.java
new file mode 100644
index 0000000..cd549d5
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/Profiler.java
@@ -0,0 +1,44 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+
+import java.util.List;
+import java.util.Map;
+
+public interface Profiler extends DBusInterface {
+ public class ProfileSignal extends DBusSignal {
+ public ProfileSignal(String path) throws DBusException {
+ super(path);
+ }
+ }
+
+ public void array(int[] v);
+
+ public void stringarray(String[] v);
+
+ public void map(Map<String, String> m);
+
+ public void list(List<String> l);
+
+ public void bytes(byte[] b);
+
+ public void struct(ProfileStruct ps);
+
+ public void string(String s);
+
+ public void NoReply();
+
+ public void Pong();
+}
+
+
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/ProfilerInstance.java b/federation/sssd/src/test/java/org/freedesktop/dbus/ProfilerInstance.java
new file mode 100644
index 0000000..530901f
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/ProfilerInstance.java
@@ -0,0 +1,56 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import java.util.List;
+import java.util.Map;
+
+public class ProfilerInstance implements Profiler {
+ public boolean isRemote() {
+ return false;
+ }
+
+ public void array(int[] v) {
+ return;
+ }
+
+ public void stringarray(String[] v) {
+ return;
+ }
+
+ public void map(Map<String, String> m) {
+ return;
+ }
+
+ public void list(List<String> l) {
+ return;
+ }
+
+ public void bytes(byte[] b) {
+ return;
+ }
+
+ public void struct(ProfileStruct ps) {
+ return;
+ }
+
+ public void string(String s) {
+ return;
+ }
+
+ public void NoReply() {
+ return;
+ }
+
+ public void Pong() {
+ return;
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/ProfileStruct.java b/federation/sssd/src/test/java/org/freedesktop/dbus/ProfileStruct.java
new file mode 100644
index 0000000..b86c137
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/ProfileStruct.java
@@ -0,0 +1,26 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+public final class ProfileStruct extends Struct {
+ @Position(0)
+ public final String a;
+ @Position(1)
+ public final UInt32 b;
+ @Position(2)
+ public final long c;
+
+ public ProfileStruct(String a, UInt32 b, long c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/test.java b/federation/sssd/src/test/java/org/freedesktop/dbus/test.java
new file mode 100644
index 0000000..af6a736
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/test.java
@@ -0,0 +1,991 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.DBus;
+import org.freedesktop.DBus.Error.MatchRuleInvalid;
+import org.freedesktop.DBus.Error.ServiceUnknown;
+import org.freedesktop.DBus.Error.UnknownObject;
+import org.freedesktop.DBus.Introspectable;
+import org.freedesktop.DBus.Peer;
+import org.freedesktop.DBus.Properties;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+import org.freedesktop.dbus.exceptions.NotConnected;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+class testnewclass implements TestNewInterface {
+ public boolean isRemote() {
+ return false;
+ }
+
+ public String getName() {
+ return toString();
+ }
+}
+
+class testclass implements TestRemoteInterface, TestRemoteInterface2, TestSignalInterface, TestSignalInterface2, Properties {
+ private DBusConnection conn;
+
+ public testclass(DBusConnection conn) {
+ this.conn = conn;
+ }
+
+ public String Introspect() {
+ return "Not XML";
+ }
+
+ public int[][] teststructstruct(TestStruct3 in) {
+ List<List<Integer>> lli = in.b;
+ int[][] out = new int[lli.size()][];
+ for (int j = 0; j < out.length; j++) {
+ out[j] = new int[lli.get(j).size()];
+ for (int k = 0; k < out[j].length; k++)
+ out[j][k] = lli.get(j).get(k);
+ }
+ return out;
+ }
+
+ public float testfloat(float[] f) {
+ if (f.length < 4 ||
+ f[0] != 17.093f ||
+ f[1] != -23f ||
+ f[2] != 0.0f ||
+ f[3] != 31.42f)
+ test.fail("testfloat got incorrect array");
+ return f[0];
+ }
+
+ public void newpathtest(Path p) {
+ if (!p.toString().equals("/new/path/test"))
+ test.fail("new path test got wrong path");
+ }
+
+ public void waitawhile() {
+ System.out.println("Sleeping.");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException Ie) {
+ }
+ System.out.println("Done sleeping.");
+ }
+
+ public <A> TestTuple<String, List<Integer>, Boolean> show(A in) {
+ System.out.println("Showing Stuff: " + in.getClass() + "(" + in + ")");
+ if (!(in instanceof Integer) || ((Integer) in).intValue() != 234)
+ test.fail("show received the wrong arguments");
+ DBusCallInfo info = DBusConnection.getCallInfo();
+ List<Integer> l = new Vector<Integer>();
+ l.add(1953);
+ return new TestTuple<String, List<Integer>, Boolean>(info.getSource(), l, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T dostuff(TestStruct foo) {
+ System.out.println("Doing Stuff " + foo);
+ System.out.println(" -- (" + foo.a.getClass() + ", " + foo.b.getClass() + ", " + foo.c.getClass() + ")");
+ if (!(foo instanceof TestStruct) ||
+ !(foo.a instanceof String) ||
+ !(foo.b instanceof UInt32) ||
+ !(foo.c instanceof Variant) ||
+ !"bar".equals(foo.a) ||
+ foo.b.intValue() != 52 ||
+ !(foo.c.getValue() instanceof Boolean) ||
+ ((Boolean) foo.c.getValue()).booleanValue() != true)
+ test.fail("dostuff received the wrong arguments");
+ return (T) foo.c.getValue();
+ }
+
+ /**
+ * Local classes MUST implement this to return false
+ */
+ public boolean isRemote() {
+ return false;
+ }
+
+ /**
+ * The method we are exporting to the Bus.
+ */
+ public List<Integer> sampleArray(List<String> ss, Integer[] is, long[] ls) {
+ System.out.println("Got an array:");
+ for (String s : ss)
+ System.out.println("--" + s);
+ if (ss.size() != 5 ||
+ !"hi".equals(ss.get(0)) ||
+ !"hello".equals(ss.get(1)) ||
+ !"hej".equals(ss.get(2)) ||
+ !"hey".equals(ss.get(3)) ||
+ !"aloha".equals(ss.get(4)))
+ test.fail("sampleArray, String array contents incorrect");
+ System.out.println("Got an array:");
+ for (Integer i : is)
+ System.out.println("--" + i);
+ if (is.length != 4 ||
+ is[0].intValue() != 1 ||
+ is[1].intValue() != 5 ||
+ is[2].intValue() != 7 ||
+ is[3].intValue() != 9)
+ test.fail("sampleArray, Integer array contents incorrect");
+ System.out.println("Got an array:");
+ for (long l : ls)
+ System.out.println("--" + l);
+ if (ls.length != 4 ||
+ ls[0] != 2 ||
+ ls[1] != 6 ||
+ ls[2] != 8 ||
+ ls[3] != 12)
+ test.fail("sampleArray, Integer array contents incorrect");
+ Vector<Integer> v = new Vector<Integer>();
+ v.add(-1);
+ v.add(-5);
+ v.add(-7);
+ v.add(-12);
+ v.add(-18);
+ return v;
+ }
+
+ public String getName() {
+ return "This Is A UTF-8 Name: س !!";
+ }
+
+ public String getNameAndThrow() throws TestException {
+ throw new TestException("test");
+ }
+
+ public boolean check() {
+ System.out.println("Being checked");
+ return false;
+ }
+
+ public <T> int frobnicate(List<Long> n, Map<String, Map<UInt16, Short>> m, T v) {
+ if (null == n)
+ test.fail("List was null");
+ if (n.size() != 3)
+ test.fail("List was wrong size (expected 3, actual " + n.size() + ")");
+ if (n.get(0) != 2L ||
+ n.get(1) != 5L ||
+ n.get(2) != 71L)
+ test.fail("List has wrong contents");
+ if (!(v instanceof Integer))
+ test.fail("v not an Integer");
+ if (((Integer) v) != 13)
+ test.fail("v is incorrect");
+ if (null == m)
+ test.fail("Map was null");
+ if (m.size() != 1)
+ test.fail("Map was wrong size");
+ if (!m.keySet().contains("stuff"))
+ test.fail("Incorrect key");
+ Map<UInt16, Short> mus = m.get("stuff");
+ if (null == mus)
+ test.fail("Sub-Map was null");
+ if (mus.size() != 3)
+ test.fail("Sub-Map was wrong size");
+ if (!(new Short((short) 5).equals(mus.get(new UInt16(4)))))
+ test.fail("Sub-Map has wrong contents");
+ if (!(new Short((short) 6).equals(mus.get(new UInt16(5)))))
+ test.fail("Sub-Map has wrong contents");
+ if (!(new Short((short) 7).equals(mus.get(new UInt16(6)))))
+ test.fail("Sub-Map has wrong contents");
+ return -5;
+ }
+
+ public DBusInterface getThis(DBusInterface t) {
+ if (!t.equals(this))
+ test.fail("Didn't get this properly");
+ return this;
+ }
+
+ public void throwme() throws TestException {
+ throw new TestException("test");
+ }
+
+ public TestSerializable<String> testSerializable(byte b, TestSerializable<String> s, int i) {
+ System.out.println("Recieving TestSerializable: " + s);
+ if (b != 12
+ || i != 13
+ || !(s.getInt() == 1)
+ || !(s.getString().equals("woo"))
+ || !(s.getVector().size() == 3)
+ || !(s.getVector().get(0) == 1)
+ || !(s.getVector().get(1) == 2)
+ || !(s.getVector().get(2) == 3))
+ test.fail("Error in recieving custom synchronisation");
+ return s;
+ }
+
+ public String recursionTest() {
+ try {
+ TestRemoteInterface tri = conn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface.class);
+ return tri.getName();
+ } catch (DBusException DBe) {
+ test.fail("Failed with error: " + DBe);
+ return "";
+ }
+ }
+
+ public int overload(String s) {
+ return 1;
+ }
+
+ public int overload(byte b) {
+ return 2;
+ }
+
+ public int overload() {
+ DBusCallInfo info = DBusConnection.getCallInfo();
+ if ("org.freedesktop.dbus.test.AlternateTestInterface".equals(info.getInterface()))
+ return 3;
+ else if ("org.freedesktop.dbus.test.TestRemoteInterface".equals(info.getInterface()))
+ return 4;
+ else
+ return -1;
+ }
+
+ public List<List<Integer>> checklist(List<List<Integer>> lli) {
+ return lli;
+ }
+
+ public TestNewInterface getNew() {
+ testnewclass n = new testnewclass();
+ try {
+ conn.exportObject("/new", n);
+ } catch (DBusException DBe) {
+ throw new DBusExecutionException(DBe.getMessage());
+ }
+ return n;
+ }
+
+ public void sig(Type[] s) {
+ if (s.length != 2
+ || !s[0].equals(Byte.class)
+ || !(s[1] instanceof ParameterizedType)
+ || !Map.class.equals(((ParameterizedType) s[1]).getRawType())
+ || ((ParameterizedType) s[1]).getActualTypeArguments().length != 2
+ || !String.class.equals(((ParameterizedType) s[1]).getActualTypeArguments()[0])
+ || !Integer.class.equals(((ParameterizedType) s[1]).getActualTypeArguments()[1]))
+ test.fail("Didn't send types correctly");
+ }
+
+ @SuppressWarnings("unchecked")
+ public void complexv(Variant<? extends Object> v) {
+ if (!"a{ss}".equals(v.getSig())
+ || !(v.getValue() instanceof Map)
+ || ((Map<Object, Object>) v.getValue()).size() != 1
+ || !"moo".equals(((Map<Object, Object>) v.getValue()).get("cow")))
+ test.fail("Didn't send variant correctly");
+ }
+
+ public void reg13291(byte[] as, byte[] bs) {
+ if (as.length != bs.length) test.fail("didn't receive identical byte arrays");
+ for (int i = 0; i < as.length; i++)
+ if (as[i] != bs[i]) test.fail("didn't receive identical byte arrays");
+ }
+
+ @SuppressWarnings("unchecked")
+ public <A> A Get(String interface_name, String property_name) {
+ return (A) new Path("/nonexistant/path");
+ }
+
+ public <A> void Set(String interface_name, String property_name, A value) {
+ }
+
+ public Map<String, Variant> GetAll(String interface_name) {
+ return new HashMap<String, Variant>();
+ }
+
+ public Path pathrv(Path a) {
+ return a;
+ }
+
+ public List<Path> pathlistrv(List<Path> a) {
+ return a;
+ }
+
+ public Map<Path, Path> pathmaprv(Map<Path, Path> a) {
+ return a;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Map<String, Variant> svm() {
+ HashMap<String, Variant> properties = new HashMap<String, Variant>();
+ HashMap<String, Variant<String>> parameters = new HashMap<String, Variant<String>>();
+
+ parameters.put("Name", new Variant<String>("Joe"));
+ parameters.put("Password", new Variant<String>("abcdef"));
+
+ properties.put("Parameters", new Variant(parameters, "a{sv}"));
+ return (Map<String, Variant>) properties;
+ }
+}
+
+/**
+ * Typed signal handler for renamed signal
+ */
+class renamedsignalhandler implements DBusSigHandler<TestSignalInterface2.TestRenamedSignal> {
+ /**
+ * Handling a signal
+ */
+ public void handle(TestSignalInterface2.TestRenamedSignal t) {
+ if (false == test.done5) {
+ test.done5 = true;
+ } else {
+ test.fail("SignalHandler R has been run too many times");
+ }
+ System.out.println("SignalHandler R Running");
+ System.out.println("string(" + t.value + ") int(" + t.number + ")");
+ if (!"Bar".equals(t.value) || !(new UInt32(42)).equals(t.number))
+ test.fail("Incorrect TestRenamedSignal parameters");
+ }
+}
+
+/**
+ * Empty signal handler
+ */
+class emptysignalhandler implements DBusSigHandler<TestSignalInterface.EmptySignal> {
+ /**
+ * Handling a signal
+ */
+ public void handle(TestSignalInterface.EmptySignal t) {
+ if (false == test.done7) {
+ test.done7 = true;
+ } else {
+ test.fail("SignalHandler E has been run too many times");
+ }
+ System.out.println("SignalHandler E Running");
+ }
+}
+
+/**
+ * Disconnect handler
+ */
+class disconnecthandler implements DBusSigHandler<DBus.Local.Disconnected> {
+ private DBusConnection conn;
+ private renamedsignalhandler sh;
+
+ public disconnecthandler(DBusConnection conn, renamedsignalhandler sh) {
+ this.conn = conn;
+ this.sh = sh;
+ }
+
+ /**
+ * Handling a signal
+ */
+ public void handle(DBus.Local.Disconnected t) {
+ if (false == test.done6) {
+ test.done6 = true;
+ System.out.println("Handling disconnect, unregistering handler");
+ try {
+ conn.removeSigHandler(TestSignalInterface2.TestRenamedSignal.class, sh);
+ } catch (DBusException DBe) {
+ DBe.printStackTrace();
+ test.fail("Disconnect handler threw an exception: " + DBe);
+ }
+ }
+ }
+}
+
+
+/**
+ * Typed signal handler
+ */
+class pathsignalhandler implements DBusSigHandler<TestSignalInterface.TestPathSignal> {
+ /**
+ * Handling a signal
+ */
+ public void handle(TestSignalInterface.TestPathSignal t) {
+ System.out.println("Path sighandler: " + t);
+ }
+}
+
+/**
+ * Typed signal handler
+ */
+class signalhandler implements DBusSigHandler<TestSignalInterface.TestSignal> {
+ /**
+ * Handling a signal
+ */
+ public void handle(TestSignalInterface.TestSignal t) {
+ if (false == test.done1) {
+ test.done1 = true;
+ } else {
+ test.fail("SignalHandler 1 has been run too many times");
+ }
+ System.out.println("SignalHandler 1 Running");
+ System.out.println("string(" + t.value + ") int(" + t.number + ")");
+ if (!"Bar".equals(t.value) || !(new UInt32(42)).equals(t.number))
+ test.fail("Incorrect TestSignal parameters");
+ }
+}
+
+/**
+ * Untyped signal handler
+ */
+class arraysignalhandler implements DBusSigHandler<TestSignalInterface.TestArraySignal> {
+ /**
+ * Handling a signal
+ */
+ public void handle(TestSignalInterface.TestArraySignal t) {
+ try {
+ if (false == test.done2) {
+ test.done2 = true;
+ } else {
+ test.fail("SignalHandler 2 has been run too many times");
+ }
+ System.out.println("SignalHandler 2 Running");
+ if (t.v.size() != 1)
+ test.fail("Incorrect TestArraySignal array length: should be 1, actually " + t.v.size());
+ System.out.println("Got a test array signal with Parameters: ");
+ for (String str : t.v.get(0).a)
+ System.out.println("--" + str);
+ System.out.println(t.v.get(0).b.getType());
+ System.out.println(t.v.get(0).b.getValue());
+ if (!(t.v.get(0).b.getValue() instanceof UInt64) ||
+ 567L != ((UInt64) t.v.get(0).b.getValue()).longValue() ||
+ t.v.get(0).a.size() != 5 ||
+ !"hi".equals(t.v.get(0).a.get(0)) ||
+ !"hello".equals(t.v.get(0).a.get(1)) ||
+ !"hej".equals(t.v.get(0).a.get(2)) ||
+ !"hey".equals(t.v.get(0).a.get(3)) ||
+ !"aloha".equals(t.v.get(0).a.get(4)))
+ test.fail("Incorrect TestArraySignal parameters");
+
+ if (t.m.keySet().size() != 2)
+ test.fail("Incorrect TestArraySignal map size: should be 2, actually " + t.m.keySet().size());
+ if (!(t.m.get(new UInt32(1)).b.getValue() instanceof UInt64) ||
+ 678L != ((UInt64) t.m.get(new UInt32(1)).b.getValue()).longValue() ||
+ !(t.m.get(new UInt32(42)).b.getValue() instanceof UInt64) ||
+ 789L != ((UInt64) t.m.get(new UInt32(42)).b.getValue()).longValue())
+ test.fail("Incorrect TestArraySignal parameters");
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ test.fail("SignalHandler 2 threw an exception: " + e);
+ }
+ }
+}
+
+/**
+ * Object path signal handler
+ */
+class objectsignalhandler implements DBusSigHandler<TestSignalInterface.TestObjectSignal> {
+ public void handle(TestSignalInterface.TestObjectSignal s) {
+ if (false == test.done3) {
+ test.done3 = true;
+ } else {
+ test.fail("SignalHandler 3 has been run too many times");
+ }
+ System.out.println(s.otherpath);
+ }
+}
+
+/**
+ * handler which should never be called
+ */
+class badarraysignalhandler<T extends DBusSignal> implements DBusSigHandler<T> {
+ /**
+ * Handling a signal
+ */
+ public void handle(T s) {
+ test.fail("This signal handler shouldn't be called");
+ }
+}
+
+/**
+ * Callback handler
+ */
+class callbackhandler implements CallbackHandler<String> {
+ public void handle(String r) {
+ System.out.println("Handling callback: " + r);
+ Collator col = Collator.getInstance();
+ col.setDecomposition(Collator.FULL_DECOMPOSITION);
+ col.setStrength(Collator.PRIMARY);
+ if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", r))
+ test.fail("call with callback, wrong return value");
+ if (test.done4) test.fail("Already ran callback handler");
+ test.done4 = true;
+ }
+
+ public void handleError(DBusExecutionException e) {
+ System.out.println("Handling error callback: " + e + " message = '" + e.getMessage() + "'");
+ if (!(e instanceof TestException)) test.fail("Exception is of the wrong sort");
+ Collator col = Collator.getInstance();
+ col.setDecomposition(Collator.FULL_DECOMPOSITION);
+ col.setStrength(Collator.PRIMARY);
+ if (0 != col.compare("test", e.getMessage()))
+ test.fail("Exception has the wrong message");
+ if (test.done8) test.fail("Already ran callback error handler");
+ test.done8 = true;
+ }
+}
+
+/**
+ * This is a test program which sends and recieves a signal, implements, exports and calls a remote method.
+ */
+public class test {
+ public static boolean done1 = false;
+ public static boolean done2 = false;
+ public static boolean done3 = false;
+ public static boolean done4 = false;
+ public static boolean done5 = false;
+ public static boolean done6 = false;
+ public static boolean done7 = false;
+ public static boolean done8 = false;
+
+ public static void fail(String message) {
+ System.out.println("Test Failed: " + message);
+ System.err.println("Test Failed: " + message);
+ if (null != serverconn) serverconn.disconnect();
+ if (null != clientconn) clientconn.disconnect();
+ System.exit(1);
+ }
+
+ static DBusConnection serverconn = null;
+ static DBusConnection clientconn = null;
+
+ @SuppressWarnings("unchecked")
+ public static void main(String[] args) {
+ try {
+ System.out.println("Creating Connection");
+ serverconn = DBusConnection.getConnection(DBusConnection.SESSION);
+ clientconn = DBusConnection.getConnection(DBusConnection.SESSION);
+ serverconn.setWeakReferences(true);
+ clientconn.setWeakReferences(true);
+
+ System.out.println("Registering Name");
+ serverconn.requestBusName("foo.bar.Test");
+
+ /** This gets a remote object matching our bus name and exported object path. */
+ Peer peer = clientconn.getRemoteObject("foo.bar.Test", "/Test", Peer.class);
+ DBus dbus = clientconn.getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class);
+
+ System.out.print("Listening for signals...");
+ signalhandler sigh = new signalhandler();
+ renamedsignalhandler rsh = new renamedsignalhandler();
+ try {
+ /** This registers an instance of the test class as the signal handler for the TestSignal class. */
+ clientconn.addSigHandler(TestSignalInterface.EmptySignal.class, new emptysignalhandler());
+ clientconn.addSigHandler(TestSignalInterface.TestSignal.class, sigh);
+ clientconn.addSigHandler(TestSignalInterface2.TestRenamedSignal.class, rsh);
+ clientconn.addSigHandler(DBus.Local.Disconnected.class, new disconnecthandler(clientconn, rsh));
+ String source = dbus.GetNameOwner("foo.bar.Test");
+ clientconn.addSigHandler(TestSignalInterface.TestArraySignal.class, source, peer, new arraysignalhandler());
+ clientconn.addSigHandler(TestSignalInterface.TestObjectSignal.class, new objectsignalhandler());
+ clientconn.addSigHandler(TestSignalInterface.TestPathSignal.class, new pathsignalhandler());
+ badarraysignalhandler<TestSignalInterface.TestSignal> bash = new badarraysignalhandler<TestSignalInterface.TestSignal>();
+ clientconn.addSigHandler(TestSignalInterface.TestSignal.class, bash);
+ clientconn.removeSigHandler(TestSignalInterface.TestSignal.class, bash);
+ System.out.println("done");
+ } catch (MatchRuleInvalid MRI) {
+ test.fail("Failed to add handlers: " + MRI.getMessage());
+ } catch (DBusException DBe) {
+ test.fail("Failed to add handlers: " + DBe.getMessage());
+ }
+
+ System.out.println("Listening for Method Calls");
+ testclass tclass = new testclass(serverconn);
+ testclass tclass2 = new testclass(serverconn);
+ /** This exports an instance of the test class as the object /Test. */
+ serverconn.exportObject("/Test", tclass);
+ serverconn.exportObject("/BadTest", tclass);
+ serverconn.exportObject("/BadTest2", tclass2);
+ serverconn.addFallback("/FallbackTest", tclass);
+
+ // explicitly unexport object
+ serverconn.unExportObject("/BadTest");
+ // implicitly unexport object
+ tclass2 = null;
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ System.runFinalization();
+
+ System.out.println("Sending Signal");
+ /** This creates an instance of the Test Signal, with the given object path, signal name and parameters, and broadcasts in on the Bus. */
+ serverconn.sendSignal(new TestSignalInterface.TestSignal("/foo/bar/Wibble", "Bar", new UInt32(42)));
+ serverconn.sendSignal(new TestSignalInterface.EmptySignal("/foo/bar/Wibble"));
+ serverconn.sendSignal(new TestSignalInterface2.TestRenamedSignal("/foo/bar/Wibble", "Bar", new UInt32(42)));
+
+ System.out.println("These things are on the bus:");
+ String[] names = dbus.ListNames();
+ for (String name : names)
+ System.out.println("\t" + name);
+
+ System.out.println("Getting our introspection data");
+ /** This gets a remote object matching our bus name and exported object path. */
+ Introspectable intro = clientconn.getRemoteObject("foo.bar.Test", "/", Introspectable.class);
+ /** Get introspection data */
+ String data;/* = intro.Introspect();
+ if (null == data || !data.startsWith("<!DOCTYPE"))
+ fail("Introspection data invalid");
+ System.out.println("Got Introspection Data: \n"+data);*/
+ intro = clientconn.getRemoteObject("foo.bar.Test", "/Test", Introspectable.class);
+ /** Get introspection data */
+ data = intro.Introspect();
+ if (null == data || !data.startsWith("<!DOCTYPE"))
+ fail("Introspection data invalid");
+ System.out.println("Got Introspection Data: \n" + data);
+
+ // setup bus name set
+ Set<String> peers = serverconn.new PeerSet();
+ peers.add("org.freedesktop.DBus");
+ clientconn.requestBusName("test.testclient");
+ peers.add("test.testclient");
+ clientconn.releaseBusName("test.testclient");
+
+ System.out.println("Pinging ourselves");
+ /** Call ping. */
+ for (int i = 0; i < 10; i++) {
+ long then = System.currentTimeMillis();
+ peer.Ping();
+ long now = System.currentTimeMillis();
+ System.out.println("Ping returned in " + (now - then) + "ms.");
+ }
+
+ System.out.println("Calling Method0/1");
+ /** This gets a remote object matching our bus name and exported object path. */
+ TestRemoteInterface tri = (TestRemoteInterface) clientconn.getPeerRemoteObject("foo.bar.Test", "/Test");
+ System.out.println("Got Remote Object: " + tri);
+ /** Call the remote object and get a response. */
+ String rname = tri.getName();
+ System.out.println("Got Remote Name: " + rname);
+
+ Map<String, Variant> svmmap = tri.svm();
+ System.out.println(svmmap.toString());
+ if (!"{ Parameters => [{ Name => [Joe],Password => [abcdef] }] }".equals(svmmap.toString()))
+ fail("incorrect reply from svm");
+
+ Path path = new Path("/nonexistantwooooooo");
+ Path p = tri.pathrv(path);
+ System.out.println(path.toString() + " => " + p.toString());
+ if (!path.equals(p)) fail("pathrv incorrect");
+ List<Path> paths = new Vector<Path>();
+ paths.add(path);
+ List<Path> ps = tri.pathlistrv(paths);
+ System.out.println(paths.toString() + " => " + ps.toString());
+ if (!paths.equals(ps)) fail("pathlistrv incorrect");
+ Map<Path, Path> pathm = new HashMap<Path, Path>();
+ pathm.put(path, path);
+ Map<Path, Path> pm = tri.pathmaprv(pathm);
+ System.out.println(pathm.toString() + " => " + pm.toString());
+ System.out.println(pm.containsKey(path) + " " + pm.get(path) + " " + path.equals(pm.get(path)));
+ System.out.println(pm.containsKey(p) + " " + pm.get(p) + " " + p.equals(pm.get(p)));
+ for (Path q : pm.keySet()) {
+ System.out.println(q);
+ System.out.println(pm.get(q));
+ }
+ if (!pm.containsKey(path) || !path.equals(pm.get(path))) fail("pathmaprv incorrect");
+
+ serverconn.sendSignal(new TestSignalInterface.TestPathSignal("/Test", path, paths, pathm));
+
+ Collator col = Collator.getInstance();
+ col.setDecomposition(Collator.FULL_DECOMPOSITION);
+ col.setStrength(Collator.PRIMARY);
+ if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", rname))
+ fail("getName return value incorrect");
+ System.out.println("sending it to sleep");
+ tri.waitawhile();
+ System.out.println("testing floats");
+ if (17.093f != tri.testfloat(new float[]{17.093f, -23f, 0.0f, 31.42f}))
+ fail("testfloat returned the wrong thing");
+ System.out.println("Structs of Structs");
+ List<List<Integer>> lli = new Vector<List<Integer>>();
+ List<Integer> li = new Vector<Integer>();
+ li.add(1);
+ li.add(2);
+ li.add(3);
+ lli.add(li);
+ lli.add(li);
+ lli.add(li);
+ TestStruct3 ts3 = new TestStruct3(new TestStruct2(new Vector<String>(), new Variant<Integer>(0)), lli);
+ int[][] out = tri.teststructstruct(ts3);
+ if (out.length != 3) fail("teststructstruct returned the wrong thing: " + Arrays.deepToString(out));
+ for (int[] o : out)
+ if (o.length != 3
+ || o[0] != 1
+ || o[1] != 2
+ || o[2] != 3) fail("teststructstruct returned the wrong thing: " + Arrays.deepToString(out));
+
+ System.out.println("frobnicating");
+ List<Long> ls = new Vector<Long>();
+ ls.add(2L);
+ ls.add(5L);
+ ls.add(71L);
+ Map<UInt16, Short> mus = new HashMap<UInt16, Short>();
+ mus.put(new UInt16(4), (short) 5);
+ mus.put(new UInt16(5), (short) 6);
+ mus.put(new UInt16(6), (short) 7);
+ Map<String, Map<UInt16, Short>> msmus = new HashMap<String, Map<UInt16, Short>>();
+ msmus.put("stuff", mus);
+ int rint = tri.frobnicate(ls, msmus, 13);
+ if (-5 != rint)
+ fail("frobnicate return value incorrect");
+
+ System.out.println("Doing stuff asynchronously with callback");
+ clientconn.callWithCallback(tri, "getName", new callbackhandler());
+ System.out.println("Doing stuff asynchronously with callback, which throws an error");
+ clientconn.callWithCallback(tri, "getNameAndThrow", new callbackhandler());
+
+ /** call something that throws */
+ try {
+ System.out.println("Throwing stuff");
+ tri.throwme();
+ test.fail("Method Execution should have failed");
+ } catch (TestException Te) {
+ System.out.println("Remote Method Failed with: " + Te.getClass().getName() + " " + Te.getMessage());
+ if (!Te.getMessage().equals("test"))
+ test.fail("Error message was not correct");
+ }
+
+ /* Test type signatures */
+ Vector<Type> ts = new Vector<Type>();
+ Marshalling.getJavaType("ya{si}", ts, -1);
+ tri.sig(ts.toArray(new Type[0]));
+
+ tri.newpathtest(new Path("/new/path/test"));
+
+ /** Try and call an invalid remote object */
+ try {
+ System.out.println("Calling Method2");
+ tri = clientconn.getRemoteObject("foo.bar.NotATest", "/Moofle", TestRemoteInterface.class);
+ System.out.println("Got Remote Name: " + tri.getName());
+ test.fail("Method Execution should have failed");
+ } catch (ServiceUnknown SU) {
+ System.out.println("Remote Method Failed with: " + SU.getClass().getName() + " " + SU.getMessage());
+ }
+
+ /** Try and call an invalid remote object */
+ try {
+ System.out.println("Calling Method3");
+ tri = clientconn.getRemoteObject("foo.bar.Test", "/Moofle", TestRemoteInterface.class);
+ System.out.println("Got Remote Name: " + tri.getName());
+ test.fail("Method Execution should have failed");
+ } catch (UnknownObject UO) {
+ System.out.println("Remote Method Failed with: " + UO.getClass().getName() + " " + UO.getMessage());
+ }
+
+ /** Try and call an explicitly unexported object */
+ try {
+ System.out.println("Calling Method4");
+ tri = clientconn.getRemoteObject("foo.bar.Test", "/BadTest", TestRemoteInterface.class);
+ System.out.println("Got Remote Name: " + tri.getName());
+ test.fail("Method Execution should have failed");
+ } catch (UnknownObject UO) {
+ System.out.println("Remote Method Failed with: " + UO.getClass().getName() + " " + UO.getMessage());
+ }
+
+ /** Try and call an implicitly unexported object */
+ try {
+ System.out.println("Calling Method5");
+ tri = clientconn.getRemoteObject("foo.bar.Test", "/BadTest2", TestRemoteInterface.class);
+ System.out.println("Got Remote Name: " + tri.getName());
+ test.fail("Method Execution should have failed");
+ } catch (UnknownObject UO) {
+ System.out.println("Remote Method Failed with: " + UO.getClass().getName() + " " + UO.getMessage());
+ }
+
+ System.out.println("Calling Method6");
+ tri = clientconn.getRemoteObject("foo.bar.Test", "/FallbackTest/0/1", TestRemoteInterface.class);
+ intro = clientconn.getRemoteObject("foo.bar.Test", "/FallbackTest/0/4", Introspectable.class);
+ System.out.println("Got Fallback Name: " + tri.getName());
+ System.out.println("Fallback Introspection Data: \n" + intro.Introspect());
+
+ System.out.println("Testing Properties returning Paths");
+ Properties prop = clientconn.getRemoteObject("foo.bar.Test", "/Test", Properties.class);
+ Path prv = (Path) prop.Get("foo.bar", "foo");
+ System.out.println("Got path " + prv);
+ System.out.println("Calling Method7--9");
+ /** This gets a remote object matching our bus name and exported object path. */
+ TestRemoteInterface2 tri2 = clientconn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface2.class);
+ System.out.print("Calling the other introspect method: ");
+ String intro2 = tri2.Introspect();
+ System.out.println(intro2);
+ if (0 != col.compare("Not XML", intro2))
+ fail("Introspect return value incorrect");
+
+ /** Call the remote object and get a response. */
+ TestTuple<String, List<Integer>, Boolean> rv = tri2.show(234);
+ System.out.println("Show returned: " + rv);
+ if (!serverconn.getUniqueName().equals(rv.a) ||
+ 1 != rv.b.size() ||
+ 1953 != rv.b.get(0) ||
+ true != rv.c.booleanValue())
+ fail("show return value incorrect (" + rv.a + "," + rv.b + "," + rv.c + ")");
+
+ System.out.println("Doing stuff asynchronously");
+ DBusAsyncReply<Boolean> stuffreply = (DBusAsyncReply<Boolean>) clientconn.callMethodAsync(tri2, "dostuff", new TestStruct("bar", new UInt32(52), new Variant<Boolean>(new Boolean(true))));
+
+ System.out.println("Checking bools");
+ if (tri2.check()) fail("bools are broken");
+
+ List<String> l = new Vector<String>();
+ l.add("hi");
+ l.add("hello");
+ l.add("hej");
+ l.add("hey");
+ l.add("aloha");
+ System.out.println("Sampling Arrays:");
+ List<Integer> is = tri2.sampleArray(l, new Integer[]{1, 5, 7, 9}, new long[]{2, 6, 8, 12});
+ System.out.println("sampleArray returned an array:");
+ for (Integer i : is)
+ System.out.println("--" + i);
+ if (is.size() != 5 ||
+ is.get(0).intValue() != -1 ||
+ is.get(1).intValue() != -5 ||
+ is.get(2).intValue() != -7 ||
+ is.get(3).intValue() != -12 ||
+ is.get(4).intValue() != -18)
+ fail("sampleArray return value incorrect");
+
+ System.out.println("Get This");
+ if (!tclass.equals(tri2.getThis(tri2)))
+ fail("Didn't get the correct this");
+
+ Boolean b = stuffreply.getReply();
+ System.out.println("Do stuff replied " + b);
+ if (true != b.booleanValue())
+ fail("dostuff return value incorrect");
+
+ System.out.print("Sending Array Signal...");
+ /** This creates an instance of the Test Signal, with the given object path, signal name and parameters, and broadcasts in on the Bus. */
+ List<TestStruct2> tsl = new Vector<TestStruct2>();
+ tsl.add(new TestStruct2(l, new Variant<UInt64>(new UInt64(567))));
+ Map<UInt32, TestStruct2> tsm = new HashMap<UInt32, TestStruct2>();
+ tsm.put(new UInt32(1), new TestStruct2(l, new Variant<UInt64>(new UInt64(678))));
+ tsm.put(new UInt32(42), new TestStruct2(l, new Variant<UInt64>(new UInt64(789))));
+ serverconn.sendSignal(new TestSignalInterface.TestArraySignal("/Test", tsl, tsm));
+
+ System.out.println("done");
+
+ System.out.print("testing custom serialization...");
+ Vector<Integer> v = new Vector<Integer>();
+ v.add(1);
+ v.add(2);
+ v.add(3);
+ TestSerializable<String> s = new TestSerializable<String>(1, "woo", v);
+ s = tri2.testSerializable((byte) 12, s, 13);
+ System.out.print("returned: " + s);
+ if (s.getInt() != 1 ||
+ !s.getString().equals("woo") ||
+ s.getVector().size() != 3 ||
+ s.getVector().get(0) != 1 ||
+ s.getVector().get(1) != 2 ||
+ s.getVector().get(2) != 3)
+ fail("Didn't get back the same TestSerializable");
+
+ System.out.println("done");
+
+ System.out.print("testing complex variants...");
+ Map m = new HashMap();
+ m.put("cow", "moo");
+ tri2.complexv(new Variant(m, "a{ss}"));
+ System.out.println("done");
+
+ System.out.print("testing recursion...");
+
+ if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", tri2.recursionTest())) fail("recursion test failed");
+
+ System.out.println("done");
+
+ System.out.print("testing method overloading...");
+ tri = clientconn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface.class);
+ if (1 != tri2.overload("foo")) test.fail("wrong overloaded method called");
+ if (2 != tri2.overload((byte) 0)) test.fail("wrong overloaded method called");
+ if (3 != tri2.overload()) test.fail("wrong overloaded method called");
+ if (4 != tri.overload()) test.fail("wrong overloaded method called");
+ System.out.println("done");
+
+ System.out.print("reg13291...");
+ byte[] as = new byte[10];
+ for (int i = 0; i < 10; i++)
+ as[i] = (byte) (100 - i);
+ tri.reg13291(as, as);
+ System.out.println("done");
+
+ System.out.print("Testing nested lists...");
+ lli = new Vector<List<Integer>>();
+ li = new Vector<Integer>();
+ li.add(1);
+ lli.add(li);
+ List<List<Integer>> reti = tri2.checklist(lli);
+ if (reti.size() != 1 ||
+ reti.get(0).size() != 1 ||
+ reti.get(0).get(0) != 1)
+ test.fail("Failed to check nested lists");
+ System.out.println("done");
+
+ System.out.print("Testing dynamic object creation...");
+ TestNewInterface tni = tri2.getNew();
+ System.out.print(tni.getName() + " ");
+ System.out.println("done");
+
+ /* send an object in a signal */
+ serverconn.sendSignal(new TestSignalInterface.TestObjectSignal("/foo/bar/Wibble", tclass));
+
+ /** Pause while we wait for the DBus messages to go back and forth. */
+ Thread.sleep(1000);
+
+ // check that bus name set has been trimmed
+ if (peers.size() != 1) fail("peers hasn't been trimmed");
+ if (!peers.contains("org.freedesktop.DBus")) fail("peers contains the wrong name");
+
+ System.out.println("Checking for outstanding errors");
+ DBusExecutionException DBEe = serverconn.getError();
+ if (null != DBEe) throw DBEe;
+ DBEe = clientconn.getError();
+ if (null != DBEe) throw DBEe;
+
+ System.out.println("Disconnecting");
+ /** Disconnect from the bus. */
+ clientconn.disconnect();
+ serverconn.disconnect();
+
+ System.out.println("Trying to do things after disconnection");
+
+ /** Remove sig handler */
+ clientconn.removeSigHandler(TestSignalInterface.TestSignal.class, sigh);
+
+ /** Call a method when disconnected */
+ try {
+ System.out.println("getName() suceeded and returned: " + tri.getName());
+ fail("Should not succeed when disconnected");
+ } catch (NotConnected NC) {
+ System.out.println("getName() failed with exception " + NC);
+ }
+ clientconn = null;
+ serverconn = null;
+
+ if (!done1) fail("Signal handler 1 failed to be run");
+ if (!done2) fail("Signal handler 2 failed to be run");
+ if (!done3) fail("Signal handler 3 failed to be run");
+ if (!done4) fail("Callback handler failed to be run");
+ if (!done5) fail("Signal handler R failed to be run");
+ if (!done6) fail("Disconnect handler failed to be run");
+ if (!done7) fail("Signal handler E failed to be run");
+ if (!done8) fail("Error callback handler failed to be run");
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ fail("Unexpected Exception Occurred: " + e);
+ }
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/test_low_level.java b/federation/sssd/src/test/java/org/freedesktop/dbus/test_low_level.java
new file mode 100644
index 0000000..71699eb
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/test_low_level.java
@@ -0,0 +1,50 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import cx.ath.matthew.debug.Debug;
+
+public class test_low_level {
+ public static void main(String[] args) throws Exception {
+ Debug.setHexDump(true);
+ String addr = System.getenv("DBUS_SESSION_BUS_ADDRESS");
+ Debug.print(addr);
+ BusAddress address = new BusAddress(addr);
+ Debug.print(address);
+
+ Transport conn = new Transport(address);
+
+ Message m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello", (byte) 0, null);
+ conn.mout.writeMessage(m);
+ m = conn.min.readMessage();
+ Debug.print(m.getClass());
+ Debug.print(m);
+ m = conn.min.readMessage();
+ Debug.print(m.getClass());
+ Debug.print(m);
+ m = conn.min.readMessage();
+ Debug.print("" + m);
+ m = new MethodCall("org.freedesktop.DBus", "/", null, "Hello", (byte) 0, null);
+ conn.mout.writeMessage(m);
+ m = conn.min.readMessage();
+ Debug.print(m);
+
+ m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "RequestName", (byte) 0, "su", "org.testname", 0);
+ conn.mout.writeMessage(m);
+ m = conn.min.readMessage();
+ Debug.print(m);
+ m = new DBusSignal(null, "/foo", "org.foo", "Foo", null);
+ conn.mout.writeMessage(m);
+ m = conn.min.readMessage();
+ Debug.print(m);
+ conn.disconnect();
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestException.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestException.java
new file mode 100644
index 0000000..98335c8
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestException.java
@@ -0,0 +1,22 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.DBus.Description;
+import org.freedesktop.dbus.exceptions.DBusExecutionException;
+
+@Description("A test exception to throw over DBus")
+@SuppressWarnings("serial")
+public class TestException extends DBusExecutionException {
+ public TestException(String message) {
+ super(message);
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestNewInterface.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestNewInterface.java
new file mode 100644
index 0000000..6395787
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestNewInterface.java
@@ -0,0 +1,24 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.DBus.Description;
+
+/**
+ * A sample remote interface which exports one method.
+ */
+public interface TestNewInterface extends DBusInterface {
+ /**
+ * A simple method with no parameters which returns a String
+ */
+ @Description("Simple test method")
+ public String getName();
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface.java
new file mode 100644
index 0000000..6fec592
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface.java
@@ -0,0 +1,68 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.DBus.Description;
+import org.freedesktop.DBus.Method;
+
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A sample remote interface which exports one method.
+ */
+public interface TestRemoteInterface extends DBusInterface {
+ /**
+ * A simple method with no parameters which returns a String
+ */
+ @Description("Simple test method")
+ public String getName();
+
+ public String getNameAndThrow();
+
+ @Description("Test of nested maps")
+ public <T> int frobnicate(List<Long> n, Map<String, Map<UInt16, Short>> m, T v);
+
+ @Description("Throws a TestException when called")
+ public void throwme() throws TestException;
+
+ @Description("Waits then doesn't return")
+ @Method.NoReply()
+ public void waitawhile();
+
+ @Description("Interface-overloaded method")
+ public int overload();
+
+ @Description("Testing Type Signatures")
+ public void sig(Type[] s);
+
+ @Description("Testing object paths as Path objects")
+ public void newpathtest(Path p);
+
+ @Description("Testing the float type")
+ public float testfloat(float[] f);
+
+ @Description("Testing structs of structs")
+ public int[][] teststructstruct(TestStruct3 in);
+
+ @Description("Regression test for #13291")
+ public void reg13291(byte[] as, byte[] bs);
+
+ public Map<String, Variant> svm();
+
+ /* test lots of things involving Path */
+ public Path pathrv(Path a);
+
+ public List<Path> pathlistrv(List<Path> a);
+
+ public Map<Path, Path> pathmaprv(Map<Path, Path> a);
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface2.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface2.java
new file mode 100644
index 0000000..7039fe1
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestRemoteInterface2.java
@@ -0,0 +1,62 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.DBus.Description;
+
+import java.util.List;
+
+@Description("An example remote interface")
+@DBusInterfaceName("org.freedesktop.dbus.test.AlternateTestInterface")
+public interface TestRemoteInterface2 extends DBusInterface {
+ @Description("Test multiple return values and implicit variant parameters.")
+ public <A> TestTuple<String, List<Integer>, Boolean> show(A in);
+
+ @Description("Test passing structs and explicit variants, returning implicit variants")
+ public <T> T dostuff(TestStruct foo);
+
+ @Description("Test arrays, boxed arrays and lists.")
+ public List<Integer> sampleArray(List<String> l, Integer[] is, long[] ls);
+
+ @Description("Test passing objects as object paths.")
+ public DBusInterface getThis(DBusInterface t);
+
+ @Description("Test bools work")
+ @DBusMemberName("checkbool")
+ public boolean check();
+
+ @Description("Test Serializable Object")
+ public TestSerializable<String> testSerializable(byte b, TestSerializable<String> s, int i);
+
+ @Description("Call another method on itself from within a call")
+ public String recursionTest();
+
+ @Description("Parameter-overloaded method (string)")
+ public int overload(String s);
+
+ @Description("Parameter-overloaded method (byte)")
+ public int overload(byte b);
+
+ @Description("Parameter-overloaded method (void)")
+ public int overload();
+
+ @Description("Nested List Check")
+ public List<List<Integer>> checklist(List<List<Integer>> lli);
+
+ @Description("Get new objects as object paths.")
+ public TestNewInterface getNew();
+
+ @Description("Test Complex Variants")
+ public void complexv(Variant<? extends Object> v);
+
+ @Description("Test Introspect on a different interface")
+ public String Introspect();
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestSerializable.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSerializable.java
new file mode 100644
index 0000000..0a8275a
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSerializable.java
@@ -0,0 +1,57 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+
+import java.util.List;
+import java.util.Vector;
+
+public class TestSerializable<A> implements DBusSerializable {
+ private int a;
+ private String b;
+ private Vector<Integer> c;
+
+ public TestSerializable(int a, A b, Vector<Integer> c) {
+ this.a = a;
+ this.b = b.toString();
+ this.c = c;
+ }
+
+ public TestSerializable() {
+ }
+
+ public void deserialize(int a, String b, List<Integer> c) {
+ this.a = a;
+ this.b = b;
+ this.c = new Vector<Integer>(c);
+ }
+
+ public Object[] serialize() throws DBusException {
+ return new Object[]{a, b, c};
+ }
+
+ public int getInt() {
+ return a;
+ }
+
+ public String getString() {
+ return b;
+ }
+
+ public Vector<Integer> getVector() {
+ return c;
+ }
+
+ public String toString() {
+ return "TestSerializable{" + a + "," + b + "," + c + "}";
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface.java
new file mode 100644
index 0000000..518a5db
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface.java
@@ -0,0 +1,89 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.DBus.Description;
+import org.freedesktop.dbus.exceptions.DBusException;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A sample signal with two parameters
+ */
+@Description("Test interface containing signals")
+public interface TestSignalInterface extends DBusInterface {
+ @Description("Test basic signal")
+ public static class TestSignal extends DBusSignal {
+ public final String value;
+ public final UInt32 number;
+
+ /**
+ * Create a signal.
+ */
+ public TestSignal(String path, String value, UInt32 number) throws DBusException {
+ super(path, value, number);
+ this.value = value;
+ this.number = number;
+ }
+ }
+
+ public static class StringSignal extends DBusSignal {
+ public final String aoeu;
+
+ public StringSignal(String path, String aoeu) throws DBusException {
+ super(path, aoeu);
+ this.aoeu = aoeu;
+ }
+ }
+
+ public static class EmptySignal extends DBusSignal {
+ public EmptySignal(String path) throws DBusException {
+ super(path);
+ }
+ }
+
+ @Description("Test signal with arrays")
+ public static class TestArraySignal extends DBusSignal {
+ public final List<TestStruct2> v;
+ public final Map<UInt32, TestStruct2> m;
+
+ public TestArraySignal(String path, List<TestStruct2> v, Map<UInt32, TestStruct2> m) throws DBusException {
+ super(path, v, m);
+ this.v = v;
+ this.m = m;
+ }
+ }
+
+ @Description("Test signal sending an object path")
+ @DBusMemberName("TestSignalObject")
+ public static class TestObjectSignal extends DBusSignal {
+ public final DBusInterface otherpath;
+
+ public TestObjectSignal(String path, DBusInterface otherpath) throws DBusException {
+ super(path, otherpath);
+ this.otherpath = otherpath;
+ }
+ }
+
+ public static class TestPathSignal extends DBusSignal {
+ public final Path otherpath;
+ public final List<Path> pathlist;
+ public final Map<Path, Path> pathmap;
+
+ public TestPathSignal(String path, Path otherpath, List<Path> pathlist, Map<Path, Path> pathmap) throws DBusException {
+ super(path, otherpath, pathlist, pathmap);
+ this.otherpath = otherpath;
+ this.pathlist = pathlist;
+ this.pathmap = pathmap;
+ }
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface2.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface2.java
new file mode 100644
index 0000000..9324b40
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestSignalInterface2.java
@@ -0,0 +1,36 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.DBus.Description;
+import org.freedesktop.dbus.exceptions.DBusException;
+
+/**
+ * A sample signal with two parameters
+ */
+@Description("Test interface containing signals")
+@DBusInterfaceName("some.other.interface.Name")
+public interface TestSignalInterface2 extends DBusInterface {
+ @Description("Test basic signal")
+ public static class TestRenamedSignal extends DBusSignal {
+ public final String value;
+ public final UInt32 number;
+
+ /**
+ * Create a signal.
+ */
+ public TestRenamedSignal(String path, String value, UInt32 number) throws DBusException {
+ super(path, value, number);
+ this.value = value;
+ this.number = number;
+ }
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct.java
new file mode 100644
index 0000000..09f42fc
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct.java
@@ -0,0 +1,26 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+public final class TestStruct extends Struct {
+ @Position(0)
+ public final String a;
+ @Position(1)
+ public final UInt32 b;
+ @Position(2)
+ public final Variant<? extends Object> c;
+
+ public TestStruct(String a, UInt32 b, Variant<? extends Object> c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct2.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct2.java
new file mode 100644
index 0000000..efd0d87
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct2.java
@@ -0,0 +1,27 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+
+import java.util.List;
+
+public final class TestStruct2 extends Struct {
+ @Position(0)
+ public final List<String> a;
+ @Position(1)
+ public final Variant<? extends Object> b;
+
+ public TestStruct2(List<String> a, Variant<? extends Object> b) throws DBusException {
+ this.a = a;
+ this.b = b;
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct3.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct3.java
new file mode 100644
index 0000000..d457053
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestStruct3.java
@@ -0,0 +1,27 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+
+import java.util.List;
+
+public final class TestStruct3 extends Struct {
+ @Position(0)
+ public final TestStruct2 a;
+ @Position(1)
+ public final List<List<Integer>> b;
+
+ public TestStruct3(TestStruct2 a, List<List<Integer>> b) throws DBusException {
+ this.a = a;
+ this.b = b;
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TestTuple.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TestTuple.java
new file mode 100644
index 0000000..4adb0d8
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TestTuple.java
@@ -0,0 +1,26 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+public final class TestTuple<A, B, C> extends Tuple {
+ @Position(0)
+ public final A a;
+ @Position(1)
+ public final B b;
+ @Position(2)
+ public final C c;
+
+ public TestTuple(A a, B b, C c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_client.java b/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_client.java
new file mode 100644
index 0000000..75f4bd8
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_client.java
@@ -0,0 +1,43 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+public class two_part_test_client {
+ public static class two_part_test_object implements TwoPartObject {
+ public boolean isRemote() {
+ return false;
+ }
+
+ public String getName() {
+ System.out.println("client name");
+ return toString();
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("get conn");
+ DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION);
+ System.out.println("get remote");
+ TwoPartInterface remote = conn.getRemoteObject("org.freedesktop.dbus.test.two_part_server", "/", TwoPartInterface.class);
+ System.out.println("get object");
+ TwoPartObject o = remote.getNew();
+ System.out.println("get name");
+ System.out.println(o.getName());
+ two_part_test_object tpto = new two_part_test_object();
+ conn.exportObject("/TestObject", tpto);
+ conn.sendSignal(new TwoPartInterface.TwoPartSignal("/FromObject", tpto));
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException Ie) {
+ }
+ conn.disconnect();
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_server.java b/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_server.java
new file mode 100644
index 0000000..c0b1ded
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/two_part_test_server.java
@@ -0,0 +1,62 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+public class two_part_test_server implements TwoPartInterface, DBusSigHandler<TwoPartInterface.TwoPartSignal> {
+ public class two_part_test_object implements TwoPartObject {
+ public boolean isRemote() {
+ return false;
+ }
+
+ public String getName() {
+ System.out.println("give name");
+ return toString();
+ }
+ }
+
+ private DBusConnection conn;
+
+ public two_part_test_server(DBusConnection conn) {
+ this.conn = conn;
+ }
+
+ public boolean isRemote() {
+ return false;
+ }
+
+ public TwoPartObject getNew() {
+ TwoPartObject o = new two_part_test_object();
+ System.out.println("export new");
+ try {
+ conn.exportObject("/12345", o);
+ } catch (Exception e) {
+ }
+ System.out.println("give new");
+ return o;
+ }
+
+ public void handle(TwoPartInterface.TwoPartSignal s) {
+ System.out.println("Got: " + s.o);
+ }
+
+ public static void main(String[] args) throws Exception {
+ DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION);
+ conn.requestBusName("org.freedesktop.dbus.test.two_part_server");
+ two_part_test_server server = new two_part_test_server(conn);
+ conn.exportObject("/", server);
+ conn.addSigHandler(TwoPartInterface.TwoPartSignal.class, server);
+ while (true) try {
+ Thread.sleep(10000);
+ } catch (InterruptedException Ie) {
+ }
+ }
+}
+
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartInterface.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartInterface.java
new file mode 100644
index 0000000..242dcb2
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartInterface.java
@@ -0,0 +1,26 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+import org.freedesktop.dbus.exceptions.DBusException;
+
+public interface TwoPartInterface extends DBusInterface {
+ public TwoPartObject getNew();
+
+ public class TwoPartSignal extends DBusSignal {
+ public final TwoPartObject o;
+
+ public TwoPartSignal(String path, TwoPartObject o) throws DBusException {
+ super(path, o);
+ this.o = o;
+ }
+ }
+}
diff --git a/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartObject.java b/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartObject.java
new file mode 100644
index 0000000..8f03bbc
--- /dev/null
+++ b/federation/sssd/src/test/java/org/freedesktop/dbus/TwoPartObject.java
@@ -0,0 +1,15 @@
+/*
+ D-Bus Java Implementation
+ Copyright (c) 2005-2006 Matthew Johnson
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of either the GNU Lesser General Public License Version 2 or the
+ Academic Free Licence Version 2.1.
+
+ Full licence texts are included in the COPYING file with this program.
+*/
+package org.freedesktop.dbus;
+
+public interface TwoPartObject extends DBusInterface {
+ public String getName();
+}
diff --git a/federation/sssd/src/test/java/org/jvnet/libpam/InteractiveTester.java b/federation/sssd/src/test/java/org/jvnet/libpam/InteractiveTester.java
new file mode 100644
index 0000000..47fef18
--- /dev/null
+++ b/federation/sssd/src/test/java/org/jvnet/libpam/InteractiveTester.java
@@ -0,0 +1,95 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2009, Sun Microsystems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.jvnet.libpam;
+
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+public class InteractiveTester extends TestCase {
+ public InteractiveTester(String testName) {
+ super(testName);
+ }
+
+ public void testPositiveCase() throws Exception {
+ for (int i = 0; i < 1000; i++)
+ testOne();
+ }
+
+ public void testOne() throws Exception {
+ UnixUser u = new PAM("sshd").authenticate(System.getProperty("user.name"), System.getProperty("password"));
+ if (!printOnce) {
+ System.out.println(u.getUID());
+ System.out.println(u.getGroups());
+ printOnce = true;
+ }
+ }
+
+ public void testGetGroups() throws Exception {
+ System.out.println(new PAM("sshd").getGroupsOfUser(System.getProperty("user.name")));
+ }
+
+ public void testConcurrent() throws Exception {
+ ExecutorService es = Executors.newFixedThreadPool(10);
+ Set<Future<?>> result = new HashSet<Future<?>>();
+ for (int i = 0; i < 1000; i++) {
+ result.add(es.submit(new Callable<Object>() {
+ public Object call() throws Exception {
+ testOne();
+ return null;
+ }
+ }));
+ }
+ // wait for completion
+ for (Future<?> f : result) {
+ f.get();
+ }
+ es.shutdown();
+ }
+
+ public void testNegative() throws Exception {
+ try {
+ new PAM("sshd").authenticate("bogus", "bogus");
+ fail("expected a failure");
+ } catch (PAMException e) {
+ // yep
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ UnixUser u = new PAM("sshd").authenticate(args[0], args[1]);
+ System.out.println(u.getUID());
+ System.out.println(u.getGroups());
+ System.out.println(u.getGecos());
+ System.out.println(u.getDir());
+ System.out.println(u.getShell());
+ }
+
+ private boolean printOnce;
+}
diff --git a/federation/sssd/src/test/java/org/jvnet/libpam/UnixUserTest.java b/federation/sssd/src/test/java/org/jvnet/libpam/UnixUserTest.java
new file mode 100644
index 0000000..4b3e5f5
--- /dev/null
+++ b/federation/sssd/src/test/java/org/jvnet/libpam/UnixUserTest.java
@@ -0,0 +1,52 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2014, R. Tyler Croy
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package org.jvnet.libpam;
+
+import junit.framework.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class UnixUserTest {
+ private UnixUser user = null;
+
+ @Before
+ public void setUp() throws PAMException {
+ user = new UnixUser("root");
+ }
+
+ @Test
+ public void testGetUserName() {
+ Assert.assertEquals("root", user.getUserName());
+ }
+
+ @Test
+ public void testGetDir() {
+ Assert.assertNotNull(user.getDir());
+ }
+
+ @Test
+ public void testGetUID() {
+ Assert.assertEquals(0, user.getUID());
+ }
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java
index 1aaaa23..07438d0 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ResourcesResource.java
@@ -25,6 +25,7 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
@@ -45,5 +46,16 @@ public interface ResourcesResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
+ List<ResourceRepresentation> find(@QueryParam("name") String name,
+ @QueryParam("uri") String uri,
+ @QueryParam("owner") String owner,
+ @QueryParam("type") String type,
+ @QueryParam("scope") String scope,
+ @QueryParam("first") Integer firstResult,
+ @QueryParam("max") Integer maxResult);
+
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
List<ResourceRepresentation> resources();
}
misc/HackingOnKeycloak.md 3(+2 -1)
diff --git a/misc/HackingOnKeycloak.md b/misc/HackingOnKeycloak.md
index 590a301..902d1ed 100644
--- a/misc/HackingOnKeycloak.md
+++ b/misc/HackingOnKeycloak.md
@@ -59,5 +59,6 @@ Here's a quick check list for a good pull request (PR):
* A JIRA associated with your PR (include the JIRA issue number in commit comment)
* All tests in testsuite pass
* Do a rebase on upstream master
+* We only accept contributions to the master branch. The exception to this is if the fix is for the latest CR release and Final has not yet been released, in which case you can send the PR to both the corresponding branch and the master branch.
-Once you're happy with your changes go to GitHub and create a PR.
+Once you're happy with your changes go to GitHub and create a PR to the master branch.
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java
new file mode 100644
index 0000000..9940732
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractExtendableRevisioned.java
@@ -0,0 +1,40 @@
+/*
+ * 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.entities;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractExtendableRevisioned extends AbstractRevisioned {
+ protected ConcurrentHashMap cachedWith = new ConcurrentHashMap();
+
+ public AbstractExtendableRevisioned(Long revision, String id) {
+ super(revision, id);
+ }
+
+ /**
+ * Cache things along with this cachable object
+ *
+ * @return
+ */
+ public ConcurrentHashMap getCachedWith() {
+ return cachedWith;
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
index 8916620..ed49ddf 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/AbstractRevisioned.java
@@ -9,6 +9,7 @@ import java.io.Serializable;
public class AbstractRevisioned implements Revisioned, Serializable {
private String id;
private Long revision;
+ private final long cacheTimestamp = System.currentTimeMillis();
public AbstractRevisioned(Long revision, String id) {
this.revision = revision;
@@ -30,4 +31,12 @@ public class AbstractRevisioned implements Revisioned, Serializable {
this.revision = revision;
}
+ /**
+ * When was this cached
+ *
+ * @return
+ */
+ public long getCacheTimestamp() {
+ return cacheTimestamp;
+ }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
index 7eab0eb..e85078e 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
@@ -152,6 +152,8 @@ public class CachedRealm extends AbstractRevisioned {
protected MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
protected Set<IdentityProviderMapperModel> identityProviderMapperSet;
+ protected Map<String, String> attributes;
+
public CachedRealm(Long revision, RealmModel model) {
super(revision, model.getId());
name = model.getName();
@@ -231,10 +233,10 @@ public class CachedRealm extends AbstractRevisioned {
eventsExpiration = model.getEventsExpiration();
eventsListeners = model.getEventsListeners();
enabledEventTypes = model.getEnabledEventTypes();
-
+
adminEventsEnabled = model.isAdminEventsEnabled();
adminEventsDetailsEnabled = model.isAdminEventsDetailsEnabled();
-
+
defaultRoles = model.getDefaultRoles();
ClientModel masterAdminClient = model.getMasterAdminClient();
this.masterAdminClient = (masterAdminClient != null) ? masterAdminClient.getId() : null;
@@ -285,6 +287,11 @@ public class CachedRealm extends AbstractRevisioned {
components.put(component.getId(), component);
}
+ try {
+ attributes = model.getAttributes();
+ } catch (UnsupportedOperationException ex) {
+ }
+
}
protected void cacheClientTemplates(RealmModel model) {
@@ -475,7 +482,7 @@ public class CachedRealm extends AbstractRevisioned {
public Set<String> getEventsListeners() {
return eventsListeners;
}
-
+
public Set<String> getEnabledEventTypes() {
return enabledEventTypes;
}
@@ -619,4 +626,28 @@ public class CachedRealm extends AbstractRevisioned {
public Map<String, ComponentModel> getComponents() {
return components;
}
+
+ public String getAttribute(String name) {
+ return attributes != null ? attributes.get(name) : null;
+ }
+
+ public Integer getAttribute(String name, Integer defaultValue) {
+ String v = getAttribute(name);
+ return v != null ? Integer.parseInt(v) : defaultValue;
+ }
+
+ public Long getAttribute(String name, Long defaultValue) {
+ String v = getAttribute(name);
+ return v != null ? Long.parseLong(v) : defaultValue;
+ }
+
+ public Boolean getAttribute(String name, Boolean defaultValue) {
+ String v = getAttribute(name);
+ return v != null ? Boolean.parseBoolean(v) : defaultValue;
+ }
+
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+
}
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 89049ca..7c24594 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
@@ -36,7 +36,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class CachedUser extends AbstractRevisioned implements InRealm {
+public class CachedUser extends AbstractExtendableRevisioned implements InRealm {
private String realm;
private String username;
private Long createdTimestamp;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 8256aa2..6950ed8 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -1465,4 +1465,64 @@ public class RealmAdapter implements RealmModel {
if (isUpdated()) return updated.getComponent(id);
return cached.getComponents().get(id);
}
+
+ public void setAttribute(String name, String value) {
+ getDelegateForUpdate();
+ updated.setAttribute(name, value);
+ }
+
+ @Override
+ public void setAttribute(String name, Boolean value) {
+ getDelegateForUpdate();
+ updated.setAttribute(name, value);
+ }
+
+ @Override
+ public void setAttribute(String name, Integer value) {
+ getDelegateForUpdate();
+ updated.setAttribute(name, value);
+ }
+
+ @Override
+ public void setAttribute(String name, Long value) {
+ getDelegateForUpdate();
+ updated.setAttribute(name, value);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ getDelegateForUpdate();
+ updated.removeAttribute(name);
+ }
+
+ @Override
+ public String getAttribute(String name) {
+ if (isUpdated()) return updated.getAttribute(name);
+ return cached.getAttribute(name);
+ }
+
+ @Override
+ public Integer getAttribute(String name, Integer defaultValue) {
+ if (isUpdated()) return updated.getAttribute(name, defaultValue);
+ return cached.getAttribute(name, defaultValue);
+ }
+
+ @Override
+ public Long getAttribute(String name, Long defaultValue) {
+ if (isUpdated()) return updated.getAttribute(name, defaultValue);
+ return cached.getAttribute(name, defaultValue);
+ }
+
+ @Override
+ public Boolean getAttribute(String name, Boolean defaultValue) {
+ if (isUpdated()) return updated.getAttribute(name, defaultValue);
+ return cached.getAttribute(name, defaultValue);
+ }
+
+ @Override
+ public Map<String, String> getAttributes() {
+ if (isUpdated()) return updated.getAttributes();
+ return cached.getAttributes();
+ }
+
}
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 d697ad2..ea6698c 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
@@ -28,6 +28,7 @@ import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -39,12 +40,13 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class UserAdapter implements UserModel {
+public class UserAdapter implements CachedUserModel {
protected UserModel updated;
protected CachedUser cached;
protected UserCacheSession userProviderCache;
@@ -65,6 +67,22 @@ public class UserAdapter implements UserModel {
if (updated == null) throw new IllegalStateException("Not found in database");
}
}
+
+ @Override
+ public void invalidate() {
+ getDelegateForUpdate();
+ }
+
+ @Override
+ public long getCacheTimestamp() {
+ return cached.getCacheTimestamp();
+ }
+
+ @Override
+ public ConcurrentHashMap getCachedWith() {
+ return cached.getCachedWith();
+ }
+
@Override
public String getId() {
if (updated != null) return updated.getId();
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index 8a62c33..b38d9aa 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -34,7 +34,9 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
-import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
+import org.keycloak.models.cache.UserCache;
import org.keycloak.models.cache.infinispan.entities.CachedFederatedIdentityLinks;
import org.keycloak.models.cache.infinispan.entities.CachedUser;
import org.keycloak.models.cache.infinispan.entities.CachedUserConsent;
@@ -47,7 +49,7 @@ import java.util.*;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class UserCacheSession implements CacheUserProvider {
+public class UserCacheSession implements UserCache {
protected static final Logger logger = Logger.getLogger(UserCacheSession.class);
protected UserCacheManager cache;
protected KeycloakSession session;
@@ -73,7 +75,6 @@ public class UserCacheSession implements CacheUserProvider {
cache.clear();
}
- @Override
public UserProvider getDelegate() {
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
if (delegate != null) return delegate;
@@ -89,6 +90,20 @@ public class UserCacheSession implements CacheUserProvider {
if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
}
+ @Override
+ public void evict(RealmModel realm, UserModel user) {
+ if (user instanceof CachedUserModel) {
+ ((CachedUserModel)user).invalidate();
+ } else {
+ invalidations.add(user.getId());
+ if (user.getEmail() != null) invalidations.add(getUserByEmailCacheKey(realm.getId(), user.getEmail()));
+ invalidations.add(getUserByUsernameCacheKey(realm.getId(), user.getUsername()));
+ if (realm.isIdentityFederationEnabled()) invalidations.add(getFederatedIdentityLinksCacheKey(user.getId()));
+ }
+ }
+
+
+
protected void runInvalidations() {
for (String realmId : realmInvalidations) {
cache.invalidateRealmUsers(realmId, invalidations);
@@ -147,8 +162,13 @@ public class UserCacheSession implements CacheUserProvider {
logger.trace("registered for invalidation return delegate");
return getDelegate().getUserById(id, realm);
}
+ if (managedUsers.containsKey(id)) {
+ logger.trace("return managedusers");
+ return managedUsers.get(id);
+ }
CachedUser cached = cache.get(id, CachedUser.class);
+ boolean wasCached = cached != null;
if (cached == null) {
logger.trace("not cached");
Long loaded = cache.getCurrentRevision(id);
@@ -157,19 +177,12 @@ public class UserCacheSession implements CacheUserProvider {
logger.trace("delegate returning null");
return null;
}
- if (managedUsers.containsKey(id)) {
- logger.trace("return managedusers");
- return managedUsers.get(id);
- }
- if (invalidations.contains(id)) return model;
cached = new CachedUser(loaded, realm, model);
cache.addRevisioned(cached, startupRevision);
- } else if (managedUsers.containsKey(id)) {
- logger.trace("return managedusers");
- return managedUsers.get(id);
}
logger.trace("returning new cache adapter");
UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+ if (!wasCached) onCache(realm, adapter);
managedUsers.put(id, adapter);
return adapter;
}
@@ -223,13 +236,7 @@ public class UserCacheSession implements CacheUserProvider {
return managedUsers.get(userId);
}
- CachedUser cached = cache.get(userId, CachedUser.class);
- if (cached == null) {
- cached = new CachedUser(loaded, realm, model);
- cache.addRevisioned(cached, startupRevision);
- }
- logger.trace("return new cache adapter");
- UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+ UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
managedUsers.put(userId, adapter);
return adapter;
} else {
@@ -244,6 +251,26 @@ public class UserCacheSession implements CacheUserProvider {
}
}
+ protected UserAdapter getUserAdapter(RealmModel realm, String userId, Long loaded, UserModel model) {
+ CachedUser cached = cache.get(userId, CachedUser.class);
+ boolean wasCached = cached != null;
+ if (cached == null) {
+ cached = new CachedUser(loaded, realm, model);
+ cache.addRevisioned(cached, startupRevision);
+ }
+ UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+ if (!wasCached) {
+ onCache(realm, adapter);
+ }
+ return adapter;
+
+ }
+
+ private void onCache(RealmModel realm, UserAdapter adapter) {
+ ((OnUserCache)getDelegate()).onCache(realm, adapter);
+ ((OnUserCache)session.userCredentialManager()).onCache(realm, adapter);
+ }
+
@Override
public UserModel getUserByEmail(String email, RealmModel realm) {
if (email == null) return null;
@@ -268,12 +295,7 @@ public class UserCacheSession implements CacheUserProvider {
if (invalidations.contains(userId)) return model;
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
- CachedUser cached = cache.get(userId, CachedUser.class);
- if (cached == null) {
- cached = new CachedUser(loaded, realm, model);
- cache.addRevisioned(cached, startupRevision);
- }
- UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+ UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
managedUsers.put(userId, adapter);
return adapter;
} else {
@@ -316,12 +338,7 @@ public class UserCacheSession implements CacheUserProvider {
if (invalidations.contains(userId)) return model;
if (managedUsers.containsKey(userId)) return managedUsers.get(userId);
- CachedUser cached = cache.get(userId, CachedUser.class);
- if (cached == null) {
- cached = new CachedUser(loaded, realm, model);
- cache.addRevisioned(cached, startupRevision);
- }
- UserAdapter adapter = new UserAdapter(cached, this, session, realm);
+ UserAdapter adapter = getUserAdapter(realm, userId, loaded, model);
managedUsers.put(userId, adapter);
return adapter;
} else {
@@ -669,4 +686,5 @@ public class UserCacheSession implements CacheUserProvider {
getDelegate().preRemove(realm, component);
}
+
}
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.UserCacheProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.UserCacheProviderFactory
new file mode 100755
index 0000000..e558cf2
--- /dev/null
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.UserCacheProviderFactory
@@ -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.models.cache.infinispan.InfinispanUserCacheProviderFactory
\ No newline at end of file
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 de6e587..4c5ccc4 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
@@ -101,14 +101,19 @@ 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");
node1Cache.remove("key");
+
System.out.println("node2 remove entry");
node2Cache.remove("key");
+
System.out.println("node2 put entry");
node2Cache.put("key", "node2");
System.out.println("node2 evict entry");
@@ -118,6 +123,28 @@ public class ClusteredCacheBehaviorTest {
node2Cache.putForExternalRead("key", "common");
System.out.println("node2 remove entry");
node2Cache.remove("key");
+ System.out.println("node1 remove entry");
+ node1Cache.remove("key");
+
+ // test remove non-existing node 2, existing node 1
+ System.out.println("Test non existent remove");
+ System.out.println("node1 create entry");
+ node1Cache.put("key", "value");
+ System.out.println("node2 remove non-existent entry");
+ System.out.println("exists?: " + node2Cache.containsKey("key"));
+ node2Cache.remove("key");
+
+ // test clear
+ System.out.println("Test clear cache");
+ System.out.println("add key to node 1, key2 to node2");
+ node1Cache.putForExternalRead("key", "value");
+ node2Cache.putForExternalRead("key", "value");
+ node2Cache.putForExternalRead("key2", "value");
+ System.out.println("Clear from node1");
+ node1Cache.clear();
+ System.out.println("node 2 exists key2?: " + node2Cache.containsKey("key2"));
+ System.out.println("node 2 exists key?: " + node2Cache.containsKey("key"));
+
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java
index db85a82..e35e5b0 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProvider.java
@@ -17,6 +17,8 @@
package org.keycloak.connections.jpa;
+import org.jboss.logging.Logger;
+
import javax.persistence.EntityManager;
/**
@@ -24,6 +26,7 @@ import javax.persistence.EntityManager;
*/
public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
+ private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProvider.class);
private final EntityManager em;
public DefaultJpaConnectionProvider(EntityManager em) {
@@ -37,6 +40,7 @@ public class DefaultJpaConnectionProvider implements JpaConnectionProvider {
@Override
public void close() {
+ logger.trace("DefaultJpaConnectionProvider close()");
em.close();
}
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 3c74264..d029e16 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
@@ -29,12 +29,22 @@ import java.util.Map;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
+import javax.persistence.SynchronizationType;
import javax.sql.DataSource;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.UserTransaction;
import org.hibernate.ejb.AvailableSettings;
+import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;
+import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
+import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -45,6 +55,7 @@ import org.keycloak.provider.ServerInfoAwareProviderFactory;
import org.keycloak.models.dblock.DBLockManager;
import org.keycloak.ServerStartupError;
import org.keycloak.timer.TimerProvider;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -60,16 +71,29 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
private volatile EntityManagerFactory emf;
private Config.Scope config;
-
- private Map<String,String> operationalInfo;
+
+ private Map<String, String> operationalInfo;
+
+ private boolean jtaEnabled;
+ private JtaTransactionManagerLookup jtaLookup;
+
+ private KeycloakSessionFactory factory;
@Override
public JpaConnectionProvider create(KeycloakSession session) {
+ logger.trace("Create JpaConnectionProvider");
lazyInit(session);
- EntityManager em = emf.createEntityManager();
+ EntityManager em = null;
+ if (!jtaEnabled) {
+ logger.trace("enlisting EntityManager in JpaKeycloakTransaction");
+ em = emf.createEntityManager();
+ } else {
+
+ em = emf.createEntityManager(SynchronizationType.SYNCHRONIZED);
+ }
em = PersistenceExceptionConverter.create(em);
- session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
+ if (!jtaEnabled) session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
return new DefaultJpaConnectionProvider(em);
}
@@ -92,85 +116,112 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
@Override
public void postInit(KeycloakSessionFactory factory) {
+ this.factory = factory;
+ checkJtaEnabled(factory);
}
+ protected void checkJtaEnabled(KeycloakSessionFactory factory) {
+ jtaLookup = (JtaTransactionManagerLookup) factory.getProviderFactory(JtaTransactionManagerLookup.class);
+ if (jtaLookup != null) {
+ if (jtaLookup.getTransactionManager() != null) {
+ jtaEnabled = true;
+ }
+ }
+ }
+
private void lazyInit(KeycloakSession session) {
if (emf == null) {
synchronized (this) {
if (emf == null) {
- logger.debug("Initializing JPA connections");
+ KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+ logger.debug("Initializing JPA connections");
- Map<String, Object> properties = new HashMap<String, Object>();
+ Map<String, Object> properties = new HashMap<String, Object>();
- String unitName = "keycloak-default";
+ String unitName = "keycloak-default";
- String dataSource = config.get("dataSource");
- if (dataSource != null) {
- if (config.getBoolean("jta", false)) {
- properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
+ String dataSource = config.get("dataSource");
+ if (dataSource != null) {
+ if (config.getBoolean("jta", jtaEnabled)) {
+ properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
+ } else {
+ properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
+ }
} else {
- properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
- }
- } else {
- properties.put(AvailableSettings.JDBC_URL, config.get("url"));
- properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
-
- String user = config.get("user");
- if (user != null) {
- properties.put(AvailableSettings.JDBC_USER, user);
- }
- String password = config.get("password");
- if (password != null) {
- properties.put(AvailableSettings.JDBC_PASSWORD, password);
- }
- }
-
- String schema = getSchema();
- if (schema != null) {
- properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
- }
-
- MigrationStrategy migrationStrategy = getMigrationStrategy();
- boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
- File databaseUpdateFile = getDatabaseUpdateFile();
-
- properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
- properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
-
- Connection connection = getConnection();
- try{
- prepareOperationalInfo(connection);
-
- String driverDialect = detectDialect(connection);
- if (driverDialect != null) {
- properties.put("hibernate.dialect", driverDialect);
+ properties.put(AvailableSettings.JDBC_URL, config.get("url"));
+ properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
+
+ String user = config.get("user");
+ if (user != null) {
+ properties.put(AvailableSettings.JDBC_USER, user);
+ }
+ String password = config.get("password");
+ if (password != null) {
+ properties.put(AvailableSettings.JDBC_PASSWORD, password);
+ }
}
- migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
-
- int globalStatsInterval = config.getInt("globalStatsInterval", -1);
- if (globalStatsInterval != -1) {
- properties.put("hibernate.generate_statistics", true);
+ String schema = getSchema();
+ if (schema != null) {
+ properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
}
- logger.trace("Creating EntityManagerFactory");
- emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader());
- logger.trace("EntityManagerFactory created");
-
- if (globalStatsInterval != -1) {
- startGlobalStats(session, globalStatsInterval);
+ MigrationStrategy migrationStrategy = getMigrationStrategy();
+ boolean initializeEmpty = config.getBoolean("initializeEmpty", true);
+ File databaseUpdateFile = getDatabaseUpdateFile();
+
+ properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
+ properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
+
+ Connection connection = getConnection();
+ try {
+ prepareOperationalInfo(connection);
+
+ String driverDialect = detectDialect(connection);
+ if (driverDialect != null) {
+ properties.put("hibernate.dialect", driverDialect);
+ }
+
+ migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session);
+
+ int globalStatsInterval = config.getInt("globalStatsInterval", -1);
+ if (globalStatsInterval != -1) {
+ properties.put("hibernate.generate_statistics", true);
+ }
+
+ logger.trace("Creating EntityManagerFactory");
+ logger.tracev("***** create EMF jtaEnabled {0} ", jtaEnabled);
+ if (jtaEnabled) {
+ properties.put(org.hibernate.cfg.AvailableSettings.JTA_PLATFORM, new AbstractJtaPlatform() {
+ @Override
+ protected TransactionManager locateTransactionManager() {
+ return jtaLookup.getTransactionManager();
+ }
+
+ @Override
+ protected UserTransaction locateUserTransaction() {
+ return null;
+ }
+ });
+ }
+ emf = JpaUtils.createEntityManagerFactory(session, unitName, properties, getClass().getClassLoader(), jtaEnabled);
+ logger.trace("EntityManagerFactory created");
+
+ if (globalStatsInterval != -1) {
+ startGlobalStats(session, globalStatsInterval);
+ }
+ } finally {
+ // Close after creating EntityManagerFactory to prevent in-mem databases from closing
+ if (connection != null) {
+ try {
+ connection.close();
+ } catch (SQLException e) {
+ logger.warn("Can't close connection", e);
+ }
+ }
}
- } finally {
- // Close after creating EntityManagerFactory to prevent in-mem databases from closing
- if (connection != null) {
- try {
- connection.close();
- } catch (SQLException e) {
- logger.warn("Can't close connection", e);
- }
- }
- }
+ });
}
}
}
@@ -182,19 +233,19 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
}
protected void prepareOperationalInfo(Connection connection) {
- try {
- operationalInfo = new LinkedHashMap<>();
- DatabaseMetaData md = connection.getMetaData();
- operationalInfo.put("databaseUrl",md.getURL());
- operationalInfo.put("databaseUser", md.getUserName());
- operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
- operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
+ try {
+ operationalInfo = new LinkedHashMap<>();
+ DatabaseMetaData md = connection.getMetaData();
+ operationalInfo.put("databaseUrl", md.getURL());
+ 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());
- }
- }
+ } catch (SQLException e) {
+ logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
+ }
+ }
protected String detectDialect(Connection connection) {
@@ -334,11 +385,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
public String getSchema() {
return config.get("schema");
}
-
+
@Override
- public Map<String,String> getOperationalInfo() {
- return operationalInfo;
- }
+ public Map<String, String> getOperationalInfo() {
+ return operationalInfo;
+ }
private MigrationStrategy getMigrationStrategy() {
String migrationStrategy = config.get("migrationStrategy");
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
index 7c48499..d080542 100644
--- 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
@@ -23,14 +23,13 @@ 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;
+import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -57,6 +56,7 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
this.session = session;
}
+
private void lazyInit() {
if (!initialized) {
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
@@ -92,35 +92,41 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
@Override
public void waitForLock() {
- lazyInit();
-
- while (maxAttempts > 0) {
- try {
- lockService.waitForLock();
- factory.setHasLock(true);
- this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
- return;
- } catch (LockRetryException le) {
- // Indicates we should try to acquire lock again in different transaction
- safeRollbackConnection();
- restart();
- maxAttempts--;
- } catch (RuntimeException re) {
- safeRollbackConnection();
- safeCloseConnection();
- throw re;
+ KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+
+ lazyInit();
+
+ while (maxAttempts > 0) {
+ try {
+ lockService.waitForLock();
+ factory.setHasLock(true);
+ this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
+ return;
+ } catch (LockRetryException le) {
+ // Indicates we should try to acquire lock again in different transaction
+ safeRollbackConnection();
+ restart();
+ maxAttempts--;
+ } catch (RuntimeException re) {
+ safeRollbackConnection();
+ safeCloseConnection();
+ throw re;
+ }
}
- }
+ });
+
}
@Override
public void releaseLock() {
- lazyInit();
+ KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+ lazyInit();
- lockService.releaseLock();
- lockService.reset();
- factory.setHasLock(false);
+ lockService.releaseLock();
+ lockService.reset();
+ factory.setHasLock(false);
+ });
}
@Override
@@ -136,21 +142,25 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
@Override
public void destroyLockInfo() {
- lazyInit();
+ KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+ lazyInit();
- try {
- this.lockService.destroy();
- dbConnection.commit();
- logger.debug("Destroyed lock table");
- } catch (DatabaseException | SQLException de) {
- logger.error("Failed to destroy lock table");
- safeRollbackConnection();
- }
+ 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();
+ KeycloakModelUtils.suspendJtaTransaction(session.getKeycloakSessionFactory(), () -> {
+ safeCloseConnection();
+ });
}
private void safeRollbackConnection() {
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
index 5ac7d2f..0385f6a 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
@@ -21,6 +21,8 @@ import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
import org.hibernate.jpa.boot.spi.Bootstrap;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
import org.keycloak.models.KeycloakSession;
@@ -46,8 +48,9 @@ public class JpaUtils {
return (schema==null) ? tableName : schema + "." + tableName;
}
- public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader) {
- PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), PersistenceUnitTransactionType.RESOURCE_LOCAL);
+ public static EntityManagerFactory createEntityManagerFactory(KeycloakSession session, String unitName, Map<String, Object> properties, ClassLoader classLoader, boolean jta) {
+ PersistenceUnitTransactionType txType = jta ? PersistenceUnitTransactionType.JTA : PersistenceUnitTransactionType.RESOURCE_LOCAL;
+ PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(classLoader), txType);
List<ParsedPersistenceXmlDescriptor> persistenceUnits = parser.doResolve(properties);
for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) {
if (persistenceUnit.getName().equals(unitName)) {
@@ -58,6 +61,7 @@ public class JpaUtils {
}
// Now build the entity manager factory, supplying a proxy classloader, so Hibernate will be able
// to find and load the extra provided entities. Set the provided classloader as parent classloader.
+ persistenceUnit.setTransactionType(txType);
return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, properties,
new ProxyClassLoader(providedEntities, classLoader)).build();
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java
new file mode 100755
index 0000000..f4ceb94
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialAttributeEntity.java
@@ -0,0 +1,111 @@
+/*
+ * 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.jpa.entities;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @NamedQuery(name="getCredentialAttribute", query="select attr from CredentialAttributeEntity attr where attr.credential = :credential"),
+ @NamedQuery(name="deleteCredentialAttributeByCredential", query="delete from CredentialAttributeEntity attr where attr.credential = :credential"),
+ @NamedQuery(name="deleteCredentialAttributeByRealm", query="delete from CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId))"),
+ @NamedQuery(name="deleteCredentialAttributeByRealmAndLink", query="delete from CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"),
+ @NamedQuery(name="deleteCredentialAttributeByUser", query="delete from CredentialAttributeEntity attr where attr.credential IN (select cred from CredentialEntity cred where cred.user = :user)"),
+})
+@Table(name="CREDENTIAL_ATTRIBUTE")
+@Entity
+public class CredentialAttributeEntity {
+
+ @Id
+ @Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
+ protected String id;
+
+ @ManyToOne(fetch= FetchType.LAZY)
+ @JoinColumn(name = "CREDENTIAL_ID")
+ protected CredentialEntity credential;
+
+ @Column(name = "NAME")
+ protected String name;
+ @Column(name = "VALUE")
+ protected String value;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public CredentialEntity getCredential() {
+ return credential;
+ }
+
+ public void setCredential(CredentialEntity credential) {
+ this.credential = credential;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof CredentialAttributeEntity)) return false;
+
+ CredentialAttributeEntity that = (CredentialAttributeEntity) o;
+
+ if (!id.equals(that.getId())) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
index ceb284c..4c8f0b3 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
@@ -19,6 +19,7 @@ package org.keycloak.models.jpa.entities;
import javax.persistence.Access;
import javax.persistence.AccessType;
+import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -27,14 +28,19 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.Collection;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@NamedQueries({
+ @NamedQuery(name="credentialByUser", query="select cred from CredentialEntity cred where cred.user = :user"),
@NamedQuery(name="credentialByUserAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type"),
+ @NamedQuery(name="credentialByNameAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type and cred.device = :device"),
@NamedQuery(name="deleteCredentialsByRealm", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteCredentialsByRealmAndLink", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
@@ -74,6 +80,8 @@ public class CredentialEntity {
@Column(name="PERIOD")
protected int period;
+ @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential")
+ protected Collection<CredentialAttributeEntity> credentialAttributes = new ArrayList<>();
public String getId() {
return id;
@@ -171,6 +179,14 @@ public class CredentialEntity {
this.period = period;
}
+ public Collection<CredentialAttributeEntity> getCredentialAttributes() {
+ return credentialAttributes;
+ }
+
+ public void setCredentialAttributes(Collection<CredentialAttributeEntity> credentialAttributes) {
+ this.credentialAttributes = credentialAttributes;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
index 2b5d35c..16b155b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
@@ -43,6 +43,7 @@ import java.util.Set;
@NamedQuery(name="getAttributesByNameAndValue", query="select attr from UserAttributeEntity attr where attr.name = :name and attr.value = :value"),
@NamedQuery(name="deleteUserAttributesByRealm", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where u.realmId=:realmId)"),
@NamedQuery(name="deleteUserAttributesByNameAndUser", query="delete from UserAttributeEntity attr where attr.user.id = :userId and attr.name = :name"),
+ @NamedQuery(name="deleteUserAttributesOtherThan", query="delete from UserAttributeEntity attr where attr.user.id = :userId and attr.id <> :attrId"),
@NamedQuery(name="deleteUserAttributesByRealmAndLink", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where u.realmId=:realmId and u.federationLink=:link)")
})
@Table(name="USER_ATTRIBUTE")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java
new file mode 100644
index 0000000..d3539fe
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStore.java
@@ -0,0 +1,220 @@
+/*
+ * 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.jpa;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
+import org.keycloak.models.jpa.entities.CredentialEntity;
+import org.keycloak.models.jpa.entities.UserEntity;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaUserCredentialStore implements UserCredentialStore {
+
+ private final KeycloakSession session;
+ protected final EntityManager em;
+
+ public JpaUserCredentialStore(KeycloakSession session, EntityManager em) {
+ this.session = session;
+ this.em = em;
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ CredentialEntity entity = em.find(CredentialEntity.class, cred.getId());
+ if (entity == null) return;
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+ } else {
+ MultivaluedHashMap<String, String> attrs = cred.getConfig();
+ MultivaluedHashMap<String, String> config = cred.getConfig();
+ if (config == null) config = new MultivaluedHashMap<>();
+
+ Iterator<CredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
+ while (it.hasNext()) {
+ CredentialAttributeEntity attr = it.next();
+ List<String> values = config.getList(attr.getName());
+ if (values == null || !values.contains(attr.getValue())) {
+ em.remove(attr);
+ it.remove();
+ } else {
+ attrs.add(attr.getName(), attr.getValue());
+ }
+
+ }
+ for (String key : config.keySet()) {
+ List<String> values = config.getList(key);
+ List<String> attrValues = attrs.getList(key);
+ for (String val : values) {
+ if (attrValues == null || !attrValues.contains(val)) {
+ CredentialAttributeEntity attr = new CredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+ }
+
+ }
+
+ }
+
+ @Override
+ public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ CredentialEntity entity = new CredentialEntity();
+ String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+ entity.setId(id);
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ UserEntity userRef = em.getReference(UserEntity.class, user.getId());
+ entity.setUser(userRef);
+ em.persist(entity);
+ MultivaluedHashMap<String, String> config = cred.getConfig();
+ if (config != null || !config.isEmpty()) {
+
+ for (String key : config.keySet()) {
+ List<String> values = config.getList(key);
+ for (String val : values) {
+ CredentialAttributeEntity attr = new CredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+
+ }
+ return toModel(entity);
+ }
+
+ @Override
+ public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+ CredentialEntity entity = em.find(CredentialEntity.class, id);
+ if (entity == null) return false;
+ em.remove(entity);
+ return true;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+ CredentialEntity entity = em.find(CredentialEntity.class, id);
+ if (entity == null) return null;
+ CredentialModel model = toModel(entity);
+ return model;
+ }
+
+ protected CredentialModel toModel(CredentialEntity entity) {
+ CredentialModel model = new CredentialModel();
+ model.setId(entity.getId());
+ model.setType(entity.getType());
+ model.setValue(entity.getValue());
+ model.setAlgorithm(entity.getAlgorithm());
+ model.setSalt(entity.getSalt());
+ model.setPeriod(entity.getPeriod());
+ model.setCounter(entity.getCounter());
+ model.setCreatedDate(entity.getCreatedDate());
+ model.setDevice(entity.getDevice());
+ model.setDigits(entity.getDigits());
+ MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+ model.setConfig(config);
+ for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+ config.add(attr.getName(), attr.getValue());
+ }
+ return model;
+ }
+
+ @Override
+ public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUser", CredentialEntity.class)
+ .setParameter("user", userEntity);
+ List<CredentialEntity> results = query.getResultList();
+ List<CredentialModel> rtn = new LinkedList<>();
+ for (CredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("user", userEntity);
+ List<CredentialEntity> results = query.getResultList();
+ List<CredentialModel> rtn = new LinkedList<>();
+ for (CredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByNameAndType", CredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("device", name)
+ .setParameter("user", userEntity);
+ List<CredentialEntity> results = query.getResultList();
+ if (results.isEmpty()) return null;
+ return toModel(results.get(0));
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.java
new file mode 100755
index 0000000..550299b
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserCredentialStoreFactory.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.models.jpa;
+
+import org.keycloak.Config;
+import org.keycloak.connections.jpa.JpaConnectionProvider;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderFactory;
+
+import javax.persistence.EntityManager;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaUserCredentialStoreFactory implements ProviderFactory<UserCredentialStore> {
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public String getId() {
+ return "jpa";
+ }
+
+ @Override
+ public UserCredentialStore create(KeycloakSession session) {
+ EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
+ return new JpaUserCredentialStore(session, em);
+ }
+
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index 0d938d6..d44c6fe 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -17,7 +17,11 @@
package org.keycloak.models.jpa;
+import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
@@ -34,6 +38,8 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserProvider;
+import org.keycloak.models.jpa.entities.CredentialAttributeEntity;
+import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserConsentEntity;
@@ -48,7 +54,9 @@ import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -58,7 +66,7 @@ import java.util.Set;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class JpaUserProvider implements UserProvider {
+public class JpaUserProvider implements UserProvider, UserCredentialStore {
private static final String EMAIL = "email";
private static final String USERNAME = "username";
@@ -367,6 +375,8 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteFederatedIdentityByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
+ num = em.createNamedQuery("deleteCredentialAttributeByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteCredentialsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserAttributesByRealm")
@@ -391,6 +401,10 @@ public class JpaUserProvider implements UserProvider {
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
+ num = em.createNamedQuery("deleteCredentialAttributeByRealmAndLink")
+ .setParameter("realmId", realm.getId())
+ .setParameter("link", link.getId())
+ .executeUpdate();
num = em.createNamedQuery("deleteCredentialsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
@@ -716,4 +730,174 @@ public class JpaUserProvider implements UserProvider {
public void preRemove(RealmModel realm, ComponentModel component) {
}
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ CredentialEntity entity = em.find(CredentialEntity.class, cred.getId());
+ if (entity == null) return;
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+ } else {
+ MultivaluedHashMap<String, String> attrs = cred.getConfig();
+ MultivaluedHashMap<String, String> config = cred.getConfig();
+ if (config == null) config = new MultivaluedHashMap<>();
+
+ Iterator<CredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
+ while (it.hasNext()) {
+ CredentialAttributeEntity attr = it.next();
+ List<String> values = config.getList(attr.getName());
+ if (values == null || !values.contains(attr.getValue())) {
+ em.remove(attr);
+ it.remove();
+ } else {
+ attrs.add(attr.getName(), attr.getValue());
+ }
+
+ }
+ for (String key : config.keySet()) {
+ List<String> values = config.getList(key);
+ List<String> attrValues = attrs.getList(key);
+ for (String val : values) {
+ if (attrValues == null || !attrValues.contains(val)) {
+ CredentialAttributeEntity attr = new CredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+ }
+
+ }
+
+ }
+
+ @Override
+ public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ CredentialEntity entity = new CredentialEntity();
+ String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+ entity.setId(id);
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ UserEntity userRef = em.getReference(UserEntity.class, user.getId());
+ entity.setUser(userRef);
+ em.persist(entity);
+ MultivaluedHashMap<String, String> config = cred.getConfig();
+ if (config != null || !config.isEmpty()) {
+
+ for (String key : config.keySet()) {
+ List<String> values = config.getList(key);
+ for (String val : values) {
+ CredentialAttributeEntity attr = new CredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+
+ }
+ return toModel(entity);
+ }
+
+ @Override
+ public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+ CredentialEntity entity = em.find(CredentialEntity.class, id);
+ if (entity == null) return false;
+ em.remove(entity);
+ return true;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+ CredentialEntity entity = em.find(CredentialEntity.class, id);
+ if (entity == null) return null;
+ CredentialModel model = toModel(entity);
+ return model;
+ }
+
+ protected CredentialModel toModel(CredentialEntity entity) {
+ CredentialModel model = new CredentialModel();
+ model.setId(entity.getId());
+ model.setType(entity.getType());
+ model.setValue(entity.getValue());
+ model.setAlgorithm(entity.getAlgorithm());
+ model.setSalt(entity.getSalt());
+ model.setPeriod(entity.getPeriod());
+ model.setCounter(entity.getCounter());
+ model.setCreatedDate(entity.getCreatedDate());
+ model.setDevice(entity.getDevice());
+ model.setDigits(entity.getDigits());
+ MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+ model.setConfig(config);
+ for (CredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+ config.add(attr.getName(), attr.getValue());
+ }
+ return model;
+ }
+
+ @Override
+ public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUser", CredentialEntity.class)
+ .setParameter("user", userEntity);
+ List<CredentialEntity> results = query.getResultList();
+ List<CredentialModel> rtn = new LinkedList<>();
+ for (CredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByUserAndType", CredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("user", userEntity);
+ List<CredentialEntity> results = query.getResultList();
+ List<CredentialModel> rtn = new LinkedList<>();
+ for (CredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
+ TypedQuery<CredentialEntity> query = em.createNamedQuery("credentialByNameAndType", CredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("device", name)
+ .setParameter("user", userEntity);
+ List<CredentialEntity> results = query.getResultList();
+ if (results.isEmpty()) return null;
+ return toModel(results.get(0));
+ }
+
+
+
+
}
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 46df41a..a5747c6 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
@@ -182,6 +182,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
em.flush();
}
+ @Override
public void setAttribute(String name, String value) {
for (RealmAttributeEntity attr : realm.getAttributes()) {
if (attr.getName().equals(name)) {
@@ -197,18 +198,22 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
realm.getAttributes().add(attr);
}
+ @Override
public void setAttribute(String name, Boolean value) {
setAttribute(name, value.toString());
}
+ @Override
public void setAttribute(String name, Integer value) {
setAttribute(name, value.toString());
}
+ @Override
public void setAttribute(String name, Long value) {
setAttribute(name, value.toString());
}
+ @Override
public void removeAttribute(String name) {
Iterator<RealmAttributeEntity> it = realm.getAttributes().iterator();
while (it.hasNext()) {
@@ -220,6 +225,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
}
}
+ @Override
public String getAttribute(String name) {
for (RealmAttributeEntity attr : realm.getAttributes()) {
if (attr.getName().equals(name)) {
@@ -229,24 +235,28 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
return null;
}
+ @Override
public Integer getAttribute(String name, Integer defaultValue) {
String v = getAttribute(name);
return v != null ? Integer.parseInt(v) : defaultValue;
}
+ @Override
public Long getAttribute(String name, Long defaultValue) {
String v = getAttribute(name);
return v != null ? Long.parseLong(v) : defaultValue;
}
+ @Override
public Boolean getAttribute(String name, Boolean defaultValue) {
String v = getAttribute(name);
return v != null ? Boolean.parseBoolean(v) : defaultValue;
}
+ @Override
public Map<String, String> getAttributes() {
// should always return a copy
Map<String, String> result = new HashMap<String, String>();
@@ -255,6 +265,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
}
return result;
}
+
@Override
public boolean isBruteForceProtected() {
return getAttribute("bruteForceProtected", false);
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 2ea12fd..49bcac6 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
@@ -125,29 +125,32 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
@Override
public void setSingleAttribute(String name, String value) {
- boolean found = false;
+ String firstExistingAttrId = null;
List<UserAttributeEntity> toRemove = new ArrayList<>();
for (UserAttributeEntity attr : user.getAttributes()) {
if (attr.getName().equals(name)) {
- if (!found) {
+ if (firstExistingAttrId == null) {
attr.setValue(value);
- found = true;
+ firstExistingAttrId = attr.getId();
} else {
toRemove.add(attr);
}
}
}
- for (UserAttributeEntity attr : toRemove) {
- em.remove(attr);
- user.getAttributes().remove(attr);
- }
+ if (firstExistingAttrId != null) {
+ // Remove attributes through HQL to avoid StaleUpdateException
+ Query query = em.createNamedQuery("deleteUserAttributesOtherThan");
+ query.setParameter("attrId", firstExistingAttrId);
+ query.setParameter("userId", user.getId());
+ int numUpdated = query.executeUpdate();
- if (found) {
- return;
- }
+ // Remove attribute from local entity
+ user.getAttributes().removeAll(toRemove);
+ } else {
- persistAttributeValue(name, value);
+ persistAttributeValue(name, value);
+ }
}
@Override
@@ -178,6 +181,15 @@ public class UserAdapter implements UserModel, JpaModel<UserEntity> {
query.setParameter("name", name);
query.setParameter("userId", user.getId());
int numUpdated = query.executeUpdate();
+
+ // KEYCLOAK-3494 : Also remove attributes from local user entity
+ List<UserAttributeEntity> toRemove = new ArrayList<>();
+ for (UserAttributeEntity attr : user.getAttributes()) {
+ if (attr.getName().equals(name)) {
+ toRemove.add(attr);
+ }
+ }
+ user.getAttributes().removeAll(toRemove);
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java
new file mode 100755
index 0000000..c6a15b3
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialAttributeEntity.java
@@ -0,0 +1,113 @@
+/*
+ * 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.storage.jpa.entity;
+
+import org.keycloak.models.jpa.entities.CredentialEntity;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @NamedQuery(name="deleteFederatedCredentialAttributeByCredential", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential = :credential"),
+ @NamedQuery(name="deleteFederatedCredentialAttributeByStorageProvider", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.storageProviderId=:storageProviderId)"),
+ @NamedQuery(name="deleteFederatedCredentialAttributeByRealm", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.realmId=:realmId)"),
+ @NamedQuery(name="deleteFederatedCredentialAttributeByRealmAndLink", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link))"),
+ @NamedQuery(name="deleteFederatedCredentialAttributeByUser", query="delete from FederatedUserCredentialAttributeEntity attr where attr.credential IN (select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId)"),
+})
+@Table(name="FED_CREDENTIAL_ATTRIBUTE")
+@Entity
+public class FederatedUserCredentialAttributeEntity {
+
+ @Id
+ @Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
+ protected String id;
+
+ @ManyToOne(fetch= FetchType.LAZY)
+ @JoinColumn(name = "CREDENTIAL_ID")
+ protected FederatedUserCredentialEntity credential;
+
+ @Column(name = "NAME")
+ protected String name;
+ @Column(name = "VALUE")
+ protected String value;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public FederatedUserCredentialEntity getCredential() {
+ return credential;
+ }
+
+ public void setCredential(FederatedUserCredentialEntity credential) {
+ this.credential = credential;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (!(o instanceof FederatedUserCredentialAttributeEntity)) return false;
+
+ FederatedUserCredentialAttributeEntity that = (FederatedUserCredentialAttributeEntity) o;
+
+ if (!id.equals(that.getId())) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
index 996608b..52a757c 100755
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java
@@ -17,10 +17,12 @@
package org.keycloak.storage.jpa.entity;
+import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import javax.persistence.Access;
import javax.persistence.AccessType;
+import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -29,7 +31,10 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.Collection;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -38,6 +43,7 @@ import javax.persistence.Table;
@NamedQueries({
@NamedQuery(name="federatedUserCredentialByUser", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId"),
@NamedQuery(name="federatedUserCredentialByUserAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
+ @NamedQuery(name="federatedUserCredentialByNameAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
@NamedQuery(name="deleteFederatedUserCredentialByUser", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId"),
@NamedQuery(name="deleteFederatedUserCredentialByUserAndType", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"),
@NamedQuery(name="deleteFederatedUserCredentialByUserAndTypeAndDevice", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"),
@@ -87,6 +93,8 @@ public class FederatedUserCredentialEntity {
protected int digits;
@Column(name="PERIOD")
protected int period;
+ @OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="credential")
+ protected Collection<FederatedUserCredentialAttributeEntity> credentialAttributes = new ArrayList<>();
public String getId() {
@@ -201,6 +209,14 @@ public class FederatedUserCredentialEntity {
this.period = period;
}
+ public Collection<FederatedUserCredentialAttributeEntity> getCredentialAttributes() {
+ return credentialAttributes;
+ }
+
+ public void setCredentialAttributes(Collection<FederatedUserCredentialAttributeEntity> credentialAttributes) {
+ this.credentialAttributes = credentialAttributes;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java
new file mode 100644
index 0000000..13fa032
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaFederatedUserCredentialStore.java
@@ -0,0 +1,218 @@
+/*
+ * 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.storage.jpa;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaFederatedUserCredentialStore implements UserCredentialStore {
+
+ private final KeycloakSession session;
+ protected final EntityManager em;
+
+ public JpaFederatedUserCredentialStore(KeycloakSession session, EntityManager em) {
+ this.session = session;
+ this.em = em;
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
+ if (entity == null) return;
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+ } else {
+ MultivaluedHashMap<String, String> attrs = cred.getConfig();
+ MultivaluedHashMap<String, String> config = cred.getConfig();
+ if (config == null) config = new MultivaluedHashMap<>();
+
+ Iterator<FederatedUserCredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
+ while (it.hasNext()) {
+ FederatedUserCredentialAttributeEntity attr = it.next();
+ List<String> values = config.getList(attr.getName());
+ if (values == null || !values.contains(attr.getValue())) {
+ em.remove(attr);
+ it.remove();
+ } else {
+ attrs.add(attr.getName(), attr.getValue());
+ }
+
+ }
+ for (String key : config.keySet()) {
+ List<String> values = config.getList(key);
+ List<String> attrValues = attrs.getList(key);
+ for (String val : values) {
+ if (attrValues == null || !attrValues.contains(val)) {
+ FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+ }
+
+ }
+
+ }
+
+ @Override
+ public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity();
+ String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+ entity.setId(id);
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ entity.setUserId(user.getId());
+ entity.setRealmId(realm.getId());
+ entity.setStorageProviderId(StorageId.resolveProviderId(user));
+ em.persist(entity);
+ MultivaluedHashMap<String, String> config = cred.getConfig();
+ if (config != null || !config.isEmpty()) {
+
+ for (String key : config.keySet()) {
+ List<String> values = config.getList(key);
+ for (String val : values) {
+ FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+
+ }
+ return toModel(entity);
+ }
+
+ @Override
+ public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+ if (entity == null) return false;
+ em.remove(entity);
+ return true;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+ if (entity == null) return null;
+ CredentialModel model = toModel(entity);
+ return model;
+ }
+
+ protected CredentialModel toModel(FederatedUserCredentialEntity entity) {
+ CredentialModel model = new CredentialModel();
+ model.setId(entity.getId());
+ model.setType(entity.getType());
+ model.setValue(entity.getValue());
+ model.setAlgorithm(entity.getAlgorithm());
+ model.setSalt(entity.getSalt());
+ model.setPeriod(entity.getPeriod());
+ model.setCounter(entity.getCounter());
+ model.setCreatedDate(entity.getCreatedDate());
+ model.setDevice(entity.getDevice());
+ model.setDigits(entity.getDigits());
+ MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+ model.setConfig(config);
+ for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+ config.add(attr.getName(), attr.getValue());
+ }
+ return model;
+ }
+
+ @Override
+ public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
+ TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class)
+ .setParameter("userId", user.getId());
+ List<FederatedUserCredentialEntity> results = query.getResultList();
+ List<CredentialModel> rtn = new LinkedList<>();
+ for (FederatedUserCredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+ TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUserAndType", FederatedUserCredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("userId", user.getId());
+ List<FederatedUserCredentialEntity> results = query.getResultList();
+ List<CredentialModel> rtn = new LinkedList<>();
+ for (FederatedUserCredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByNameAndType", FederatedUserCredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("device", name)
+ .setParameter("userId", user.getId());
+ List<FederatedUserCredentialEntity> results = query.getResultList();
+ if (results.isEmpty()) return null;
+ return toModel(results.get(0));
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
index f6710dd..1c72da0 100644
--- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java
@@ -18,6 +18,8 @@ package org.keycloak.storage.jpa;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialModel;
+import org.keycloak.credential.UserCredentialStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
@@ -49,6 +51,7 @@ import org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity;
import org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity;
+import org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity;
import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity;
import org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity;
import org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity;
@@ -59,6 +62,7 @@ import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -75,7 +79,8 @@ public class JpaUserFederatedStorageProvider implements
UserCredentialsFederatedStorage,
UserGroupMembershipFederatedStorage,
UserRequiredActionsFederatedStorage,
- UserRoleMappingsFederatedStorage {
+ UserRoleMappingsFederatedStorage,
+ UserCredentialStore {
private final KeycloakSession session;
protected EntityManager em;
@@ -607,6 +612,200 @@ public class JpaUserFederatedStorageProvider implements
}
@Override
+ public boolean removeCredential(RealmModel realm, String id) {
+ return false;
+ }
+
+ @Override
+ public CredentialModel getCredentialById(String id) {
+ return null;
+ }
+
+ @Override
+ public List<CredentialModel> getCredentials(RealmModel realm) {
+ return null;
+ }
+
+ @Override
+ public List<CredentialModel> getUserCredentials(RealmModel realm, UserModel user) {
+ return null;
+ }
+
+ @Override
+ public List<CredentialModel> getCredentialsByType(RealmModel realm, UserModel user, String type) {
+ return null;
+ }
+
+ @Override
+ public CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ return null;
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId());
+ if (entity == null) return;
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ if (entity.getCredentialAttributes().isEmpty() && (cred.getConfig() == null || cred.getConfig().isEmpty())) {
+
+ } else {
+ MultivaluedHashMap<String, String> attrs = cred.getConfig();
+ MultivaluedHashMap<String, String> config = cred.getConfig();
+ if (config == null) config = new MultivaluedHashMap<>();
+
+ Iterator<FederatedUserCredentialAttributeEntity> it = entity.getCredentialAttributes().iterator();
+ while (it.hasNext()) {
+ FederatedUserCredentialAttributeEntity attr = it.next();
+ List<String> values = config.getList(attr.getName());
+ if (values == null || !values.contains(attr.getValue())) {
+ em.remove(attr);
+ it.remove();
+ } else {
+ attrs.add(attr.getName(), attr.getValue());
+ }
+
+ }
+ for (String key : config.keySet()) {
+ List<String> values = config.getList(key);
+ List<String> attrValues = attrs.getList(key);
+ for (String val : values) {
+ if (attrValues == null || !attrValues.contains(val)) {
+ FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+ }
+
+ }
+
+ }
+
+ @Override
+ public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ FederatedUserCredentialEntity entity = new FederatedUserCredentialEntity();
+ String id = cred.getId() == null ? KeycloakModelUtils.generateId() : cred.getId();
+ entity.setId(id);
+ entity.setAlgorithm(cred.getAlgorithm());
+ entity.setCounter(cred.getCounter());
+ entity.setCreatedDate(cred.getCreatedDate());
+ entity.setDevice(cred.getDevice());
+ entity.setDigits(cred.getDigits());
+ entity.setHashIterations(cred.getHashIterations());
+ entity.setPeriod(cred.getPeriod());
+ entity.setSalt(cred.getSalt());
+ entity.setType(cred.getType());
+ entity.setValue(cred.getValue());
+ entity.setUserId(user.getId());
+ entity.setRealmId(realm.getId());
+ entity.setStorageProviderId(StorageId.resolveProviderId(user));
+ em.persist(entity);
+ MultivaluedHashMap<String, String> config = cred.getConfig();
+ if (config != null || !config.isEmpty()) {
+
+ for (String key : config.keySet()) {
+ List<String> values = config.getList(key);
+ for (String val : values) {
+ FederatedUserCredentialAttributeEntity attr = new FederatedUserCredentialAttributeEntity();
+ attr.setId(KeycloakModelUtils.generateId());
+ attr.setValue(val);
+ attr.setName(key);
+ attr.setCredential(entity);
+ em.persist(attr);
+ entity.getCredentialAttributes().add(attr);
+ }
+ }
+
+ }
+ return toModel(entity);
+ }
+
+ @Override
+ public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+ if (entity == null) return false;
+ em.remove(entity);
+ return true;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+ FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, id);
+ if (entity == null) return null;
+ CredentialModel model = toModel(entity);
+ return model;
+ }
+
+ protected CredentialModel toModel(FederatedUserCredentialEntity entity) {
+ CredentialModel model = new CredentialModel();
+ model.setId(entity.getId());
+ model.setType(entity.getType());
+ model.setValue(entity.getValue());
+ model.setAlgorithm(entity.getAlgorithm());
+ model.setSalt(entity.getSalt());
+ model.setPeriod(entity.getPeriod());
+ model.setCounter(entity.getCounter());
+ model.setCreatedDate(entity.getCreatedDate());
+ model.setDevice(entity.getDevice());
+ model.setDigits(entity.getDigits());
+ MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
+ model.setConfig(config);
+ for (FederatedUserCredentialAttributeEntity attr : entity.getCredentialAttributes()) {
+ config.add(attr.getName(), attr.getValue());
+ }
+ return model;
+ }
+
+ @Override
+ public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
+ TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class)
+ .setParameter("userId", user.getId());
+ List<FederatedUserCredentialEntity> results = query.getResultList();
+ List<CredentialModel> rtn = new LinkedList<>();
+ for (FederatedUserCredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+ TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByUserAndType", FederatedUserCredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("userId", user.getId());
+ List<FederatedUserCredentialEntity> results = query.getResultList();
+ List<CredentialModel> rtn = new LinkedList<>();
+ for (FederatedUserCredentialEntity entity : results) {
+ rtn.add(toModel(entity));
+ }
+ return rtn;
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ TypedQuery<FederatedUserCredentialEntity> query = em.createNamedQuery("federatedUserCredentialByNameAndType", FederatedUserCredentialEntity.class)
+ .setParameter("type", type)
+ .setParameter("device", name)
+ .setParameter("userId", user.getId());
+ List<FederatedUserCredentialEntity> results = query.getResultList();
+ if (results.isEmpty()) return null;
+ return toModel(results.get(0));
+ }
+
+ @Override
public void preRemove(RealmModel realm) {
int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
@@ -620,6 +819,8 @@ public class JpaUserFederatedStorageProvider implements
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteBrokerLinkByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
+ num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealm")
+ .setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteFederatedUserCredentialsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
num = em.createNamedQuery("deleteUserFederatedAttributesByRealm")
@@ -642,6 +843,10 @@ public class JpaUserFederatedStorageProvider implements
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
.executeUpdate();
+ num = em.createNamedQuery("deleteFederatedCredentialAttributeByRealmAndLink")
+ .setParameter("realmId", realm.getId())
+ .setParameter("link", link.getId())
+ .executeUpdate();
num = em.createNamedQuery("deleteFederatedUserCredentialsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", link.getId())
@@ -699,6 +904,10 @@ public class JpaUserFederatedStorageProvider implements
.setParameter("userId", user.getId())
.setParameter("realmId", realm.getId())
.executeUpdate();
+ em.createNamedQuery("deleteFederatedCredentialAttributeByUser")
+ .setParameter("userId", user.getId())
+ .setParameter("realmId", realm.getId())
+ .executeUpdate();
em.createNamedQuery("deleteFederatedUserCredentialByUser")
.setParameter("userId", user.getId())
.setParameter("realmId", realm.getId())
@@ -737,6 +946,9 @@ public class JpaUserFederatedStorageProvider implements
em.createNamedQuery("deleteFederatedUserConsentsByStorageProvider")
.setParameter("storageProviderId", model.getId())
.executeUpdate();
+ em.createNamedQuery("deleteFederatedCredentialAttributeByStorageProvider")
+ .setParameter("storageProviderId", model.getId())
+ .executeUpdate();
em.createNamedQuery("deleteFederatedUserCredentialsByStorageProvider")
.setParameter("storageProviderId", model.getId())
.executeUpdate();
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml
index 63afbb2..adc49fb 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.2.0.xml
@@ -18,10 +18,44 @@
<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="thomas.darimont@gmail.com" id="2.2.0">
+ <changeSet author="bburke@redhat.com" id="2.2.0">
+
<addColumn tableName="ADMIN_EVENT_ENTITY">
<column name="RESOURCE_TYPE" type="VARCHAR(64)"></column>
</addColumn>
+
+ <createTable tableName="CREDENTIAL_ATTRIBUTE">
+ <column name="ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="CREDENTIAL_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="NAME" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="VALUE" type="VARCHAR(4000)"/>
+ </createTable>
+
+ <createTable tableName="FED_CREDENTIAL_ATTRIBUTE">
+ <column name="ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="CREDENTIAL_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="NAME" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="VALUE" type="VARCHAR(4000)"/>
+ </createTable>
+ <modifyDataType tableName="CREDENTIAL" columnName="VALUE" newDataType="VARCHAR(4000)"/>
+
+ <addForeignKeyConstraint baseColumnNames="CREDENTIAL_ID" baseTableName="FED_CREDENTIAL_ATTRIBUTE" constraintName="FK_FED_CRED_ATTR" referencedColumnNames="ID" referencedTableName="FED_USER_CREDENTIAL"/>
+ <addForeignKeyConstraint baseColumnNames="CREDENTIAL_ID" baseTableName="CREDENTIAL_ATTRIBUTE" constraintName="FK_CRED_ATTR" referencedColumnNames="ID" referencedTableName="CREDENTIAL"/>
+
+
</changeSet>
+
</databaseChangeLog>
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml
index 0b2ff23..6288fe5 100755
--- a/model/jpa/src/main/resources/META-INF/persistence.xml
+++ b/model/jpa/src/main/resources/META-INF/persistence.xml
@@ -22,6 +22,7 @@
<persistence-unit name="keycloak-default" transaction-type="RESOURCE_LOCAL">
<class>org.keycloak.models.jpa.entities.ClientEntity</class>
<class>org.keycloak.models.jpa.entities.CredentialEntity</class>
+ <class>org.keycloak.models.jpa.entities.CredentialAttributeEntity</class>
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
<class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class>
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
@@ -74,6 +75,7 @@
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity</class>
<class>org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity</class>
<class>org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity</class>
+ <class>org.keycloak.storage.jpa.entity.FederatedUserCredentialAttributeEntity</class>
<class>org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity</class>
<class>org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity</class>
<class>org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity</class>
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index c0537ba..9ad2c1e 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -24,6 +24,7 @@ import com.mongodb.QueryBuilder;
import org.keycloak.component.ComponentModel;
import org.keycloak.connections.mongo.api.MongoStore;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
+import org.keycloak.credential.CredentialInput;
import org.keycloak.models.ClientModel;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.FederatedIdentityModel;
@@ -635,4 +636,5 @@ public class MongoUserProvider implements UserProvider {
public void preRemove(RealmModel realm, ComponentModel component) {
}
+
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index e7dc5f6..86a9001 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -1230,7 +1230,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
updateRealm();
}
-
+
@Override
public boolean isAdminEventsEnabled() {
return realm.isAdminEventsEnabled();
@@ -1240,7 +1240,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
public void setAdminEventsEnabled(boolean enabled) {
realm.setAdminEventsEnabled(enabled);
updateRealm();
-
+
}
@Override
@@ -1253,7 +1253,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
realm.setAdminEventsDetailsEnabled(enabled);
updateRealm();
}
-
+
@Override
public ClientModel getMasterAdminClient() {
MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getMasterAdminClient(), invocationContext);
@@ -2181,4 +2181,62 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
return null;
}
+
+ @Override
+ public void setAttribute(String name, String value) {
+ realm.getAttributes().put(name, value);
+ updateRealm();
+ }
+
+ @Override
+ public void setAttribute(String name, Boolean value) {
+ setAttribute(name, value.toString());
+ }
+
+ @Override
+ public void setAttribute(String name, Integer value) {
+ setAttribute(name, value.toString());
+ }
+
+ @Override
+ public void setAttribute(String name, Long value) {
+ setAttribute(name, value.toString());
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ realm.getAttributes().remove(name);
+ updateRealm();
+ }
+
+ @Override
+ public String getAttribute(String name) {
+ return realm.getAttributes().get(name);
+ }
+
+ @Override
+ public Integer getAttribute(String name, Integer defaultValue) {
+ String v = getAttribute(name);
+ return v != null ? Integer.parseInt(v) : defaultValue;
+ }
+
+ @Override
+ public Long getAttribute(String name, Long defaultValue) {
+ String v = getAttribute(name);
+ return v != null ? Long.parseLong(v) : defaultValue;
+ }
+
+ @Override
+ public Boolean getAttribute(String name, Boolean defaultValue) {
+ String v = getAttribute(name);
+ return v != null ? Boolean.parseBoolean(v) : defaultValue;
+ }
+
+ @Override
+ public Map<String, String> getAttributes() {
+ Map<String, String> attributes = new HashMap<>();
+ attributes.putAll(realm.getAttributes());
+ return attributes;
+ }
+
}
pom.xml 32(+26 -6)
diff --git a/pom.xml b/pom.xml
index 7208837..da7a2ea 100755
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,7 @@
~ 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -39,6 +40,7 @@
<product.name-html>\u003Cdiv class="kc-logo-text"\u003E\u003Cspan\u003EKeycloak\u003C\u002Fspan\u003E\u003C\u002Fdiv\u003E</product.name-html>
<product.version>${project.version}</product.version>
<product.build-time>${timestamp}</product.build-time>
+ <product.default-profile>community</product.default-profile>
<!-- WildFly -->
<eap.version>7.0.0.Beta</eap.version>
@@ -94,6 +96,7 @@
<mariadb.version>1.3.7</mariadb.version>
<servlet.api.30.version>1.0.2.Final</servlet.api.30.version>
<twitter4j.version>4.0.4</twitter4j.version>
+ <jna.version>4.1.0</jna.version>
<!-- Test -->
<greenmail.version>1.3.1b</greenmail.version>
@@ -685,6 +688,17 @@
<artifactId>keycloak-kerberos-federation</artifactId>
<version>${project.version}</version>
</dependency>
+ <!-- Dependencies for RHEL IdM -->
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-sssd-federation</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>net.java.dev.jna</groupId>
+ <artifactId>jna</artifactId>
+ <version>${jna.version}</version>
+ </dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-ldap-federation</artifactId>
@@ -1062,6 +1076,18 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
+ <artifactId>keycloak-fuse-adapter-dist</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-fuse-adapter-dist</artifactId>
+ <version>${project.version}</version>
+ <type>tar.gz</type>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-adapter-dist</artifactId>
<version>${project.version}</version>
<type>zip</type>
@@ -1103,12 +1129,6 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-src-dist</artifactId>
- <version>${project.version}</version>
- <type>zip</type>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
<artifactId>keycloak-proxy-dist</artifactId>
<version>${project.version}</version>
<type>zip</type>
README.md 4(+2 -2)
diff --git a/README.md b/README.md
index 14addf7..570576c 100755
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ To stop the server press `Ctrl + C`.
Help and Documentation
----------------------
-* [Documentation](http://keycloak.jboss.org/docs) - User Guide, Admin REST API and Javadocs
+* [Documentation](http://www.keycloak.org/documentation.html) - User Guide, Admin REST API and Javadocs
* [User Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-user) - Mailing list to ask for help and general questions about Keycloak
* [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests
@@ -72,4 +72,4 @@ Contributing
License
-------
-* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
\ No newline at end of file
+* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java b/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java
index e8be6d6..953797b 100755
--- a/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java
+++ b/server-spi/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java
@@ -50,7 +50,7 @@ public abstract class AbstractIdentityProviderFactory<T extends IdentityProvider
}
@Override
- public Map<String, String> parseConfig(InputStream inputStream) {
+ public Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream) {
return new HashMap<String, String>();
}
}
diff --git a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java b/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java
index fba8e64..93ef720 100755
--- a/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java
+++ b/server-spi/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java
@@ -17,6 +17,7 @@
package org.keycloak.broker.provider;
import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderFactory;
import java.io.InputStream;
@@ -47,8 +48,9 @@ public interface IdentityProviderFactory<T extends IdentityProvider> extends Pro
* <p>Creates an {@link IdentityProvider} based on the configuration from
* <code>inputStream</code>.</p>
*
+ * @param session
* @param inputStream The input stream from where configuration will be loaded from..
* @return
*/
- Map<String, String> parseConfig(InputStream inputStream);
+ Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream);
}
\ No newline at end of file
diff --git a/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java b/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java
new file mode 100644
index 0000000..7a0393a
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/component/PrioritizedComponentModel.java
@@ -0,0 +1,58 @@
+/*
+ * 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.component;
+
+import org.keycloak.component.ComponentModel;
+import org.keycloak.storage.UserStorageProviderModel;
+
+import java.util.Comparator;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class PrioritizedComponentModel extends ComponentModel {
+ public static Comparator<ComponentModel> comparator = new Comparator<ComponentModel>() {
+ @Override
+ public int compare(ComponentModel o1, ComponentModel o2) {
+ return parsePriority(o1) - parsePriority(o2);
+ }
+ };
+
+ public PrioritizedComponentModel(ComponentModel copy) {
+ super(copy);
+ }
+
+ public PrioritizedComponentModel() {
+ }
+
+ public static int parsePriority(ComponentModel component) {
+ String priority = component.getConfig().getFirst("priority");
+ if (priority == null) return 0;
+ return Integer.valueOf(priority);
+
+ }
+
+ public int getPriority() {
+ return parsePriority(this);
+
+ }
+
+ public void setPriority(int priority) {
+ getConfig().putSingle("priority", Integer.toString(priority));
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java
new file mode 100644
index 0000000..805fb25
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInput.java
@@ -0,0 +1,25 @@
+/*
+ * 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.credential;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialInput {
+ String getType();
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
new file mode 100644
index 0000000..3456f8f
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputUpdater.java
@@ -0,0 +1,32 @@
+/*
+ * 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.credential;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialInputUpdater {
+ boolean supportsCredentialType(String credentialType);
+ boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input);
+ void disableCredentialType(RealmModel realm, UserModel user, String credentialType);
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java b/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java
new file mode 100644
index 0000000..a7a4c6d
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialInputValidator.java
@@ -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.
+ */
+package org.keycloak.credential;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialInputValidator {
+ boolean supportsCredentialType(String credentialType);
+ boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType);
+ boolean isValid(RealmModel realm, UserModel user, CredentialInput input);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
new file mode 100755
index 0000000..24b7772
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialModel.java
@@ -0,0 +1,158 @@
+/*
+ * 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.credential;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Used just in cases when we want to "directly" update or retrieve the hash or salt of user credential (For example during export/import)
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CredentialModel implements Serializable {
+ public static final String PASSWORD = "password";
+ public static final String PASSWORD_HISTORY = "password-history";
+ public static final String PASSWORD_TOKEN = "password-token";
+
+ // Secret is same as password but it is not hashed
+ public static final String SECRET = "secret";
+ public static final String TOTP = "totp";
+ public static final String HOTP = "hotp";
+ public static final String CLIENT_CERT = "cert";
+ public static final String KERBEROS = "kerberos";
+ public static final String OTP = "otp";
+
+
+
+ private String id;
+ private String type;
+ private String value;
+ private String device;
+ private byte[] salt;
+ private int hashIterations;
+ private Long createdDate;
+
+ // otp stuff
+ private int counter;
+ private String algorithm;
+ private int digits;
+ private int period;
+ private MultivaluedHashMap<String, String> config;
+
+
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getDevice() {
+ return device;
+ }
+
+ public void setDevice(String device) {
+ this.device = device;
+ }
+
+ public byte[] getSalt() {
+ return salt;
+ }
+
+ public void setSalt(byte[] salt) {
+ this.salt = salt;
+ }
+
+ public int getHashIterations() {
+ return hashIterations;
+ }
+
+ public void setHashIterations(int iterations) {
+ this.hashIterations = iterations;
+ }
+
+ public Long getCreatedDate() {
+ return createdDate;
+ }
+
+ public void setCreatedDate(Long createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public int getCounter() {
+ return counter;
+ }
+
+ public void setCounter(int counter) {
+ this.counter = counter;
+ }
+
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ public void setAlgorithm(String algorithm) {
+ this.algorithm = algorithm;
+ }
+
+ public int getDigits() {
+ return digits;
+ }
+
+ public void setDigits(int digits) {
+ this.digits = digits;
+ }
+
+ public int getPeriod() {
+ return period;
+ }
+
+ public void setPeriod(int period) {
+ this.period = period;
+ }
+
+ public MultivaluedHashMap<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(MultivaluedHashMap<String, String> config) {
+ this.config = config;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java b/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java
new file mode 100644
index 0000000..a830433
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialProvider.java
@@ -0,0 +1,31 @@
+/*
+ * 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.credential;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialProvider extends Provider {
+ @Override
+ default
+ void close() {
+
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java b/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java
new file mode 100755
index 0000000..480dd1c
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/CredentialProviderFactory.java
@@ -0,0 +1,85 @@
+/*
+ * 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.credential;
+
+import org.keycloak.Config;
+import org.keycloak.component.ComponentFactory;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.ComponentValidationException;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.storage.UserStorageProvider;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CredentialProviderFactory<T extends CredentialProvider> extends ComponentFactory<T, CredentialProvider> {
+ /**
+ * called per Keycloak transaction.
+ *
+ * @param session
+ * @param model
+ * @return
+ */
+ T create(KeycloakSession session, ComponentModel model);
+
+ /**
+ * This is the name of the provider and will be showed in the admin console as an option.
+ *
+ * @return
+ */
+ @Override
+ String getId();
+
+ @Override
+ default void init(Config.Scope config) {
+
+ }
+
+ @Override
+ default void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ default void close() {
+
+ }
+
+ @Override
+ default String getHelpText() {
+ return "";
+ }
+
+ @Override
+ default List<ProviderConfigProperty> getConfigProperties() {
+ return Collections.EMPTY_LIST;
+ }
+
+ @Override
+ default void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException {
+
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.java
new file mode 100644
index 0000000..c998739
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/credential/UserCredentialStore.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.credential;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.provider.Provider;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialStore extends Provider {
+ void updateCredential(RealmModel realm, UserModel user, CredentialModel cred);
+ CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred);
+ boolean removeStoredCredential(RealmModel realm, UserModel user, String id);
+ CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id);
+ List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user);
+ List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type);
+ CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
+}
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 c2e6dab..a59508b 100755
--- a/server-spi/src/main/java/org/keycloak/migration/MigrationModel.java
+++ b/server-spi/src/main/java/org/keycloak/migration/MigrationModel.java
@@ -23,11 +23,6 @@ package org.keycloak.migration;
* @version $Revision: 1 $
*/
public interface MigrationModel {
- /**
- * Must have the form of major.minor.micro as the version is parsed and numbers are compared
- */
- String LATEST_VERSION = "2.1.0";
-
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 6a2f448..e2b55e1 100755
--- a/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/server-spi/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -18,6 +18,7 @@
package org.keycloak.migration;
import org.jboss.logging.Logger;
+import org.keycloak.migration.migrators.MigrateTo1_2_0;
import org.keycloak.migration.migrators.MigrateTo1_3_0;
import org.keycloak.migration.migrators.MigrateTo1_4_0;
import org.keycloak.migration.migrators.MigrateTo1_5_0;
@@ -28,7 +29,8 @@ import org.keycloak.migration.migrators.MigrateTo1_9_0;
import org.keycloak.migration.migrators.MigrateTo1_9_2;
import org.keycloak.migration.migrators.MigrateTo2_0_0;
import org.keycloak.migration.migrators.MigrateTo2_1_0;
-import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1;
+import org.keycloak.migration.migrators.MigrateTo2_2_0;
+import org.keycloak.migration.migrators.Migration;
import org.keycloak.models.KeycloakSession;
/**
@@ -38,82 +40,41 @@ import org.keycloak.models.KeycloakSession;
public class MigrationModelManager {
private static Logger logger = Logger.getLogger(MigrationModelManager.class);
+ private static final Migration[] migrations = {
+ new MigrateTo1_2_0(),
+ new MigrateTo1_3_0(),
+ new MigrateTo1_4_0(),
+ new MigrateTo1_5_0(),
+ new MigrateTo1_6_0(),
+ new MigrateTo1_7_0(),
+ new MigrateTo1_8_0(),
+ new MigrateTo1_9_0(),
+ new MigrateTo1_9_2(),
+ new MigrateTo2_0_0(),
+ new MigrateTo2_1_0(),
+ new MigrateTo2_2_0(),
+ };
+
public static void migrate(KeycloakSession session) {
+ ModelVersion latest = migrations[migrations.length-1].getVersion();
MigrationModel model = session.realms().getMigrationModel();
- String storedVersion = model.getStoredVersion();
- if (MigrationModel.LATEST_VERSION.equals(storedVersion)) return;
ModelVersion stored = null;
- if (storedVersion != null) {
- stored = new ModelVersion(storedVersion);
- }
-
- if (stored == null || stored.lessThan(MigrationTo1_2_0_CR1.VERSION)) {
- if (stored != null) {
- logger.debug("Migrating older model to 1.2.0.CR1 updates");
- }
- new MigrationTo1_2_0_CR1().migrate(session);
- }
- if (stored == null || stored.lessThan(MigrateTo1_3_0.VERSION)) {
- if (stored != null) {
- logger.debug("Migrating older model to 1.3.0 updates");
- }
- new MigrateTo1_3_0().migrate(session);
- }
- if (stored == null || stored.lessThan(MigrateTo1_4_0.VERSION)) {
- if (stored != null) {
- logger.debug("Migrating older model to 1.4.0 updates");
- }
- new MigrateTo1_4_0().migrate(session);
- }
- if (stored == null || stored.lessThan(MigrateTo1_5_0.VERSION)) {
- if (stored != null) {
- logger.debug("Migrating older model to 1.5.0 updates");
- }
- new MigrateTo1_5_0().migrate(session);
- }
- if (stored == null || stored.lessThan(MigrateTo1_6_0.VERSION)) {
- if (stored != null) {
- logger.debug("Migrating older model to 1.6.0 updates");
+ if (model.getStoredVersion() != null) {
+ stored = new ModelVersion(model.getStoredVersion());
+ if (latest.equals(stored)) {
+ return;
}
- new MigrateTo1_6_0().migrate(session);
}
- if (stored == null || stored.lessThan(MigrateTo1_7_0.VERSION)) {
- if (stored != null) {
- logger.debug("Migrating older model to 1.7.0 updates");
- }
- new MigrateTo1_7_0().migrate(session);
- }
- if (stored == null || stored.lessThan(MigrateTo1_8_0.VERSION)) {
- if (stored != null) {
- logger.debug("Migrating older model to 1.8.0 updates");
- }
- new MigrateTo1_8_0().migrate(session);
- }
- if (stored == null || stored.lessThan(MigrateTo1_9_0.VERSION)) {
- if (stored != null) {
- logger.debug("Migrating older model to 1.9.0 updates");
- }
- 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);
- }
- if (stored == null || stored.lessThan(MigrateTo2_0_0.VERSION)) {
- if (stored != null) {
- logger.debug("Migrating older model to 2.0.0 updates");
- }
- new MigrateTo2_0_0().migrate(session);
- }
- if (stored == null || stored.lessThan(MigrateTo2_1_0.VERSION)) {
- if (stored != null) {
- logger.debug("Migrating older model to 2.1.0 updates");
+
+ for (Migration m : migrations) {
+ if (stored == null || stored.lessThan(m.getVersion())) {
+ if (stored != null) {
+ logger.debugf("Migrating older model to %s", m.getVersion());
+ }
+ m.migrate(session);
}
- new MigrateTo2_1_0().migrate(session);
}
- model.setStoredVersion(MigrationModel.LATEST_VERSION);
+ model.setStoredVersion(latest.toString());
}
}
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
index 8d573c7..ee33714 100755
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java
@@ -37,9 +37,13 @@ import javax.naming.directory.SearchControls;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class MigrateTo1_3_0 {
+public class MigrateTo1_3_0 implements Migration {
+
public static final ModelVersion VERSION = new ModelVersion("1.3.0");
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java
index 5fbab26..9f28f91 100755
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java
@@ -34,9 +34,13 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class MigrateTo1_4_0 {
+public class MigrateTo1_4_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.4.0");
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
+
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
index 1a7f83e..6969d5c 100755
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_5_0.java
@@ -32,9 +32,13 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class MigrateTo1_5_0 {
+public class MigrateTo1_5_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.5.0");
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
+
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
index 17ddf1b..c3d9222 100644
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
@@ -27,10 +27,14 @@ import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class MigrateTo1_6_0 {
+public class MigrateTo1_6_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.6.0");
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
+
public void migrate(KeycloakSession session) {
MigrationProvider provider = session.getProvider(MigrationProvider.class);
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
index 47abe62..3d4a5d5 100644
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
@@ -31,10 +31,14 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class MigrateTo1_7_0 {
+public class MigrateTo1_7_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.7.0");
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
+
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java
index 94e0243..549cde9 100644
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_8_0.java
@@ -30,10 +30,14 @@ import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class MigrateTo1_8_0 {
+public class MigrateTo1_8_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.8.0");
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
+
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java
index fb24598..e91f126 100644
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo1_9_0.java
@@ -33,10 +33,14 @@ import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class MigrateTo1_9_0 {
+public class MigrateTo1_9_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.9.0");
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
+
public void migrate(KeycloakSession session) {
RealmModel realm = session.realms().getRealm(Config.getAdminRealm());
if (realm != null && realm.getDisplayNameHtml() != null && realm.getDisplayNameHtml().equals("<strong>Keycloak</strong>")) {
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
index 2473937..3a41058 100644
--- 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
@@ -25,10 +25,14 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-public class MigrateTo1_9_2 {
+public class MigrateTo1_9_2 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("1.9.2");
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
+
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
if (realm.getBrowserSecurityHeaders() != null) {
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java
index 23368f3..d36a858 100644
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_0_0.java
@@ -23,10 +23,14 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
-public class MigrateTo2_0_0 {
+public class MigrateTo2_0_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("2.0.0");
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
+
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
migrateAuthorizationServices(realm);
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
index 9e7b931..995dafb 100644
--- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_1_0.java
@@ -38,9 +38,13 @@ import java.util.stream.Collectors;
*
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/
-public class MigrateTo2_1_0 {
+public class MigrateTo2_1_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("2.1.0");
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
+
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
migrateDefaultRequiredAction(realm);
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_2_0.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_2_0.java
new file mode 100644
index 0000000..1dbcba4
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrateTo2_2_0.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.migration.migrators;
+
+import org.jboss.logging.Logger;
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticatorConfigModel;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MigrateTo2_2_0 implements Migration {
+ public static final ModelVersion VERSION = new ModelVersion("2.2.0");
+
+ private static final Logger LOG = Logger.getLogger(MigrateTo2_2_0.class);
+
+ public ModelVersion getVersion() {
+ return VERSION;
+ }
+
+ public void migrate(KeycloakSession session) {
+ for (RealmModel realm : session.realms().getRealms()) {
+ addIdentityProviderAuthenticator(realm);
+ }
+ }
+
+ private void addIdentityProviderAuthenticator(RealmModel realm) {
+ String defaultProvider = null;
+ for (IdentityProviderModel provider : realm.getIdentityProviders()) {
+ if (provider.isEnabled() && provider.isAuthenticateByDefault()) {
+ defaultProvider = provider.getAlias();
+ break;
+ }
+ }
+
+ DefaultAuthenticationFlows.addIdentityProviderAuthenticator(realm, defaultProvider);
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/Migration.java b/server-spi/src/main/java/org/keycloak/migration/migrators/Migration.java
new file mode 100644
index 0000000..e37dad2
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/migration/migrators/Migration.java
@@ -0,0 +1,32 @@
+/*
+ * 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.KeycloakSession;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Migration {
+
+ void migrate(KeycloakSession session);
+
+ ModelVersion getVersion();
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/migration/ModelVersion.java b/server-spi/src/main/java/org/keycloak/migration/ModelVersion.java
index 383edd5..8845879 100755
--- a/server-spi/src/main/java/org/keycloak/migration/ModelVersion.java
+++ b/server-spi/src/main/java/org/keycloak/migration/ModelVersion.java
@@ -98,4 +98,19 @@ public class ModelVersion {
if (comp < 0) return true;
return false;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ModelVersion)) {
+ return false;
+ }
+
+ ModelVersion v = (ModelVersion) obj;
+ return v.getMajor() == major && v.getMinor() == minor && v.getMicro() != micro;
+ }
+
+ @Override
+ public String toString() {
+ return major + "." + minor + "." + micro;
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
new file mode 100644
index 0000000..5d9c7cd
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CachedUserModel.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import org.keycloak.models.UserModel;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface CachedUserModel extends UserModel {
+ void invalidate();
+
+ /**
+ * When was the user loaded from database.
+ *
+ * @return
+ */
+ long getCacheTimestamp();
+
+ /**
+ * Returns a map that contains custom things that are cached along with the user. You can write to this map.
+ *
+ * @return
+ */
+ ConcurrentHashMap getCachedWith();
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
index c6723fb..a0efa39 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/CacheUserProviderSpi.java
@@ -39,11 +39,11 @@ public class CacheUserProviderSpi implements Spi {
@Override
public Class<? extends Provider> getProviderClass() {
- return CacheUserProvider.class;
+ return UserCache.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
- return CacheUserProviderFactory.class;
+ return UserCacheProviderFactory.class;
}
}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
new file mode 100755
index 0000000..f309079
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/cache/UserCache.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserProvider;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCache extends UserProvider {
+ /**
+ * Evict user from cache.
+ *
+ * @param user
+ */
+ void evict(RealmModel realm, UserModel user);
+ void clear();
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/Constants.java b/server-spi/src/main/java/org/keycloak/models/Constants.java
index 916565a..edc9567 100755
--- a/server-spi/src/main/java/org/keycloak/models/Constants.java
+++ b/server-spi/src/main/java/org/keycloak/models/Constants.java
@@ -50,5 +50,8 @@ public interface Constants {
String KEY = "key";
// Prefix for user attributes used in various "context"data maps
- public static final String USER_ATTRIBUTES_PREFIX = "user.attributes.";
+ String USER_ATTRIBUTES_PREFIX = "user.attributes.";
+
+ // Indication to admin-rest-endpoint that realm keys should be re-generated
+ String GENERATE = "GENERATE";
}
diff --git a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java
index c5a6ecf..cccdacd 100755
--- a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -94,6 +94,8 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private Map<String, String> smtpConfig = new HashMap<String, String>();
private Map<String, String> socialConfig = new HashMap<String, String>();
+ private Map<String, String> attributes = new HashMap<>();
+
private boolean eventsEnabled;
private long eventsExpiration;
private List<String> eventsListeners = new ArrayList<String>();
@@ -692,6 +694,13 @@ public class RealmEntity extends AbstractIdentifiableEntity {
public void setComponentEntities(List<ComponentEntity> componentEntities) {
this.componentEntities = componentEntities;
}
-}
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(Map<String, String> attributes) {
+ this.attributes = attributes;
+ }
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java b/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java
index 82e373f..2425c7d 100755
--- a/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/IdentityProviderModel.java
@@ -120,10 +120,12 @@ public class IdentityProviderModel implements Serializable {
this.storeToken = storeToken;
}
+ @Deprecated
public boolean isAuthenticateByDefault() {
return authenticateByDefault;
}
+ @Deprecated
public void setAuthenticateByDefault(boolean authenticateByDefault) {
this.authenticateByDefault = authenticateByDefault;
}
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
index 8b6dcd3..2a1ad0f 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -17,6 +17,7 @@
package org.keycloak.models;
+import org.keycloak.models.cache.UserCache;
import org.keycloak.provider.Provider;
import org.keycloak.scripting.ScriptingProvider;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
@@ -74,6 +75,7 @@ public interface KeycloakSession {
Object removeAttribute(String attribute);
void setAttribute(String name, Object value);
+
void enlistForClose(Provider provider);
KeycloakSessionFactory getKeycloakSessionFactory();
@@ -101,7 +103,14 @@ public interface KeycloakSession {
void close();
/**
- * A cached view of all users in system.
+ * The user cache
+ *
+ * @return may be null if cache is disabled
+ */
+ UserCache getUserCache();
+
+ /**
+ * A possibly cached view of all users in system.
*
* @return
*/
@@ -115,8 +124,10 @@ public interface KeycloakSession {
*/
UserProvider userStorageManager();
+ UserCredentialManager userCredentialManager();
+
/**
- * A cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI.
+ * A possibly cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI.
*/
UserProvider userStorage();
@@ -129,6 +140,7 @@ public interface KeycloakSession {
/**
* Hybrid storage for UserStorageProviders that can't store a specific piece of keycloak data in their external storage.
+ * No cache in front.
*
* @return
*/
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
index 0e2dcbe..a18d8cf 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
@@ -23,6 +23,21 @@ package org.keycloak.models;
*/
public interface KeycloakTransactionManager extends KeycloakTransaction {
+ enum JTAPolicy {
+ /**
+ * Do not interact with JTA at all
+ *
+ */
+ NOT_SUPPORTED,
+ /**
+ * A new JTA Transaction will be created when Keycloak TM begin() is called. If an existing JTA transaction
+ * exists, it is suspended and resumed after the Keycloak transaction finishes.
+ */
+ REQUIRES_NEW,
+ }
+
+ JTAPolicy getJTAPolicy();
+ void setJTAPolicy(JTAPolicy policy);
void enlist(KeycloakTransaction transaction);
void enlistAfterCompletion(KeycloakTransaction transaction);
diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
index 61dc9c1..7ff7813 100755
--- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java
@@ -110,6 +110,17 @@ public interface RealmModel extends RoleContainerModel {
void setEditUsernameAllowed(boolean editUsernameAllowed);
+ void setAttribute(String name, String value);
+ void setAttribute(String name, Boolean value);
+ void setAttribute(String name, Integer value);
+ void setAttribute(String name, Long value);
+ void removeAttribute(String name);
+ String getAttribute(String name);
+ Integer getAttribute(String name, Integer defaultValue);
+ Long getAttribute(String name, Long defaultValue);
+ Boolean getAttribute(String name, Boolean defaultValue);
+ Map<String, String> getAttributes();
+
//--- brute force settings
boolean isBruteForceProtected();
void setBruteForceProtected(boolean value);
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.java
new file mode 100644
index 0000000..2da5716
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialManager.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.credential.CredentialInput;
+import org.keycloak.credential.UserCredentialStore;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface UserCredentialManager extends UserCredentialStore {
+ boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs);
+
+ void updateCredential(RealmModel realm, UserModel user, CredentialInput input);
+ void disableCredential(RealmModel realm, UserModel user, String credentialType);
+
+ boolean isConfiguredFor(RealmModel realm, UserModel user, String type);
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
index 3e03508..4be355d 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialModel.java
@@ -17,23 +17,26 @@
package org.keycloak.models;
+import org.keycloak.credential.CredentialInput;
+import org.keycloak.credential.CredentialModel;
+
import java.util.UUID;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class UserCredentialModel {
- public static final String PASSWORD = "password";
- public static final String PASSWORD_HISTORY = "password-history";
- public static final String PASSWORD_TOKEN = "password-token";
+public class UserCredentialModel implements CredentialInput {
+ public static final String PASSWORD = CredentialModel.PASSWORD;
+ public static final String PASSWORD_HISTORY = CredentialModel.PASSWORD_HISTORY;
+ public static final String PASSWORD_TOKEN = CredentialModel.PASSWORD_TOKEN;
// Secret is same as password but it is not hashed
- public static final String SECRET = "secret";
- public static final String TOTP = "totp";
- public static final String HOTP = "hotp";
- public static final String CLIENT_CERT = "cert";
- public static final String KERBEROS = "kerberos";
+ public static final String SECRET = CredentialModel.SECRET;
+ public static final String TOTP = CredentialModel.TOTP;
+ public static final String HOTP = CredentialModel.HOTP;
+ public static final String CLIENT_CERT = CredentialModel.CLIENT_CERT;
+ public static final String KERBEROS = CredentialModel.KERBEROS;
protected String type;
protected String value;
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 cb6867a..e103705 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -19,6 +19,7 @@ package org.keycloak.models;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.policy.PasswordPolicyManagerProvider;
import org.keycloak.policy.PolicyError;
diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
index d6ef2cd..642131b 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java
@@ -18,6 +18,7 @@
package org.keycloak.models;
import org.keycloak.component.ComponentModel;
+import org.keycloak.credential.CredentialInput;
import org.keycloak.provider.Provider;
import org.keycloak.storage.user.UserCredentialValidatorProvider;
import org.keycloak.storage.user.UserLookupProvider;
@@ -85,4 +86,5 @@ public interface UserProvider extends Provider,
void close();
void preRemove(RealmModel realm, ComponentModel component);
+
}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/server-spi/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index 7b2c61b..a738d05 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -276,6 +276,7 @@ public class DefaultAuthenticationFlows {
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
+ addIdentityProviderAuthenticator(realm, null);
AuthenticationFlowModel forms = new AuthenticationFlowModel();
forms.setTopLevel(false);
@@ -317,6 +318,45 @@ public class DefaultAuthenticationFlows {
realm.addAuthenticatorExecution(execution);
}
+ public static void addIdentityProviderAuthenticator(RealmModel realm, String defaultProvider) {
+ String browserFlowId = null;
+ for (AuthenticationFlowModel f : realm.getAuthenticationFlows()) {
+ if (f.getAlias().equals(DefaultAuthenticationFlows.BROWSER_FLOW)) {
+ browserFlowId = f.getId();
+ break;
+ }
+ }
+
+ if (browserFlowId != null) {
+ for (AuthenticationExecutionModel e : realm.getAuthenticationExecutions(browserFlowId)) {
+ if ("identity-provider-redirector".equals(e.getAuthenticator())) {
+ return;
+ }
+ }
+
+ AuthenticationExecutionModel execution;
+ execution = new AuthenticationExecutionModel();
+ execution.setParentFlow(browserFlowId);
+ execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+ execution.setAuthenticator("identity-provider-redirector");
+ execution.setPriority(25);
+ execution.setAuthenticatorFlow(false);
+
+ if (defaultProvider != null) {
+ AuthenticatorConfigModel configModel = new AuthenticatorConfigModel();
+
+ Map<String, String> config = new HashMap<>();
+ config.put("defaultProvider", defaultProvider);
+ configModel.setConfig(config);
+ configModel = realm.addAuthenticatorConfig(configModel);
+
+ execution.setAuthenticatorConfig(configModel.getId());
+ }
+
+ realm.addAuthenticatorExecution(execution);
+ }
+ }
+
public static void clientAuthFlow(RealmModel realm) {
AuthenticationFlowModel clients = new AuthenticationFlowModel();
clients.setAlias(CLIENT_AUTHENTICATION_FLOW);
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 bc20d49..b8adc62 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
@@ -44,8 +44,14 @@ import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.common.util.CertificateUtils;
import org.keycloak.common.util.PemUtils;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
import javax.crypto.spec.SecretKeySpec;
+import javax.naming.InitialContext;
+import javax.sql.DataSource;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
import java.io.IOException;
import java.io.StringWriter;
import java.security.Key;
@@ -56,6 +62,7 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
+import java.sql.DriverManager;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -63,6 +70,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import java.util.function.Function;
/**
* Set of helper methods, which are useful in various model implementations.
@@ -303,6 +311,7 @@ public final class KeycloakModelUtils {
}
}
+
public static String getMasterRealmAdminApplicationClientId(String realmName) {
return realmName + "-realm";
}
@@ -651,4 +660,33 @@ public final class KeycloakModelUtils {
}
}
}
+
+ public static void suspendJtaTransaction(KeycloakSessionFactory factory, Runnable runnable) {
+ JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)factory.getProviderFactory(JtaTransactionManagerLookup.class);
+ Transaction suspended = null;
+ try {
+ if (lookup != null) {
+ if (lookup.getTransactionManager() != null) {
+ try {
+ suspended = lookup.getTransactionManager().suspend();
+ } catch (SystemException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ runnable.run();
+ } finally {
+ if (suspended != null) {
+ try {
+ lookup.getTransactionManager().resume(suspended);
+ } catch (InvalidTransactionException e) {
+ throw new RuntimeException(e);
+ } catch (SystemException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ }
+
+ }
}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index e2d4799..2d75c54 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -390,6 +390,10 @@ public class ModelToRepresentation {
exportRequiredActions(realm, rep);
exportGroups(realm, rep);
}
+
+ Map<String, String> attributes = realm.getAttributes();
+ rep.setAttributes(attributes);
+
return rep;
}
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 d1d5b05..8ea1e97 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
@@ -373,6 +373,15 @@ public class RepresentationToModel {
if(rep.getDefaultLocale() != null){
newRealm.setDefaultLocale(rep.getDefaultLocale());
}
+
+ // import attributes
+
+ if (rep.getAttributes() != null) {
+ for (Map.Entry<String, String> attr : rep.getAttributes().entrySet()) {
+ newRealm.setAttribute(attr.getKey(), attr.getValue());
+ }
+ }
+
}
protected static void importComponents(RealmModel newRealm, MultivaluedHashMap<String, ComponentExportRepresentation> components, String parentId) {
@@ -556,6 +565,19 @@ public class RepresentationToModel {
if (newRealm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW) == null) {
DefaultAuthenticationFlows.firstBrokerLoginFlow(newRealm, true);
}
+
+ // Added in 2.2
+ String defaultProvider = null;
+ if (rep.getIdentityProviders() != null) {
+ for (IdentityProviderRepresentation i : rep.getIdentityProviders()) {
+ if (i.isEnabled() && i.isAuthenticateByDefault()) {
+ defaultProvider = i.getProviderId();
+ break;
+ }
+ }
+ }
+
+ DefaultAuthenticationFlows.addIdentityProviderAuthenticator(newRealm, defaultProvider);
}
private static void convertDeprecatedSocialProviders(RealmRepresentation rep) {
@@ -719,6 +741,21 @@ public class RepresentationToModel {
if (rep.getRealm() != null) {
renameRealm(realm, rep.getRealm());
}
+
+ // Import attributes first, so the stuff saved directly on representation (displayName, bruteForce etc) has bigger priority
+ if (rep.getAttributes() != null) {
+ Set<String> attrsToRemove = new HashSet<>(realm.getAttributes().keySet());
+ attrsToRemove.removeAll(rep.getAttributes().keySet());
+
+ for (Map.Entry<String, String> entry : rep.getAttributes().entrySet()) {
+ realm.setAttribute(entry.getKey(), entry.getValue());
+ }
+
+ for (String attr : attrsToRemove) {
+ realm.removeAttribute(attr);
+ }
+ }
+
if (rep.getDisplayName() != null) realm.setDisplayName(rep.getDisplayName());
if (rep.getDisplayNameHtml() != null) realm.setDisplayNameHtml(rep.getDisplayNameHtml());
if (rep.isEnabled() != null) realm.setEnabled(rep.isEnabled());
@@ -783,7 +820,7 @@ public class RepresentationToModel {
realm.setUserFederationProviders(providerModels);
}
- if ("GENERATE".equals(rep.getPublicKey())) {
+ if (Constants.GENERATE.equals(rep.getPublicKey())) {
KeycloakModelUtils.generateRealmKeys(realm);
} else {
if (rep.getPrivateKey() != null && rep.getPublicKey() != null) {
@@ -1004,6 +1041,8 @@ public class RepresentationToModel {
client.updateDefaultRoles(resourceRep.getDefaultRoles());
}
+
+
if (resourceRep.getProtocolMappers() != null) {
// first, remove all default/built in mappers
Set<ProtocolMapperModel> mappers = client.getProtocolMappers();
@@ -1012,6 +1051,9 @@ public class RepresentationToModel {
for (ProtocolMapperRepresentation mapper : resourceRep.getProtocolMappers()) {
client.addProtocolMapper(toModel(mapper));
}
+
+
+
}
if (resourceRep.getClientTemplate() != null) {
diff --git a/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java b/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java
index 1942422..8055fae 100755
--- a/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java
+++ b/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java
@@ -17,6 +17,10 @@
package org.keycloak.protocol;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperContainerModel;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
@@ -30,4 +34,16 @@ public interface ProtocolMapper extends Provider, ProviderFactory<ProtocolMapper
String getDisplayCategory();
String getDisplayType();
+ /**
+ * Called when instance of mapperModel is created/updated for this protocolMapper through admin endpoint
+ *
+ * @param session
+ * @param realm
+ * @param client client or clientTemplate
+ * @param mapperModel
+ * @throws ProtocolMapperConfigException if configuration provided in mapperModel is not valid
+ */
+ default void validateConfig(KeycloakSession session, RealmModel realm, ProtocolMapperContainerModel client, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException {
+ };
+
}
diff --git a/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java b/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java
new file mode 100644
index 0000000..3f1f676
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.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.protocol;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ProtocolMapperConfigException extends Exception {
+
+ private String messageKey;
+ private Object[] parameters;
+
+ public ProtocolMapperConfigException(String message) {
+ super(message);
+ }
+
+ public ProtocolMapperConfigException(String message, String messageKey) {
+ super(message);
+ this.messageKey = messageKey;
+ }
+
+ public ProtocolMapperConfigException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ProtocolMapperConfigException(String message, String messageKey, Throwable cause) {
+ super(message, cause);
+ this.messageKey = messageKey;
+ }
+
+ public ProtocolMapperConfigException(String message, Object ... parameters) {
+ super(message);
+ this.parameters = parameters;
+ }
+
+ public ProtocolMapperConfigException(String messageKey, String message, Object ... parameters) {
+ super(message);
+ this.messageKey = messageKey;
+ this.parameters = parameters;
+ }
+
+ public String getMessageKey() {
+ return messageKey;
+ }
+
+ public Object[] getParameters() {
+ return parameters;
+ }
+
+ public void setParameters(Object[] parameters) {
+ this.parameters = parameters;
+ }
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java b/server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.java
new file mode 100644
index 0000000..b4e993a
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/provider/EnvironmentDependentProviderFactory.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.provider;
+
+/**
+ * Providers that are only supported in some environments can implement this interface to be able to determine if they
+ * should be available or not.
+ *
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface EnvironmentDependentProviderFactory {
+
+ /**
+ * @return <code>true</code> if the provider is supported and should be available, <code>false</code> otherwise
+ */
+ boolean isSupported();
+
+}
diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
index 7239182..2202f62 100644
--- a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
+++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.storage.federated;
+import org.keycloak.credential.CredentialModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
@@ -28,8 +29,20 @@ import java.util.List;
* @version $Revision: 1 $
*/
public interface UserCredentialsFederatedStorage {
+ // deprecated
void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred);
void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred);
List<UserCredentialValueModel> getCredentials(RealmModel realm, UserModel user);
+
+ // new
+ void updateCredential(RealmModel realm, UserModel user, CredentialModel cred);
+ CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred);
+ boolean removeCredential(RealmModel realm, String id);
+ CredentialModel getCredentialById(String id);
+ List<CredentialModel> getCredentials(RealmModel realm);
+ List<CredentialModel> getUserCredentials(RealmModel realm, UserModel user);
+ List<CredentialModel> getCredentialsByType(RealmModel realm, UserModel user, String type);
+ CredentialModel getCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type);
+
}
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
index 351107f..42ad397 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java
@@ -17,14 +17,8 @@
package org.keycloak.storage;
-import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
-import org.keycloak.models.RealmModel;
-
-import java.io.Serializable;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Map;
+import org.keycloak.component.PrioritizedComponentModel;
/**
* Stored configuration of a User Storage provider instance.
@@ -32,14 +26,7 @@ import java.util.Map;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
*/
-public class UserStorageProviderModel extends ComponentModel {
-
- public static Comparator<UserStorageProviderModel> comparator = new Comparator<UserStorageProviderModel>() {
- @Override
- public int compare(UserStorageProviderModel o1, UserStorageProviderModel o2) {
- return o1.getPriority() - o2.getPriority();
- }
- };
+public class UserStorageProviderModel extends PrioritizedComponentModel {
public UserStorageProviderModel() {
setProviderType(UserStorageProvider.class.getName());
@@ -49,14 +36,4 @@ public class UserStorageProviderModel extends ComponentModel {
super(copy);
}
- public int getPriority() {
- String priority = getConfig().getFirst("priority");
- if (priority == null) return 0;
- return Integer.valueOf(priority);
-
- }
-
- public void setPriority(int priority) {
- getConfig().putSingle("priority", Integer.toString(priority));
- }
}
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 d2431d8..5ab0346 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
@@ -61,4 +61,5 @@ org.keycloak.authorization.AuthorizationSpi
org.keycloak.models.cache.authorization.CachedStoreFactorySpi
org.keycloak.protocol.oidc.TokenIntrospectionSpi
org.keycloak.policy.PasswordPolicySpi
-org.keycloak.policy.PasswordPolicyManagerSpi
\ No newline at end of file
+org.keycloak.policy.PasswordPolicyManagerSpi
+org.keycloak.transaction.TransactionManagerLookupSpi
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
index 620e0c9..e56e7e6 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -132,6 +132,9 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
clientSession.setNote(IS_DIFFERENT_BROWSER, "true");
}
+ // User successfully confirmed linking by email verification. His email was defacto verified
+ existingUser.setEmailVerified(true);
+
context.setUser(existingUser);
context.success();
} else {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
new file mode 100644
index 0000000..795d0c2
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticator.java
@@ -0,0 +1,103 @@
+/*
+ * 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.authentication.authenticators.browser;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.constants.AdapterConstants;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.services.Urls;
+import org.keycloak.services.managers.ClientSessionCode;
+
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class IdentityProviderAuthenticator implements Authenticator {
+
+ private static final Logger LOG = Logger.getLogger(IdentityProviderAuthenticator.class);
+
+ @Override
+ public void authenticate(AuthenticationFlowContext context) {
+ if (context.getUriInfo().getQueryParameters().containsKey(AdapterConstants.KC_IDP_HINT)) {
+ String providerId = context.getUriInfo().getQueryParameters().getFirst(AdapterConstants.KC_IDP_HINT);
+ if (providerId == null || providerId.equals("")) {
+ LOG.tracef("Skipping: kc_idp_hint query parameter is empty");
+ context.attempted();
+ } else {
+ LOG.tracef("Redirecting: %s set to %s", AdapterConstants.KC_IDP_HINT, providerId);
+ redirect(context, providerId);
+ }
+ } else if (context.getAuthenticatorConfig() != null && context.getAuthenticatorConfig().getConfig().containsKey(IdentityProviderAuthenticatorFactory.DEFAULT_PROVIDER)) {
+ String defaultProvider = context.getAuthenticatorConfig().getConfig().get(IdentityProviderAuthenticatorFactory.DEFAULT_PROVIDER);
+ LOG.tracef("Redirecting: default provider set to %s", defaultProvider);
+ redirect(context, defaultProvider);
+ } else {
+ LOG.tracef("No default provider set or %s query parameter provided", AdapterConstants.KC_IDP_HINT);
+ context.attempted();
+ }
+ }
+
+ private void redirect(AuthenticationFlowContext context, String providerId) {
+ List<IdentityProviderModel> identityProviders = context.getRealm().getIdentityProviders();
+ for (IdentityProviderModel identityProvider : identityProviders) {
+ if (identityProvider.isEnabled() && providerId.equals(identityProvider.getAlias())) {
+ String accessCode = new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode();
+ Response response = Response.temporaryRedirect(
+ Urls.identityProviderAuthnRequest(context.getUriInfo().getBaseUri(), providerId, context.getRealm().getName(), accessCode))
+ .build();
+
+ LOG.debugf("Redirecting to %s", providerId);
+ context.forceChallenge(response);
+ return;
+ }
+ }
+
+ LOG.warnf("Provider not found or not enabled for realm %s", providerId);
+ context.attempted();
+ }
+
+ @Override
+ public void action(AuthenticationFlowContext context) {
+ }
+
+ @Override
+ public boolean requiresUser() {
+ return false;
+ }
+
+ @Override
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+ return true;
+ }
+
+ @Override
+ public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java
new file mode 100644
index 0000000..635c95e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/IdentityProviderAuthenticatorFactory.java
@@ -0,0 +1,102 @@
+/*
+ * 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.authentication.authenticators.browser;
+
+import org.keycloak.Config;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.keycloak.provider.ProviderConfigProperty.STRING_TYPE;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class IdentityProviderAuthenticatorFactory implements AuthenticatorFactory {
+
+ protected static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+ AuthenticationExecutionModel.Requirement.ALTERNATIVE, AuthenticationExecutionModel.Requirement.DISABLED
+ };
+
+ protected static final String DEFAULT_PROVIDER = "defaultProvider";
+
+ @Override
+ public String getDisplayType() {
+ return "Identity Provider Redirector";
+ }
+
+ @Override
+ public String getReferenceCategory() {
+ return null;
+ }
+
+ @Override
+ public boolean isConfigurable() {
+ return true;
+ }
+
+ @Override
+ public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+ return REQUIREMENT_CHOICES;
+ }
+
+ @Override
+ public boolean isUserSetupAllowed() {
+ return true;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Redirects to default Identity Provider or Identity Provider specified with kc_idp_hint query parameter";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ ProviderConfigProperty rep = new ProviderConfigProperty(DEFAULT_PROVIDER, "Default Identity Provider", "To automatically redirect to an identity provider set to the alias of the identity provider", STRING_TYPE, null);
+ return Collections.singletonList(rep);
+ }
+
+ @Override
+ public Authenticator create(KeycloakSession session) {
+ return new IdentityProviderAuthenticator();
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return "identity-provider-redirector";
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
index 789d381..86dd4e8 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
@@ -40,6 +40,7 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -166,30 +167,13 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
}
protected PublicKey getSignatureValidationKey(ClientModel client, ClientAuthenticationFlowContext context) {
- CertificateRepresentation certInfo = CertificateInfoHelper.getCertificateFromClient(client, ATTR_PREFIX);
-
- String encodedCertificate = certInfo.getCertificate();
- String encodedPublicKey = certInfo.getPublicKey();
-
- if (encodedCertificate == null && encodedPublicKey == null) {
- Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", "Client '" + client.getClientId() + "' doesn't have certificate or publicKey configured");
- context.failure(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, challengeResponse);
- return null;
- }
-
- if (encodedCertificate != null && encodedPublicKey != null) {
- Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", "Client '" + client.getClientId() + "' has both publicKey and certificate configured");
+ try {
+ return CertificateInfoHelper.getSignatureValidationKey(client, ATTR_PREFIX);
+ } catch (ModelException me) {
+ Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "unauthorized_client", me.getMessage());
context.failure(AuthenticationFlowError.CLIENT_CREDENTIALS_SETUP_REQUIRED, challengeResponse);
return null;
}
-
- // TODO: Caching of publicKeys / certificates, so it doesn't need to be always computed from pem. For performance reasons...
- if (encodedCertificate != null) {
- X509Certificate clientCert = KeycloakModelUtils.getCertificate(encodedCertificate);
- return clientCert.getPublicKey();
- } else {
- return KeycloakModelUtils.getPublicKey(encodedPublicKey);
- }
}
@Override
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
index 4af34ad..076f9af 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -47,6 +47,7 @@ import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.Urls;
+import org.keycloak.services.resources.admin.RealmAuth;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
@@ -74,20 +75,23 @@ import static java.util.Arrays.asList;
public class PolicyEvaluationService {
private final AuthorizationProvider authorization;
+ private final RealmAuth auth;
@Context
private HttpRequest httpRequest;
private final ResourceServer resourceServer;
- PolicyEvaluationService(ResourceServer resourceServer, AuthorizationProvider authorization) {
+ PolicyEvaluationService(ResourceServer resourceServer, AuthorizationProvider authorization, RealmAuth auth) {
this.resourceServer = resourceServer;
this.authorization = authorization;
+ this.auth = auth;
}
@POST
@Consumes("application/json")
@Produces("application/json")
public void evaluate(PolicyEvaluationRequest evaluationRequest, @Suspended AsyncResponse asyncResponse) {
+ this.auth.requireView();
KeycloakIdentity identity = createIdentity(evaluationRequest);
EvaluationContext evaluationContext = createEvaluationContext(evaluationRequest, identity);
authorization.evaluators().from(createPermissions(evaluationRequest, evaluationContext, authorization), evaluationContext).evaluate(createDecisionCollector(authorization, identity, asyncResponse));
@@ -167,6 +171,13 @@ public class PolicyEvaluationService {
collect.addAll(storeFactory.getResourceStore().findByScope(scope.getId()).stream().map(resource12 -> new ResourcePermission(resource12, asList(scope), resourceServer)).collect(Collectors.toList()));
}
+ collect.addAll(storeFactory.getResourceStore().findByResourceServer(resourceServer.getId()).stream().map(new Function<Resource, ResourcePermission>() {
+ @Override
+ public ResourcePermission apply(Resource resource) {
+ return new ResourcePermission(resource, resource.getScopes(), resourceServer);
+ }
+ }).collect(Collectors.toList()));
+
return collect.stream();
}
}).collect(Collectors.toList());
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
index 3b9c194..b179378 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java
@@ -273,7 +273,7 @@ public class PolicyService {
@Path("evaluate")
public PolicyEvaluationService getPolicyEvaluateResource() {
this.auth.requireView();
- PolicyEvaluationService resource = new PolicyEvaluationService(this.resourceServer, this.authorization);
+ PolicyEvaluationService resource = new PolicyEvaluationService(this.resourceServer, this.authorization, this.auth);
ResteasyProviderFactory.getInstance().injectProperties(resource);
diff --git a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java
index d320a64..4f0c64a 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/representation/PolicyEvaluationResponse.java
@@ -132,7 +132,10 @@ public class PolicyEvaluationResponse {
scopes.add(scope);
}
if (evaluationResultRepresentation.getStatus().equals(Effect.PERMIT)) {
- result.getAllowedScopes().add(scope);
+ List<ScopeRepresentation> allowedScopes = result.getAllowedScopes();
+ if (!allowedScopes.contains(scope)) {
+ allowedScopes.add(scope);
+ }
}
}
}
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
index d31146f..64ef061 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java
@@ -177,7 +177,7 @@ public class ResourceSetService {
@GET
@NoCache
@Produces("application/json")
- public Response findAll(@QueryParam("name") String name,
+ public Response find(@QueryParam("name") String name,
@QueryParam("uri") String uri,
@QueryParam("owner") String owner,
@QueryParam("type") String type,
diff --git a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java
index 44464b4..f050f32 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java
@@ -107,6 +107,11 @@ public class ScopeService {
}
Scope scope = storeFactory.getScopeStore().findById(id);
+
+ if (scope == null) {
+ return Response.status(Status.NOT_FOUND).build();
+ }
+
PolicyStore policyStore = storeFactory.getPolicyStore();
List<Policy> policies = policyStore.findByScopeIds(Arrays.asList(scope.getId()), resourceServer.getId());
diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
index b02a935..fdaa12f 100644
--- a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
+++ b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java
@@ -109,7 +109,7 @@ public class ResourceService {
}
private Set<String> findAll() {
- Response response = this.resourceManager.findAll(null, null, null, null, null, -1, -1);
+ Response response = this.resourceManager.find(null, null, null, null, null, -1, -1);
List<ResourceRepresentation> resources = (List<ResourceRepresentation>) response.getEntity();
return resources.stream().map(ResourceRepresentation::getId).collect(Collectors.toSet());
}
diff --git a/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java
index d3e8eba..56f24a2 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProviderFactory.java
@@ -18,6 +18,7 @@ package org.keycloak.broker.oidc;
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
import java.io.InputStream;
import java.util.Map;
@@ -45,8 +46,8 @@ public class KeycloakOIDCIdentityProviderFactory extends AbstractIdentityProvide
}
@Override
- public Map<String, String> parseConfig(InputStream inputStream) {
- return OIDCIdentityProviderFactory.parseOIDCConfig(inputStream);
+ public Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream) {
+ return OIDCIdentityProviderFactory.parseOIDCConfig(session, inputStream);
}
diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
index 82c2cdd..a0e5017 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
@@ -19,6 +19,7 @@ package org.keycloak.broker.oidc;
import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
@@ -56,11 +57,11 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory
}
@Override
- public Map<String, String> parseConfig(InputStream inputStream) {
- return parseOIDCConfig(inputStream);
+ public Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream) {
+ return parseOIDCConfig(session, inputStream);
}
- protected static Map<String, String> parseOIDCConfig(InputStream inputStream) {
+ protected static Map<String, String> parseOIDCConfig(KeycloakSession session, InputStream inputStream) {
OIDCConfigurationRepresentation rep;
try {
rep = JsonSerialization.readValue(inputStream, OIDCConfigurationRepresentation.class);
@@ -74,14 +75,14 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory
config.setTokenUrl(rep.getTokenEndpoint());
config.setUserInfoUrl(rep.getUserinfoEndpoint());
if (rep.getJwksUri() != null) {
- sendJwksRequest(rep, config);
+ sendJwksRequest(session, rep, config);
}
return config.getConfig();
}
- protected static void sendJwksRequest(OIDCConfigurationRepresentation rep, OIDCIdentityProviderConfig config) {
+ protected static void sendJwksRequest(KeycloakSession session, OIDCConfigurationRepresentation rep, OIDCIdentityProviderConfig config) {
try {
- JSONWebKeySet keySet = JWKSUtils.sendJwksRequest(rep.getJwksUri());
+ JSONWebKeySet keySet = JWKSUtils.sendJwksRequest(session, rep.getJwksUri());
PublicKey key = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG);
if (key == null) {
logger.supportedJwkNotFound(JWK.Use.SIG.asString());
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
index dac6750..2617995 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
@@ -24,6 +24,7 @@ import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.util.DocumentUtil;
@@ -54,7 +55,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
}
@Override
- public Map<String, String> parseConfig(InputStream inputStream) {
+ public Map<String, String> parseConfig(KeycloakSession session, InputStream inputStream) {
try {
Object parsedObject = new SAMLParser().parse(inputStream);
EntityDescriptorType entityType;
diff --git a/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java b/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java
new file mode 100644
index 0000000..6d9baf0
--- /dev/null
+++ b/services/src/main/java/org/keycloak/credential/LocalOTPCredentialManager.java
@@ -0,0 +1,224 @@
+/*
+ * 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.credential;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.OTPPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
+import org.keycloak.models.utils.HmacOTP;
+import org.keycloak.models.utils.TimeBasedOTP;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LocalOTPCredentialManager implements CredentialInputValidator, CredentialInputUpdater, OnUserCache {
+ private static final Logger logger = Logger.getLogger(LocalOTPCredentialManager.class);
+
+ protected KeycloakSession session;
+
+ protected List<CredentialModel> getCachedCredentials(UserModel user, String type) {
+ if (!(user instanceof CachedUserModel)) return Collections.EMPTY_LIST;
+ CachedUserModel cached = (CachedUserModel)user;
+ List<CredentialModel> rtn = (List<CredentialModel>)cached.getCachedWith().get(LocalOTPCredentialManager.class.getName() + "." + type);
+ if (rtn == null) return Collections.EMPTY_LIST;
+ return rtn;
+ }
+
+ protected UserCredentialStore getCredentialStore() {
+ return session.userCredentialManager();
+ }
+
+ @Override
+ public void onCache(RealmModel realm, CachedUserModel user) {
+ List<CredentialModel> creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
+ user.getCachedWith().put(LocalOTPCredentialManager.class.getName() + "." + CredentialModel.TOTP, creds);
+
+ }
+
+ public LocalOTPCredentialManager(KeycloakSession session) {
+ this.session = session;
+ }
+
+ @Override
+ public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+ if (!supportsCredentialType(input.getType())) return false;
+
+ if (!(input instanceof UserCredentialModel)) {
+ logger.debug("Expected instance of UserCredentialModel for CredentialInput");
+ return false;
+ }
+ UserCredentialModel inputModel = (UserCredentialModel)input;
+ CredentialModel model = null;
+ if (inputModel.getDevice() != null) {
+ model = getCredentialStore().getStoredCredentialByNameAndType(realm, user, inputModel.getDevice(), CredentialModel.TOTP);
+ if (model == null) {
+ model = getCredentialStore().getStoredCredentialByNameAndType(realm, user, inputModel.getDevice(), CredentialModel.HOTP);
+ }
+ }
+ if (model == null) {
+ // delete all existing
+ disableCredentialType(realm, user, CredentialModel.OTP);
+ model = new CredentialModel();
+ }
+
+ OTPPolicy policy = realm.getOTPPolicy();
+ model.setDigits(policy.getDigits());
+ model.setCounter(policy.getInitialCounter());
+ model.setAlgorithm(policy.getAlgorithm());
+ model.setType(policy.getType());
+ model.setValue(inputModel.getValue());
+ model.setDevice(inputModel.getDevice());
+ model.setPeriod(policy.getPeriod());
+ if (model.getId() == null) {
+ getCredentialStore().createCredential(realm, user, model);
+ } else {
+ getCredentialStore().updateCredential(realm, user, model);
+ }
+ session.getUserCache().evict(realm, user);
+ return true;
+
+
+
+ }
+
+ @Override
+ public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) {
+ boolean disableTOTP = false, disableHOTP = false;
+ if (CredentialModel.OTP.equals(credentialType)) {
+ disableTOTP = true;
+ disableHOTP = true;
+ } else if (CredentialModel.HOTP.equals(credentialType)) {
+ disableHOTP = true;
+
+ } else if (CredentialModel.TOTP.equals(credentialType)) {
+ disableTOTP = true;
+ }
+ if (disableHOTP) {
+ List<CredentialModel> hotp = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP);
+ for (CredentialModel cred : hotp) {
+ getCredentialStore().removeStoredCredential(realm, user, cred.getId());
+ }
+
+ }
+ if (disableTOTP) {
+ List<CredentialModel> totp = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
+ if (!totp.isEmpty()) {
+ for (CredentialModel cred : totp) {
+ getCredentialStore().removeStoredCredential(realm, user, cred.getId());
+ }
+ }
+
+ }
+ if (disableTOTP || disableHOTP) {
+ session.getUserCache().evict(realm, user);
+ }
+ }
+
+ @Override
+ public boolean supportsCredentialType(String credentialType) {
+ return CredentialModel.OTP.equals(credentialType)
+ || CredentialModel.HOTP.equals(credentialType)
+ || CredentialModel.TOTP.equals(credentialType);
+ }
+
+ @Override
+ public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
+ if (!supportsCredentialType(credentialType)) return false;
+ if (CredentialModel.OTP.equals(credentialType)) {
+ if (realm.getOTPPolicy().getType().equals(CredentialModel.HOTP)) {
+ return configuredForHOTP(realm, user);
+ } else {
+ return configuredForTOTP(realm, user);
+ }
+ } else if (CredentialModel.HOTP.equals(credentialType)) {
+ return configuredForHOTP(realm, user);
+
+ } else if (CredentialModel.TOTP.equals(credentialType)) {
+ return configuredForTOTP(realm, user);
+ } else {
+ return false;
+ }
+
+ }
+
+ protected boolean configuredForHOTP(RealmModel realm, UserModel user) {
+ return !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP).isEmpty();
+ }
+
+ protected boolean configuredForTOTP(RealmModel realm, UserModel user) {
+ return !getCachedCredentials(user, CredentialModel.TOTP).isEmpty()
+ || !getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP).isEmpty();
+ }
+
+ public static boolean validOTP(RealmModel realm, String token, String secret) {
+ OTPPolicy policy = realm.getOTPPolicy();
+ if (policy.getType().equals(UserCredentialModel.TOTP)) {
+ TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
+ return validator.validateTOTP(token, secret.getBytes());
+ } else {
+ HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+ int c = validator.validateHOTP(token, secret, policy.getInitialCounter());
+ return c > -1;
+ }
+
+ }
+
+ @Override
+ public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
+ if (! (input instanceof UserCredentialModel)) {
+ logger.debug("Expected instance of UserCredentialModel for CredentialInput");
+ return false;
+
+ }
+ String token = ((UserCredentialModel)input).getValue();
+ OTPPolicy policy = realm.getOTPPolicy();
+ if (realm.getOTPPolicy().getType().equals(CredentialModel.HOTP)) {
+ HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow());
+ for (CredentialModel cred : getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.HOTP)) {
+ int counter = validator.validateHOTP(token, cred.getValue(), cred.getCounter());
+ if (counter < 0) continue;
+ cred.setCounter(counter);
+ getCredentialStore().updateCredential(realm, user, cred);
+ return true;
+ }
+ } else {
+ TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow());
+ List<CredentialModel> creds = getCachedCredentials(user, CredentialModel.TOTP);
+ if (creds.isEmpty()) {
+ creds = getCredentialStore().getStoredCredentialsByType(realm, user, CredentialModel.TOTP);
+ } else {
+ logger.debugv("Cache hit for TOTP for user {0}", user.getUsername());
+ }
+ for (CredentialModel cred : creds) {
+ if (validator.validateTOTP(token, cred.getValue().getBytes())) {
+ return true;
+ }
+ }
+
+ }
+ return false;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
new file mode 100644
index 0000000..5f67768
--- /dev/null
+++ b/services/src/main/java/org/keycloak/credential/UserCredentialStoreManager.java
@@ -0,0 +1,249 @@
+/*
+ * 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.credential;
+
+import org.keycloak.common.util.reflections.Types;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.component.PrioritizedComponentModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialManager;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.CachedUserModel;
+import org.keycloak.models.cache.OnUserCache;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.UserStorageManager;
+import org.keycloak.storage.UserStorageProvider;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserCredentialStoreManager implements UserCredentialManager, OnUserCache {
+ protected KeycloakSession session;
+
+ public UserCredentialStoreManager(KeycloakSession session) {
+ this.session = session;
+ }
+
+ protected UserCredentialStore getStoreForUser(UserModel user) {
+ if (StorageId.isLocalStorage(user)) {
+ return (UserCredentialStore)session.userLocalStorage();
+ } else {
+ return (UserCredentialStore)session.userFederatedStorage();
+ }
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ getStoreForUser(user).updateCredential(realm, user, cred);
+
+ }
+
+ @Override
+ public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
+ return getStoreForUser(user).createCredential(realm, user, cred);
+ }
+
+ @Override
+ public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
+ return getStoreForUser(user).removeStoredCredential(realm, user, id);
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
+ return getStoreForUser(user).getStoredCredentialById(realm, user, id);
+ }
+
+ @Override
+ public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
+ return getStoreForUser(user).getStoredCredentials(realm, user);
+ }
+
+ @Override
+ public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
+ return getStoreForUser(user).getStoredCredentialsByType(realm, user, type);
+ }
+
+ @Override
+ public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
+ return getStoreForUser(user).getStoredCredentialByNameAndType(realm, user, name, type);
+ }
+
+
+ @Override
+ public boolean isValid(RealmModel realm, UserModel user, List<CredentialInput> inputs) {
+
+ List<CredentialInput> toValidate = new LinkedList<>();
+ toValidate.addAll(inputs);
+ if (!StorageId.isLocalStorage(user)) {
+ String providerId = StorageId.resolveProviderId(user);
+ UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+ if (provider instanceof CredentialInputValidator) {
+ Iterator<CredentialInput> it = toValidate.iterator();
+ while (it.hasNext()) {
+ CredentialInput input = it.next();
+ CredentialInputValidator validator = (CredentialInputValidator)provider;
+ if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
+ it.remove();
+ }
+ }
+ }
+ }
+
+ if (toValidate.isEmpty()) return true;
+
+ List<ComponentModel> components = getCredentialProviderComponents(realm);
+ for (ComponentModel component : components) {
+ CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+ if (!Types.supports(CredentialInputValidator.class, factory, CredentialProviderFactory.class)) continue;
+ Iterator<CredentialInput> it = toValidate.iterator();
+ while (it.hasNext()) {
+ CredentialInput input = it.next();
+ CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
+ if (validator == null) {
+ validator = (CredentialInputValidator)factory.create(session, component);
+ session.setAttribute(component.getId(), validator);
+ }
+ if (validator.supportsCredentialType(input.getType()) && validator.isValid(realm, user, input)) {
+ it.remove();
+ }
+ }
+ }
+
+ return toValidate.isEmpty();
+ }
+
+ protected List<ComponentModel> getCredentialProviderComponents(RealmModel realm) {
+ List<ComponentModel> components = realm.getComponents(realm.getId(), CredentialProvider.class.getName());
+ if (components.isEmpty()) return components;
+ List<ComponentModel> copy = new LinkedList<>();
+ copy.addAll(components);
+ Collections.sort(copy, PrioritizedComponentModel.comparator);
+ return copy;
+ }
+
+ @Override
+ public void updateCredential(RealmModel realm, UserModel user, CredentialInput input) {
+ if (!StorageId.isLocalStorage(user)) {
+ String providerId = StorageId.resolveProviderId(user);
+ UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+ if (provider instanceof CredentialInputUpdater) {
+ CredentialInputUpdater updater = (CredentialInputUpdater)provider;
+ if (updater.supportsCredentialType(input.getType())) {
+ if (updater.updateCredential(realm, user, input)) return;
+ }
+ }
+ }
+
+ List<ComponentModel> components = getCredentialProviderComponents(realm);
+ for (ComponentModel component : components) {
+ CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+ if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+ CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
+ if (updater == null) {
+ updater = (CredentialInputUpdater)factory.create(session, component);
+ session.setAttribute(component.getId(), updater);
+ }
+ if (!updater.supportsCredentialType(input.getType())) continue;
+ if (updater.updateCredential(realm, user, input)) return;
+ }
+
+ }
+ @Override
+ public void disableCredential(RealmModel realm, UserModel user, String credentialType) {
+ if (!StorageId.isLocalStorage(user)) {
+ String providerId = StorageId.resolveProviderId(user);
+ UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+ if (provider instanceof CredentialInputUpdater) {
+ CredentialInputUpdater updater = (CredentialInputUpdater)provider;
+ if (updater.supportsCredentialType(credentialType)) {
+ updater.disableCredentialType(realm, user, credentialType);
+ }
+ }
+ }
+
+ List<ComponentModel> components = getCredentialProviderComponents(realm);
+ for (ComponentModel component : components) {
+ CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+ if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+ CredentialInputUpdater updater = (CredentialInputUpdater)session.getAttribute(component.getId());
+ if (updater == null) {
+ updater = (CredentialInputUpdater)factory.create(session, component);
+ session.setAttribute(component.getId(), updater);
+ }
+ if (!updater.supportsCredentialType(credentialType)) continue;
+ updater.disableCredentialType(realm, user, credentialType);
+ }
+
+ }
+ @Override
+ public boolean isConfiguredFor(RealmModel realm, UserModel user, String type) {
+ if (!StorageId.isLocalStorage(user)) {
+ String providerId = StorageId.resolveProviderId(user);
+ UserStorageProvider provider = UserStorageManager.getStorageProvider(session, realm, providerId);
+ if (provider instanceof CredentialInputValidator) {
+ CredentialInputValidator validator = (CredentialInputValidator)provider;
+ if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
+ return true;
+ }
+ }
+ }
+
+ List<ComponentModel> components = getCredentialProviderComponents(realm);
+ for (ComponentModel component : components) {
+ CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+ if (!Types.supports(CredentialInputUpdater.class, factory, CredentialProviderFactory.class)) continue;
+ CredentialInputValidator validator = (CredentialInputValidator)session.getAttribute(component.getId());
+ if (validator == null) {
+ validator = (CredentialInputValidator)factory.create(session, component);
+ session.setAttribute(component.getId(), validator);
+ }
+ if (validator.supportsCredentialType(type) && validator.isConfiguredFor(realm, user, type)) {
+ return true;
+ }
+ }
+ return false;
+
+ }
+
+ @Override
+ public void onCache(RealmModel realm, CachedUserModel user) {
+ List<ComponentModel> components = getCredentialProviderComponents(realm);
+ for (ComponentModel component : components) {
+ CredentialProviderFactory factory = (CredentialProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(CredentialProvider.class, component.getProviderId());
+ if (!Types.supports(OnUserCache.class, factory, CredentialProviderFactory.class)) continue;
+ OnUserCache validator = (OnUserCache)session.getAttribute(component.getId());
+ if (validator == null) {
+ validator = (OnUserCache)factory.create(session, component);
+ session.setAttribute(component.getId(), validator);
+ }
+ validator.onCache(realm, user);
+ }
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java
index acb1514..ceaaf94 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/AccountBean.java
@@ -18,6 +18,7 @@
package org.keycloak.forms.account.freemarker.model;
import org.jboss.logging.Logger;
+import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import javax.ws.rs.core.MultivaluedMap;
@@ -55,8 +56,8 @@ public class AccountBean {
if (profileFormData != null) {
for (String key : profileFormData.keySet()) {
- if (key.startsWith("user.attributes.")) {
- String attribute = key.substring("user.attributes.".length());
+ if (key.startsWith(Constants.USER_ATTRIBUTES_PREFIX)) {
+ String attribute = key.substring(Constants.USER_ATTRIBUTES_PREFIX.length());
attributes.put(attribute, profileFormData.getFirst(key));
}
}
@@ -72,7 +73,11 @@ public class AccountBean {
}
public String getUsername() {
- return profileFormData != null ? profileFormData.getFirst("username") : user.getUsername();
+ if (profileFormData != null && profileFormData.containsKey("username")) {
+ return profileFormData.getFirst("username");
+ } else {
+ return user.getUsername();
+ }
}
public String getEmail() {
diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
index bfbf7a1..12dba27 100755
--- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
+++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
@@ -17,8 +17,6 @@
package org.keycloak.protocol;
-import java.util.List;
-
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
@@ -31,14 +29,11 @@ import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.services.ServicesLogger;
-import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.resources.LoginActionsService;
/**
@@ -95,15 +90,6 @@ public abstract class AuthorizationEndpointBase {
* @return response to be returned to the browser
*/
protected Response handleBrowserAuthenticationRequest(ClientSessionModel clientSession, LoginProtocol protocol, boolean isPassive, boolean redirectToAuthentication) {
-
- List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
- for (IdentityProviderModel identityProvider : identityProviders) {
- if (identityProvider.isEnabled() && identityProvider.isAuthenticateByDefault()) {
- // TODO if we are isPassive we should propagate this flag to default identity provider also if possible
- return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode());
- }
- }
-
AuthenticationFlowModel flow = getAuthenticationFlow();
String flowId = flow.getId();
AuthenticationProcessor processor = createProcessor(clientSession, flowId, LoginActionsService.AUTHENTICATE_PATH);
@@ -147,11 +133,4 @@ public abstract class AuthorizationEndpointBase {
return realm.getBrowserFlow();
}
- protected Response buildRedirectToIdentityProvider(String providerId, String accessCode) {
- logger.debug("Automatically redirect to identity provider: " + providerId);
- return Response.temporaryRedirect(
- Urls.identityProviderAuthnRequest(this.uriInfo.getBaseUri(), providerId, this.realm.getName(), accessCode))
- .build();
- }
-
}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index 931b04c..3f18b27 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -17,11 +17,6 @@
package org.keycloak.protocol.oidc.endpoints;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
import javax.ws.rs.GET;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
@@ -34,14 +29,14 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
-import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
+import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequestParserProcessor;
import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder;
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
@@ -49,7 +44,6 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPageException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls;
-import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
@@ -67,43 +61,10 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
/**
* Prefix used to store additional HTTP GET params from original client request into {@link ClientSessionModel} note to be available later in Authenticators, RequiredActions etc. Prefix is used to
* prevent collisions with internally used notes.
- *
+ *
* @see ClientSessionModel#getNote(String)
- * @see #KNOWN_REQ_PARAMS
- * @see #additionalReqParams
*/
public static final String CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX = "client_request_param_";
- /**
- * Max number of additional req params copied into client session note to prevent DoS attacks
- *
- * @see #additionalReqParams
- */
- public static final int ADDITIONAL_REQ_PARAMS_MAX_MUMBER = 5;
- /**
- * Max size of additional req param value copied into client session note to prevent DoS attacks - params with longer value are ignored
- *
- * @see #additionalReqParams
- */
- public static final int ADDITIONAL_REQ_PARAMS_MAX_SIZE = 200;
-
- /** Set of known protocol GET params not to be stored into {@link #additionalReqParams} */
- private static final Set<String> KNOWN_REQ_PARAMS = new HashSet<>();
- static {
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLIENT_ID_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REDIRECT_URI_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.STATE_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.SCOPE_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.LOGIN_HINT_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.PROMPT_PARAM);
- KNOWN_REQ_PARAMS.add(AdapterConstants.KC_IDP_HINT);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.NONCE_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.MAX_AGE_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
- KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
- }
private enum Action {
REGISTER, CODE, FORGOT_CREDENTIALS
@@ -116,19 +77,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
private OIDCResponseType parsedResponseType;
private OIDCResponseMode parsedResponseMode;
- private String clientId;
+ private AuthorizationEndpointRequest request;
private String redirectUri;
- private String redirectUriParam;
- private String responseType;
- private String responseMode;
- private String state;
- private String scope;
- private String loginHint;
- private String prompt;
- private String nonce;
- private String maxAge;
- private String idpHint;
- protected Map<String, String> additionalReqParams = new HashMap<>();
public AuthorizationEndpoint(RealmModel realm, EventBuilder event) {
super(realm, event);
@@ -139,34 +89,25 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
public Response build() {
MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
- clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
- responseType = params.getFirst(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
- responseMode = params.getFirst(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
- redirectUriParam = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM);
- state = params.getFirst(OIDCLoginProtocol.STATE_PARAM);
- scope = params.getFirst(OIDCLoginProtocol.SCOPE_PARAM);
- loginHint = params.getFirst(OIDCLoginProtocol.LOGIN_HINT_PARAM);
- prompt = params.getFirst(OIDCLoginProtocol.PROMPT_PARAM);
- idpHint = params.getFirst(AdapterConstants.KC_IDP_HINT);
- nonce = params.getFirst(OIDCLoginProtocol.NONCE_PARAM);
- maxAge = params.getFirst(OIDCLoginProtocol.MAX_AGE_PARAM);
-
- extractAdditionalReqParams(params);
+ String clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
checkSsl();
checkRealm();
- checkClient();
+ checkClient(clientId);
+
+ request = AuthorizationEndpointRequestParserProcessor.parseRequest(event, session, client, params);
+
checkRedirectUri();
Response errorResponse = checkResponseType();
if (errorResponse != null) {
return errorResponse;
}
- if (!TokenUtil.isOIDCRequest(scope)) {
+ if (!TokenUtil.isOIDCRequest(request.getScope())) {
logger.oidcScopeMissing();
}
- errorResponse = checkOIDCParams(params);
+ errorResponse = checkOIDCParams();
if (errorResponse != null) {
return errorResponse;
}
@@ -186,27 +127,6 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
throw new RuntimeException("Unknown action " + action);
}
- protected void extractAdditionalReqParams(MultivaluedMap<String, String> params) {
- for (String paramName : params.keySet()) {
- if (!KNOWN_REQ_PARAMS.contains(paramName)) {
- String value = params.getFirst(paramName);
- if (value != null && value.trim().isEmpty()) {
- value = null;
- }
- if (value != null && value.length() <= ADDITIONAL_REQ_PARAMS_MAX_SIZE) {
- if (additionalReqParams.size() >= ADDITIONAL_REQ_PARAMS_MAX_MUMBER) {
- logger.debug("Maximal number of additional OIDC params (" + ADDITIONAL_REQ_PARAMS_MAX_MUMBER + ") exceeded, ignoring rest of them!");
- break;
- }
- additionalReqParams.put(paramName, value);
- } else {
- logger.debug("OIDC Additional param " + paramName + " ignored because value is empty or longer than " + ADDITIONAL_REQ_PARAMS_MAX_SIZE);
- }
- }
-
- }
- }
-
public AuthorizationEndpoint register() {
event.event(EventType.REGISTER);
action = Action.REGISTER;
@@ -243,7 +163,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
}
}
- private void checkClient() {
+ private void checkClient(String clientId) {
if (clientId == null) {
event.error(Errors.INVALID_REQUEST);
throw new ErrorPageException(session, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM);
@@ -271,6 +191,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
}
private Response checkResponseType() {
+ String responseType = request.getResponseType();
+
if (responseType == null) {
logger.missingParameter(OAuth2Constants.RESPONSE_TYPE);
event.error(Errors.INVALID_REQUEST);
@@ -292,7 +214,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
OIDCResponseMode parsedResponseMode = null;
try {
- parsedResponseMode = OIDCResponseMode.parse(responseMode, parsedResponseType);
+ parsedResponseMode = OIDCResponseMode.parse(request.getResponseMode(), parsedResponseType);
} catch (IllegalArgumentException iae) {
logger.invalidParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
event.error(Errors.INVALID_REQUEST);
@@ -325,20 +247,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
return null;
}
- private Response checkOIDCParams(MultivaluedMap<String, String> params) {
- if (params.getFirst(OIDCLoginProtocol.REQUEST_PARAM) != null) {
- logger.unsupportedParameter(OIDCLoginProtocol.REQUEST_PARAM);
- event.error(Errors.INVALID_REQUEST);
- return redirectErrorToClient(parsedResponseMode, OAuthErrorException.REQUEST_NOT_SUPPORTED, null);
- }
-
- if (params.getFirst(OIDCLoginProtocol.REQUEST_URI_PARAM) != null) {
- logger.unsupportedParameter(OIDCLoginProtocol.REQUEST_URI_PARAM);
- event.error(Errors.INVALID_REQUEST);
- return redirectErrorToClient(parsedResponseMode, OAuthErrorException.REQUEST_URI_NOT_SUPPORTED, null);
- }
-
- if (parsedResponseType.isImplicitOrHybridFlow() && nonce == null) {
+ private Response checkOIDCParams() {
+ if (parsedResponseType.isImplicitOrHybridFlow() && request.getNonce() == null) {
logger.missingParameter(OIDCLoginProtocol.NONCE_PARAM);
event.error(Errors.INVALID_REQUEST);
return redirectErrorToClient(parsedResponseMode, OAuthErrorException.INVALID_REQUEST, "Missing parameter: nonce");
@@ -355,14 +265,16 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
errorResponseBuilder.addParam(OAuth2Constants.ERROR_DESCRIPTION, errorDescription);
}
- if (state != null) {
- errorResponseBuilder.addParam(OAuth2Constants.STATE, state);
+ if (request.getState() != null) {
+ errorResponseBuilder.addParam(OAuth2Constants.STATE, request.getState());
}
return errorResponseBuilder.build();
}
private void checkRedirectUri() {
+ String redirectUriParam = request.getRedirectUriParam();
+
event.detail(Details.REDIRECT_URI, redirectUriParam);
redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client);
@@ -377,43 +289,31 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL);
clientSession.setRedirectUri(redirectUri);
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
- clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType);
- clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam);
+ clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, request.getResponseType());
+ clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, request.getRedirectUriParam());
clientSession.setNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
- if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
- if (nonce != null) clientSession.setNote(OIDCLoginProtocol.NONCE_PARAM, nonce);
- if (maxAge != null) clientSession.setNote(OIDCLoginProtocol.MAX_AGE_PARAM, maxAge);
- if (scope != null) clientSession.setNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
- if (loginHint != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
- if (prompt != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, prompt);
- if (idpHint != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, idpHint);
- if (responseMode != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, responseMode);
-
- if (additionalReqParams != null) {
- for (String paramName : additionalReqParams.keySet()) {
- clientSession.setNote(CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + paramName, additionalReqParams.get(paramName));
+ if (request.getState() != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, request.getState());
+ if (request.getNonce() != null) clientSession.setNote(OIDCLoginProtocol.NONCE_PARAM, request.getNonce());
+ if (request.getMaxAge() != null) clientSession.setNote(OIDCLoginProtocol.MAX_AGE_PARAM, String.valueOf(request.getMaxAge()));
+ if (request.getScope() != null) clientSession.setNote(OIDCLoginProtocol.SCOPE_PARAM, request.getScope());
+ if (request.getLoginHint() != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, request.getLoginHint());
+ if (request.getPrompt() != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, request.getPrompt());
+ if (request.getIdpHint() != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, request.getIdpHint());
+ if (request.getResponseMode() != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode());
+
+ if (request.getAdditionalReqParams() != null) {
+ for (String paramName : request.getAdditionalReqParams().keySet()) {
+ clientSession.setNote(CLIENT_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + paramName, request.getAdditionalReqParams().get(paramName));
}
}
}
private Response buildAuthorizationCodeAuthorizationResponse() {
-
- if (idpHint != null && !"".equals(idpHint)) {
- IdentityProviderModel identityProviderModel = realm.getIdentityProviderByAlias(idpHint);
-
- if (identityProviderModel == null) {
- return session.getProvider(LoginFormsProvider.class)
- .setError(Messages.IDENTITY_PROVIDER_NOT_FOUND, idpHint)
- .createErrorPage();
- }
- return buildRedirectToIdentityProvider(idpHint, new ClientSessionCode(realm, clientSession).getCode());
- }
-
this.event.event(EventType.LOGIN);
clientSession.setNote(Details.AUTH_TYPE, CODE_AUTH_TYPE);
- return handleBrowserAuthenticationRequest(clientSession, new OIDCLoginProtocol(session, realm, uriInfo, headers, event), TokenUtil.hasPrompt(prompt, OIDCLoginProtocol.PROMPT_VALUE_NONE), false);
+ return handleBrowserAuthenticationRequest(clientSession, new OIDCLoginProtocol(session, realm, uriInfo, headers, event), TokenUtil.hasPrompt(request.getPrompt(), OIDCLoginProtocol.PROMPT_VALUE_NONE), false);
}
private Response buildRegister() {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java
new file mode 100644
index 0000000..998a58c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.protocol.oidc.endpoints.request;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthorizationEndpointRequest {
+
+ String clientId;
+ String redirectUriParam;
+ String responseType;
+ String responseMode;
+ String state;
+ String scope;
+ String loginHint;
+ String prompt;
+ String nonce;
+ Integer maxAge;
+ String idpHint;
+ Map<String, String> additionalReqParams = new HashMap<>();
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public String getRedirectUriParam() {
+ return redirectUriParam;
+ }
+
+ public String getResponseType() {
+ return responseType;
+ }
+
+ public String getResponseMode() {
+ return responseMode;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public String getLoginHint() {
+ return loginHint;
+ }
+
+ public String getPrompt() {
+ return prompt;
+ }
+
+ public String getNonce() {
+ return nonce;
+ }
+
+ public Integer getMaxAge() {
+ return maxAge;
+ }
+
+ public String getIdpHint() {
+ return idpHint;
+ }
+
+ public Map<String, String> getAdditionalReqParams() {
+ return additionalReqParams;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java
new file mode 100644
index 0000000..8db219c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequestParserProcessor.java
@@ -0,0 +1,73 @@
+/*
+ * 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.protocol.oidc.endpoints.request;
+
+import java.io.InputStream;
+import java.util.Map;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+import org.keycloak.common.util.StreamUtil;
+import org.keycloak.connections.httpclient.HttpClientProvider;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.services.ErrorPageException;
+import org.keycloak.services.ServicesLogger;
+import org.keycloak.services.messages.Messages;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class AuthorizationEndpointRequestParserProcessor {
+
+ private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+
+ public static AuthorizationEndpointRequest parseRequest(EventBuilder event, KeycloakSession session, ClientModel client, MultivaluedMap<String, String> requestParams) {
+ try {
+ AuthorizationEndpointRequest request = new AuthorizationEndpointRequest();
+
+ new AuthzEndpointQueryStringParser(requestParams).parseRequest(request);
+
+ String requestParam = requestParams.getFirst(OIDCLoginProtocol.REQUEST_PARAM);
+ String requestUriParam = requestParams.getFirst(OIDCLoginProtocol.REQUEST_URI_PARAM);
+
+ if (requestParam != null && requestUriParam != null) {
+ throw new RuntimeException("Illegal to use both 'request' and 'request_uri' parameters together");
+ }
+
+ if (requestParam != null) {
+ new AuthzEndpointRequestObjectParser(requestParam, client).parseRequest(request);
+ } else if (requestUriParam != null) {
+ InputStream is = session.getProvider(HttpClientProvider.class).get(requestUriParam);
+ String retrievedRequest = StreamUtil.readString(is);
+
+ new AuthzEndpointRequestObjectParser(retrievedRequest, client).parseRequest(request);
+ }
+
+ return request;
+
+ } catch (Exception e) {
+ logger.invalidRequest(e);
+ event.error(Errors.INVALID_REQUEST);
+ throw new ErrorPageException(session, Messages.INVALID_REQUEST);
+ }
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointQueryStringParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointQueryStringParser.java
new file mode 100644
index 0000000..8384fdc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointQueryStringParser.java
@@ -0,0 +1,52 @@
+/*
+ * 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.protocol.oidc.endpoints.request;
+
+import java.util.Set;
+
+import javax.ws.rs.core.MultivaluedMap;
+
+/**
+ * Parse the parameters from request queryString
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class AuthzEndpointQueryStringParser extends AuthzEndpointRequestParser {
+
+ private final MultivaluedMap<String, String> requestParams;
+
+ public AuthzEndpointQueryStringParser(MultivaluedMap<String, String> requestParams) {
+ this.requestParams = requestParams;
+ }
+
+ @Override
+ protected String getParameter(String paramName) {
+ return requestParams.getFirst(paramName);
+ }
+
+ @Override
+ protected Integer getIntParameter(String paramName) {
+ String paramVal = requestParams.getFirst(paramName);
+ return paramVal==null ? null : Integer.parseInt(paramVal);
+ }
+
+ @Override
+ protected Set<String> keySet() {
+ return requestParams.keySet();
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java
new file mode 100644
index 0000000..658a2c0
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java
@@ -0,0 +1,88 @@
+/*
+ * 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.protocol.oidc.endpoints.request;
+
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.JWSHeader;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.ClientModel;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.services.util.CertificateInfoHelper;
+import org.keycloak.util.JsonSerialization;
+
+/**
+ * Parse the parameters from OIDC "request" object
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
+
+ private final Map<String, Object> requestParams;
+
+ public AuthzEndpointRequestObjectParser(String requestObject, ClientModel client) throws Exception {
+ JWSInput input = new JWSInput(requestObject);
+ JWSHeader header = input.getHeader();
+
+ Algorithm requestedSignatureAlgorithm = OIDCAdvancedConfigWrapper.fromClientModel(client).getRequestObjectSignatureAlg();
+
+ if (requestedSignatureAlgorithm != null && requestedSignatureAlgorithm != header.getAlgorithm()) {
+ throw new RuntimeException("Request object signed with different algorithm than client requested algorithm");
+ }
+
+ if (header.getAlgorithm() == Algorithm.none) {
+ this.requestParams = JsonSerialization.readValue(input.getContent(), TypedHashMap.class);
+ } else if (header.getAlgorithm() == Algorithm.RS256) {
+ PublicKey clientPublicKey = CertificateInfoHelper.getSignatureValidationKey(client, JWTClientAuthenticator.ATTR_PREFIX);
+ boolean verified = RSAProvider.verify(input, clientPublicKey);
+ if (!verified) {
+ throw new RuntimeException("Failed to verify signature on 'request' object");
+ }
+
+ this.requestParams = JsonSerialization.readValue(input.getContent(), TypedHashMap.class);
+ } else {
+ throw new RuntimeException("Unsupported JWA algorithm used for signed request");
+ }
+ }
+
+ @Override
+ protected String getParameter(String paramName) {
+ Object val = this.requestParams.get(paramName);
+ return val==null ? null : val.toString();
+ }
+
+ @Override
+ protected Integer getIntParameter(String paramName) {
+ Object val = this.requestParams.get(paramName);
+ return val==null ? null : Integer.parseInt(getParameter(paramName));
+ }
+
+ @Override
+ protected Set<String> keySet() {
+ return requestParams.keySet();
+ }
+
+ static class TypedHashMap extends HashMap<String, Object> {
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java
new file mode 100644
index 0000000..e322d4b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java
@@ -0,0 +1,122 @@
+/*
+ * 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.protocol.oidc.endpoints.request;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.keycloak.constants.AdapterConstants;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.services.ServicesLogger;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+abstract class AuthzEndpointRequestParser {
+
+ private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+
+ /**
+ * Max number of additional req params copied into client session note to prevent DoS attacks
+ *
+ */
+ public static final int ADDITIONAL_REQ_PARAMS_MAX_MUMBER = 5;
+
+ /**
+ * Max size of additional req param value copied into client session note to prevent DoS attacks - params with longer value are ignored
+ *
+ */
+ public static final int ADDITIONAL_REQ_PARAMS_MAX_SIZE = 200;
+
+ /** Set of known protocol GET params not to be stored into additionalReqParams} */
+ private static final Set<String> KNOWN_REQ_PARAMS = new HashSet<>();
+ static {
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLIENT_ID_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REDIRECT_URI_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.STATE_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.SCOPE_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.LOGIN_HINT_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.PROMPT_PARAM);
+ KNOWN_REQ_PARAMS.add(AdapterConstants.KC_IDP_HINT);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.NONCE_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.MAX_AGE_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
+ KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
+ }
+
+
+ public void parseRequest(AuthorizationEndpointRequest request) {
+ String clientId = getParameter(OIDCLoginProtocol.CLIENT_ID_PARAM);
+
+ if (request.clientId != null && !request.clientId.equals(clientId)) {
+ throw new IllegalArgumentException("The client_id parameter doesn't match the one from OIDC 'request' or 'request_uri'");
+ }
+
+ request.clientId = clientId;
+ request.responseType = replaceIfNotNull(request.responseType, getParameter(OIDCLoginProtocol.RESPONSE_TYPE_PARAM));
+ request.responseMode = replaceIfNotNull(request.responseMode, getParameter(OIDCLoginProtocol.RESPONSE_MODE_PARAM));
+ request.redirectUriParam = replaceIfNotNull(request.redirectUriParam, getParameter(OIDCLoginProtocol.REDIRECT_URI_PARAM));
+ request.state = replaceIfNotNull(request.state, getParameter(OIDCLoginProtocol.STATE_PARAM));
+ request.scope = replaceIfNotNull(request.scope, getParameter(OIDCLoginProtocol.SCOPE_PARAM));
+ request.loginHint = replaceIfNotNull(request.loginHint, getParameter(OIDCLoginProtocol.LOGIN_HINT_PARAM));
+ request.prompt = replaceIfNotNull(request.prompt, getParameter(OIDCLoginProtocol.PROMPT_PARAM));
+ request.idpHint = replaceIfNotNull(request.idpHint, getParameter(AdapterConstants.KC_IDP_HINT));
+ request.nonce = replaceIfNotNull(request.nonce, getParameter(OIDCLoginProtocol.NONCE_PARAM));
+ request.maxAge = replaceIfNotNull(request.maxAge, getIntParameter(OIDCLoginProtocol.MAX_AGE_PARAM));
+
+ extractAdditionalReqParams(request.additionalReqParams);
+ }
+
+
+ protected void extractAdditionalReqParams(Map<String, String> additionalReqParams) {
+ for (String paramName : keySet()) {
+ if (!KNOWN_REQ_PARAMS.contains(paramName)) {
+ String value = getParameter(paramName);
+ if (value != null && value.trim().isEmpty()) {
+ value = null;
+ }
+ if (value != null && value.length() <= ADDITIONAL_REQ_PARAMS_MAX_SIZE) {
+ if (additionalReqParams.size() >= ADDITIONAL_REQ_PARAMS_MAX_MUMBER) {
+ logger.debug("Maximal number of additional OIDC params (" + ADDITIONAL_REQ_PARAMS_MAX_MUMBER + ") exceeded, ignoring rest of them!");
+ break;
+ }
+ additionalReqParams.put(paramName, value);
+ } else {
+ logger.debug("OIDC Additional param " + paramName + " ignored because value is empty or longer than " + ADDITIONAL_REQ_PARAMS_MAX_SIZE);
+ }
+ }
+
+ }
+ }
+
+ protected <T> T replaceIfNotNull(T previousVal, T newVal) {
+ return newVal==null ? previousVal : newVal;
+ }
+
+
+ protected abstract String getParameter(String paramName);
+
+ protected abstract Integer getIntParameter(String paramName);
+
+ protected abstract Set<String> keySet();
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
index f5e4caa..28d9514 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java
@@ -19,10 +19,9 @@ package org.keycloak.protocol.oidc.endpoints;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.common.ClientConnection;
import org.keycloak.OAuthErrorException;
import org.keycloak.RSATokenVerifier;
+import org.keycloak.common.ClientConnection;
import org.keycloak.common.VerificationException;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@@ -30,20 +29,15 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.jose.jws.JWSBuilder;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.*;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.Urls;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.resources.Cors;
-import org.keycloak.services.Urls;
import org.keycloak.utils.MediaType;
import javax.ws.rs.GET;
@@ -54,7 +48,6 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;
@@ -179,8 +172,8 @@ public class UserInfoEndpoint {
tokenManager.transformUserInfoAccessToken(session, userInfo, realm, clientModel, userModel, userSession, clientSession);
Map<String, Object> claims = new HashMap<String, Object>();
- claims.putAll(userInfo.getOtherClaims());
claims.put("sub", userModel.getId());
+ claims.putAll(userInfo.getOtherClaims());
Response.ResponseBuilder responseBuilder;
OIDCAdvancedConfigWrapper cfg = OIDCAdvancedConfigWrapper.fromClientModel(clientModel);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
index 6fda3f0..79a3919 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCClientInstallation.java
@@ -51,7 +51,6 @@ public class KeycloakOIDCClientInstallation implements ClientInstallationProvide
ClientManager.InstallationAdapterConfig rep = new ClientManager.InstallationAdapterConfig();
rep.setAuthServerUrl(baseUri.toString());
rep.setRealm(realm.getName());
- rep.setRealmKey(realm.getPublicKeyPem());
rep.setSslRequired(realm.getSslRequired().name().toLowerCase());
if (client.isPublicClient() && !client.isBearerOnly()) rep.setPublicClient(true);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java
index 33ab677..d0bc939 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/installation/KeycloakOIDCJbossSubsystemClientInstallation.java
@@ -40,7 +40,6 @@ public class KeycloakOIDCJbossSubsystemClientInstallation implements ClientInsta
StringBuffer buffer = new StringBuffer();
buffer.append("<secure-deployment name=\"WAR MODULE NAME.war\">\n");
buffer.append(" <realm>").append(realm.getName()).append("</realm>\n");
- buffer.append(" <realm-public-key>").append(realm.getPublicKeyPem()).append("</realm-public-key>\n");
buffer.append(" <auth-server-url>").append(baseUri.toString()).append("</auth-server-url>\n");
if (client.isBearerOnly()){
buffer.append(" <bearer-only>true</bearer-only>\n");
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
new file mode 100644
index 0000000..b153fe4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractPairwiseSubMapper.java
@@ -0,0 +1,125 @@
+package org.keycloak.protocol.oidc.mappers;
+
+import org.keycloak.models.*;
+import org.keycloak.protocol.ProtocolMapperConfigException;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator;
+import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Set the 'sub' claim to pairwise .
+ *
+ * @author <a href="mailto:martin.hardselius@gmail.com">Martin Hardselius</a>
+ */
+public abstract class AbstractPairwiseSubMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
+ public static final String PROVIDER_ID_SUFFIX = "-pairwise-sub-mapper";
+
+ public static String getId(String prefix) {
+ return prefix + PROVIDER_ID_SUFFIX;
+ }
+
+ public abstract String getIdPrefix();
+
+ /**
+ * Generates a pairwise subject identifier.
+ *
+ * @param mappingModel
+ * @param sectorIdentifier client sector identifier
+ * @param localSub local subject identifier (user id)
+ * @return A pairwise subject identifier
+ */
+ public abstract String generateSub(ProtocolMapperModel mappingModel, String sectorIdentifier, String localSub);
+
+ /**
+ * Override to add additional provider configuration properties. By default, a pairwise sub mapper will only contain configuration for a sector identifier URI.
+ *
+ * @return A list of provider configuration properties.
+ */
+ public List<ProviderConfigProperty> getAdditionalConfigProperties() {
+ return new LinkedList<>();
+ }
+
+ /**
+ * Override to add additional configuration validation. Called when instance of mapperModel is created/updated for this protocolMapper through admin endpoint.
+ *
+ * @param session
+ * @param realm
+ * @param mapperContainer client or clientTemplate
+ * @param mapperModel
+ * @throws ProtocolMapperConfigException if configuration provided in mapperModel is not valid
+ */
+ public void validateAdditionalConfig(KeycloakSession session, RealmModel realm, ProtocolMapperContainerModel mapperContainer, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException {
+ }
+
+ @Override
+ public final String getDisplayCategory() {
+ return AbstractOIDCProtocolMapper.TOKEN_MAPPER_CATEGORY;
+ }
+
+ @Override
+ public final IDToken transformIDToken(IDToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+ return token;
+ }
+
+ @Override
+ public final AccessToken transformAccessToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+ return token;
+ }
+
+ @Override
+ public final AccessToken transformUserInfoToken(AccessToken token, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ setSubject(token, generateSub(mappingModel, getSectorIdentifier(clientSession.getClient(), mappingModel), userSession.getUser().getId()));
+ return token;
+ }
+
+ private void setSubject(IDToken token, String pairwiseSub) {
+ token.getOtherClaims().put("sub", pairwiseSub);
+ }
+
+ @Override
+ public final List<ProviderConfigProperty> getConfigProperties() {
+ List<ProviderConfigProperty> configProperties = new LinkedList<>();
+ configProperties.add(PairwiseSubMapperHelper.createSectorIdentifierConfig());
+ configProperties.addAll(getAdditionalConfigProperties());
+ return configProperties;
+ }
+
+ private String getSectorIdentifier(ClientModel client, ProtocolMapperModel mappingModel) {
+ String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(mappingModel);
+ if (sectorIdentifierUri != null && !sectorIdentifierUri.isEmpty()) {
+ return PairwiseSubMapperUtils.resolveValidSectorIdentifier(sectorIdentifierUri);
+ }
+ return PairwiseSubMapperUtils.resolveValidSectorIdentifier(client.getRootUrl(), client.getRedirectUris());
+ }
+
+ @Override
+ public final void validateConfig(KeycloakSession session, RealmModel realm, ProtocolMapperContainerModel mapperContainer, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException {
+ ClientModel client = null;
+ if (mapperContainer instanceof ClientModel) {
+ client = (ClientModel) mapperContainer;
+ PairwiseSubMapperValidator.validate(session, client, mapperModel);
+ }
+ validateAdditionalConfig(session, realm, mapperContainer, mapperModel);
+
+ if (client != null) {
+ // Propagate changes to the sector identifier uri
+ OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientModel(client);
+ configWrapper.setSectorIdentifierUri(PairwiseSubMapperHelper.getSectorIdentifierUri(mapperModel));
+ }
+ }
+
+ @Override
+ public final String getId() {
+ return getIdPrefix() + PROVIDER_ID_SUFFIX;
+ }
+}
+
+
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/PairwiseSubMapperHelper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/PairwiseSubMapperHelper.java
new file mode 100644
index 0000000..a2aa4b3
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/PairwiseSubMapperHelper.java
@@ -0,0 +1,47 @@
+package org.keycloak.protocol.oidc.mappers;
+
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.services.ServicesLogger;
+
+public class PairwiseSubMapperHelper {
+ private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+
+ public static final String SECTOR_IDENTIFIER_URI = "sectorIdentifierUri";
+ public static final String SECTOR_IDENTIFIER_URI_LABEL = "sectorIdentifierUri.label";
+ public static final String SECTOR_IDENTIFIER_URI_HELP_TEXT = "sectorIdentifierUri.tooltip";
+
+ public static final String PAIRWISE_SUB_ALGORITHM_SALT = "pairwiseSubAlgorithmSalt";
+ public static final String PAIRWISE_SUB_ALGORITHM_SALT_LABEL = "pairwiseSubAlgorithmSalt.label";
+ public static final String PAIRWISE_SUB_ALGORITHM_SALT_HELP_TEXT = "pairwiseSubAlgorithmSalt.tooltip";
+
+ public static String getSectorIdentifierUri(ProtocolMapperModel mappingModel) {
+ return mappingModel.getConfig().get(SECTOR_IDENTIFIER_URI);
+ }
+
+ public static String getSalt(ProtocolMapperModel mappingModel) {
+ return mappingModel.getConfig().get(PAIRWISE_SUB_ALGORITHM_SALT);
+ }
+
+ public static void setSalt(ProtocolMapperModel mappingModel, String salt) {
+ mappingModel.getConfig().put(PAIRWISE_SUB_ALGORITHM_SALT, salt);
+ }
+
+ public static ProviderConfigProperty createSectorIdentifierConfig() {
+ ProviderConfigProperty property = new ProviderConfigProperty();
+ property.setName(SECTOR_IDENTIFIER_URI);
+ property.setType(ProviderConfigProperty.STRING_TYPE);
+ property.setLabel(SECTOR_IDENTIFIER_URI_LABEL);
+ property.setHelpText(SECTOR_IDENTIFIER_URI_HELP_TEXT);
+ return property;
+ }
+
+ public static ProviderConfigProperty createSaltConfig() {
+ ProviderConfigProperty property = new ProviderConfigProperty();
+ property.setName(PAIRWISE_SUB_ALGORITHM_SALT);
+ property.setType(ProviderConfigProperty.STRING_TYPE);
+ property.setLabel(PAIRWISE_SUB_ALGORITHM_SALT_LABEL);
+ property.setHelpText(PAIRWISE_SUB_ALGORITHM_SALT_HELP_TEXT);
+ return property;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA265PairwiseSubMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA265PairwiseSubMapper.java
new file mode 100644
index 0000000..bc1caaa
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/SHA265PairwiseSubMapper.java
@@ -0,0 +1,119 @@
+package org.keycloak.protocol.oidc.mappers;
+
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.services.ServicesLogger;
+
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.*;
+
+public class SHA265PairwiseSubMapper extends AbstractPairwiseSubMapper {
+ public static final String PROVIDER_ID = "sha256";
+ private static final String HASH_ALGORITHM = "SHA-256";
+ private static final String ALPHA_NUMERIC = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+ private final Charset charset;
+
+ public SHA265PairwiseSubMapper() throws NoSuchAlgorithmException {
+ charset = Charset.forName("UTF-8");
+ MessageDigest.getInstance(HASH_ALGORITHM);
+ }
+
+ public static ProtocolMapperModel createPairwiseMapper() {
+ return createPairwiseMapper(null);
+ }
+
+ public static ProtocolMapperModel createPairwiseMapper(String sectorIdentifierUri) {
+ Map<String, String> config;
+ ProtocolMapperModel pairwise = new ProtocolMapperModel();
+ pairwise.setName("pairwise subject identifier");
+ pairwise.setProtocolMapper(AbstractPairwiseSubMapper.getId(PROVIDER_ID));
+ pairwise.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+ pairwise.setConsentRequired(false);
+ config = new HashMap<>();
+ config.put(PairwiseSubMapperHelper.SECTOR_IDENTIFIER_URI, sectorIdentifierUri);
+ pairwise.setConfig(config);
+ return pairwise;
+ }
+
+ public static ProtocolMapperModel createPairwiseMapper(String sectorIdentifierUri, String salt) {
+ Map<String, String> config;
+ ProtocolMapperModel pairwise = new ProtocolMapperModel();
+ pairwise.setName("pairwise subject identifier");
+ pairwise.setProtocolMapper(AbstractPairwiseSubMapper.getId(PROVIDER_ID));
+ pairwise.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+ pairwise.setConsentRequired(false);
+ config = new HashMap<>();
+ config.put(PairwiseSubMapperHelper.SECTOR_IDENTIFIER_URI, sectorIdentifierUri);
+ config.put(PairwiseSubMapperHelper.PAIRWISE_SUB_ALGORITHM_SALT, salt);
+ pairwise.setConfig(config);
+ return pairwise;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Calculates a pairwise subject identifier using a salted sha-256 hash.";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getAdditionalConfigProperties() {
+ List<ProviderConfigProperty> configProperties = new LinkedList<>();
+ configProperties.add(PairwiseSubMapperHelper.createSaltConfig());
+ return configProperties;
+ }
+
+ @Override
+ public String generateSub(ProtocolMapperModel mappingModel, String sectorIdentifier, String localSub) {
+ String saltStr = getSalt(mappingModel);
+
+ Charset charset = Charset.forName("UTF-8");
+ byte[] salt = saltStr.getBytes(charset);
+ String pairwiseSub = generateSub(sectorIdentifier, localSub, salt);
+ logger.infof("local sub = '%s', pairwise sub = '%s'", localSub, pairwiseSub);
+ return pairwiseSub;
+ }
+
+ private String generateSub(String sectorIdentifier, String localSub, byte[] salt) {
+ MessageDigest sha256;
+ try {
+ sha256 = MessageDigest.getInstance(HASH_ALGORITHM);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ }
+ sha256.update(sectorIdentifier.getBytes(charset));
+ sha256.update(localSub.getBytes(charset));
+ byte[] hash = sha256.digest(salt);
+ return UUID.nameUUIDFromBytes(hash).toString();
+ }
+
+ private String getSalt(ProtocolMapperModel mappingModel) {
+ String salt = PairwiseSubMapperHelper.getSalt(mappingModel);
+ if (salt == null || salt.trim().isEmpty()) {
+ salt = createSalt(32);
+ PairwiseSubMapperHelper.setSalt(mappingModel, salt);
+ }
+ return salt;
+ }
+
+ private String createSalt(int len) {
+ Random rnd = new SecureRandom();
+ StringBuilder sb = new StringBuilder(len);
+ for (int i = 0; i < len; i++)
+ sb.append(ALPHA_NUMERIC.charAt(rnd.nextInt(ALPHA_NUMERIC.length())));
+ return sb.toString();
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Pairwise subject identifier";
+ }
+
+ @Override
+ public String getIdPrefix() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java
index 56e7a48..f7475a9 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java
@@ -19,10 +19,12 @@ package org.keycloak.protocol.oidc.mappers;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperContainerModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.ProtocolMapperConfigException;
import org.keycloak.protocol.ProtocolMapperUtils;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.AccessToken;
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
index 37fe204..e16d7b8 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java
@@ -17,12 +17,13 @@
package org.keycloak.protocol.oidc;
-import java.util.HashMap;
-
import org.keycloak.jose.jws.Algorithm;
import org.keycloak.models.ClientModel;
+import org.keycloak.protocol.oidc.utils.SubjectType;
import org.keycloak.representations.idm.ClientRepresentation;
+import java.util.HashMap;
+
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@@ -30,6 +31,13 @@ public class OIDCAdvancedConfigWrapper {
private static final String USER_INFO_RESPONSE_SIGNATURE_ALG = "user.info.response.signature.alg";
+ private static final String REQUEST_OBJECT_SIGNATURE_ALG = "request.object.signature.alg";
+
+ private static final String SUBJECT_TYPE = "oidc.subject_type";
+ private static final String SECTOR_IDENTIFIER_URI = "oidc.sector_identifier_uri";
+ private static final String PUBLIC = "public";
+ private static final String PAIRWISE = "pairwise";
+
private final ClientModel clientModel;
private final ClientRepresentation clientRep;
@@ -62,6 +70,37 @@ public class OIDCAdvancedConfigWrapper {
return getUserInfoSignedResponseAlg() != null;
}
+ public Algorithm getRequestObjectSignatureAlg() {
+ String alg = getAttribute(REQUEST_OBJECT_SIGNATURE_ALG);
+ return alg==null ? null : Enum.valueOf(Algorithm.class, alg);
+ }
+
+ public void setRequestObjectSignatureAlg(Algorithm alg) {
+ String algStr = alg==null ? null : alg.toString();
+ setAttribute(REQUEST_OBJECT_SIGNATURE_ALG, algStr);
+ }
+
+ public void setSubjectType(SubjectType subjectType) {
+ if (subjectType == null) {
+ setAttribute(SUBJECT_TYPE, SubjectType.PUBLIC.toString());
+ return;
+ }
+ setAttribute(SUBJECT_TYPE, subjectType.toString());
+ }
+
+ public SubjectType getSubjectType() {
+ String subjectType = getAttribute(SUBJECT_TYPE);
+ return subjectType == null ? SubjectType.PUBLIC : Enum.valueOf(SubjectType.class, subjectType);
+ }
+
+ public void setSectorIdentifierUri(String sectorIdentifierUri) {
+ setAttribute(SECTOR_IDENTIFIER_URI, sectorIdentifierUri);
+ }
+
+ public String getSectorIdentifierUri() {
+ return getAttribute(SECTOR_IDENTIFIER_URI);
+ }
+
private String getAttribute(String attrKey) {
if (clientModel != null) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index 3c4c3aa..c263405 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -66,6 +66,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
public static final String REQUEST_PARAM = "request";
public static final String REQUEST_URI_PARAM = "request_uri";
public static final String UI_LOCALES_PARAM = OAuth2Constants.UI_LOCALES_PARAM;
+ public static final String CLAIMS_PARAM = "claims";
public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
public static final String ISSUER = "iss";
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
index 087b1f6..aa56dc7 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java
@@ -19,31 +19,15 @@ package org.keycloak.protocol.oidc;
import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.common.util.UriUtils;
import org.keycloak.events.EventBuilder;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientTemplateModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ProtocolMapperModel;
-import org.keycloak.models.RealmModel;
+import org.keycloak.models.*;
import org.keycloak.protocol.AbstractLoginProtocolFactory;
import org.keycloak.protocol.LoginProtocol;
-import org.keycloak.protocol.oidc.mappers.AddressMapper;
-import org.keycloak.protocol.oidc.mappers.FullNameMapper;
-import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
-import org.keycloak.protocol.oidc.mappers.UserPropertyMapper;
-import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
+import org.keycloak.protocol.oidc.mappers.*;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientTemplateRepresentation;
import org.keycloak.services.ServicesLogger;
-import org.keycloak.services.managers.AuthenticationManager;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
+import java.util.*;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -146,6 +130,9 @@ public class OIDCLoginProtocolFactory extends AbstractLoginProtocolFactory {
ProtocolMapperModel address = AddressMapper.createAddressMapper();
builtins.add(address);
+ ProtocolMapperModel pairwise = SHA265PairwiseSubMapper.createPairwiseMapper();
+ builtins.add(pairwise);
+
model = UserSessionNoteMapper.createClaimMapper(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME,
KerberosConstants.GSS_DELEGATION_CREDENTIAL,
KerberosConstants.GSS_DELEGATION_CREDENTIAL, "String",
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
index dfca144..2653b9b 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java
@@ -50,11 +50,13 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
public static final List<String> DEFAULT_USER_INFO_SIGNING_ALG_VALUES_SUPPORTED = list(Algorithm.RS256.toString());
+ public static final List<String> DEFAULT_REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED = list(Algorithm.none.toString(), Algorithm.RS256.toString());
+
public static final List<String> DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS);
public static final List<String> DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, OIDCResponseType.TOKEN, "id_token token", "code id_token", "code token", "code id_token token");
- public static final List<String> DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public");
+ public static final List<String> DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public", "pairwise");
public static final List<String> DEFAULT_RESPONSE_MODES_SUPPORTED = list("query", "fragment", "form_post");
@@ -93,6 +95,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
config.setUserInfoSigningAlgValuesSupported(DEFAULT_USER_INFO_SIGNING_ALG_VALUES_SUPPORTED);
+ config.setRequestObjectSigningAlgValuesSupported(DEFAULT_REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED);
config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED);
config.setSubjectTypesSupported(DEFAULT_SUBJECT_TYPES_SUPPORTED);
config.setResponseModesSupported(DEFAULT_RESPONSE_MODES_SUPPORTED);
@@ -107,8 +110,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
config.setScopesSupported(SCOPES_SUPPORTED);
- config.setRequestParameterSupported(false);
- config.setRequestUriParameterSupported(false);
+ config.setRequestParameterSupported(true);
+ config.setRequestUriParameterSupported(true);
return config;
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
index ee5241b..181e0d2 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java
@@ -67,6 +67,9 @@ public class OIDCConfigurationRepresentation {
@JsonProperty("userinfo_signing_alg_values_supported")
private List<String> userInfoSigningAlgValuesSupported;
+ @JsonProperty("request_object_signing_alg_values_supported")
+ private List<String> requestObjectSigningAlgValuesSupported;
+
@JsonProperty("response_modes_supported")
private List<String> responseModesSupported;
@@ -195,6 +198,14 @@ public class OIDCConfigurationRepresentation {
this.userInfoSigningAlgValuesSupported = userInfoSigningAlgValuesSupported;
}
+ public List<String> getRequestObjectSigningAlgValuesSupported() {
+ return requestObjectSigningAlgValuesSupported;
+ }
+
+ public void setRequestObjectSigningAlgValuesSupported(List<String> requestObjectSigningAlgValuesSupported) {
+ this.requestObjectSigningAlgValuesSupported = requestObjectSigningAlgValuesSupported;
+ }
+
public List<String> getResponseModesSupported() {
return responseModesSupported;
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java
index c856a81..62d2c58 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/JWKSUtils.java
@@ -18,21 +18,27 @@
package org.keycloak.protocol.oidc.utils;
import java.io.IOException;
+import java.io.InputStream;
import java.security.PublicKey;
-import org.keycloak.broker.provider.util.SimpleHttp;
+import org.keycloak.common.util.StreamUtil;
+import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKParser;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.util.JsonSerialization;
/**
+ * TODO: Merge with JWKSUtils from keycloak-core?
+ *
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JWKSUtils {
- public static JSONWebKeySet sendJwksRequest(String jwksURI) throws IOException {
- String keySetString = SimpleHttp.doGet(jwksURI).asString();
+ public static JSONWebKeySet sendJwksRequest(KeycloakSession session, String jwksURI) throws IOException {
+ InputStream is = session.getProvider(HttpClientProvider.class).get(jwksURI);
+ String keySetString = StreamUtil.readString(is);
return JsonSerialization.readValue(keySetString, JSONWebKeySet.class);
}
@@ -40,7 +46,7 @@ public class JWKSUtils {
public static PublicKey getKeyForUse(JSONWebKeySet keySet, JWK.Use requestedUse) {
for (JWK jwk : keySet.getKeys()) {
JWKParser parser = JWKParser.create(jwk);
- if (parser.getJwk().getPublicKeyUse().equals(requestedUse.asString()) && parser.isAlgorithmSupported(jwk.getKeyType())) {
+ if (parser.getJwk().getPublicKeyUse().equals(requestedUse.asString()) && parser.isKeyTypeSupported(jwk.getKeyType())) {
return parser.toPublicKey();
}
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperUtils.java
new file mode 100644
index 0000000..5e2dadc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperUtils.java
@@ -0,0 +1,159 @@
+package org.keycloak.protocol.oidc.utils;
+
+import org.keycloak.protocol.oidc.mappers.AbstractPairwiseSubMapper;
+import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.services.ServicesLogger;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class PairwiseSubMapperUtils {
+ private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+
+ /**
+ * Returns a set of valid redirect URIs from the root url and redirect URIs registered on a client.
+ *
+ * @param clientRootUrl
+ * @param clientRedirectUris
+ * @return
+ */
+ public static Set<String> resolveValidRedirectUris(String clientRootUrl, Set<String> clientRedirectUris) {
+ Set<String> validRedirects = new HashSet<String>();
+ for (String redirectUri : clientRedirectUris) {
+ if (redirectUri.startsWith("/")) {
+ redirectUri = relativeToAbsoluteURI(clientRootUrl, redirectUri);
+ logger.debugv("replacing relative valid redirect with: {0}", redirectUri);
+ }
+ if (redirectUri != null) {
+ validRedirects.add(redirectUri);
+ }
+ }
+ return validRedirects.stream()
+ .filter(r -> r != null && !r.trim().isEmpty())
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * Tries to resolve a valid sector identifier from a sector identifier URI.
+ *
+ * @param sectorIdentifierUri
+ * @return a sector identifier iff. the sector identifier URI is a valid URI, contains a valid scheme and contains a valid host component.
+ */
+ public static String resolveValidSectorIdentifier(String sectorIdentifierUri) {
+ URI uri;
+ try {
+ uri = new URI(sectorIdentifierUri);
+ } catch (URISyntaxException e) {
+ logger.debug("Invalid sector identifier URI", e);
+ return null;
+ }
+
+ if (uri.getScheme() == null) {
+ logger.debugv("Invalid sector identifier URI: {0}", sectorIdentifierUri);
+ return null;
+ }
+
+ /*if (!uri.getScheme().equalsIgnoreCase("https")) {
+ logger.debugv("The sector identifier URI scheme must be HTTPS. Was '{0}'", uri.getScheme());
+ return null;
+ }*/
+
+ if (uri.getHost() == null) {
+ logger.debug("The sector identifier URI must specify a host");
+ return null;
+ }
+
+ return uri.getHost();
+ }
+
+ /**
+ * Tries to resolve a valid sector identifier from the redirect URIs registered on a client.
+ *
+ * @param clientRootUrl Root url registered on the client.
+ * @param clientRedirectUris Redirect URIs registered on the client.
+ * @return a sector identifier iff. all the registered redirect URIs are located at the same host, otherwise {@code null}.
+ */
+ public static String resolveValidSectorIdentifier(String clientRootUrl, Set<String> clientRedirectUris) {
+ Set<String> hosts = new HashSet<>();
+ for (String redirectUri : resolveValidRedirectUris(clientRootUrl, clientRedirectUris)) {
+ try {
+ URI uri = new URI(redirectUri);
+ hosts.add(uri.getHost());
+ } catch (URISyntaxException e) {
+ logger.debugv("client redirect uris contained an invalid uri: {0}", redirectUri);
+ }
+ }
+ if (hosts.isEmpty()) {
+ logger.debug("could not infer any valid sector_identifiers from client redirect uris");
+ return null;
+ }
+ if (hosts.size() > 1) {
+ logger.debug("the client redirect uris contained multiple hosts");
+ return null;
+ }
+ return hosts.iterator().next();
+ }
+
+ /**
+ * Checks if the the registered client redirect URIs matches the set of redirect URIs from the sector identifier URI.
+ *
+ * @param clientRootUrl root url registered on the client.
+ * @param clientRedirectUris redirect URIs registered on the client.
+ * @param sectorRedirects value of the sector identifier URI.
+ * @return {@code true} iff. the all the redirect URIs can be described by the {@code sectorRedirects}, i.e if the registered redirect URIs is a subset of the {@code sectorRedirects}, otherwise {@code false}.
+ */
+ public static boolean matchesRedirects(String clientRootUrl, Set<String> clientRedirectUris, Set<String> sectorRedirects) {
+ Set<String> validRedirects = resolveValidRedirectUris(clientRootUrl, clientRedirectUris);
+ for (String redirect : validRedirects) {
+ if (!matchesRedirect(sectorRedirects, redirect)) return false;
+ }
+ return true;
+ }
+
+ private static boolean matchesRedirect(Set<String> validRedirects, String redirect) {
+ for (String validRedirect : validRedirects) {
+ if (validRedirect.endsWith("*") && !validRedirect.contains("?")) {
+ // strip off the query component - we don't check them when wildcards are effective
+ String r = redirect.contains("?") ? redirect.substring(0, redirect.indexOf("?")) : redirect;
+ // strip off *
+ int length = validRedirect.length() - 1;
+ validRedirect = validRedirect.substring(0, length);
+ if (r.startsWith(validRedirect)) return true;
+ // strip off trailing '/'
+ if (length - 1 > 0 && validRedirect.charAt(length - 1) == '/') length--;
+ validRedirect = validRedirect.substring(0, length);
+ if (validRedirect.equals(r)) return true;
+ } else if (validRedirect.equals(redirect)) return true;
+ }
+ return false;
+ }
+
+ private static String relativeToAbsoluteURI(String rootUrl, String relative) {
+ if (rootUrl == null || rootUrl.isEmpty()) {
+ return null;
+ }
+ relative = rootUrl + relative;
+ return relative;
+ }
+
+ public static ProtocolMapperRepresentation getPairwiseSubMapperRepresentation(ClientRepresentation client) {
+ List<ProtocolMapperRepresentation> mappers = client.getProtocolMappers();
+ if (mappers == null) {
+ return null;
+ }
+ for (ProtocolMapperRepresentation mapper : mappers) {
+ if (mapper.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)) return mapper;
+ }
+ return null;
+ }
+
+ public static String getSubjectIdentifierUri(ProtocolMapperRepresentation pairwiseMapper) {
+ return pairwiseMapper.getConfig().get(PairwiseSubMapperHelper.SECTOR_IDENTIFIER_URI);
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperValidator.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperValidator.java
new file mode 100644
index 0000000..e6d8a6e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/PairwiseSubMapperValidator.java
@@ -0,0 +1,113 @@
+package org.keycloak.protocol.oidc.utils;
+
+import org.keycloak.connections.httpclient.HttpClientProvider;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.protocol.ProtocolMapperConfigException;
+import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper;
+import org.keycloak.util.JsonSerialization;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:martin.hardselius@gmail.com">Martin Hardselius</a>
+ */
+public class PairwiseSubMapperValidator {
+
+ public static final String PAIRWISE_MALFORMED_CLIENT_REDIRECT_URI = "pairwiseMalformedClientRedirectURI";
+ public static final String PAIRWISE_CLIENT_REDIRECT_URIS_MISSING_HOST = "pairwiseClientRedirectURIsMissingHost";
+ public static final String PAIRWISE_CLIENT_REDIRECT_URIS_MULTIPLE_HOSTS = "pairwiseClientRedirectURIsMultipleHosts";
+ public static final String PAIRWISE_MALFORMED_SECTOR_IDENTIFIER_URI = "pairwiseMalformedSectorIdentifierURI";
+ public static final String PAIRWISE_FAILED_TO_GET_REDIRECT_URIS = "pairwiseFailedToGetRedirectURIs";
+ public static final String PAIRWISE_REDIRECT_URIS_MISMATCH = "pairwiseRedirectURIsMismatch";
+
+ public static void validate(KeycloakSession session, ClientModel client, ProtocolMapperModel mapperModel) throws ProtocolMapperConfigException {
+ String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(mapperModel);
+ String rootUrl = client.getRootUrl();
+ Set<String> redirectUris = client.getRedirectUris();
+ validate(session, rootUrl, redirectUris, sectorIdentifierUri);
+ }
+
+ public static void validate(KeycloakSession session, String rootUrl, Set<String> redirectUris, String sectorIdentifierUri) throws ProtocolMapperConfigException {
+ if (sectorIdentifierUri == null || sectorIdentifierUri.isEmpty()) {
+ validateClientRedirectUris(rootUrl, redirectUris);
+ return;
+ }
+ validateSectorIdentifierUri(sectorIdentifierUri);
+ validateSectorIdentifierUri(session, rootUrl, redirectUris, sectorIdentifierUri);
+ }
+
+ private static void validateClientRedirectUris(String rootUrl, Set<String> redirectUris) throws ProtocolMapperConfigException {
+ Set<String> hosts = new HashSet<>();
+ for (String redirectUri : PairwiseSubMapperUtils.resolveValidRedirectUris(rootUrl, redirectUris)) {
+ try {
+ URI uri = new URI(redirectUri);
+ hosts.add(uri.getHost());
+ } catch (URISyntaxException e) {
+ throw new ProtocolMapperConfigException("Client contained an invalid redirect URI.",
+ PAIRWISE_MALFORMED_CLIENT_REDIRECT_URI, e);
+ }
+ }
+
+ if (hosts.isEmpty()) {
+ throw new ProtocolMapperConfigException("Client redirect URIs must contain a valid host component.",
+ PAIRWISE_CLIENT_REDIRECT_URIS_MISSING_HOST);
+ }
+
+ if (hosts.size() > 1) {
+ throw new ProtocolMapperConfigException("Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.", PAIRWISE_CLIENT_REDIRECT_URIS_MULTIPLE_HOSTS);
+ }
+ }
+
+ private static void validateSectorIdentifierUri(String sectorIdentifierUri) throws ProtocolMapperConfigException {
+ URI uri;
+ try {
+ uri = new URI(sectorIdentifierUri);
+ } catch (URISyntaxException e) {
+ throw new ProtocolMapperConfigException("Invalid Sector Identifier URI.",
+ PAIRWISE_MALFORMED_SECTOR_IDENTIFIER_URI, e);
+ }
+ if (uri.getScheme() == null || uri.getHost() == null) {
+ throw new ProtocolMapperConfigException("Invalid Sector Identifier URI.",
+ PAIRWISE_MALFORMED_SECTOR_IDENTIFIER_URI);
+ }
+ }
+
+ private static void validateSectorIdentifierUri(KeycloakSession session, String rootUrl, Set<String> redirectUris, String sectorIdentifierUri) throws ProtocolMapperConfigException {
+ Set<String> sectorRedirects = getSectorRedirects(session, sectorIdentifierUri);
+ if (!PairwiseSubMapperUtils.matchesRedirects(rootUrl, redirectUris, sectorRedirects)) {
+ throw new ProtocolMapperConfigException("Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.",
+ PAIRWISE_REDIRECT_URIS_MISMATCH);
+ }
+ }
+
+ private static Set<String> getSectorRedirects(KeycloakSession session, String sectorIdentifierUri) throws ProtocolMapperConfigException {
+ InputStream is = null;
+ try {
+ is = session.getProvider(HttpClientProvider.class).get(sectorIdentifierUri);
+ List<String> sectorRedirects = JsonSerialization.readValue(is, TypedList.class);
+ return new HashSet<>(sectorRedirects);
+ } catch (IOException e) {
+ throw new ProtocolMapperConfigException("Failed to get redirect URIs from the Sector Identifier URI.",
+ PAIRWISE_FAILED_TO_GET_REDIRECT_URIS, e);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }
+
+ public static class TypedList extends ArrayList<String> {}
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/SubjectType.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/SubjectType.java
new file mode 100644
index 0000000..ec1ba97
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/SubjectType.java
@@ -0,0 +1,13 @@
+package org.keycloak.protocol.oidc.utils;
+
+public enum SubjectType {
+ PUBLIC,
+ PAIRWISE;
+
+ public static SubjectType parse(String subjectTypeStr) {
+ if (subjectTypeStr == null) {
+ return PUBLIC;
+ }
+ return Enum.valueOf(SubjectType.class, subjectTypeStr.toUpperCase());
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
index d634b00..f5ad8b0 100755
--- a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java
@@ -19,22 +19,22 @@ package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
-import org.keycloak.models.ClientInitialAccessModel;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientRegistrationTrustedHostModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.*;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.protocol.oidc.mappers.AbstractPairwiseSubMapper;
+import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper;
+import org.keycloak.protocol.oidc.mappers.SHA265PairwiseSubMapper;
+import org.keycloak.protocol.oidc.utils.SubjectType;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ForbiddenException;
-import org.keycloak.services.resources.admin.AdminRoot;
import org.keycloak.services.validation.ClientValidator;
+import org.keycloak.services.validation.PairwiseClientValidator;
import org.keycloak.services.validation.ValidationMessages;
import javax.ws.rs.core.Response;
-import java.util.Properties;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -55,7 +55,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
auth.requireCreate();
ValidationMessages validationMessages = new ValidationMessages();
- if (!ClientValidator.validate(client, validationMessages)) {
+ if (!ClientValidator.validate(client, validationMessages) || !PairwiseClientValidator.validate(session, client, validationMessages)) {
String errorCode = validationMessages.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA;
throw new ErrorResponseException(
errorCode,
@@ -66,6 +66,10 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
try {
ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
+ OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
+ if (configWrapper.getSubjectType().equals(SubjectType.PAIRWISE)) {
+ addPairwiseSubMapper(clientModel, configWrapper.getSectorIdentifierUri());
+ }
client = ModelToRepresentation.toRepresentation(clientModel);
@@ -119,7 +123,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
}
ValidationMessages validationMessages = new ValidationMessages();
- if (!ClientValidator.validate(rep, validationMessages)) {
+ if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) {
String errorCode = validationMessages.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA;
throw new ErrorResponseException(
errorCode,
@@ -128,6 +132,7 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
);
}
+ updateSubjectType(rep, client);
RepresentationToModel.updateClient(rep, client);
rep = ModelToRepresentation.toRepresentation(client);
@@ -140,6 +145,43 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist
return rep;
}
+ private void updateSubjectType(ClientRepresentation rep, ClientModel client) {
+ OIDCAdvancedConfigWrapper repConfigWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(rep);
+ SubjectType repSubjectType = repConfigWrapper.getSubjectType();
+ OIDCAdvancedConfigWrapper clientConfigWrapper = OIDCAdvancedConfigWrapper.fromClientModel(client);
+ SubjectType clientSubjectType = clientConfigWrapper.getSubjectType();
+
+ if (repSubjectType.equals(SubjectType.PAIRWISE) && clientSubjectType.equals(SubjectType.PAIRWISE)) {
+ updateSectorIdentifier(client, repConfigWrapper.getSectorIdentifierUri());
+ }
+
+ if (repSubjectType.equals(SubjectType.PAIRWISE) && clientSubjectType.equals(SubjectType.PUBLIC)) {
+ addPairwiseSubMapper(client, repConfigWrapper.getSectorIdentifierUri());
+ }
+
+ if (repSubjectType.equals(SubjectType.PUBLIC) && clientSubjectType.equals(SubjectType.PAIRWISE)) {
+ removePairwiseSubMapper(client);
+ }
+ }
+
+ private void updateSectorIdentifier(ClientModel client, String sectorIdentifierUri) {
+ client.getProtocolMappers().stream().filter(mapping -> mapping.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)).forEach(mapping -> {
+ mapping.getConfig().put(PairwiseSubMapperHelper.SECTOR_IDENTIFIER_URI, sectorIdentifierUri);
+ });
+ }
+
+ private void addPairwiseSubMapper(ClientModel client, String sectorIdentifierUri) {
+ client.addProtocolMapper(SHA265PairwiseSubMapper.createPairwiseMapper(sectorIdentifierUri));
+ }
+
+ private void removePairwiseSubMapper(ClientModel client) {
+ for (ProtocolMapperModel mapping : client.getProtocolMappers()) {
+ if (mapping.getProtocolMapper().endsWith(AbstractPairwiseSubMapper.PROVIDER_ID_SUFFIX)) {
+ client.removeProtocolMapper(mapping);
+ }
+ }
+ }
+
public void delete(String clientId) {
event.event(EventType.CLIENT_DELETE).client(clientId);
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
index a95cc09..c377313 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java
@@ -32,6 +32,7 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.protocol.oidc.utils.JWKSUtils;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
+import org.keycloak.protocol.oidc.utils.SubjectType;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
@@ -53,6 +54,7 @@ public class DescriptionConverter {
public static ClientRepresentation toInternal(KeycloakSession session, OIDCClientRepresentation clientOIDC) throws ClientRegistrationException {
ClientRepresentation client = new ClientRepresentation();
+
client.setClientId(clientOIDC.getClientId());
client.setName(clientOIDC.getClientName());
client.setRedirectUris(clientOIDC.getRedirectUris());
@@ -89,14 +91,12 @@ public class DescriptionConverter {
}
client.setClientAuthenticatorType(clientAuthFactory.getId());
- // Externalize to ClientAuthenticator itself?
- if (authMethod != null && authMethod.equals(OIDCLoginProtocol.PRIVATE_KEY_JWT)) {
-
- PublicKey publicKey = retrievePublicKey(clientOIDC);
- if (publicKey == null) {
- throw new ClientRegistrationException("Didn't find key of supported keyType for use " + JWK.Use.SIG.asString());
- }
+ PublicKey publicKey = retrievePublicKey(session, clientOIDC);
+ if (authMethod != null && authMethod.equals(OIDCLoginProtocol.PRIVATE_KEY_JWT) && publicKey == null) {
+ throw new ClientRegistrationException("Didn't find key of supported keyType for use " + JWK.Use.SIG.asString());
+ }
+ if (publicKey != null) {
String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
CertificateRepresentation rep = new CertificateRepresentation();
@@ -104,20 +104,30 @@ public class DescriptionConverter {
CertificateInfoHelper.updateClientRepresentationCertificateInfo(client, rep, JWTClientAuthenticator.ATTR_PREFIX);
}
+ OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
if (clientOIDC.getUserinfoSignedResponseAlg() != null) {
- String userInfoSignedResponseAlg = clientOIDC.getUserinfoSignedResponseAlg();
- Algorithm algorithm = Enum.valueOf(Algorithm.class, userInfoSignedResponseAlg);
+ Algorithm algorithm = Enum.valueOf(Algorithm.class, clientOIDC.getUserinfoSignedResponseAlg());
+ configWrapper.setUserInfoSignedResponseAlg(algorithm);
+ }
- OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setUserInfoSignedResponseAlg(algorithm);
+ if (clientOIDC.getRequestObjectSigningAlg() != null) {
+ Algorithm algorithm = Enum.valueOf(Algorithm.class, clientOIDC.getRequestObjectSigningAlg());
+ configWrapper.setRequestObjectSignatureAlg(algorithm);
+ }
+
+ SubjectType subjectType = SubjectType.parse(clientOIDC.getSubjectType());
+ configWrapper.setSubjectType(subjectType);
+ if (subjectType.equals(SubjectType.PAIRWISE)) {
+ configWrapper.setSectorIdentifierUri(clientOIDC.getSectorIdentifierUri());
}
return client;
}
- private static PublicKey retrievePublicKey(OIDCClientRepresentation clientOIDC) {
+ private static PublicKey retrievePublicKey(KeycloakSession session, OIDCClientRepresentation clientOIDC) {
if (clientOIDC.getJwksUri() == null && clientOIDC.getJwks() == null) {
- throw new ClientRegistrationException("Requested client authentication method '%s' but jwks_uri nor jwks were available in config");
+ return null;
}
if (clientOIDC.getJwksUri() != null && clientOIDC.getJwks() != null) {
@@ -129,7 +139,7 @@ public class DescriptionConverter {
keySet = clientOIDC.getJwks();
} else {
try {
- keySet = JWKSUtils.sendJwksRequest(clientOIDC.getJwksUri());
+ keySet = JWKSUtils.sendJwksRequest(session, clientOIDC.getJwksUri());
} catch (IOException ioe) {
throw new ClientRegistrationException("Failed to send JWKS request to specified jwks_uri", ioe);
}
@@ -166,6 +176,14 @@ public class DescriptionConverter {
if (config.isUserInfoSignatureRequired()) {
response.setUserinfoSignedResponseAlg(config.getUserInfoSignedResponseAlg().toString());
}
+ if (config.getRequestObjectSignatureAlg() != null) {
+ response.setRequestObjectSigningAlg(config.getRequestObjectSignatureAlg().toString());
+ }
+
+ response.setSubjectType(config.getSubjectType().toString().toLowerCase());
+ if (config.getSubjectType().equals(SubjectType.PAIRWISE)) {
+ response.setSectorIdentifierUri(config.getSectorIdentifierUri());
+ }
return response;
}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 1fae787..b1ae4dd 100644
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -16,15 +16,18 @@
*/
package org.keycloak.services;
+import org.keycloak.credential.UserCredentialStore;
+import org.keycloak.credential.UserCredentialStoreManager;
import org.keycloak.models.*;
import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.cache.UserCache;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.scripting.ScriptingProvider;
import org.keycloak.storage.UserStorageManager;
import org.keycloak.storage.federated.UserFederatedStorageProvider;
+import javax.transaction.TransactionManager;
import java.util.*;
/**
@@ -40,6 +43,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
private RealmProvider model;
private UserProvider userModel;
private UserStorageManager userStorageManager;
+ private UserCredentialStoreManager userCredentialStorageManager;
private ScriptingProvider scriptingProvider;
private UserSessionProvider sessionProvider;
private UserFederationManager federationManager;
@@ -48,7 +52,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
this.factory = factory;
- this.transactionManager = new DefaultKeycloakTransactionManager();
+ this.transactionManager = new DefaultKeycloakTransactionManager(this);
federationManager = new UserFederationManager(this);
context = new DefaultKeycloakContext(this);
}
@@ -68,7 +72,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
private UserProvider getUserProvider() {
- CacheUserProvider cache = getProvider(CacheUserProvider.class);
+ UserCache cache = getProvider(UserCache.class);
if (cache != null) {
return cache;
} else {
@@ -77,6 +81,12 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
@Override
+ public UserCache getUserCache() {
+ return getProvider(UserCache.class);
+
+ }
+
+ @Override
public void enlistForClose(Provider provider) {
closable.add(provider);
}
@@ -126,6 +136,12 @@ public class DefaultKeycloakSession implements KeycloakSession {
}
@Override
+ public UserCredentialManager userCredentialManager() {
+ if (userCredentialStorageManager == null) userCredentialStorageManager = new UserCredentialStoreManager(this);
+ return userCredentialStorageManager;
+ }
+
+ @Override
public UserProvider userStorage() {
if (userModel == null) {
userModel = getUserProvider();
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 45bef3c..36f9b7f 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -20,6 +20,7 @@ import org.keycloak.Config;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.EnvironmentDependentProviderFactory;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
@@ -49,7 +50,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
private Map<Class<? extends Provider>, String> provider = new HashMap<>();
private volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<>();
- private TransactionManager tm;
// TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
protected long serverStartupTimestamp;
@@ -97,8 +97,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
// make the session factory ready for hot deployment
ProviderManagerRegistry.SINGLETON.setDeployer(this);
- JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)getProviderFactory(JtaTransactionManagerLookup.class);
- if (lookup != null) tm = lookup.getTransactionManager();
}
protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = new HashMap<>();
@@ -193,8 +191,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
}
Config.Scope scope = Config.scope(spi.getName(), provider);
- if (scope.getBoolean("enabled", true)) {
-
+ if (isEnabled(factory, scope)) {
factory.init(scope);
if (spi.isInternal() && !isInternal(factory)) {
@@ -205,10 +202,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
logger.debugv("Loaded SPI {0} (provider = {1})", spi.getName(), provider);
}
+
} else {
for (ProviderFactory factory : pm.load(spi)) {
Config.Scope scope = Config.scope(spi.getName(), factory.getId());
- if (scope.getBoolean("enabled", true)) {
+ if (isEnabled(factory, scope)) {
factory.init(scope);
if (spi.isInternal() && !isInternal(factory)) {
@@ -223,7 +221,16 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
}
}
return factoryMap;
+ }
+ private boolean isEnabled(ProviderFactory factory, Config.Scope scope) {
+ if (!scope.getBoolean("enabled", true)) {
+ return false;
+ }
+ if (factory instanceof EnvironmentDependentProviderFactory) {
+ return ((EnvironmentDependentProviderFactory) factory).isSupported();
+ }
+ return true;
}
protected void loadSPIs(ProviderManager pm, List<Spi> spiList) {
@@ -282,9 +289,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
public KeycloakSession create() {
KeycloakSession session = new DefaultKeycloakSession(this);
- if (tm != null) {
- session.getTransactionManager().enlist(new JtaTransactionWrapper(tm));
- }
return session;
}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
index 53f92f4..81379a1 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
@@ -16,9 +16,13 @@
*/
package org.keycloak.services;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.KeycloakTransactionManager;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
+import org.keycloak.transaction.JtaTransactionWrapper;
+import javax.transaction.TransactionManager;
import java.util.LinkedList;
import java.util.List;
@@ -34,6 +38,12 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
private boolean active;
private boolean rollback;
+ private KeycloakSession session;
+ private JTAPolicy jtaPolicy = JTAPolicy.REQUIRES_NEW;
+
+ public DefaultKeycloakTransactionManager(KeycloakSession session) {
+ this.session = session;
+ }
@Override
public void enlist(KeycloakTransaction transaction) {
@@ -63,11 +73,30 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
}
@Override
+ public JTAPolicy getJTAPolicy() {
+ return jtaPolicy;
+ }
+
+ @Override
+ public void setJTAPolicy(JTAPolicy policy) {
+ jtaPolicy = policy;
+
+ }
+
+ @Override
public void begin() {
if (active) {
throw new IllegalStateException("Transaction already active");
}
+ if (jtaPolicy == JTAPolicy.REQUIRES_NEW) {
+ JtaTransactionManagerLookup jtaLookup = session.getProvider(JtaTransactionManagerLookup.class);
+ TransactionManager tm = jtaLookup.getTransactionManager();
+ if (tm != null) {
+ enlist(new JtaTransactionWrapper(tm));
+ }
+ }
+
for (KeycloakTransaction tx : transactions) {
tx.begin();
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index e53ac00..aa536f5 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -408,7 +408,7 @@ public class AuthenticationManager {
// refresh the cookies!
createLoginCookie(session, realm, userSession.getUser(), userSession, uriInfo, clientConnection);
if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
- if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
+ if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getLoginUsername(), uriInfo, clientConnection);
// Update userSession note with authTime. But just if flag SSO_AUTH is not set
if (!isSSOAuthentication(clientSession)) {
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index 7bdf5b1..3de62db 100644
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -265,7 +265,6 @@ public class ClientManager {
InstallationAdapterConfig rep = new InstallationAdapterConfig();
rep.setAuthServerUrl(baseUri.toString());
rep.setRealm(realmModel.getName());
- rep.setRealmKey(realmModel.getPublicKeyPem());
rep.setSslRequired(realmModel.getSslRequired().name().toLowerCase());
if (clientModel.isPublicClient() && !clientModel.isBearerOnly()) rep.setPublicClient(true);
@@ -286,7 +285,6 @@ public class ClientManager {
StringBuffer buffer = new StringBuffer();
buffer.append("<secure-deployment name=\"WAR MODULE NAME.war\">\n");
buffer.append(" <realm>").append(realmModel.getName()).append("</realm>\n");
- buffer.append(" <realm-public-key>").append(realmModel.getPublicKeyPem()).append("</realm-public-key>\n");
buffer.append(" <auth-server-url>").append(baseUri.toString()).append("</auth-server-url>\n");
if (clientModel.isBearerOnly()){
buffer.append(" <bearer-only>true</bearer-only>\n");
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 dd17b07..955dff0 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
@@ -18,9 +18,9 @@ package org.keycloak.services.resources.admin;
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.authorization.admin.AuthorizationService;
+import org.keycloak.common.Profile;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.ClientModel;
@@ -50,11 +50,14 @@ import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.ErrorResponse;
import org.keycloak.common.util.Time;
import org.keycloak.services.validation.ClientValidator;
+import org.keycloak.services.validation.PairwiseClientValidator;
import org.keycloak.services.validation.ValidationMessages;
+import org.keycloak.utils.ProfileHelper;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
+import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
@@ -107,7 +110,7 @@ public class ClientResource {
@Path("protocol-mappers")
public ProtocolMappersResource getProtocolMappers() {
- ProtocolMappersResource mappers = new ProtocolMappersResource(client, auth, adminEvent);
+ ProtocolMappersResource mappers = new ProtocolMappersResource(realm, client, auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(mappers);
return mappers;
}
@@ -127,7 +130,7 @@ public class ClientResource {
}
ValidationMessages validationMessages = new ValidationMessages();
- if (!ClientValidator.validate(rep, validationMessages)) {
+ if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) {
Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
throw new ErrorResponseException(
validationMessages.getStringMessages(),
@@ -152,10 +155,12 @@ public class ClientResource {
RepresentationToModel.updateClient(rep, client);
- if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
- authorization().enable();
- } else {
- authorization().disable();
+ if (Profile.isPreviewEnabled()) {
+ if (TRUE.equals(rep.getAuthorizationServicesEnabled())) {
+ authorization().enable();
+ } else {
+ authorization().disable();
+ }
}
}
@@ -176,7 +181,9 @@ public class ClientResource {
ClientRepresentation representation = ModelToRepresentation.toRepresentation(client);
- representation.setAuthorizationServicesEnabled(authorization().isEnabled());
+ if (Profile.isPreviewEnabled()) {
+ representation.setAuthorizationServicesEnabled(authorization().isEnabled());
+ }
return representation;
}
@@ -561,6 +568,8 @@ public class ClientResource {
@Path("/authz")
public AuthorizationService authorization() {
+ ProfileHelper.requirePreview();
+
AuthorizationService resource = new AuthorizationService(this.session, this.client, this.auth);
ResteasyProviderFactory.getInstance().injectProperties(resource);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
index dacea26..1be0cac 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java
@@ -31,6 +31,7 @@ import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.validation.ClientValidator;
+import org.keycloak.services.validation.PairwiseClientValidator;
import org.keycloak.services.validation.ValidationMessages;
import javax.ws.rs.*;
@@ -120,7 +121,7 @@ public class ClientsResource {
auth.requireManage();
ValidationMessages validationMessages = new ValidationMessages();
- if (!ClientValidator.validate(rep, validationMessages)) {
+ if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) {
Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
throw new ErrorResponseException(
validationMessages.getStringMessages(),
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java
index baf9bb6..761e307 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java
@@ -81,7 +81,7 @@ public class ClientTemplateResource {
@Path("protocol-mappers")
public ProtocolMappersResource getProtocolMappers() {
- ProtocolMappersResource mappers = new ProtocolMappersResource(template, auth, adminEvent);
+ ProtocolMappersResource mappers = new ProtocolMappersResource(realm, template, auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(mappers);
return mappers;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
index 958a849..8e7c9ac 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java
@@ -116,7 +116,7 @@ public class IdentityProvidersResource {
InputPart file = formDataMap.get("file").get(0);
InputStream inputStream = file.getBody(InputStream.class, null);
IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
- Map<String, String> config = providerFactory.parseConfig(inputStream);
+ Map<String, String> config = providerFactory.parseConfig(session, inputStream);
return config;
}
@@ -143,7 +143,7 @@ public class IdentityProvidersResource {
try {
IdentityProviderFactory providerFactory = getProviderFactorytById(providerId);
Map<String, String> config;
- config = providerFactory.parseConfig(inputStream);
+ config = providerFactory.parseConfig(session, inputStream);
return config;
} finally {
try {
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 a7195e1..638e657 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
@@ -43,6 +43,7 @@ import org.keycloak.policy.PasswordPolicyProviderFactory;
import org.keycloak.provider.*;
import org.keycloak.representations.idm.ComponentTypeRepresentation;
import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation;
+import org.keycloak.representations.info.ProfileInfoRepresentation;
import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeProvider;
import org.keycloak.models.KeycloakSession;
@@ -84,6 +85,7 @@ public class ServerInfoAdminResource {
ServerInfoRepresentation info = new ServerInfoRepresentation();
info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp()));
info.setMemoryInfo(MemoryInfoRepresentation.create());
+ info.setProfileInfo(ProfileInfoRepresentation.create());
setSocialProviders(info);
setIdentityProviders(info);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
index b9da2bf..8040820 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
@@ -20,32 +20,26 @@ import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelDuplicateException;
-import org.keycloak.models.ProtocolMapperContainerModel;
-import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.*;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.protocol.ProtocolMapperConfigException;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.services.ErrorResponse;
+import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.resources.admin.RealmAuth.Resource;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
+import javax.ws.rs.*;
import javax.ws.rs.core.Context;
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.LinkedList;
import java.util.List;
+import java.util.Properties;
/**
* Base resource for managing users
@@ -56,6 +50,8 @@ import java.util.List;
public class ProtocolMappersResource {
protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+ protected RealmModel realm;
+
protected ProtocolMapperContainerModel client;
protected RealmAuth auth;
@@ -68,7 +64,8 @@ public class ProtocolMappersResource {
@Context
protected KeycloakSession session;
- public ProtocolMappersResource(ProtocolMapperContainerModel client, RealmAuth auth, AdminEventBuilder adminEvent) {
+ public ProtocolMappersResource(RealmModel realm, ProtocolMapperContainerModel client, RealmAuth auth, AdminEventBuilder adminEvent) {
+ this.realm = realm;
this.auth = auth;
this.client = client;
this.adminEvent = adminEvent.resource(ResourceType.PROTOCOL_MAPPER);
@@ -119,6 +116,7 @@ public class ProtocolMappersResource {
ProtocolMapperModel model = null;
try {
model = RepresentationToModel.toModel(rep);
+ validateModel(model);
model = client.addProtocolMapper(model);
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success();
@@ -146,6 +144,7 @@ public class ProtocolMappersResource {
ProtocolMapperModel model = null;
for (ProtocolMapperRepresentation rep : reps) {
model = RepresentationToModel.toModel(rep);
+ validateModel(model);
model = client.addProtocolMapper(model);
}
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(reps).success();
@@ -216,6 +215,9 @@ public class ProtocolMappersResource {
ProtocolMapperModel model = client.getProtocolMapperById(id);
if (model == null) throw new NotFoundException("Model not found");
model = RepresentationToModel.toModel(rep);
+
+ validateModel(model);
+
client.updateProtocolMapper(model);
adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
}
@@ -242,4 +244,18 @@ public class ProtocolMappersResource {
}
+ private void validateModel(ProtocolMapperModel model) {
+ try {
+ ProtocolMapper mapper = (ProtocolMapper)session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, model.getProtocolMapper());
+ if (mapper != null) {
+ mapper.validateConfig(session, realm, client, model);
+ }
+ } catch (ProtocolMapperConfigException 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.getMessageKey(), ex.getMessage()), ex.getParameters()),
+ Response.Status.BAD_REQUEST);
+ }
+ }
+
}
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 4fd941b..1caa5fb 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
@@ -34,9 +34,8 @@ import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
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.Constants;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
@@ -44,7 +43,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.cache.CacheRealmProvider;
-import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.cache.UserCache;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
@@ -55,7 +54,6 @@ import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
-import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.PartialImportRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -67,7 +65,6 @@ import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.UsersSyncManager;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.resources.admin.RealmAuth.Resource;
-import org.keycloak.timer.TimerProvider;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -85,8 +82,6 @@ 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.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -287,7 +282,7 @@ public class RealmAdminResource {
logger.debug("updating realm: " + realm.getName());
try {
- if (!"GENERATE".equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) {
+ if (!Constants.GENERATE.equals(rep.getPublicKey()) && (rep.getPrivateKey() != null && rep.getPublicKey() != null)) {
try {
KeyPairVerifier.verify(rep.getPrivateKey(), rep.getPublicKey());
} catch (VerificationException e) {
@@ -295,7 +290,7 @@ public class RealmAdminResource {
}
}
- if (!"GENERATE".equals(rep.getPublicKey()) && (rep.getCertificate() != null)) {
+ if (!Constants.GENERATE.equals(rep.getPublicKey()) && (rep.getCertificate() != null)) {
try {
X509Certificate cert = PemUtils.decodeCertificate(rep.getCertificate());
if (cert == null) {
@@ -866,7 +861,7 @@ public class RealmAdminResource {
public void clearUserCache() {
auth.requireManage();
- CacheUserProvider cache = session.getProvider(CacheUserProvider.class);
+ UserCache cache = session.getProvider(UserCache.class);
if (cache != null) {
cache.clear();
}
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 3986988..26daf84 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
@@ -184,6 +184,8 @@ public class UsersResource {
return ErrorResponse.exists("User is read only!");
} catch (ModelException me) {
return ErrorResponse.exists("Could not update user!");
+ } catch (Exception me) { // JPA may be committed by JTA which can't
+ return ErrorResponse.exists("Could not update user!");
}
}
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 6cdf52e..68e0806 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -45,10 +45,13 @@ import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner;
import org.keycloak.services.util.JsonConfigProvider;
import org.keycloak.services.util.ObjectMapperResolver;
import org.keycloak.timer.TimerProvider;
+import org.keycloak.transaction.JtaTransactionManagerLookup;
import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.SystemEnvProperties;
import javax.servlet.ServletContext;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
@@ -155,11 +158,28 @@ public class KeycloakApplication extends Application {
// Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock
protected ExportImportManager migrateAndBootstrap() {
ExportImportManager exportImportManager;
+ logger.debug("Calling migrateModel");
migrateModel();
+ logger.debug("bootstrap");
KeycloakSession session = sessionFactory.create();
try {
session.getTransactionManager().begin();
+ JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup) sessionFactory.getProviderFactory(JtaTransactionManagerLookup.class);
+ if (lookup != null) {
+ if (lookup.getTransactionManager() != null) {
+ try {
+ Transaction transaction = lookup.getTransactionManager().getTransaction();
+ logger.debugv("bootstrap current transaction? {0}", transaction != null);
+ if (transaction != null) {
+ logger.debugv("bootstrap current transaction status? {0}", transaction.getStatus());
+ }
+ } catch (SystemException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
exportImportManager = new ExportImportManager(session);
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 1e42e7b..d0ca449 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -35,6 +35,7 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.ResolveRelative;
+import org.keycloak.utils.ProfileHelper;
import org.keycloak.wellknown.WellKnownProvider;
import javax.ws.rs.GET;
@@ -254,6 +255,8 @@ public class RealmsResource {
@Path("{realm}/authz")
public Object getAuthorizationService(@PathParam("realm") String name) {
+ ProfileHelper.requirePreview();
+
init(name);
AuthorizationProvider authorization = this.session.getProvider(AuthorizationProvider.class);
AuthorizationService service = new AuthorizationService(authorization);
diff --git a/services/src/main/java/org/keycloak/services/ServicesLogger.java b/services/src/main/java/org/keycloak/services/ServicesLogger.java
index de7eef7..0595abb 100644
--- a/services/src/main/java/org/keycloak/services/ServicesLogger.java
+++ b/services/src/main/java/org/keycloak/services/ServicesLogger.java
@@ -430,4 +430,12 @@ public interface ServicesLogger extends BasicLogger {
@Message(id=96, value="Not found JWK of supported keyType under jwks_uri for usage: %s")
void supportedJwkNotFound(String usage);
+ @LogMessage(level = WARN)
+ @Message(id=97, value="Invalid request")
+ void invalidRequest(@Cause Throwable t);
+
+ @LogMessage(level = ERROR)
+ @Message(id=98, value="Failed to get redirect uris from sector identifier URI: %s")
+ void failedToGetRedirectUrisFromSectorIdentifierUri(@Cause Throwable t, String sectorIdentifierUri);
+
}
diff --git a/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java b/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java
index b309927..359d28d 100644
--- a/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/CertificateInfoHelper.java
@@ -17,9 +17,18 @@
package org.keycloak.services.util;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
import java.util.HashMap;
+import javax.ws.rs.core.Response;
+
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.ClientAuthenticationFlowContext;
+import org.keycloak.authentication.authenticators.client.ClientAuthUtil;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.CertificateRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
@@ -34,6 +43,8 @@ public class CertificateInfoHelper {
public static final String PUBLIC_KEY = "public.key";
+ // CLIENT MODEL METHODS
+
public static CertificateRepresentation getCertificateFromClient(ClientModel client, String attributePrefix) {
String privateKeyAttribute = attributePrefix + "." + PRIVATE_KEY;
String certificateAttribute = attributePrefix + "." + X509CERTIFICATE;
@@ -75,6 +86,32 @@ public class CertificateInfoHelper {
}
+ public static PublicKey getSignatureValidationKey(ClientModel client, String attributePrefix) throws ModelException {
+ CertificateRepresentation certInfo = getCertificateFromClient(client, attributePrefix);
+
+ String encodedCertificate = certInfo.getCertificate();
+ String encodedPublicKey = certInfo.getPublicKey();
+
+ if (encodedCertificate == null && encodedPublicKey == null) {
+ throw new ModelException("Client doesn't have certificate or publicKey configured");
+ }
+
+ if (encodedCertificate != null && encodedPublicKey != null) {
+ throw new ModelException("Client has both publicKey and certificate configured");
+ }
+
+ // TODO: Caching of publicKeys / certificates, so it doesn't need to be always computed from pem. For performance reasons...
+ if (encodedCertificate != null) {
+ X509Certificate clientCert = KeycloakModelUtils.getCertificate(encodedCertificate);
+ return clientCert.getPublicKey();
+ } else {
+ return KeycloakModelUtils.getPublicKey(encodedPublicKey);
+ }
+ }
+
+
+ // CLIENT REPRESENTATION METHODS
+
public static void updateClientRepresentationCertificateInfo(ClientRepresentation client, CertificateRepresentation rep, String attributePrefix) {
String privateKeyAttribute = attributePrefix + "." + PRIVATE_KEY;
String certificateAttribute = attributePrefix + "." + X509CERTIFICATE;
diff --git a/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java b/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java
new file mode 100644
index 0000000..6cc2033
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java
@@ -0,0 +1,41 @@
+package org.keycloak.services.validation;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.protocol.ProtocolMapperConfigException;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
+import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator;
+import org.keycloak.protocol.oidc.utils.SubjectType;
+import org.keycloak.representations.idm.ClientRepresentation;
+
+import java.util.HashSet;
+import java.util.Set;
+
+
+/**
+ * @author <a href="mailto:martin.hardselius@gmail.com">Martin Hardselius</a>
+ */
+public class PairwiseClientValidator {
+
+ public static boolean validate(KeycloakSession session, ClientRepresentation client, ValidationMessages messages) {
+ OIDCAdvancedConfigWrapper configWrapper = OIDCAdvancedConfigWrapper.fromClientRepresentation(client);
+ if (configWrapper.getSubjectType().equals(SubjectType.PAIRWISE)) {
+ String sectorIdentifierUri = configWrapper.getSectorIdentifierUri();
+ String rootUrl = client.getRootUrl();
+ Set<String> redirectUris = new HashSet<>();
+ if (client.getRedirectUris() != null) redirectUris.addAll(client.getRedirectUris());
+ return validate(session, rootUrl, redirectUris, sectorIdentifierUri, messages);
+ }
+ return true;
+ }
+
+ public static boolean validate(KeycloakSession session, String rootUrl, Set<String> redirectUris, String sectorIdentifierUri, ValidationMessages messages) {
+ try {
+ PairwiseSubMapperValidator.validate(session, rootUrl, redirectUris, sectorIdentifierUri);
+ } catch (ProtocolMapperConfigException e) {
+ messages.add(e.getMessage(), e.getMessageKey());
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java b/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java
index e26ebff..a7e82c8 100644
--- a/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java
+++ b/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java
@@ -57,8 +57,11 @@ public class ValidationMessages {
}
public boolean fieldHasError(String fieldId) {
+ if (fieldId == null) {
+ return false;
+ }
for (ValidationMessage message : messages) {
- if (message.getFieldId().equals(fieldId)) {
+ if (fieldId.equals(message.getFieldId())) {
return true;
}
}
diff --git a/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java b/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java
index ecc3071..e387ff4 100644
--- a/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java
+++ b/services/src/main/java/org/keycloak/transaction/JtaTransactionWrapper.java
@@ -16,7 +16,9 @@
*/
package org.keycloak.transaction;
+import org.jboss.logging.Logger;
import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.storage.UserStorageManager;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
@@ -31,16 +33,22 @@ import javax.transaction.UserTransaction;
* @version $Revision: 1 $
*/
public class JtaTransactionWrapper implements KeycloakTransaction {
+ private static final Logger logger = Logger.getLogger(JtaTransactionWrapper.class);
protected TransactionManager tm;
protected Transaction ut;
protected Transaction suspended;
+ protected Exception ended;
public JtaTransactionWrapper(TransactionManager tm) {
this.tm = tm;
try {
+
suspended = tm.suspend();
+ logger.debug("new JtaTransactionWrapper");
+ logger.debugv("was existing? {0}", suspended != null);
tm.begin();
ut = tm.getTransaction();
+ //ended = new Exception();
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -53,16 +61,20 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
@Override
public void commit() {
try {
- ut.commit();
+ logger.debug("JtaTransactionWrapper commit");
+ tm.commit();
} catch (Exception e) {
throw new RuntimeException(e);
+ } finally {
+ end();
}
}
@Override
public void rollback() {
try {
- ut.rollback();
+ logger.debug("JtaTransactionWrapper rollback");
+ tm.rollback();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
@@ -74,7 +86,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
@Override
public void setRollbackOnly() {
try {
- ut.setRollbackOnly();
+ tm.setRollbackOnly();
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -83,7 +95,7 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
@Override
public boolean getRollbackOnly() {
try {
- return ut.getStatus() == Status.STATUS_MARKED_ROLLBACK;
+ return tm.getStatus() == Status.STATUS_MARKED_ROLLBACK;
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -92,15 +104,28 @@ public class JtaTransactionWrapper implements KeycloakTransaction {
@Override
public boolean isActive() {
try {
- return ut.getStatus() == Status.STATUS_ACTIVE;
+ return tm.getStatus() == Status.STATUS_ACTIVE;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
+ /*
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (ended != null) {
+ logger.error("TX didn't close at position", ended);
+ }
+
+ }
+ */
protected void end() {
+ ended = null;
+ logger.debug("JtaTransactionWrapper end");
if (suspended != null) {
try {
+ logger.debug("JtaTransactionWrapper resuming suspended");
tm.resume(suspended);
} catch (Exception e) {
throw new RuntimeException(e);
diff --git a/services/src/main/java/org/keycloak/utils/ProfileHelper.java b/services/src/main/java/org/keycloak/utils/ProfileHelper.java
new file mode 100644
index 0000000..719bd24
--- /dev/null
+++ b/services/src/main/java/org/keycloak/utils/ProfileHelper.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import org.keycloak.common.Profile;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProfileHelper {
+
+ public static void requirePreview() {
+ if (!Profile.isPreviewEnabled()) {
+ throw new WebApplicationException("Feature not available in current profile", Response.Status.NOT_IMPLEMENTED);
+ }
+ }
+
+}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
index 97c9299..fa7ee28 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -20,6 +20,7 @@ org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory
org.keycloak.authentication.authenticators.browser.OTPFormAuthenticatorFactory
org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory
org.keycloak.authentication.authenticators.browser.SpnegoAuthenticatorFactory
+org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticatorFactory
org.keycloak.authentication.authenticators.directgrant.ValidateOTP
org.keycloak.authentication.authenticators.directgrant.ValidatePassword
org.keycloak.authentication.authenticators.directgrant.ValidateUsername
@@ -33,4 +34,4 @@ org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFac
org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory
org.keycloak.authentication.authenticators.broker.IdpUsernamePasswordFormFactory
org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticatorFactory
-org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticator
+org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticator
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
index 9d1e10e..77830dc 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
+++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
@@ -34,5 +34,5 @@ org.keycloak.protocol.saml.mappers.UserSessionNoteStatementMapper
org.keycloak.protocol.saml.mappers.GroupMembershipMapper
org.keycloak.protocol.oidc.mappers.UserClientRoleMappingMapper
org.keycloak.protocol.oidc.mappers.UserRealmRoleMappingMapper
-
+org.keycloak.protocol.oidc.mappers.SHA265PairwiseSubMapper
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 77cba5e..55b31a0 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
@@ -18,5 +18,4 @@
org.keycloak.exportimport.ClientDescriptionConverterSpi
org.keycloak.wellknown.WellKnownSpi
org.keycloak.services.clientregistration.ClientRegistrationSpi
-org.keycloak.transaction.TransactionManagerLookupSpi
testsuite/integration/pom.xml 53(+53 -0)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index aca1d78..1e92f9b 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -166,6 +166,14 @@
<groupId>org.keycloak</groupId>
<artifactId>federation-properties-example</artifactId>
</dependency>
+
+ <!-- Dependency on services from integration-arquillian -->
+ <dependency>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-testsuite-providers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
@@ -503,6 +511,51 @@
</profile>
<profile>
+ <id>mongodb-server</id>
+
+ <properties>
+ <keycloak.connectionsMongo.host>localhost</keycloak.connectionsMongo.host>
+ <keycloak.connectionsMongo.port>27018</keycloak.connectionsMongo.port>
+ <keycloak.connectionsMongo.db>keycloak</keycloak.connectionsMongo.db>
+ <keycloak.connectionsMongo.bindIp>127.0.0.1</keycloak.connectionsMongo.bindIp>
+ </properties>
+
+ <build>
+ <plugins>
+
+ <!-- Embedded mongo -->
+ <plugin>
+ <groupId>com.github.joelittlejohn.embedmongo</groupId>
+ <artifactId>embedmongo-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>start-mongodb</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>start</goal>
+ </goals>
+ <configuration>
+ <port>${keycloak.connectionsMongo.port}</port>
+ <logging>file</logging>
+ <logFile>${project.build.directory}/mongodb.log</logFile>
+ <bindIp>${keycloak.connectionsMongo.bindIp}</bindIp>
+ </configuration>
+ </execution>
+ <execution>
+ <id>stop-mongodb</id>
+ <phase>post-integration-test</phase>
+ <goals>
+ <goal>stop</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ </profile>
+
+ <profile>
<id>clean-jpa</id>
<build>
<plugins>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
index 3b1b440..67b82bb 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
@@ -287,6 +287,10 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+
+ // Assert user's email is verified now
+ UserModel user = getFederatedUser();
+ Assert.assertTrue(user.isEmailVerified());
}
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 7683d0c..f6a85e4 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
@@ -45,8 +45,8 @@ 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.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java
index 7c1f47b..36c291f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java
@@ -20,19 +20,19 @@ package org.keycloak.testsuite.broker;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
-import org.keycloak.testsuite.KeycloakServer;
-import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
@@ -94,6 +94,16 @@ public class IdentityProviderHintTest {
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
- assertEquals("Could not find an identity provider with the identifier.", this.driver.findElement(By.className("instruction")).getText());
+ System.out.println(driver.getPageSource());
+ assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+ }
+
+ private AuthenticationExecutionInfoRepresentation findExecution(RealmResource realm) {
+ for (AuthenticationExecutionInfoRepresentation e : realm.flows().getExecutions("browser")) {
+ if (e.getProviderId().equals("identity-provider-redirector")) {
+ return e;
+ }
+ }
+ return null;
}
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java
index 5d327f4..3831787 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java
@@ -32,7 +32,7 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.testsuite.DummyUserFederationProviderFactory;
+import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java
index 84a901a..efa688c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java
@@ -36,7 +36,7 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.UsersSyncManager;
-import org.keycloak.testsuite.DummyUserFederationProviderFactory;
+import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.timer.TimerProvider;
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
index b3ad0c4..2fb2aaf 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ConcurrentTransactionsTest.java
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.model;
+import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
@@ -151,14 +152,17 @@ public class ConcurrentTransactionsTest extends AbstractModelTest {
}
- // KEYCLOAK-3296
+ // KEYCLOAK-3296 , KEYCLOAK-3494
@Test
public void removeUserAttribute() throws Exception {
RealmModel realm = realmManager.createRealm("original");
KeycloakSession session = realmManager.getSession();
- UserModel user = session.users().addUser(realm, "john");
- user.setSingleAttribute("foo", "val1");
+ UserModel john = session.users().addUser(realm, "john");
+ john.setSingleAttribute("foo", "val1");
+
+ UserModel john2 = session.users().addUser(realm, "john2");
+ john2.setAttribute("foo", Arrays.asList("val1", "val2"));
final KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
commit();
@@ -182,12 +186,18 @@ public class ConcurrentTransactionsTest extends AbstractModelTest {
UserModel john = session.users().getUserByUsername("john", realm);
String attrVal = john.getFirstAttribute("foo");
+ UserModel john2 = session.users().getUserByUsername("john2", realm);
+ String attrVal2 = john2.getFirstAttribute("foo");
+
// Wait until it's read in both threads
readAttrLatch.countDown();
readAttrLatch.await();
- // Remove user attribute in both threads
+ // KEYCLOAK-3296 : Remove user attribute in both threads
john.removeAttribute("foo");
+
+ // KEYCLOAK-3494 : Set single attribute in both threads
+ john2.setSingleAttribute("foo", "bar");
} catch (Exception e) {
throw new RuntimeException(e);
}
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 4b3f78e..e2af241 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
@@ -211,6 +211,31 @@ public class UserModelTest extends AbstractModelTest {
Assert.assertEquals("val23", attrVals.get(0));
}
+ // KEYCLOAK-3494
+ @Test
+ public void testUpdateUserAttribute() throws Exception {
+ RealmModel realm = realmManager.createRealm("original");
+ UserModel user = session.users().addUser(realm, "user");
+
+ user.setSingleAttribute("key1", "value1");
+
+ commit();
+
+ realm = realmManager.getRealmByName("original");
+ user = session.users().getUserByUsername("user", realm);
+
+ // Update attribute
+ List<String> attrVals = new ArrayList<>(Arrays.asList( "val2" ));
+ user.setAttribute("key1", attrVals);
+ Map<String, List<String>> allAttrVals = user.getAttributes();
+
+ // Ensure same transaction is able to see updated value
+ Assert.assertEquals(1, allAttrVals.size());
+ Assert.assertEquals(allAttrVals.get("key1"), Arrays.asList("val2"));
+
+ commit();
+ }
+
@Test
public void testSearchByString() {
RealmModel realm = realmManager.createRealm("original");
diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index 5d5369c..f0ff6ac 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -74,4 +74,7 @@ log4j.logger.org.apache.directory.server.core=warn
log4j.logger.org.apache.directory.server.ldap.LdapProtocolHandler=error
# Enable to view HttpClient connection pool activity
-#log4j.logger.org.apache.http.impl.conn=debug
\ No newline at end of file
+#log4j.logger.org.apache.http.impl.conn=debug
+
+# Enable to view details from identity provider authenticator
+# log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
index 1b4de73..d4a16d3 100755
--- a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
+++ b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
@@ -15,5 +15,4 @@
# limitations under the License.
#
-org.keycloak.testsuite.DummyUserFederationProviderFactory
org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml
index 977fba8..7bada0c 100644
--- a/testsuite/integration-arquillian/pom.xml
+++ b/testsuite/integration-arquillian/pom.xml
@@ -113,12 +113,17 @@
<artifactId>xml-maven-plugin</artifactId>
<version>1.0.1</version>
</plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.0.0</version>
+ </plugin>
</plugins>
</pluginManagement>
</build>
<modules>
<module>test-apps</module>
+ <module>test-utils</module>
<module>servers</module>
<module>tests</module>
</modules>
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl
index e42a21c..bf199cb 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl
@@ -17,12 +17,8 @@
<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">
+ exclude-result-prefixes="xalan">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
<xsl:strip-space elements="*"/>
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl
index 589ee4c..445b973 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl
@@ -1,11 +1,7 @@
<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">
+ exclude-result-prefixes="xalan">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
<xsl:strip-space elements="*"/>
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl
index f32c036..d104e37 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl
@@ -36,9 +36,14 @@
</provider>
</spi>
</xsl:variable>
+ <xsl:variable name="themeModuleDefinition">
+ <modules>
+ <module>org.keycloak.testsuite.integration-arquillian-testsuite-providers</module>
+ </modules>
+ </xsl:variable>
- <!--inject provider-->
- <xsl:template match="//*[local-name()='providers']/*[local-name()='provider']">
+ <!--inject provider; note: due to ibmjdk issues it tries to find out provider which has no attributes-->
+ <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsKS)]//*[local-name()='provider' and not(@*)]">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
@@ -46,6 +51,14 @@
<xsl:text>module:org.keycloak.testsuite.integration-arquillian-testsuite-providers</xsl:text>
</provider>
</xsl:template>
+
+ <!--inject provider for themes -->
+ <xsl:template match="//*[local-name()='theme']">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ <xsl:copy-of select="$themeModuleDefinition"/>
+ </xsl:copy>
+ </xsl:template>
<!--inject truststore-->
<xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsKS)]">
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingExportImportResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingExportImportResource.java
new file mode 100644
index 0000000..4f9151c
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingExportImportResource.java
@@ -0,0 +1,142 @@
+/*
+ * 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.rest.resource;
+
+import java.io.File;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.keycloak.exportimport.ExportImportManager;
+import org.keycloak.models.KeycloakSession;
+
+import static org.keycloak.exportimport.ExportImportConfig.ACTION;
+import static org.keycloak.exportimport.ExportImportConfig.DEFAULT_USERS_PER_FILE;
+import static org.keycloak.exportimport.ExportImportConfig.DIR;
+import static org.keycloak.exportimport.ExportImportConfig.FILE;
+import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
+import static org.keycloak.exportimport.ExportImportConfig.REALM_NAME;
+import static org.keycloak.exportimport.ExportImportConfig.USERS_PER_FILE;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TestingExportImportResource {
+
+ private final KeycloakSession session;
+
+ public TestingExportImportResource(KeycloakSession session) {
+ this.session = session;
+ }
+
+ @GET
+ @Path("/run-import")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response runImport() {
+ new ExportImportManager(session).runImport();
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/run-export")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response runExport() {
+ new ExportImportManager(session).runExport();
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("/get-users-per-file")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Integer getUsersPerFile() {
+ String usersPerFile = System.getProperty(USERS_PER_FILE, String.valueOf(DEFAULT_USERS_PER_FILE));
+ return Integer.parseInt(usersPerFile.trim());
+ }
+
+ @PUT
+ @Path("/set-users-per-file")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile) {
+ System.setProperty(USERS_PER_FILE, String.valueOf(usersPerFile));
+ }
+
+ @GET
+ @Path("/get-dir")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public String getDir() {
+ return System.getProperty(DIR);
+ }
+
+ @PUT
+ @Path("/set-dir")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public String setDir(@QueryParam("dir") String dir) {
+ return System.setProperty(DIR, dir);
+ }
+
+ @PUT
+ @Path("/export-import-provider")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider) {
+ System.setProperty(PROVIDER, exportImportProvider);
+ }
+
+ @PUT
+ @Path("/export-import-file")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void setFile(@QueryParam("file") String file) {
+ System.setProperty(FILE, file);
+ }
+
+ @PUT
+ @Path("/export-import-action")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void setAction(@QueryParam("exportImportAction") String exportImportAction) {
+ System.setProperty(ACTION, exportImportAction);
+ }
+
+ @PUT
+ @Path("/set-realm-name")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void setRealmName(@QueryParam("realmName") String realmName) {
+ if (realmName != null && !realmName.isEmpty()) {
+ System.setProperty(REALM_NAME, realmName);
+ } else {
+ System.getProperties().remove(REALM_NAME);
+ }
+ }
+
+ @GET
+ @Path("/get-test-dir")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public String getExportImportTestDirectory() {
+ System.setProperty("project.build.directory", "target");
+ String absolutePath = new File(System.getProperty("project.build.directory", "target")).getAbsolutePath();
+ return absolutePath;
+ }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
new file mode 100644
index 0000000..8b5df6c
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestingOIDCEndpointsApplicationResource.java
@@ -0,0 +1,152 @@
+/*
+ * 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.rest.resource;
+
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.BadRequestException;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.jose.jwk.JSONWebKeySet;
+import org.keycloak.jose.jwk.JWK;
+import org.keycloak.jose.jwk.JWKBuilder;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.testsuite.rest.TestApplicationResourceProviderFactory;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TestingOIDCEndpointsApplicationResource {
+
+ public static final String PRIVATE_KEY = "privateKey";
+ public static final String PUBLIC_KEY = "publicKey";
+
+ private final TestApplicationResourceProviderFactory.OIDCClientData clientData;
+
+ public TestingOIDCEndpointsApplicationResource(TestApplicationResourceProviderFactory.OIDCClientData oidcClientData) {
+ this.clientData = oidcClientData;
+ }
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/generate-keys")
+ @NoCache
+ public Map<String, String> generateKeys() {
+ try {
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+ generator.initialize(2048);
+ clientData.setSigningKeyPair(generator.generateKeyPair());
+ } catch (NoSuchAlgorithmException e) {
+ throw new BadRequestException("Error generating signing keypair", e);
+ }
+
+ String privateKeyPem = KeycloakModelUtils.getPemFromKey(clientData.getSigningKeyPair().getPrivate());
+ String publicKeyPem = KeycloakModelUtils.getPemFromKey(clientData.getSigningKeyPair().getPublic());
+
+ Map<String, String> res = new HashMap<>();
+ res.put(PRIVATE_KEY, privateKeyPem);
+ res.put(PUBLIC_KEY, publicKeyPem);
+ return res;
+ }
+
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/get-jwks")
+ @NoCache
+ public JSONWebKeySet getJwks() {
+ JSONWebKeySet keySet = new JSONWebKeySet();
+
+ if (clientData.getSigningKeyPair() == null) {
+ keySet.setKeys(new JWK[] {});
+ } else {
+ keySet.setKeys(new JWK[] { JWKBuilder.create().rs256(clientData.getSigningKeyPair().getPublic()) });
+ }
+
+ return keySet;
+ }
+
+
+ @GET
+ @Path("/set-oidc-request")
+ @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
+ @NoCache
+ public void setOIDCRequest(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId,
+ @QueryParam("redirectUri") String redirectUri, @QueryParam("maxAge") String maxAge,
+ @QueryParam("jwaAlgorithm") String jwaAlgorithm) {
+ Map<String, Object> oidcRequest = new HashMap<>();
+ oidcRequest.put(OIDCLoginProtocol.CLIENT_ID_PARAM, clientId);
+ oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
+ oidcRequest.put(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
+ if (maxAge != null) {
+ oidcRequest.put(OIDCLoginProtocol.MAX_AGE_PARAM, Integer.parseInt(maxAge));
+ }
+
+ Algorithm alg = Enum.valueOf(Algorithm.class, jwaAlgorithm);
+ if (alg == Algorithm.none) {
+ clientData.setOidcRequest(new JWSBuilder().jsonContent(oidcRequest).none());
+ } else if (alg == Algorithm.RS256) {
+ if (clientData.getSigningKeyPair() == null) {
+ throw new BadRequestException("Requested RS256, but signing key not set");
+ }
+
+ PrivateKey privateKey = clientData.getSigningKeyPair().getPrivate();
+ clientData.setOidcRequest(new JWSBuilder().jsonContent(oidcRequest).rsa256(privateKey));
+ } else {
+ throw new BadRequestException("Unknown argument: " + jwaAlgorithm);
+ }
+ }
+
+
+ @GET
+ @Path("/get-oidc-request")
+ @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
+ @NoCache
+ public String getOIDCRequest() {
+ return clientData.getOidcRequest();
+ }
+
+ @GET
+ @Path("/set-sector-identifier-redirect-uris")
+ @Produces(MediaType.APPLICATION_JSON)
+ public void setSectorIdentifierRedirectUris(@QueryParam("redirectUris") List<String> redirectUris) {
+ clientData.setSectorIdentifierRedirectUris(new ArrayList<>());
+ clientData.getSectorIdentifierRedirectUris().addAll(redirectUris);
+ }
+
+ @GET
+ @Path("/get-sector-identifier-redirect-uris")
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<String> getSectorIdentifierRedirectUris() {
+ return clientData.getSectorIdentifierRedirectUris();
+ }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java
index bc63c99..5f392a0 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProvider.java
@@ -27,6 +27,8 @@ import org.keycloak.representations.adapters.action.PushNotBeforeAction;
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resources.RealmsResource;
+import org.keycloak.testsuite.rest.resource.TestingExportImportResource;
+import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -53,14 +55,16 @@ public class TestApplicationResourceProvider implements RealmResourceProvider {
private final BlockingQueue<LogoutAction> adminLogoutActions;
private final BlockingQueue<PushNotBeforeAction> adminPushNotBeforeActions;
private final BlockingQueue<TestAvailabilityAction> adminTestAvailabilityAction;
+ private final TestApplicationResourceProviderFactory.OIDCClientData oidcClientData;
public TestApplicationResourceProvider(KeycloakSession session, BlockingQueue<LogoutAction> adminLogoutActions,
BlockingQueue<PushNotBeforeAction> adminPushNotBeforeActions,
- BlockingQueue<TestAvailabilityAction> adminTestAvailabilityAction) {
+ BlockingQueue<TestAvailabilityAction> adminTestAvailabilityAction, TestApplicationResourceProviderFactory.OIDCClientData oidcClientData) {
this.session = session;
this.adminLogoutActions = adminLogoutActions;
this.adminPushNotBeforeActions = adminPushNotBeforeActions;
this.adminTestAvailabilityAction = adminTestAvailabilityAction;
+ this.oidcClientData = oidcClientData;
}
@POST
@@ -164,6 +168,11 @@ public class TestApplicationResourceProvider implements RealmResourceProvider {
return sb.toString();
}
+ @Path("/oidc-client-endpoints")
+ public TestingOIDCEndpointsApplicationResource getTestingOIDCClientEndpoints() {
+ return new TestingOIDCEndpointsApplicationResource(oidcClientData);
+ }
+
@Override
public Object getResource() {
return this;
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java
index 98ca2ba..d8d2a8d 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestApplicationResourceProviderFactory.java
@@ -18,16 +18,16 @@
package org.keycloak.testsuite.rest;
import org.keycloak.Config.Scope;
-import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.representations.adapters.action.AdminAction;
import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resource.RealmResourceProviderFactory;
+import java.security.KeyPair;
+import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
@@ -40,9 +40,11 @@ public class TestApplicationResourceProviderFactory implements RealmResourceProv
private BlockingQueue<PushNotBeforeAction> pushNotBeforeActions = new LinkedBlockingDeque<>();
private BlockingQueue<TestAvailabilityAction> testAvailabilityActions = new LinkedBlockingDeque<>();
+ private final OIDCClientData oidcClientData = new OIDCClientData();
+
@Override
public RealmResourceProvider create(KeycloakSession session) {
- return new TestApplicationResourceProvider(session, adminLogoutActions, pushNotBeforeActions, testAvailabilityActions);
+ return new TestApplicationResourceProvider(session, adminLogoutActions, pushNotBeforeActions, testAvailabilityActions, oidcClientData);
}
@Override
@@ -62,4 +64,35 @@ public class TestApplicationResourceProviderFactory implements RealmResourceProv
return "app";
}
+
+ public static class OIDCClientData {
+
+ private KeyPair signingKeyPair;
+ private String oidcRequest;
+ private List<String> sectorIdentifierRedirectUris;
+
+ public KeyPair getSigningKeyPair() {
+ return signingKeyPair;
+ }
+
+ public void setSigningKeyPair(KeyPair signingKeyPair) {
+ this.signingKeyPair = signingKeyPair;
+ }
+
+ public String getOidcRequest() {
+ return oidcRequest;
+ }
+
+ public void setOidcRequest(String oidcRequest) {
+ this.oidcRequest = oidcRequest;
+ }
+
+ public List<String> getSectorIdentifierRedirectUris() {
+ return sectorIdentifierRedirectUris;
+ }
+
+ public void setSectorIdentifierRedirectUris(List<String> sectorIdentifierRedirectUris) {
+ this.sectorIdentifierRedirectUris = sectorIdentifierRedirectUris;
+ }
+ }
}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index b658d35..2085cfa 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -23,15 +23,20 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import org.infinispan.Cache;
+import org.keycloak.OAuth2Constants;
import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.ResourceType;
+import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.services.managers.ClientSessionCode;
@@ -76,6 +81,7 @@ import org.keycloak.models.UserProvider;
import org.keycloak.representations.idm.AuthDetailsRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.rest.resource.TestingExportImportResource;
import static org.keycloak.exportimport.ExportImportConfig.*;
@@ -565,22 +571,6 @@ public class TestingResourceProvider implements RealmResourceProvider {
}
@GET
- @Path("/run-import")
- @Produces(MediaType.APPLICATION_JSON)
- public Response runImport() {
- new ExportImportManager(session).runImport();
- return Response.ok().build();
- }
-
- @GET
- @Path("/run-export")
- @Produces(MediaType.APPLICATION_JSON)
- public Response runExport() {
- new ExportImportManager(session).runExport();
- return Response.ok().build();
- }
-
- @GET
@Path("/valid-credentials")
@Produces(MediaType.APPLICATION_JSON)
public boolean validCredentials(@QueryParam("realmName") String realmName, @QueryParam("userName") String userName, @QueryParam("password") String password) {
@@ -647,83 +637,14 @@ public class TestingResourceProvider implements RealmResourceProvider {
return ModelToRepresentation.toRepresentation(user);
}
+ @Path("/export-import")
+ public TestingExportImportResource getExportImportResource() {
+ return new TestingExportImportResource(session);
+ }
+
private RealmModel getRealmByName(String realmName) {
RealmProvider realmProvider = session.getProvider(RealmProvider.class);
return realmProvider.getRealmByName(realmName);
}
- @GET
- @Path("/get-users-per-file")
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- public Integer getUsersPerFile() {
- String usersPerFile = System.getProperty(USERS_PER_FILE, String.valueOf(DEFAULT_USERS_PER_FILE));
- return Integer.parseInt(usersPerFile.trim());
- }
-
- @PUT
- @Path("/set-users-per-file")
- @Consumes(MediaType.APPLICATION_JSON)
- public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile) {
- System.setProperty(USERS_PER_FILE, String.valueOf(usersPerFile));
- }
-
- @GET
- @Path("/get-dir")
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- public String getDir() {
- return System.getProperty(DIR);
- }
-
- @PUT
- @Path("/set-dir")
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- public String setDir(@QueryParam("dir") String dir) {
- return System.setProperty(DIR, dir);
- }
-
- @PUT
- @Path("/export-import-provider")
- @Consumes(MediaType.APPLICATION_JSON)
- public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider) {
- System.setProperty(PROVIDER, exportImportProvider);
- }
-
- @PUT
- @Path("/export-import-file")
- @Consumes(MediaType.APPLICATION_JSON)
- public void setFile(@QueryParam("file") String file) {
- System.setProperty(FILE, file);
- }
-
- @PUT
- @Path("/export-import-action")
- @Consumes(MediaType.APPLICATION_JSON)
- public void setAction(@QueryParam("exportImportAction") String exportImportAction) {
- System.setProperty(ACTION, exportImportAction);
- }
-
- @PUT
- @Path("/set-realm-name")
- @Consumes(MediaType.APPLICATION_JSON)
- public void setRealmName(@QueryParam("realmName") String realmName) {
- if (realmName != null && !realmName.isEmpty()) {
- System.setProperty(REALM_NAME, realmName);
- } else {
- System.getProperties().remove(REALM_NAME);
- }
- }
-
- @GET
- @Path("/get-test-dir")
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- public String getExportImportTestDirectory() {
- System.setProperty("project.build.directory", "target");
- String absolutePath = new File(System.getProperty("project.build.directory", "target")).getAbsolutePath();
- return absolutePath;
- }
-
}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json
new file mode 100644
index 0000000..03978db
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/keycloak-themes.json
@@ -0,0 +1,6 @@
+{
+ "themes": [{
+ "name" : "address",
+ "types": [ "admin", "account", "login" ]
+ }]
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl
new file mode 100755
index 0000000..d2a6af1
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/account.ftl
@@ -0,0 +1,114 @@
+<#import "template.ftl" as layout>
+<@layout.mainLayout active='account' bodyClass='user'; section>
+
+ <div class="row">
+ <div class="col-md-10">
+ <h2>${msg("editAccountHtmlTtile")}</h2>
+ </div>
+ <div class="col-md-2 subtitle">
+ <span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
+ </div>
+ </div>
+
+ <form action="${url.accountUrl}" class="form-horizontal" method="post">
+
+ <input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
+
+ <div class="form-group ${messagesPerField.printIfExists('username','has-error')}">
+ <div class="col-sm-2 col-md-2">
+ <label for="username" class="control-label">${msg("username")}</label> <#if realm.editUsernameAllowed><span class="required">*</span></#if>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="username" name="username" <#if !realm.editUsernameAllowed>disabled="disabled"</#if> value="${(account.username!'')?html}"/>
+ </div>
+ </div>
+
+ <div class="form-group ${messagesPerField.printIfExists('email','has-error')}">
+ <div class="col-sm-2 col-md-2">
+ <label for="email" class="control-label">${msg("email")}</label> <span class="required">*</span>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="email" name="email" autofocus value="${(account.email!'')?html}"/>
+ </div>
+ </div>
+
+ <div class="form-group ${messagesPerField.printIfExists('firstName','has-error')}">
+ <div class="col-sm-2 col-md-2">
+ <label for="firstName" class="control-label">${msg("firstName")}</label> <span class="required">*</span>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="firstName" name="firstName" value="${(account.firstName!'')?html}"/>
+ </div>
+ </div>
+
+ <div class="form-group ${messagesPerField.printIfExists('lastName','has-error')}">
+ <div class="col-sm-2 col-md-2">
+ <label for="lastName" class="control-label">${msg("lastName")}</label> <span class="required">*</span>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="lastName" name="lastName" value="${(account.lastName!'')?html}"/>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="col-sm-2 col-md-2">
+ <label for="user.attributes.street" class="control-label">${msg("street")}</label>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="user.attributes.street" name="user.attributes.street" value="${(account.attributes.street!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-2 col-md-2">
+ <label for="user.attributes.locality" class="control-label">${msg("locality")}</label>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="user.attributes.locality" name="user.attributes.locality" value="${(account.attributes.locality!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-2 col-md-2">
+ <label for="user.attributes.region" class="control-label">${msg("region")}</label>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="user.attributes.region" name="user.attributes.region" value="${(account.attributes.region!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-2 col-md-2">
+ <label for="user.attributes.postal_code" class="control-label">${msg("postal_code")}</label>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="user.attributes.postal_code" name="user.attributes.postal_code" value="${(account.attributes.postal_code!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-2 col-md-2">
+ <label for="user.attributes.country" class="control-label">${msg("country")}</label>
+ </div>
+
+ <div class="col-sm-10 col-md-10">
+ <input type="text" class="form-control" id="user.attributes.country" name="user.attributes.country" value="${(account.attributes.country!'')?html}"/>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
+ <div class="">
+ <#if url.referrerURI??><a href="${url.referrerURI}">${msg("backToApplication")}/a></#if>
+ <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
+ <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Cancel">${msg("doCancel")}</button>
+ </div>
+ </div>
+ </div>
+ </form>
+
+</@layout.mainLayout>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties
new file mode 100644
index 0000000..3e50437
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/account/theme.properties
@@ -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.
+#
+
+parent=keycloak
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html
new file mode 100755
index 0000000..af512de
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/resources/partials/user-attributes.html
@@ -0,0 +1,72 @@
+<!--
+ ~ 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.
+ -->
+
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <ol class="breadcrumb">
+ <li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
+ <li>{{user.username}}</li>
+ </ol>
+
+ <kc-tabs-user></kc-tabs-user>
+
+ <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageUsers">
+ <div class="form-group clearfix block">
+ <label class="col-md-2 control-label" for="street">Street</label>
+ <div class="col-md-6">
+ <input ng-model="user.attributes.street" class="form-control" type="text" name="street" id="street" />
+ </div>
+ <kc-tooltip>Street address.</kc-tooltip>
+ </div>
+ <div class="form-group clearfix block">
+ <label class="col-md-2 control-label" for="locality">City or Locality</label>
+ <div class="col-md-6">
+ <input ng-model="user.attributes.locality" class="form-control" type="text" name="locality" id="locality" />
+ </div>
+ <kc-tooltip>City or locality.</kc-tooltip>
+ </div>
+ <div class="form-group clearfix block">
+ <label class="col-md-2 control-label" for="region">State, Province, or Region</label>
+ <div class="col-md-6">
+ <input ng-model="user.attributes.region" class="form-control" type="text" name="region" id="region" />
+ </div>
+ <kc-tooltip>State, province, prefecture, or region.</kc-tooltip>
+ </div>
+ <div class="form-group clearfix block">
+ <label class="col-md-2 control-label" for="postal_code">Zip or Postal code</label>
+ <div class="col-md-6">
+ <input ng-model="user.attributes.postal_code" class="form-control" type="text" name="postal_code" id="postal_code" />
+ </div>
+ <kc-tooltip>Zip code or postal code.</kc-tooltip>
+ </div>
+ <div class="form-group clearfix block">
+ <label class="col-md-2 control-label" for="country">Country</label>
+ <div class="col-md-6">
+ <input ng-model="user.attributes.country" class="form-control" type="text" name="country" id="country" />
+ </div>
+ <kc-tooltip>Country name.</kc-tooltip>
+ </div>
+
+ <div class="form-group" data-ng-show="access.manageUsers">
+ <div class="col-md-10 col-md-offset-2">
+ <button kc-save data-ng-disabled="!changed">Save</button>
+ <button kc-reset data-ng-disabled="!changed">Cancel</button>
+ </div>
+ </div>
+ </form>
+</div>
+
+<kc-menu></kc-menu>
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties
new file mode 100644
index 0000000..3e50437
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/admin/theme.properties
@@ -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.
+#
+
+parent=keycloak
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl
new file mode 100755
index 0000000..e02a340
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/login-update-profile.ftl
@@ -0,0 +1,95 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout; section>
+ <#if section = "title">
+ ${msg("loginProfileTitle")}
+ <#elseif section = "header">
+ ${msg("loginProfileTitle")}
+ <#elseif section = "form">
+ <form id="kc-update-profile-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="email" name="email" value="${(user.email!'')?html}" class="${properties.kcInputClass!}" />
+ </div>
+ </div>
+
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="firstName" name="firstName" value="${(user.firstName!'')?html}" class="${properties.kcInputClass!}" />
+ </div>
+ </div>
+
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="lastName" name="lastName" value="${(user.lastName!'')?html}" class="${properties.kcInputClass!}" />
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.street" class="${properties.kcLabelClass!}">${msg("street")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.street" name="user.attributes.street" value="${(user.attributes.street!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.locality" class="${properties.kcLabelClass!}">${msg("locality")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.locality" name="user.attributes.locality" value="${(user.attributes.locality!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.region" class="${properties.kcLabelClass!}">${msg("region")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.region" name="user.attributes.region" value="${(user.attributes.region!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${msg("postal_code")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.postal_code" name="user.attributes.postal_code" value="${(user.attributes.postal_code!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.country" class="${properties.kcLabelClass!}">${msg("country")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country" value="${(user.attributes.country!'')?html}"/>
+ </div>
+ </div>
+
+
+ <div class="${properties.kcFormGroupClass!}">
+ <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
+ <div class="${properties.kcFormOptionsWrapperClass!}">
+ </div>
+ </div>
+
+ <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
+ <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doSubmit")}" />
+ </div>
+ </div>
+ </form>
+ </#if>
+</@layout.registrationLayout>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl
new file mode 100755
index 0000000..3247305
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/register.ftl
@@ -0,0 +1,131 @@
+<#import "template.ftl" as layout>
+<@layout.registrationLayout; section>
+ <#if section = "title">
+ ${msg("registerWithTitle",(realm.name!''))}
+ <#elseif section = "header">
+ ${msg("registerWithTitleHtml",(realm.name!''))}
+ <#elseif section = "form">
+ <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
+ <#if !realm.registrationEmailAsUsername>
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('username',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="username" class="${properties.kcLabelClass!}">${msg("username")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="username" class="${properties.kcInputClass!}" name="username" value="${(register.formData.username!'')?html}" />
+ </div>
+ </div>
+ </#if>
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('firstName',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="firstName" class="${properties.kcLabelClass!}">${msg("firstName")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="firstName" class="${properties.kcInputClass!}" name="firstName" value="${(register.formData.firstName!'')?html}" />
+ </div>
+ </div>
+
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('lastName',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="lastName" class="${properties.kcLabelClass!}">${msg("lastName")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="lastName" class="${properties.kcInputClass!}" name="lastName" value="${(register.formData.lastName!'')?html}" />
+ </div>
+ </div>
+
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('email',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="email" class="${properties.kcLabelClass!}">${msg("email")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" id="email" class="${properties.kcInputClass!}" name="email" value="${(register.formData.email!'')?html}" />
+ </div>
+ </div>
+
+ <#if passwordRequired>
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="password" id="password" class="${properties.kcInputClass!}" name="password" />
+ </div>
+ </div>
+
+ <div class="${properties.kcFormGroupClass!} ${messagesPerField.printIfExists('password-confirm',properties.kcFormGroupErrorClass!)}">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
+ </div>
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="password" id="password-confirm" class="${properties.kcInputClass!}" name="password-confirm" />
+ </div>
+ </div>
+ </#if>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.street" class="${properties.kcLabelClass!}">${msg("street")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.street" name="user.attributes.street" value="${(register.formData['user.attributes.street']!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.locality" class="${properties.kcLabelClass!}">${msg("locality")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.locality" name="user.attributes.locality" value="${(register.formData['user.attributes.locality']!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.region" class="${properties.kcLabelClass!}">${msg("region")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.region" name="user.attributes.region" value="${(register.formData['user.attributes.region']!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.postal_code" class="${properties.kcLabelClass!}">${msg("postal_code")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.postal_code" name="user.attributes.postal_code" value="${(register.formData['user.attributes.postal_code']!'')?html}"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="${properties.kcLabelWrapperClass!}">
+ <label for="user.attributes.country" class="${properties.kcLabelClass!}">${msg("country")}</label>
+ </div>
+
+ <div class="${properties.kcInputWrapperClass!}">
+ <input type="text" class="${properties.kcInputClass!}" id="user.attributes.country" name="user.attributes.country" value="${(register.formData['user.attributes.country']!'')?html}"/>
+ </div>
+ </div>
+ <#if recaptchaRequired??>
+ <div class="form-group">
+ <div class="${properties.kcInputWrapperClass!}">
+ <div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}"></div>
+ </div>
+ </div>
+ </#if>
+
+ <div class="${properties.kcFormGroupClass!}">
+ <div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
+ <div class="${properties.kcFormOptionsWrapperClass!}">
+ <span><a href="${url.loginUrl}">${msg("backToLogin")}</a></span>
+ </div>
+ </div>
+
+ <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
+ <input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" type="submit" value="${msg("doRegister")}"/>
+ </div>
+ </div>
+ </form>
+ </#if>
+</@layout.registrationLayout>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties
new file mode 100644
index 0000000..3e50437
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/theme/address/login/theme.properties
@@ -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.
+#
+
+parent=keycloak
\ No newline at end of file
testsuite/integration-arquillian/servers/migration/pom.xml 424(+194 -230)
diff --git a/testsuite/integration-arquillian/servers/migration/pom.xml b/testsuite/integration-arquillian/servers/migration/pom.xml
index 6f6301a..b7a5ff9 100644
--- a/testsuite/integration-arquillian/servers/migration/pom.xml
+++ b/testsuite/integration-arquillian/servers/migration/pom.xml
@@ -1,20 +1,20 @@
<?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">
@@ -25,231 +25,195 @@
</parent>
<modelVersion>4.0.0</modelVersion>
- <artifactId>integration-arquillian-migration-servers</artifactId>
+ <artifactId>integration-arquillian-migration-server</artifactId>
<packaging>pom</packaging>
- <name>Migration Servers</name>
+ <name>Migration Server</name>
+
+ <properties>
+ <keycloak.server.home>${project.build.directory}/unpacked/keycloak-${migrated.auth.server.version}</keycloak.server.home>
+ <jdbc.mvn.driver.deployment.dir>${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main</jdbc.mvn.driver.deployment.dir>
+ </properties>
- <profiles>
- <profile>
- <id>migration-kc16</id>
- <modules>
- <module>wildfly_kc16</module>
- </modules>
- </profile>
- <profile>
- <id>migration-kc15</id>
- <modules>
- <module>wildfly_kc15</module>
- </modules>
- </profile>
- <profile>
- <id>migration-kc14</id>
- <modules>
- <module>wildfly_kc14</module>
- </modules>
- </profile>
- <profile>
- <id>migration-kc13</id>
- <modules>
- <module>wildfly_kc13</module>
- </modules>
- </profile>
- <profile>
- <id>migration-kc12</id>
- <modules>
- <module>wildfly_kc12</module>
- </modules>
- </profile>
- </profiles>
-
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-enforcer-plugin</artifactId>
+ <artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
+ <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>migrated.auth.server.version</property>
+ </requireProperty>
+ <requireProperty>
+ <property>jdbc.mvn.groupId</property>
+ </requireProperty>
+ <requireProperty>
+ <property>jdbc.mvn.artifactId</property>
+ </requireProperty>
+ <requireProperty>
+ <property>jdbc.mvn.version</property>
+ </requireProperty>
+ <requireProperty>
+ <property>keycloak.connectionsJpa.url</property>
+ </requireProperty>
+ <requireProperty>
+ <property>keycloak.connectionsJpa.user</property>
+ </requireProperty>
+ <requireProperty>
+ <property>keycloak.connectionsJpa.password</property>
+ </requireProperty>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack-server</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-server-dist</artifactId>
+ <version>${migrated.auth.server.version}</version>
+ <type>zip</type>
+ <outputDirectory>${project.build.directory}/unpacked</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ <execution>
+ <id>jdbc-driver</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>${jdbc.mvn.groupId}</groupId>
+ <artifactId>${jdbc.mvn.artifactId}</artifactId>
+ <version>${jdbc.mvn.version}</version>
+ <type>jar</type>
+ </artifactItem>
+ </artifactItems>
+ <outputDirectory>${jdbc.mvn.driver.deployment.dir}</outputDirectory>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>configure-wildfly-datasource</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <!-- create module.xml in modules -->
+ <transformationSet>
+ <dir>${keycloak.server.home}/modules/system/layers/base/com/h2database/h2/main</dir>
+ <stylesheet>src/main/xslt/module.xsl</stylesheet>
+ <includes>
+ <include>module.xml</include>
+ </includes>
+ <outputDir>${jdbc.mvn.driver.deployment.dir}</outputDir>
+ <parameters>
+ <parameter>
+ <name>database</name>
+ <value>${jdbc.mvn.artifactId}</value>
+ </parameter>
+ <parameter>
+ <name>version</name>
+ <value>${jdbc.mvn.version}</value>
+ </parameter>
+ </parameters>
+ </transformationSet>
+ <!-- add datasource to standalone.xml -->
+ <transformationSet>
+ <dir>${keycloak.server.home}/standalone/configuration</dir>
+ <stylesheet>src/main/xslt/datasource.xsl</stylesheet>
+ <includes>
+ <include>standalone.xml</include>
+ </includes>
+ <outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
+ <parameters>
+ <parameter>
+ <name>jdbc.url</name>
+ <value>${keycloak.connectionsJpa.url}</value>
+ </parameter>
+ <parameter>
+ <name>driver</name>
+ <value>${jdbc.mvn.artifactId}</value>
+ </parameter>
+ <parameter>
+ <name>username</name>
+ <value>${keycloak.connectionsJpa.user}</value>
+ </parameter>
+ <parameter>
+ <name>password</name>
+ <value>${keycloak.connectionsJpa.password}</value>
+ </parameter>
+ </parameters>
+ </transformationSet>
+ <!-- add logger for org.hibernate.dialect.Dialect to standalone.xml-->
+ <transformationSet>
+ <dir>${keycloak.server.home}/standalone/configuration</dir>
+ <stylesheet>src/main/xslt/add-dialect-logger.xsl</stylesheet>
+ <includes>
+ <include>standalone.xml</include>
+ </includes>
+ <outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>create-zip</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+ <descriptor>assembly.xml</descriptor>
+ </descriptors>
+ <appendAssemblyId>false</appendAssemblyId>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
- <pluginManagement>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-deploy-plugin</artifactId>
- <configuration>
- <skip>true</skip>
- </configuration>
- </plugin>
- <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>jdbc.mvn.groupId</property>
- </requireProperty>
- <requireProperty>
- <property>jdbc.mvn.artifactId</property>
- </requireProperty>
- <requireProperty>
- <property>jdbc.mvn.version</property>
- </requireProperty>
- <requireProperty>
- <property>keycloak.connectionsJpa.url</property>
- </requireProperty>
- <requireProperty>
- <property>keycloak.connectionsJpa.user</property>
- </requireProperty>
- <requireProperty>
- <property>keycloak.connectionsJpa.password</property>
- </requireProperty>
- </rules>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-dependency-plugin</artifactId>
- <executions>
- <execution>
- <id>unpack-server</id>
- <phase>generate-resources</phase>
- <goals>
- <goal>unpack</goal>
- </goals>
- <configuration>
- <artifactItems>
- <artifactItem>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-server-dist</artifactId>
- <version>${server.version}</version>
- <type>zip</type>
- <outputDirectory>${project.build.directory}/unpacked</outputDirectory>
- </artifactItem>
- </artifactItems>
- </configuration>
- </execution>
- <execution>
- <id>jdbc-driver</id>
- <phase>process-resources</phase>
- <goals>
- <goal>copy</goal>
- </goals>
- <configuration>
- <artifactItems>
- <artifactItem>
- <groupId>${jdbc.mvn.groupId}</groupId>
- <artifactId>${jdbc.mvn.artifactId}</artifactId>
- <version>${jdbc.mvn.version}</version>
- <type>jar</type>
- </artifactItem>
- </artifactItems>
- <outputDirectory>${jdbc.mvn.driver.deployment.dir}</outputDirectory>
- <overWriteIfNewer>true</overWriteIfNewer>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>xml-maven-plugin</artifactId>
- <executions>
- <execution>
- <id>configure-wildfly-datasource</id>
- <phase>process-resources</phase>
- <goals>
- <goal>transform</goal>
- </goals>
- <configuration>
- <transformationSets>
- <!-- create module.xml in modules -->
- <transformationSet>
- <dir>${keycloak.server.home}/modules/system/layers/base/com/h2database/h2/main</dir>
- <stylesheet>src/main/xslt/module.xsl</stylesheet>
- <includes>
- <include>module.xml</include>
- </includes>
- <outputDir>${jdbc.mvn.driver.deployment.dir}</outputDir>
- <parameters>
- <parameter>
- <name>database</name>
- <value>${jdbc.mvn.artifactId}</value>
- </parameter>
- <parameter>
- <name>version</name>
- <value>${jdbc.mvn.version}</value>
- </parameter>
- </parameters>
- </transformationSet>
- <!-- add datasource to standalone.xml -->
- <transformationSet>
- <dir>${keycloak.server.home}/standalone/configuration</dir>
- <stylesheet>src/main/xslt/datasource.xsl</stylesheet>
- <includes>
- <include>standalone.xml</include>
- </includes>
- <outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
- <parameters>
- <parameter>
- <name>jdbc.url</name>
- <value>${keycloak.connectionsJpa.url}</value>
- </parameter>
- <parameter>
- <name>driver</name>
- <value>${jdbc.mvn.artifactId}</value>
- </parameter>
- <parameter>
- <name>username</name>
- <value>${keycloak.connectionsJpa.user}</value>
- </parameter>
- <parameter>
- <name>password</name>
- <value>${keycloak.connectionsJpa.password}</value>
- </parameter>
- </parameters>
- </transformationSet>
- <!-- add logger for org.hibernate.dialect.Dialect to standalone.xml-->
- <transformationSet>
- <dir>${keycloak.server.home}/standalone/configuration</dir>
- <stylesheet>src/main/xslt/add-dialect-logger.xsl</stylesheet>
- <includes>
- <include>standalone.xml</include>
- </includes>
- <outputDir>${keycloak.server.home}/standalone/configuration</outputDir>
- </transformationSet>
- </transformationSets>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-assembly-plugin</artifactId>
- <executions>
- <execution>
- <id>create-zip</id>
- <phase>package</phase>
- <goals>
- <goal>single</goal>
- </goals>
- <configuration>
- <descriptors>
- <descriptor>assembly.xml</descriptor>
- </descriptors>
- <appendAssemblyId>false</appendAssemblyId>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </pluginManagement>
</build>
</project>
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
index 6547d2f..1ce85dd 100644
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json
@@ -50,7 +50,7 @@
"mavenArtifactVersion": "2.1.0-SNAPSHOT",
"mavenArtifactId": "photoz-authz-policy",
"sessionName": "MainOwnerSession",
- "mavenArtifactGroupId": "org.keycloak",
+ "mavenArtifactGroupId": "org.keycloak.testsuite",
"moduleName": "PhotozAuthzOwnerPolicy",
"applyPolicies": "[]",
"scannerPeriod": "1",
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index ac85ebd..15594da 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -36,8 +36,6 @@
<exclude.console>-</exclude.console>
<exclude.account>-</exclude.account>
<exclude.client>-</exclude.client>
- <!--exclude migration tests by default, enabled by 'migration' profile in tests/pom.xml-->
- <exclude.migration>**/migration/**/*Test.java</exclude.migration>
<!--exclude cluster tests by default, enabled by 'auth-server-*-cluster' profiles in tests/pom.xml-->
<exclude.cluster>**/cluster/**/*Test.java</exclude.cluster>
</properties>
@@ -102,7 +100,6 @@
<exclude>${exclude.console}</exclude>
<exclude>${exclude.account}</exclude>
<exclude>${exclude.client}</exclude>
- <exclude>${exclude.migration}</exclude>
<exclude>${exclude.cluster}</exclude>
</excludes>
</configuration>
@@ -137,6 +134,7 @@
<goal>copy-resources</goal>
</goals>
<configuration>
+ <skip>${skip.add.user.json}</skip>
<outputDirectory>${auth.server.config.dir}</outputDirectory>
<resources>
<resource>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java
index c282dff..aad5bb8 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/filter/AdapterActionsFilter.java
@@ -27,8 +27,13 @@ import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
import org.keycloak.common.util.Time;
/**
@@ -38,6 +43,11 @@ import org.keycloak.common.util.Time;
*/
public class AdapterActionsFilter implements Filter {
+ public static final String TIME_OFFSET_PARAM = "timeOffset";
+ public static final String RESET_PUBLIC_KEY_PARAM = "resetPublicKey";
+
+ private static final Logger log = Logger.getLogger(AdapterActionsFilter.class);
+
@Override
public void init(FilterConfig filterConfig) throws ServletException {
@@ -45,17 +55,29 @@ public class AdapterActionsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest servletReq = (HttpServletRequest) request;
HttpServletResponse servletResp = (HttpServletResponse) response;
//Accept timeOffset as argument to enforce timeouts
- String timeOffsetParam = request.getParameter("timeOffset");
+ String timeOffsetParam = request.getParameter(TIME_OFFSET_PARAM);
+ String resetPublicKey = request.getParameter(RESET_PUBLIC_KEY_PARAM);
+
if (timeOffsetParam != null && !timeOffsetParam.isEmpty()) {
- Time.setOffset(Integer.parseInt(timeOffsetParam));
+ int timeOffset = Integer.parseInt(timeOffsetParam);
+ log.infof("Time offset updated to %d for application %s", timeOffset, servletReq.getRequestURI());
+ Time.setOffset(timeOffset);
+ writeResponse(servletResp, "Offset set successfully");
+ } else if (resetPublicKey != null && !resetPublicKey.isEmpty()) {
+ AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getServletContext().getAttribute(AdapterDeploymentContext.class.getName());
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(null);
+ deployment.setPublicKeyLocator(new JWKPublicKeyLocator());
+ log.infof("Restarted publicKey locator for application %s", servletReq.getRequestURI());
+ writeResponse(servletResp, "PublicKeyLocator restarted successfully");
+ } else {
+ // Continue request
+ chain.doFilter(request, response);
}
- // Continue request
- chain.doFilter(request, response);
-
}
@Override
@@ -64,8 +86,9 @@ public class AdapterActionsFilter implements Filter {
}
private void writeResponse(HttpServletResponse response, String responseText) throws IOException {
+ response.setContentType("text/html");
PrintWriter writer = response.getWriter();
- writer.println(responseText);
+ writer.println("<html><body>" + responseText + "</body></html>");
writer.flush();
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java
index d370dd0..6cb1f37 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/AbstractShowTokensPage.java
@@ -23,6 +23,7 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
import org.keycloak.util.JsonSerialization;
+import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -43,6 +44,8 @@ public abstract class AbstractShowTokensPage extends AbstractPageWithInjectedUrl
return JsonSerialization.readValue(accessToken.getText(), AccessToken.class);
} catch (IOException e) {
e.printStackTrace();
+ } catch (NoSuchElementException nsee) {
+ log.warn("No accessToken element found on the page");
}
return null;
@@ -53,7 +56,10 @@ public abstract class AbstractShowTokensPage extends AbstractPageWithInjectedUrl
return JsonSerialization.readValue(refreshToken.getText(), RefreshToken.class);
} catch (IOException e) {
e.printStackTrace();
+ } catch (NoSuchElementException nsee) {
+ log.warn("No idToken element found on the page");
}
+
return null;
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BasicAuth.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BasicAuth.java
new file mode 100644
index 0000000..8880d83
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BasicAuth.java
@@ -0,0 +1,61 @@
+/*
+ * 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.adapter.page;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
+
+import javax.ws.rs.core.UriBuilder;
+import java.net.URL;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class BasicAuth extends AbstractPageWithInjectedUrl {
+
+ public static final String DEPLOYMENT_NAME = "basic-auth";
+
+ @ArquillianResource
+ @OperateOnDeployment(DEPLOYMENT_NAME)
+ private URL url;
+
+ @Override
+ public URL getInjectedUrl() {
+ //EAP6 URL fix
+ URL fixedUrl = createInjectedURL("basic-auth");
+ return fixedUrl != null ? fixedUrl : url;
+ }
+
+ @Override
+ public UriBuilder createUriBuilder() {
+ return super.createUriBuilder()
+ .userInfo("{user}:{password}")
+ .path("basic-auth")
+ .queryParam("value", "{value}");
+ }
+
+ public BasicAuth setTemplateValues(String user, String password, String value) {
+ setUriParameter("user", user);
+ setUriParameter("password", password);
+ setUriParameter("value", value);
+ return this;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/BasicAuthServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/BasicAuthServlet.java
new file mode 100644
index 0000000..3c51343
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/BasicAuthServlet.java
@@ -0,0 +1,27 @@
+package org.keycloak.testsuite.adapter.servlet;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * @author mhajas
+ */
+@WebServlet("/basic-auth")
+public class BasicAuthServlet extends HttpServlet {
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ String value = req.getParameter("value");
+ System.out.println("In BasicAuthServlet with value: " + value);
+
+ resp.setContentType("text/plain");
+ PrintWriter pw = resp.getWriter();
+ pw.printf(value);
+ pw.flush();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
index 984ba7d..d958e6e 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java
@@ -26,7 +26,7 @@ import org.jboss.arquillian.container.spi.Container;
import org.jboss.arquillian.container.spi.ContainerRegistry;
import org.jboss.arquillian.container.spi.event.StartContainer;
import org.jboss.arquillian.container.spi.event.StartSuiteContainers;
-import org.jboss.arquillian.container.test.api.ContainerController;
+import org.jboss.arquillian.container.spi.event.StopContainer;
import org.jboss.arquillian.core.api.Event;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.InstanceProducer;
@@ -53,10 +53,9 @@ public class AuthServerTestEnricher {
private Instance<ContainerRegistry> containerRegistry;
@Inject
- private Instance<ContainerController> containerController;
-
- @Inject
private Event<StartContainer> startContainerEvent;
+ @Inject
+ private Event<StopContainer> stopContainerEvent;
private static final String AUTH_SERVER_CONTAINER_DEFAULT = "auth-server-undertow";
private static final String AUTH_SERVER_CONTAINER_PROPERTY = "auth.server.container";
@@ -65,8 +64,8 @@ public class AuthServerTestEnricher {
private static final String AUTH_SERVER_CLUSTER_PROPERTY = "auth.server.cluster";
public static final boolean AUTH_SERVER_CLUSTER = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CLUSTER_PROPERTY, "false"));
- private static final String MIGRATED_AUTH_SERVER_CONTAINER_PROPERTY = "migrated.auth.server.container";
- public static final String MIGRATED_AUTH_SERVER_CONTAINER = System.getProperty(MIGRATED_AUTH_SERVER_CONTAINER_PROPERTY); // == null if migration not enabled
+ private static final String MIGRATION_PROPERTY = "auth.server.jboss.migration";
+ private static final Boolean MIGRATION_ENABLED = Boolean.parseBoolean(System.getProperty(MIGRATION_PROPERTY));
@Inject
@SuiteScoped
@@ -132,23 +131,19 @@ public class AuthServerTestEnricher {
throw new RuntimeException(String.format("No auth server container matching '%sN' found in arquillian.xml.", authServerBackend));
}
- if (MIGRATED_AUTH_SERVER_CONTAINER != null) {
+ if (MIGRATION_ENABLED) {
// init migratedAuthServerInfo
- if (MIGRATED_AUTH_SERVER_CONTAINER.startsWith("migrated-auth-server-")) {
- for (ContainerInfo container : suiteContext.getContainers()) {
- // migrated auth server
- if (container.getQualifier().equals(MIGRATED_AUTH_SERVER_CONTAINER)) {
- updateWithAuthServerInfo(container);
- suiteContext.setMigratedAuthServerInfo(container);
- }
+ for (ContainerInfo container : suiteContext.getContainers()) {
+ // migrated auth server
+ if (container.getQualifier().equals("auth-server-jboss-migration")) {
+ updateWithAuthServerInfo(container);
+ suiteContext.setMigratedAuthServerInfo(container);
}
- } else {
- throw new IllegalArgumentException(String.format("Value of %s should start with 'migrated-auth-server-' prefix.", MIGRATED_AUTH_SERVER_CONTAINER_PROPERTY));
}
// validate setup
if (suiteContext.getMigratedAuthServerInfo() == null) {
throw new RuntimeException(String.format("Migration test was enabled but no auth server from which to migrate was activated. "
- + "A container matching '%s' needs to be enabled in arquillian.xml.", MIGRATED_AUTH_SERVER_CONTAINER));
+ + "A container matching auth-server-jboss-migration needs to be enabled in arquillian.xml."));
}
}
@@ -178,7 +173,8 @@ public class AuthServerTestEnricher {
public void stopMigratedContainer(@Observes(precedence = 1) StartSuiteContainers event) {
if (suiteContext.isAuthServerMigrationEnabled()) {
- containerController.get().stop(suiteContext.getAuthServerInfo().getQualifier());
+ log.info("## STOP old container: " + suiteContext.getMigratedAuthServerInfo().getQualifier());
+ stopContainerEvent.fire(new StopContainer(suiteContext.getMigratedAuthServerInfo().getArquillianContainer()));
}
}
@@ -189,14 +185,6 @@ public class AuthServerTestEnricher {
LogChecker.checkJBossServerLog(jbossHomePath);
}
}
-//
-// public void startAuthServerContainer(@Observes BeforeSuite event) {
-// startContainerEvent.fire(new StartContainer(suiteContext.getAuthServerInfo().getArquillianContainer()));
-// }
-//
-// public void stopAuthServerContainer(@Observes AfterSuite event) {
-// containerController.get().stop(suiteContext.getAuthServerInfo().getQualifier());
-// }
public void initializeTestContext(@Observes(precedence = 2) BeforeClass event) {
TestContext testContext = new TestContext(suiteContext, event.getTestClass().getJavaClass());
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
index 9cd7625..4af2988 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java
@@ -49,8 +49,6 @@ import static org.keycloak.testsuite.util.IOUtil.*;
*/
public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
- public static final String REALM_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
-
protected final Logger log = org.jboss.logging.Logger.getLogger(this.getClass());
private final boolean authServerSslRequired = Boolean.parseBoolean(System.getProperty("auth.server.ssl.required"));
@@ -129,7 +127,6 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor {
// ac.setRealmKey(null); // TODO verify if realm key is required for relative scneario
} else {
adapterConfig.setAuthServerUrl(getAuthServerContextRoot() + "/auth");
- adapterConfig.setRealmKey(REALM_KEY);
}
if ("true".equals(System.getProperty("app.server.ssl.required"))) {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java
index 333c5dd..78d26d5 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java
@@ -38,7 +38,7 @@ public class MigrationTestExecutionDecider implements TestExecutionDecider {
if (migrationTest && migrationAnnotation != null) {
String versionFrom = migrationAnnotation.versionFrom();
- if (migratedAuthServerVersion.equals(versionFrom)) {
+ if (versionFrom.equals(migratedAuthServerVersion)) {
return ExecutionDecision.execute();
} else {
return ExecutionDecision.dontExecute(method.getName() + "doesn't fit with migration version.");
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
index 929c23d..d576b49 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java
@@ -98,6 +98,9 @@ public final class SuiteContext {
for (ContainerInfo bInfo : getAuthServerBackendsInfo()) {
containers += "Backend: " + bInfo + "\n";
}
+ if (isAuthServerMigrationEnabled()) {
+ containers += "Migrated from: " + System.getProperty("migrated.auth.server.version") + "\n";
+ }
return "SUITE CONTEXT:\n"
+ containers;
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java
index 2efc26b..2d277fd 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResource.java
@@ -20,7 +20,9 @@ package org.keycloak.testsuite.client.resources;
import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
+import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
+import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@@ -53,4 +55,6 @@ public interface TestApplicationResource {
@Path("/clear-admin-actions")
Response clearAdminActions();
+ @Path("/oidc-client-endpoints")
+ TestOIDCEndpointsApplicationResource oidcClientEndpoints();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResourceUrls.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResourceUrls.java
new file mode 100644
index 0000000..88b7b38
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestApplicationResourceUrls.java
@@ -0,0 +1,54 @@
+/*
+ * 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.client.resources;
+
+import org.keycloak.testsuite.util.OAuthClient;
+
+import javax.ws.rs.core.UriBuilder;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TestApplicationResourceUrls {
+
+ private static UriBuilder oidcClientEndpoints() {
+ return UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT)
+ .path(TestApplicationResource.class)
+ .path(TestApplicationResource.class, "oidcClientEndpoints");
+ }
+
+ public static String clientRequestUri() {
+ UriBuilder builder = oidcClientEndpoints()
+ .path(TestOIDCEndpointsApplicationResource.class, "getOIDCRequest");
+
+ return builder.build().toString();
+ }
+
+ public static String clientJwksUri() {
+ UriBuilder builder = oidcClientEndpoints()
+ .path(TestOIDCEndpointsApplicationResource.class, "getJwks");
+
+ return builder.build().toString();
+ }
+
+ public static String pairwiseSectorIdentifierUri() {
+ UriBuilder builder = oidcClientEndpoints()
+ .path(TestOIDCEndpointsApplicationResource.class, "getSectorIdentifierRedirectUris");
+ return builder.build().toString();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingExportImportResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingExportImportResource.java
new file mode 100644
index 0000000..27fa360
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingExportImportResource.java
@@ -0,0 +1,93 @@
+/*
+ * 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.client.resources;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface TestingExportImportResource {
+
+ @GET
+ @Path("/run-import")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response runImport();
+
+ @GET
+ @Path("/run-export")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response runExport();
+
+ @GET
+ @Path("/get-users-per-file")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Integer getUsersPerFile();
+
+ @PUT
+ @Path("/set-users-per-file")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile);
+
+ @GET
+ @Path("/get-dir")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public String getDir();
+
+ @PUT
+ @Path("/set-dir")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public String setDir(@QueryParam("dir") String dir);
+
+ @PUT
+ @Path("/export-import-provider")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider);
+
+ @PUT
+ @Path("/export-import-file")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void setFile(@QueryParam("file") String file);
+
+ @PUT
+ @Path("/export-import-action")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void setAction(@QueryParam("exportImportAction") String exportImportAction);
+
+ @PUT
+ @Path("/set-realm-name")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void setRealmName(@QueryParam("realmName") String realmName);
+
+ @GET
+ @Path("/get-test-dir")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public String getExportImportTestDirectory();
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
index fcf5d83..0dbcd58 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
@@ -17,8 +17,8 @@
package org.keycloak.testsuite.client.resources;
-import java.util.Date;
import java.util.List;
+
import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
@@ -37,7 +37,6 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Map;
import org.jboss.resteasy.annotations.cache.NoCache;
-import org.keycloak.exportimport.ExportImportManager;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@@ -205,16 +204,6 @@ public interface TestingResource {
@Path("/update-pass-through-auth-state")
@Produces(MediaType.APPLICATION_JSON)
AuthenticatorState updateAuthenticator(AuthenticatorState state);
-
- @GET
- @Path("/run-import")
- @Produces(MediaType.APPLICATION_JSON)
- public Response runImport();
-
- @GET
- @Path("/run-export")
- @Produces(MediaType.APPLICATION_JSON)
- public Response runExport();
@GET
@Path("/valid-credentials")
@@ -250,53 +239,7 @@ public interface TestingResource {
@Produces(MediaType.APPLICATION_JSON)
public UserRepresentation getUserByServiceAccountClient(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId);
+ @Path("export-import")
+ TestingExportImportResource exportImport();
- @GET
- @Path("/get-users-per-file")
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- public Integer getUsersPerFile();
-
- @PUT
- @Path("/set-users-per-file")
- @Consumes(MediaType.APPLICATION_JSON)
- public void setUsersPerFile(@QueryParam("usersPerFile") Integer usersPerFile);
-
- @GET
- @Path("/get-dir")
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- public String getDir();
-
- @PUT
- @Path("/set-dir")
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- public String setDir(@QueryParam("dir") String dir);
-
- @PUT
- @Path("/export-import-provider")
- @Consumes(MediaType.APPLICATION_JSON)
- public void setProvider(@QueryParam("exportImportProvider") String exportImportProvider);
-
- @PUT
- @Path("/export-import-file")
- @Consumes(MediaType.APPLICATION_JSON)
- public void setFile(@QueryParam("file") String file);
-
- @PUT
- @Path("/export-import-action")
- @Consumes(MediaType.APPLICATION_JSON)
- public void setAction(@QueryParam("exportImportAction") String exportImportAction);
-
- @PUT
- @Path("/set-realm-name")
- @Consumes(MediaType.APPLICATION_JSON)
- public void setRealmName(@QueryParam("realmName") String realmName);
-
- @GET
- @Path("/get-test-dir")
- @Consumes(MediaType.APPLICATION_JSON)
- @Produces(MediaType.APPLICATION_JSON)
- public String getExportImportTestDirectory();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.java
new file mode 100644
index 0000000..9c4f324
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestOIDCEndpointsApplicationResource.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.testsuite.client.resources;
+
+import org.keycloak.jose.jwk.JSONWebKeySet;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface TestOIDCEndpointsApplicationResource {
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/generate-keys")
+ Map<String, String> generateKeys();
+
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/get-jwks")
+ JSONWebKeySet getJwks();
+
+
+ @GET
+ @Path("/set-oidc-request")
+ @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
+ void setOIDCRequest(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId,
+ @QueryParam("redirectUri") String redirectUri, @QueryParam("maxAge") String maxAge,
+ @QueryParam("jwaAlgorithm") String jwaAlgorithm);
+
+ @GET
+ @Path("/get-oidc-request")
+ @Produces(org.keycloak.utils.MediaType.APPLICATION_JWT)
+ String getOIDCRequest();
+
+ @GET
+ @Path("/set-sector-identifier-redirect-uris")
+ @Produces(MediaType.APPLICATION_JSON)
+ void setSectorIdentifierRedirectUris(@QueryParam("redirectUris") List<String> redirectUris);
+
+ @GET
+ @Path("/get-sector-identifier-redirect-uris")
+ @Produces(MediaType.APPLICATION_JSON)
+ List<String> getSectorIdentifierRedirectUris();
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index 2159c2f..2a77847 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -17,7 +17,9 @@
package org.keycloak.testsuite.pages;
+import org.keycloak.models.Constants;
import org.keycloak.services.resources.RealmsResource;
+import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -96,6 +98,14 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
submitButton.click();
}
+ public void updateAttribute(String attrName, String attrValue) {
+ WebElement attrElement = findAttributeInputElement(attrName);
+ attrElement.clear();
+ attrElement.sendKeys(attrValue);
+ submitButton.click();
+ }
+
+
public void clickCancel() {
cancelButton.click();
}
@@ -117,6 +127,11 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
return emailInput.getAttribute("value");
}
+ public String getAttribute(String attrName) {
+ WebElement attrElement = findAttributeInputElement(attrName);
+ return attrElement.getAttribute("value");
+ }
+
public boolean isCurrent() {
return driver.getTitle().contains("Account Management") && driver.getPageSource().contains("Edit Account");
}
@@ -140,4 +155,9 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
public boolean isPasswordUpdateSupported() {
return driver.getPageSource().contains(getPath() + "/password");
}
+
+ private WebElement findAttributeInputElement(String attrName) {
+ String attrId = Constants.USER_ATTRIBUTES_PREFIX + attrName;
+ return driver.findElement(By.id(attrId));
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java
index 78913d6..94a8fb6 100755
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -71,6 +71,9 @@ public class LoginPage extends AbstractPage {
@FindBy(className = "alert-info")
private WebElement loginInfoMessage;
+ @FindBy(className = "instruction")
+ private WebElement instruction;
+
@FindBy(id = "kc-current-locale-link")
private WebElement languageText;
@@ -128,6 +131,10 @@ public class LoginPage extends AbstractPage {
return loginErrorMessage != null ? loginErrorMessage.getText() : null;
}
+ public String getInstruction() {
+ return instruction != null ? instruction.getText() : null;
+ }
+
public String getSuccessMessage() {
return loginSuccessMessage != null ? loginSuccessMessage.getText() : null;
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java
new file mode 100644
index 0000000..7fbf59b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import org.junit.Assume;
+import org.keycloak.common.Profile;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProfileAssume {
+
+ public static void assumePreview() {
+ Assume.assumeTrue("Ignoring test as community/preview profile is not enabled", Profile.isPreviewEnabled());
+ }
+
+ public static void assumePreviewDisabled() {
+ Assume.assumeFalse("Ignoring test as community/preview profile is enabled", Profile.isPreviewEnabled());
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 586351d..2253828 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -105,6 +105,10 @@ public class OAuthClient {
private String nonce;
+ private String request;
+
+ private String requestUri;
+
private Map<String, PublicKey> publicKeys = new HashMap<>();
public void init(Keycloak adminClient, WebDriver driver) {
@@ -121,6 +125,9 @@ public class OAuthClient {
clientSessionState = null;
clientSessionHost = null;
maxAge = null;
+ nonce = null;
+ request = null;
+ requestUri = null;
}
public AuthorizationEndpointResponse doLogin(String username, String password) {
@@ -536,6 +543,12 @@ public class OAuthClient {
if (maxAge != null) {
b.queryParam(OIDCLoginProtocol.MAX_AGE_PARAM, maxAge);
}
+ if (request != null) {
+ b.queryParam(OIDCLoginProtocol.REQUEST_PARAM, request);
+ }
+ if (requestUri != null) {
+ b.queryParam(OIDCLoginProtocol.REQUEST_URI_PARAM, requestUri);
+ }
return b.build(realm).toString();
}
@@ -644,6 +657,16 @@ public class OAuthClient {
return this;
}
+ public OAuthClient request(String request) {
+ this.request = request;
+ return this;
+ }
+
+ public OAuthClient requestUri(String requestUri) {
+ this.requestUri = requestUri;
+ return this;
+ }
+
public String getRealm() {
return realm;
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index e4dfe0e..35d11d1 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -377,12 +377,15 @@ public class AccountTest extends TestRealmKeycloakTest {
}
@Test
- public void changeProfile() {
+ public void changeProfile() throws Exception {
+ setEditUsernameAllowed(false);
+
profilePage.open();
loginPage.login("test-user@localhost", "password");
events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT).assertEvent();
+ Assert.assertEquals("test-user@localhost", profilePage.getUsername());
Assert.assertEquals("Tom", profilePage.getFirstName());
Assert.assertEquals("Brady", profilePage.getLastName());
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
@@ -391,6 +394,7 @@ public class AccountTest extends TestRealmKeycloakTest {
profilePage.updateProfile("", "New last", "new@email.com");
Assert.assertEquals("Please specify first name.", profilePage.getError());
+ Assert.assertEquals("test-user@localhost", profilePage.getUsername());
Assert.assertEquals("", profilePage.getFirstName());
Assert.assertEquals("New last", profilePage.getLastName());
Assert.assertEquals("new@email.com", profilePage.getEmail());
@@ -417,6 +421,7 @@ public class AccountTest extends TestRealmKeycloakTest {
profilePage.clickCancel();
+ Assert.assertEquals("test-user@localhost", profilePage.getUsername());
Assert.assertEquals("Tom", profilePage.getFirstName());
Assert.assertEquals("Brady", profilePage.getLastName());
Assert.assertEquals("test-user@localhost", profilePage.getEmail());
@@ -426,6 +431,7 @@ public class AccountTest extends TestRealmKeycloakTest {
profilePage.updateProfile("New first", "New last", "new@email.com");
Assert.assertEquals("Your account has been updated.", profilePage.getSuccess());
+ Assert.assertEquals("test-user@localhost", profilePage.getUsername());
Assert.assertEquals("New first", profilePage.getFirstName());
Assert.assertEquals("New last", profilePage.getLastName());
Assert.assertEquals("new@email.com", profilePage.getEmail());
@@ -436,18 +442,21 @@ public class AccountTest extends TestRealmKeycloakTest {
// reset user for other tests
profilePage.updateProfile("Tom", "Brady", "test-user@localhost");
events.clear();
+
+ // Revert
+ setEditUsernameAllowed(true);
}
- private void setEditUsernameAllowed() {
+ private void setEditUsernameAllowed(boolean allowed) {
RealmRepresentation testRealm = testRealm().toRepresentation();
- testRealm.setEditUsernameAllowed(true);
+ testRealm.setEditUsernameAllowed(allowed);
testRealm().update(testRealm);
}
@Test
public void changeUsername() {
// allow to edit the username in realm
- setEditUsernameAllowed();
+ setEditUsernameAllowed(true);
profilePage.open();
loginPage.login("test-user@localhost", "password");
@@ -504,7 +513,7 @@ public class AccountTest extends TestRealmKeycloakTest {
@Test
public void changeUsernameLoginWithOldUsername() {
addUser("change-username", "change-username@localhost");
- setEditUsernameAllowed();
+ setEditUsernameAllowed(true);
profilePage.open();
loginPage.login("change-username", "password");
@@ -530,7 +539,7 @@ public class AccountTest extends TestRealmKeycloakTest {
@Test
public void changeEmailLoginWithOldEmail() {
addUser("change-email", "change-username@localhost");
- setEditUsernameAllowed();
+ setEditUsernameAllowed(true);
profilePage.open();
loginPage.login("change-username@localhost", "password");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java
new file mode 100644
index 0000000..810d9c2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomThemeTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.account.custom;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.TestRealmKeycloakTest;
+import org.keycloak.testsuite.account.AccountTest;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CustomThemeTest extends TestRealmKeycloakTest {
+
+ @Override
+ public void configureTestRealm(RealmRepresentation testRealm) {
+ testRealm.setAccountTheme("address");
+
+ UserRepresentation user2 = UserBuilder.create()
+ .enabled(true)
+ .username("test-user-no-access@localhost")
+ .email("test-user-no-access@localhost")
+ .password("password")
+ .build();
+
+ RealmBuilder.edit(testRealm)
+ .user(user2);
+ }
+
+ @Rule
+ public AssertEvents events = new AssertEvents(this);
+
+ @Page
+ protected LoginPage loginPage;
+
+ @Page
+ protected AccountUpdateProfilePage profilePage;
+
+ // KEYCLOAK-3494
+ @Test
+ public void changeProfile() throws Exception {
+ profilePage.open();
+ loginPage.login("test-user@localhost", "password");
+
+ events.expectLogin().client("account").detail(Details.REDIRECT_URI, AccountTest.ACCOUNT_REDIRECT).assertEvent();
+
+ Assert.assertEquals("test-user@localhost", profilePage.getEmail());
+ Assert.assertEquals("", profilePage.getAttribute("street"));
+
+ profilePage.updateAttribute("street", "Elm 1");
+ Assert.assertEquals("Elm 1", profilePage.getAttribute("street"));
+
+ profilePage.updateAttribute("street", "Elm 2");
+ Assert.assertEquals("Elm 2", profilePage.getAttribute("street"));
+
+ events.expectAccount(EventType.UPDATE_PROFILE).assertEvent();
+ }
+
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractExampleAdapterTest.java
index 9e7f4f1..065477a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractExampleAdapterTest.java
@@ -17,11 +17,6 @@
package org.keycloak.testsuite.adapter;
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.nio.file.Paths;
-
import org.apache.commons.io.IOUtils;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
@@ -29,6 +24,11 @@ import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Paths;
+
/**
*
* @author tkyjovsk
@@ -50,11 +50,11 @@ public abstract class AbstractExampleAdapterTest extends AbstractAdapterTest {
Assert.assertNotNull("Property ${examples.version.suffix} must bet set.", EXAMPLES_VERSION_SUFFIX);
System.out.println(EXAMPLES_VERSION_SUFFIX);
+ EXAMPLES_HOME_DIR = EXAMPLES_HOME + "/example-realms";
+
if (!System.getProperty("unpacked.container.folder.name","").isEmpty()) {
- EXAMPLES_HOME_DIR = EXAMPLES_HOME + "/" + System.getProperty("unpacked.container.folder.name","") + "-examples";
TEST_APPS_HOME_DIR = EXAMPLES_HOME + "/" + System.getProperty("unpacked.container.folder.name","") + "-test-apps";
} else {
- EXAMPLES_HOME_DIR = EXAMPLES_HOME + "/keycloak-examples-" + EXAMPLES_VERSION_SUFFIX;
TEST_APPS_HOME_DIR = EXAMPLES_HOME + "/Keycloak-" + EXAMPLES_VERSION_SUFFIX + "-test-apps";
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java
index 127c863..a40275f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java
@@ -22,6 +22,7 @@ import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
import org.keycloak.testsuite.util.WaitUtils;
import org.openqa.selenium.By;
@@ -113,7 +114,7 @@ public abstract class AbstractServletsAdapterTest extends AbstractAdapterTest {
protected void setAdapterAndServerTimeOffset(int timeOffset, String servletUri) {
setTimeOffset(timeOffset);
String timeOffsetUri = UriBuilder.fromUri(servletUri)
- .queryParam("timeOffset", timeOffset)
+ .queryParam(AdapterActionsFilter.TIME_OFFSET_PARAM, timeOffset)
.build().toString();
driver.navigate().to(timeOffsetUri);
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 b1df6e0..4db4a3c 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
@@ -98,7 +98,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
@Override
public void addAdapterTestRealms(List<RealmRepresentation> testRealms) {
testRealms.add(
- loadRealm(new File(EXAMPLES_HOME_DIR + "/preconfigured-demo/testrealm.json")));
+ loadRealm(new File(EXAMPLES_HOME_DIR + "/demo-template/testrealm.json")));
}
@Override
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseExampleAdapterTest.java
index cc13724..b0583fb 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractFuseExampleAdapterTest.java
@@ -17,22 +17,23 @@
package org.keycloak.testsuite.adapter.example;
-import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
-import java.io.File;
-import java.util.List;
import org.jboss.arquillian.graphene.page.Page;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.testsuite.auth.page.account.Account;
-import static org.keycloak.testsuite.util.IOUtil.loadRealm;
-import static org.keycloak.testsuite.adapter.AbstractExampleAdapterTest.EXAMPLES_HOME_DIR;
+import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.adapter.page.fuse.AdminInterface;
import org.keycloak.testsuite.adapter.page.fuse.CustomerListing;
import org.keycloak.testsuite.adapter.page.fuse.CustomerPortalFuseExample;
import org.keycloak.testsuite.adapter.page.fuse.ProductPortalFuseExample;
+import org.keycloak.testsuite.auth.page.account.Account;
+
+import java.io.File;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
+import static org.keycloak.testsuite.util.IOUtil.loadRealm;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
import static org.keycloak.testsuite.util.WaitUtils.pause;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
index 66702a9..9d25421 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java
@@ -23,13 +23,7 @@ import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Before;
import org.junit.Test;
-import org.keycloak.admin.client.resource.AuthorizationResource;
-import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.admin.client.resource.ClientsResource;
-import org.keycloak.admin.client.resource.ResourcesResource;
-import org.keycloak.admin.client.resource.RoleResource;
-import org.keycloak.admin.client.resource.UserResource;
-import org.keycloak.admin.client.resource.UsersResource;
+import org.keycloak.admin.client.resource.*;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
@@ -45,11 +39,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -80,8 +70,15 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd
}
@Before
- public void beforePhotozExampleAdapterTest() {
+ public void beforePhotozExampleAdapterTest() throws FileNotFoundException {
deleteAllCookiesForClientPage();
+
+ for (PolicyRepresentation policy : getAuthorizationResource().policies().policies()) {
+ if ("Only Owner Policy".equals(policy.getName())) {
+ policy.getConfig().put("mavenArtifactVersion", System.getProperty("project.version"));
+ getAuthorizationResource().policies().policy(policy.getId()).update(policy);
+ }
+ }
}
@Override
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
index 083867f..065de77 100644
--- 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
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.adapter.servlet;
+import org.apache.commons.io.FileUtils;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.shrinkwrap.api.spec.WebArchive;
@@ -25,19 +26,31 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
+import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.common.Version;
import org.keycloak.common.util.Time;
import org.keycloak.constants.AdapterConstants;
+import org.keycloak.models.Constants;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.VersionRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter;
import org.keycloak.testsuite.adapter.page.*;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.account.Applications;
+import org.keycloak.testsuite.auth.page.login.OAuthGrant;
+import org.keycloak.testsuite.console.page.events.Config;
+import org.keycloak.testsuite.console.page.events.LoginEvents;
+import org.keycloak.testsuite.util.URLAssert;
import org.keycloak.testsuite.util.URLUtils;
import org.keycloak.util.BasicAuthHelper;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
@@ -46,15 +59,23 @@ import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.io.File;
+import java.io.IOException;
import java.net.URI;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import static org.junit.Assert.*;
+import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
import static org.keycloak.testsuite.util.WaitUtils.pause;
+import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
/**
*
@@ -78,6 +99,16 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
private InputPortal inputPortal;
@Page
private TokenMinTTLPage tokenMinTTLPage;
+ @Page
+ private OAuthGrant oAuthGrantPage;
+ @Page
+ private Applications applicationsPage;
+ @Page
+ private LoginEvents loginEventsPage;
+ @Page
+ private BasicAuth basicAuthPage;
+ @Page
+ private Config configPage;
@Deployment(name = CustomerPortal.DEPLOYMENT_NAME)
protected static WebArchive customerPortal() {
@@ -119,6 +150,20 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
return servletDeployment(TokenMinTTLPage.DEPLOYMENT_NAME, AdapterActionsFilter.class, AbstractShowTokensServlet.class, TokenMinTTLServlet.class, ErrorServlet.class);
}
+ @Deployment(name = BasicAuth.DEPLOYMENT_NAME)
+ protected static WebArchive basicAuth() {
+ return servletDeployment(BasicAuth.DEPLOYMENT_NAME, BasicAuthServlet.class);
+ }
+
+ @Override
+ public void setDefaultPageUriParameters() {
+ super.setDefaultPageUriParameters();
+ configPage.setConsoleRealm(DEMO);
+ loginEventsPage.setConsoleRealm(DEMO);
+ applicationsPage.setAuthRealm(DEMO);
+ loginEventsPage.setConsoleRealm(DEMO);
+ }
+
@Before
public void beforeDemoServletsAdapterTest() {
// Delete all cookies from token-min-ttl page to be sure we are logged out
@@ -167,6 +212,59 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
}
@Test
+ public void testRealmKeyRotationWithNewKeyDownload() throws Exception {
+ // Login success first
+ tokenMinTTLPage.navigateTo();
+ testRealmLoginPage.form().waitForUsernameInputPresent();
+ assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
+ testRealmLoginPage.form().login("bburke@redhat.com", "password");
+ assertCurrentUrlEquals(tokenMinTTLPage);
+
+ AccessToken token = tokenMinTTLPage.getAccessToken();
+ Assert.assertEquals("bburke@redhat.com", token.getPreferredUsername());
+
+ // Logout
+ String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder())
+ .queryParam(OAuth2Constants.REDIRECT_URI, tokenMinTTLPage.toString())
+ .build("demo").toString();
+ driver.navigate().to(logoutUri);
+
+ // Generate new realm key
+ RealmRepresentation realmRep = testRealmResource().toRepresentation();
+ String oldPublicKey = realmRep.getPublicKey();
+ String oldPrivateKey = realmRep.getPrivateKey();
+ realmRep.setPublicKey(Constants.GENERATE);
+ testRealmResource().update(realmRep);
+
+ // Try to login again. It should fail now
+ tokenMinTTLPage.navigateTo();
+ testRealmLoginPage.form().waitForUsernameInputPresent();
+ assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
+ testRealmLoginPage.form().login("bburke@redhat.com", "password");
+ URLAssert.assertCurrentUrlStartsWith(driver, tokenMinTTLPage.getInjectedUrl().toString());
+ assertNull(tokenMinTTLPage.getAccessToken());
+
+ String adapterActionsUrl = tokenMinTTLPage.toString() + "/unsecured/foo";
+ setAdapterAndServerTimeOffset(300, adapterActionsUrl);
+
+ // Try to login. Should work now due to realm key change
+ tokenMinTTLPage.navigateTo();
+ assertCurrentUrlEquals(tokenMinTTLPage);
+ token = tokenMinTTLPage.getAccessToken();
+ Assert.assertEquals("bburke@redhat.com", token.getPreferredUsername());
+ driver.navigate().to(logoutUri);
+
+ // Revert public keys change
+ String timeOffsetUri = UriBuilder.fromUri(adapterActionsUrl)
+ .queryParam(AdapterActionsFilter.RESET_PUBLIC_KEY_PARAM, "true")
+ .build().toString();
+ driver.navigate().to(timeOffsetUri);
+ waitUntilElement(By.tagName("body")).is().visible();
+
+ setAdapterAndServerTimeOffset(0, adapterActionsUrl);
+ }
+
+ @Test
public void testLoginSSOAndLogout() {
// test login to customer-portal which does a bearer request to customer-db
customerPortal.navigateTo();
@@ -261,7 +359,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
demoRealmRep.setSsoSessionIdleTimeout(1);
testRealmResource().update(demoRealmRep);
- pause(2000);
+ pause(2000);
productPortal.navigateTo();
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
@@ -323,6 +421,10 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
demoRealmRep.setSsoSessionIdleTimeout(originalIdle);
testRealmResource().update(demoRealmRep);
+
+ String logoutUri = OIDCLoginProtocolService.logoutUrl(authServerPage.createUriBuilder())
+ .queryParam(OAuth2Constants.REDIRECT_URI, securePortal.toString()).build("demo").toString();
+ driver.navigate().to(logoutUri);
}
@Test
@@ -444,6 +546,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
// Sets 5 minutes offset and assert access token will be still the same
setAdapterAndServerTimeOffset(300, tokenMinTTLPage.toString());
+ tokenMinTTLPage.navigateTo();
token = tokenMinTTLPage.getAccessToken();
int tokenIssued2 = token.getIssuedAt();
Assert.assertEquals(tokenIssued1, tokenIssued2);
@@ -451,6 +554,7 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
// Sets 9 minutes offset and assert access token will be refreshed (accessTokenTimeout is 10 minutes, token-min-ttl is 2 minutes. Hence 8 minutes or more should be sufficient)
setAdapterAndServerTimeOffset(540, tokenMinTTLPage.toString());
+ tokenMinTTLPage.navigateTo();
token = tokenMinTTLPage.getAccessToken();
int tokenIssued3 = token.getIssuedAt();
Assert.assertTrue(tokenIssued3 > tokenIssued1);
@@ -486,5 +590,193 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
setAdapterAndServerTimeOffset(0, tokenMinTTLPage.toString());
}
+ @Test
+ public void testBasicAuth() {
+ String value = "hello";
+ Client client = ClientBuilder.newClient();
+
+ Response response = client.target(basicAuthPage
+ .setTemplateValues("mposolda", "password", value).buildUri()).request().get();
+
+ assertEquals(200, response.getStatus());
+ assertEquals(value, response.readEntity(String.class));
+ response.close();
+
+ response = client.target(basicAuthPage
+ .setTemplateValues("invalid-user", "password", value).buildUri()).request().get();
+ assertEquals(401, response.getStatus());
+ String readResponse = response.readEntity(String.class);
+ assertTrue(readResponse.contains("Unauthorized") || readResponse.contains("Status 401"));
+ response.close();
+
+ response = client.target(basicAuthPage
+ .setTemplateValues("admin", "invalid-password", value).buildUri()).request().get();
+ assertEquals(401, response.getStatus());
+ readResponse = response.readEntity(String.class);
+ assertTrue(readResponse.contains("Unauthorized") || readResponse.contains("Status 401"));
+ response.close();
+
+ client.close();
+ }
+
+ @Test
+ public void grantServerBasedApp() {
+ ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "customer-portal");
+ ClientRepresentation client = clientResource.toRepresentation();
+ client.setConsentRequired(true);
+ clientResource.update(client);
+
+ RealmRepresentation realm = testRealmResource().toRepresentation();
+ realm.setEventsEnabled(true);
+ realm.setEnabledEventTypes(Arrays.asList("REVOKE_GRANT", "LOGIN"));
+ testRealmResource().update(realm);
+
+ customerPortal.navigateTo();
+
+ loginPage.form().login("bburke@redhat.com", "password");
+
+ assertTrue(oAuthGrantPage.isCurrent());
+
+ oAuthGrantPage.accept();
+
+ waitUntilElement(By.xpath("//body")).text().contains("Bill Burke");
+ waitUntilElement(By.xpath("//body")).text().contains("Stian Thorgersen");
+
+ applicationsPage.navigateTo();
+ applicationsPage.revokeGrantForApplication("customer-portal");
+
+ customerPortal.navigateTo();
+
+ assertTrue(oAuthGrantPage.isCurrent());
+
+ loginEventsPage.navigateTo();
+
+ if (!testContext.isAdminLoggedIn()) {
+ loginPage.form().login(adminUser);
+ testContext.setAdminLoggedIn(true);
+ }
+
+ loginEventsPage.table().filter();
+ loginEventsPage.table().filterForm().addEventType("REVOKE_GRANT");
+ loginEventsPage.table().update();
+
+ List<WebElement> resultList = loginEventsPage.table().rows();
+
+ assertEquals(1, resultList.size());
+
+ 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' 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();
+ loginEventsPage.table().filterForm().addEventType("LOGIN");
+ loginEventsPage.table().update();
+ resultList = loginEventsPage.table().rows();
+
+ assertEquals(1, resultList.size());
+
+ 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' 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']"));
+
+ configPage.navigateTo();
+ configPage.form().clearLoginEvents();
+ driver.findElement(By.xpath("//div[@class='modal-dialog']//button[text()='Delete']")).click();
+ }
+
+ @Test
+ public void historyOfAccessResourceTest() throws IOException {
+ RealmRepresentation realm = testRealmResource().toRepresentation();
+ realm.setEventsEnabled(true);
+ realm.setEnabledEventTypes(Arrays.asList("LOGIN", "LOGIN_ERROR", "LOGOUT", "CODE_TO_TOKEN"));
+ testRealmResource().update(realm);
+
+ customerPortal.navigateTo();
+
+ testRealmLoginPage.form().login("bburke@redhat.com", "password");
+
+ waitUntilElement(By.xpath("//body")).text().contains("Bill Burke");
+ waitUntilElement(By.xpath("//body")).text().contains("Stian Thorgersen");
+
+ driver.navigate().to(testRealmPage.getOIDCLogoutUrl() + "?redirect_uri=" + customerPortal);
+
+ loginEventsPage.navigateTo();
+
+ if (!testContext.isAdminLoggedIn()) {
+ loginPage.form().login(adminUser);
+ testContext.setAdminLoggedIn(true);
+ }
+
+ loginEventsPage.table().filter();
+ loginEventsPage.table().filterForm().addEventType("LOGOUT");
+ loginEventsPage.table().update();
+
+ List<WebElement> resultList = loginEventsPage.table().rows();
+
+ assertEquals(1, resultList.size());
+
+ 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' or text()='0:0:0:0:0:0:0:1']"));
+
+ loginEventsPage.table().reset();
+ loginEventsPage.table().filterForm().addEventType("LOGIN");
+ loginEventsPage.table().update();
+ resultList = loginEventsPage.table().rows();
+
+ assertEquals(1, resultList.size());
+
+ 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' 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();
+ loginEventsPage.table().filterForm().addEventType("CODE_TO_TOKEN");
+ loginEventsPage.table().update();
+ resultList = loginEventsPage.table().rows();
+
+ 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' or text()='0:0:0:0:0:0:0:1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='refresh_token_type']/../td[text()='Refresh']"));
+
+ configPage.navigateTo();
+ configPage.form().clearLoginEvents();
+ driver.findElement(By.xpath("//div[@class='modal-dialog']//button[text()='Delete']")).click();
+
+ String serverLogPath = null;
+
+ if (System.getProperty("app.server").equals("wildfly") || System.getProperty("app.server").equals("eap6") || System.getProperty("app.server").equals("eap")) {
+ serverLogPath = System.getProperty("app.server.home") + "/standalone/log/server.log";
+ }
+
+ String appServerUrl;
+ if (Boolean.parseBoolean(System.getProperty("app.server.ssl.required"))) {
+ appServerUrl = "https://localhost:" + System.getProperty("app.server.https.port", "8543") + "/";
+ } else {
+ appServerUrl = "http://localhost:" + System.getProperty("app.server.http.port", "8280") + "/";
+ }
+
+ if (serverLogPath != null) {
+ log.info("Checking app server log at: " + serverLogPath);
+ File serverLog = new File(serverLogPath);
+ String serverLogContent = FileUtils.readFileToString(serverLog);
+ UserRepresentation bburke = ApiUtil.findUserByUsername(testRealmResource(), "bburke@redhat.com");
+
+ Pattern pattern = Pattern.compile("User '" + bburke.getId() + "' invoking '" + appServerUrl + "customer-portal[^\\s]+' on client 'customer-portal'");
+ Matcher matcher = pattern.matcher(serverLogContent);
+
+ assertTrue(matcher.find());
+ assertTrue(serverLogContent.contains("User '" + bburke.getId() + "' invoking '" + appServerUrl + "customer-db/' on client 'customer-db'"));
+ } else {
+ log.info("Checking app server log on app-server: \"" + System.getProperty("app.server") + "\" is not supported.");
+ }
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java
index fa6d0a8..b8c10b6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOfflineServletsAdapterTest.java
@@ -85,7 +85,7 @@ public abstract class AbstractOfflineServletsAdapterTest extends AbstractServlet
String refreshTokenId = offlineTokenPage.getRefreshToken().getId();
setAdapterAndServerTimeOffset(9999);
-
+ offlineTokenPage.navigateTo();
assertCurrentUrlStartsWith(offlineTokenPage);
Assert.assertNotEquals(offlineTokenPage.getRefreshToken().getId(), refreshTokenId);
Assert.assertNotEquals(offlineTokenPage.getAccessToken().getId(), accessTokenId);
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 2973caa..5bdd305 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
@@ -327,8 +327,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
salesPostPassiveServletPage.navigateTo();
if (forbiddenIfNotAuthenticated) {
- waitUntilElement(By.xpath("//body")).text().not().contains("principal=");
- assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT));
+ assertOnForbiddenPage();
} else {
waitUntilElement(By.xpath("//body")).text().contains("principal=null");
}
@@ -407,9 +406,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
salesPostPassiveServletPage.navigateTo();
if (forbiddenIfNotAuthenticated) {
- waitUntilElement(By.xpath("//body")).text().not().contains("principal=");
- //Different 403 status page on EAP and Wildfly
- assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT));
+ assertOnForbiddenPage();
} else {
waitUntilElement(By.xpath("//body")).text().contains("principal=null");
}
@@ -422,9 +419,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
salesPostPassiveServletPage.navigateTo();
if (forbiddenIfNotAuthenticated) {
- waitUntilElement(By.xpath("//body")).text().not().contains("principal=");
- //Different 403 status page on EAP and Wildfly
- assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT));
+ assertOnForbiddenPage();
} else {
waitUntilElement(By.xpath("//body")).text().contains("principal=null");
}
@@ -724,4 +719,16 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd
driver.navigate().to(employee2ServletPage.toString() + "/setCheckRoles?roles=" + roles);
employee2ServletPage.logout();
}
+
+ private void assertOnForbiddenPage() {
+ switch (System.getProperty("app.server")) {
+ case "eap6":
+ waitUntilElement(By.xpath("//body")).text().not().contains("principal=");
+ String source = driver.getPageSource();
+ assertTrue(source.isEmpty() || source.contains("<body></body>"));
+ break;
+ default:
+ waitUntilElement(By.xpath("//body")).text().contains(FORBIDDEN_TEXT);
+ }
+ }
}
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 f115c49..c32ba08 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
@@ -104,7 +104,7 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
}
void compareExecution(AuthenticationExecutionExportRepresentation expected, AuthenticationExecutionExportRepresentation actual) {
- Assert.assertEquals("Execution flowAlias - " + actual.getAuthenticator(), expected.getFlowAlias(), actual.getFlowAlias());
+ Assert.assertEquals("Execution flowAlias - " + actual.getFlowAlias(), 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());
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 42015ed..d7caba4 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
@@ -94,7 +94,7 @@ public class ExecutionTest extends AbstractAuthenticationTest {
// we'll need auth-cookie later
AuthenticationExecutionInfoRepresentation authCookieExec = findExecutionByProvider("auth-cookie", executionReps);
- compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
+ compareExecution(newExecInfo("Review Profile", "idp-review-profile", true, 0, 4, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
// remove execution
authMgmtResource.removeExecution(exec.getId());
@@ -164,7 +164,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(newExecInfo("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec);
+ compareExecution(newExecInfo("Cookie", "auth-cookie", false, 0, 3, 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 fc50bc6..90f8874 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
@@ -203,7 +203,7 @@ 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");
+ browser.getAuthenticationExecutions().get(3).setFlowAlias("Copy of browser forms");
compareFlows(browser, copyOfBrowser);
// get new flow directly and compare
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 3640af5..ee79c27 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
@@ -125,12 +125,14 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
AuthenticationFlowRepresentation flow = newFlow("browser", "browser based authentication", "basic-flow", true, true);
addExecExport(flow, null, false, "auth-cookie", false, null, ALTERNATIVE, 10);
addExecExport(flow, null, false, "auth-spnego", false, null, DISABLED, 20);
+ addExecExport(flow, null, false, "identity-provider-redirector", false, null, ALTERNATIVE, 25);
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, "Identity Provider Redirector", "identity-provider-redirector", true, 0, 2, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
+ addExecInfo(execs, "forms", null, false, 0, 3, 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));
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
index 858e06e..4e07e6f 100644
--- 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
@@ -146,6 +146,7 @@ public class ProvidersTest extends AbstractAuthenticationTest {
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", "HTTP Basic Authentication", "Validates username and password from Authorization HTTP header");
+ addProviderInfo(result, "identity-provider-redirector", "Identity Provider Redirector", "Redirects to default Identity Provider or Identity Provider specified with kc_idp_hint query parameter");
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 " +
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractAuthorizationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractAuthorizationTest.java
index c4979e0..2c3aac7 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractAuthorizationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AbstractAuthorizationTest.java
@@ -19,11 +19,13 @@ package org.keycloak.testsuite.admin.client.authorization;
import org.junit.After;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ResourceScopeResource;
import org.keycloak.admin.client.resource.ResourceScopesResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
+import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.client.AbstractClientTest;
import javax.ws.rs.core.Response;
@@ -38,6 +40,11 @@ public abstract class AbstractAuthorizationTest extends AbstractClientTest {
protected static final String RESOURCE_SERVER_CLIENT_ID = "test-resource-server";
+ @BeforeClass
+ public static void enabled() {
+ ProfileAssume.assumePreview();
+ }
+
@Before
public void onBeforeAuthzTests() {
createOidcClient(RESOURCE_SERVER_CLIENT_ID);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java
new file mode 100644
index 0000000..072aa5f
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/AuthorizationDisabledInPreviewTest.java
@@ -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.
+ */
+
+package org.keycloak.testsuite.admin.client.authorization;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.keycloak.testsuite.ProfileAssume;
+import org.keycloak.testsuite.admin.client.AbstractClientTest;
+
+import javax.ws.rs.ServerErrorException;
+import javax.ws.rs.core.Response;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AuthorizationDisabledInPreviewTest extends AbstractClientTest {
+
+ @BeforeClass
+ public static void enabled() {
+ ProfileAssume.assumePreviewDisabled();
+ }
+
+ @Test
+ public void testAuthzServicesRemoved() {
+ String id = testRealmResource().clients().findAll().get(0).getId();
+ try {
+ testRealmResource().clients().get(id).authorization().getSettings();
+ } catch (ServerErrorException e) {
+ assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), e.getResponse().getStatus());
+ }
+ }
+
+}
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 fb03891..c7339ed 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
@@ -21,11 +21,11 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.events.admin.OperationType;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
/**
* Test getting the installation/configuration files for OIDC and SAML.
@@ -81,7 +81,7 @@ public class InstallationTest extends AbstractClientTest {
private void assertOidcInstallationConfig(String config) {
RealmRepresentation realmRep = realmRep();
assertTrue(config.contains(realmRep.getId()));
- assertTrue(config.contains(realmRep.getPublicKey()));
+ assertFalse(config.contains(realmRep.getPublicKey()));
assertTrue(config.contains(authServerUrl()));
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
index 81de894..b6fca90 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java
@@ -17,10 +17,12 @@
package org.keycloak.testsuite.admin;
+import org.apache.bcel.generic.RETURN;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.AuthorizationResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.Constants;
@@ -45,6 +47,10 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserFederationMapperRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.representations.idm.authorization.PolicyRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceRepresentation;
+import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
+import org.keycloak.representations.idm.authorization.ScopeRepresentation;
import org.keycloak.services.resources.admin.RealmAuth.Resource;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
@@ -68,6 +74,8 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.*;
+import static org.keycloak.services.resources.admin.RealmAuth.Resource.AUTHORIZATION;
+import static org.keycloak.services.resources.admin.RealmAuth.Resource.CLIENT;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -771,6 +779,123 @@ public class PermissionsTest extends AbstractKeycloakTest {
}
@Test
+ public void clientAuthorization() {
+ invoke(new InvocationWithResponse() {
+ public void invoke(RealmResource realm, AtomicReference<Response> response) {
+ realm.clients().create(ClientBuilder.create().clientId("foo-authz").build());
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ foo.setServiceAccountsEnabled(true);
+ foo.setAuthorizationServicesEnabled(true);
+ realm.clients().get(foo.getId()).update(foo);
+ }
+ }, CLIENT, true);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ realm.clients().get(foo.getId()).authorization().getSettings();
+ }
+ }, AUTHORIZATION, false);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ ResourceServerRepresentation settings = authorization.getSettings();
+ authorization.update(settings);
+ }
+ }, AUTHORIZATION, true);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ authorization.resources().resources();
+ }
+ }, AUTHORIZATION, false);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ authorization.scopes().scopes();
+ }
+ }, AUTHORIZATION, false);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ authorization.policies().policies();
+ }
+ }, AUTHORIZATION, false);
+ invoke(new InvocationWithResponse() {
+ public void invoke(RealmResource realm, AtomicReference<Response> response) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ response.set(authorization.resources().create(new ResourceRepresentation("Test", Collections.emptySet())));
+ }
+ }, AUTHORIZATION, true);
+ invoke(new InvocationWithResponse() {
+ public void invoke(RealmResource realm, AtomicReference<Response> response) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ response.set(authorization.scopes().create(new ScopeRepresentation("Test")));
+ }
+ }, AUTHORIZATION, true);
+ invoke(new InvocationWithResponse() {
+ public void invoke(RealmResource realm, AtomicReference<Response> response) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ PolicyRepresentation representation = new PolicyRepresentation();
+ representation.setName("Test PermissionsTest");
+ representation.setType("js");
+ HashMap<String, String> config = new HashMap<>();
+ config.put("code", "");
+ representation.setConfig(config);
+ response.set(authorization.policies().create(representation));
+ }
+ }, AUTHORIZATION, true);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ authorization.resources().resource("nosuch").update(new ResourceRepresentation());
+ }
+ }, AUTHORIZATION, true);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ authorization.scopes().scope("nosuch").update(new ScopeRepresentation());
+ }
+ }, AUTHORIZATION, true);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ authorization.policies().policy("nosuch").update(new PolicyRepresentation());
+ }
+ }, AUTHORIZATION, true);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ authorization.resources().resource("nosuch").remove();
+ }
+ }, AUTHORIZATION, true);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ authorization.scopes().scope("nosuch").remove();
+ }
+ }, AUTHORIZATION, true);
+ invoke(new Invocation() {
+ public void invoke(RealmResource realm) {
+ org.keycloak.representations.idm.ClientRepresentation foo = realm.clients().findByClientId("foo-authz").get(0);
+ AuthorizationResource authorization = realm.clients().get(foo.getId()).authorization();
+ authorization.policies().policy("nosuch").remove();
+ }
+ }, AUTHORIZATION, true);
+ }
+
+ @Test
public void roles() {
invoke(new Invocation() {
public void invoke(RealmResource realm) {
@@ -1543,6 +1668,8 @@ public class PermissionsTest extends AbstractKeycloakTest {
return AdminRoles.VIEW_EVENTS;
case IDENTITY_PROVIDER:
return AdminRoles.VIEW_IDENTITY_PROVIDERS;
+ case AUTHORIZATION:
+ return AdminRoles.VIEW_AUTHORIZATION;
default:
throw new RuntimeException("Unexpected resouce");
}
@@ -1560,6 +1687,8 @@ public class PermissionsTest extends AbstractKeycloakTest {
return AdminRoles.MANAGE_EVENTS;
case IDENTITY_PROVIDER:
return AdminRoles.MANAGE_IDENTITY_PROVIDERS;
+ case AUTHORIZATION:
+ return AdminRoles.MANAGE_AUTHORIZATION;
default:
throw new RuntimeException("Unexpected resouce");
}
@@ -1577,6 +1706,8 @@ public class PermissionsTest extends AbstractKeycloakTest {
return AdminRoles.VIEW_IDENTITY_PROVIDERS;
case IDENTITY_PROVIDER:
return AdminRoles.VIEW_REALM;
+ case AUTHORIZATION:
+ return AdminRoles.VIEW_IDENTITY_PROVIDERS;
default:
throw new RuntimeException("Unexpected resouce");
}
@@ -1594,6 +1725,8 @@ public class PermissionsTest extends AbstractKeycloakTest {
return AdminRoles.MANAGE_IDENTITY_PROVIDERS;
case IDENTITY_PROVIDER:
return AdminRoles.MANAGE_REALM;
+ case AUTHORIZATION:
+ return AdminRoles.MANAGE_IDENTITY_PROVIDERS;
default:
throw new RuntimeException("Unexpected resouce");
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
index f08f790..926d31d 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java
@@ -57,14 +57,10 @@ import java.util.LinkedList;
import java.util.List;
import java.util.HashSet;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.Map;
-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;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -237,6 +233,7 @@ public class RealmTest extends AbstractAdminTest {
assertEquals(Boolean.FALSE, rep.isRegistrationAllowed());
assertEquals(Boolean.FALSE, rep.isRegistrationEmailAsUsername());
assertEquals(Boolean.FALSE, rep.isEditUsernameAllowed());
+
}
@Test
@@ -267,6 +264,45 @@ public class RealmTest extends AbstractAdminTest {
}
@Test
+ public void updateRealmAttributes() {
+ // first change
+ RealmRepresentation rep = new RealmRepresentation();
+ rep.setAttributes(new HashMap<>());
+ rep.getAttributes().put("foo1", "bar1");
+ rep.getAttributes().put("foo2", "bar2");
+
+ rep.setBruteForceProtected(true);
+ rep.setDisplayName("dn1");
+
+ realm.update(rep);
+ assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM);
+
+ rep = realm.toRepresentation();
+
+ assertEquals("bar1", rep.getAttributes().get("foo1"));
+ assertEquals("bar2", rep.getAttributes().get("foo2"));
+ assertTrue(rep.isBruteForceProtected());
+ assertEquals("dn1", rep.getDisplayName());
+
+ // second change
+ rep.setBruteForceProtected(false);
+ rep.setDisplayName("dn2");
+ rep.getAttributes().put("foo1", "bar11");
+ rep.getAttributes().remove("foo2");
+
+ realm.update(rep);
+ assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, Matchers.nullValue(String.class), rep, ResourceType.REALM);
+
+ rep = realm.toRepresentation();
+
+ assertFalse(rep.isBruteForceProtected());
+ assertEquals("dn2", rep.getDisplayName());
+
+ assertEquals("bar11", rep.getAttributes().get("foo1"));
+ assertFalse(rep.getAttributes().containsKey("foo2"));
+ }
+
+ @Test
public void getRealmRepresentation() {
RealmRepresentation rep = realm.toRepresentation();
Assert.assertEquals(REALM_NAME, rep.getRealm());
@@ -393,6 +429,13 @@ public class RealmTest extends AbstractAdminTest {
assertEquals(realm.getBrowserSecurityHeaders(), storedRealm.getBrowserSecurityHeaders());
}
+ if (realm.getAttributes() != null) {
+ HashMap<String, String> attributes = new HashMap<>();
+ attributes.putAll(storedRealm.getAttributes());
+ attributes.entrySet().retainAll(realm.getAttributes().entrySet());
+ assertEquals(realm.getAttributes(), attributes);
+ }
+
}
@Test
@@ -516,6 +559,26 @@ public class RealmTest extends AbstractAdminTest {
}
@Test
+ public void rotateRealmKeys() {
+ RealmRepresentation realmRep = realm.toRepresentation();
+ String publicKey = realmRep.getPublicKey();
+ String cert = realmRep.getCertificate();
+ assertNotNull(publicKey);
+ assertNotNull(cert);
+
+ RealmRepresentation newRealmRep = new RealmRepresentation();
+ newRealmRep.setRealm(REALM_NAME);
+ newRealmRep.setPublicKey("GENERATE");
+ realm.update(newRealmRep);
+
+ realmRep = realm.toRepresentation();
+ assertNotNull(realmRep.getPublicKey());
+ assertNotNull(realmRep.getCertificate());
+ assertNotEquals(publicKey, realmRep.getPublicKey());
+ assertNotEquals(cert, realmRep.getCertificate());
+ }
+
+ @Test
public void clearRealmCache() {
RealmRepresentation realmRep = realm.toRepresentation();
assertTrue(testingClient.testing().isCached("realms", realmRep.getId()));
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
index 0a4fe4b..818a84a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
@@ -35,14 +35,11 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
private ClientRepresentation client;
private ClientRepresentation client2;
private ClientRepresentation clientPublic;
- private String publicKey;
@Before
public void before() throws Exception {
super.before();
- publicKey = adminClient.realm(REALM_NAME).toRepresentation().getPublicKey();
-
client = new ClientRepresentation();
client.setEnabled(true);
client.setClientId("RegistrationAccessTokenTest");
@@ -92,7 +89,6 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
assertEquals(1, config.getCredentials().size());
assertEquals(client.getSecret(), config.getCredentials().get("secret"));
- assertEquals(publicKey, config.getRealmKey());
assertEquals(client.getClientId(), config.getResource());
assertEquals(SslRequired.EXTERNAL.name().toLowerCase(), config.getSslRequired());
}
@@ -132,7 +128,6 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
assertEquals(0, config.getCredentials().size());
- assertEquals(publicKey, config.getRealmKey());
assertEquals(clientPublic.getClientId(), config.getResource());
assertEquals(SslRequired.EXTERNAL.name().toLowerCase(), config.getSslRequired());
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
index 608d7a7..d13bfa1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java
@@ -50,12 +50,12 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
+import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
+import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
import org.keycloak.testsuite.util.OAuthClient;
import java.security.PrivateKey;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.*;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
@@ -236,8 +236,11 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.PRIVATE_KEY_JWT);
- // Corresponds to PRIVATE_KEY
- JSONWebKeySet keySet = loadJson(getClass().getResourceAsStream("/clientreg-test/jwks.json"), JSONWebKeySet.class);
+ // Generate keys for client
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ Map<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
+
+ JSONWebKeySet keySet = oidcClientEndpointsResource.getJwks();
clientRep.setJwks(keySet);
OIDCClientRepresentation response = reg.oidc().create(clientRep);
@@ -246,7 +249,7 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
Assert.assertNull(response.getClientSecretExpiresAt());
// Tries to authenticate client with privateKey JWT
- String signedJwt = getClientSignedJWT(response.getClientId());
+ String signedJwt = getClientSignedJWT(response.getClientId(), generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY));
OAuthClient.AccessTokenResponse accessTokenResponse = doClientCredentialsGrantRequest(signedJwt);
Assert.assertEquals(200, accessTokenResponse.getStatusCode());
AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
@@ -260,8 +263,11 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
clientRep.setGrantTypes(Collections.singletonList(OAuth2Constants.CLIENT_CREDENTIALS));
clientRep.setTokenEndpointAuthMethod(OIDCLoginProtocol.PRIVATE_KEY_JWT);
- // Use the realmKey for client authentication too
- clientRep.setJwksUri(oauth.getCertsUrl(REALM_NAME));
+ // Generate keys for client
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ Map<String, String> generatedKeys = oidcClientEndpointsResource.generateKeys();
+
+ clientRep.setJwksUri(TestApplicationResourceUrls.clientJwksUri());
OIDCClientRepresentation response = reg.oidc().create(clientRep);
Assert.assertEquals(OIDCLoginProtocol.PRIVATE_KEY_JWT, response.getTokenEndpointAuthMethod());
@@ -269,7 +275,7 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
Assert.assertNull(response.getClientSecretExpiresAt());
// Tries to authenticate client with privateKey JWT
- String signedJwt = getClientSignedJWT(response.getClientId());
+ String signedJwt = getClientSignedJWT(response.getClientId(), generatedKeys.get(TestingOIDCEndpointsApplicationResource.PRIVATE_KEY));
OAuthClient.AccessTokenResponse accessTokenResponse = doClientCredentialsGrantRequest(signedJwt);
Assert.assertEquals(200, accessTokenResponse.getStatusCode());
AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
@@ -277,27 +283,161 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
}
@Test
+ public void createPairwiseClient() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setSubjectType("pairwise");
+
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ Assert.assertEquals("pairwise", response.getSubjectType());
+ }
+
+ @Test
+ public void updateClientToPairwise() throws Exception {
+ OIDCClientRepresentation response = create();
+ Assert.assertEquals("public", response.getSubjectType());
+
+ reg.auth(Auth.token(response));
+ response.setSubjectType("pairwise");
+ OIDCClientRepresentation updated = reg.oidc().update(response);
+
+ Assert.assertEquals("pairwise", updated.getSubjectType());
+ }
+
+ @Test
+ public void updateSectorIdentifierUri() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setSubjectType("pairwise");
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ Assert.assertEquals("pairwise", response.getSubjectType());
+ Assert.assertNull(response.getSectorIdentifierUri());
+
+ reg.auth(Auth.token(response));
+
+ // Push redirect uris to the sector identifier URI
+ List<String> sectorRedirects = new ArrayList<>();
+ sectorRedirects.addAll(response.getRedirectUris());
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
+
+ response.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
+
+ OIDCClientRepresentation updated = reg.oidc().update(response);
+
+ Assert.assertEquals("pairwise", updated.getSubjectType());
+ Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), updated.getSectorIdentifierUri());
+
+ }
+
+ @Test
+ public void createPairwiseClientWithSectorIdentifierURI() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+
+ // Push redirect uris to the sector identifier URI
+ List<String> sectorRedirects = new ArrayList<>();
+ sectorRedirects.addAll(clientRep.getRedirectUris());
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
+
+ clientRep.setSubjectType("pairwise");
+ clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
+
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ Assert.assertEquals("pairwise", response.getSubjectType());
+ Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), response.getSectorIdentifierUri());
+ }
+
+ @Test
+ public void createPairwiseClientWithRedirectsToMultipleHostsWithoutSectorIdentifierURI() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+
+ List<String> redirects = new ArrayList<>();
+ redirects.add("http://redirect1");
+ redirects.add("http://redirect2");
+
+ clientRep.setSubjectType("pairwise");
+ clientRep.setRedirectUris(redirects);
+
+ assertCreateFail(clientRep, 400, "Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.");
+ }
+
+ @Test
+ public void createPairwiseClientWithRedirectsToMultipleHosts() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+
+ // Push redirect URIs to the sector identifier URI
+ List<String> redirects = new ArrayList<>();
+ redirects.add("http://redirect1");
+ redirects.add("http://redirect2");
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(redirects);
+
+ clientRep.setSubjectType("pairwise");
+ clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
+ clientRep.setRedirectUris(redirects);
+
+ OIDCClientRepresentation response = reg.oidc().create(clientRep);
+ Assert.assertEquals("pairwise", response.getSubjectType());
+ Assert.assertEquals(TestApplicationResourceUrls.pairwiseSectorIdentifierUri(), response.getSectorIdentifierUri());
+ Assert.assertNames(response.getRedirectUris(), "http://redirect1", "http://redirect2");
+ }
+
+ @Test
+ public void createPairwiseClientWithSectorIdentifierURIContainingMismatchedRedirects() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+
+ // Push redirect uris to the sector identifier URI
+ List<String> sectorRedirects = new ArrayList<>();
+ sectorRedirects.add("http://someotherredirect");
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+ oidcClientEndpointsResource.setSectorIdentifierRedirectUris(sectorRedirects);
+
+ clientRep.setSubjectType("pairwise");
+ clientRep.setSectorIdentifierUri(TestApplicationResourceUrls.pairwiseSectorIdentifierUri());
+
+ assertCreateFail(clientRep, 400, "Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.");
+ }
+
+ @Test
+ public void createPairwiseClientWithInvalidSectorIdentifierURI() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setSubjectType("pairwise");
+ clientRep.setSectorIdentifierUri("malformed");
+ assertCreateFail(clientRep, 400, "Invalid Sector Identifier URI.");
+ }
+
+ @Test
+ public void createPairwiseClientWithUnreachableSectorIdentifierURI() throws Exception {
+ OIDCClientRepresentation clientRep = createRep();
+ clientRep.setSubjectType("pairwise");
+ clientRep.setSectorIdentifierUri("http://localhost/dummy");
+ assertCreateFail(clientRep, 400, "Failed to get redirect URIs from the Sector Identifier URI.");
+ }
+
+ @Test
public void testSignaturesRequired() throws Exception {
OIDCClientRepresentation clientRep = createRep();
clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString());
+ clientRep.setRequestObjectSigningAlg(Algorithm.RS256.toString());
OIDCClientRepresentation response = reg.oidc().create(clientRep);
Assert.assertEquals(Algorithm.RS256.toString(), response.getUserinfoSignedResponseAlg());
+ Assert.assertEquals(Algorithm.RS256.toString(), response.getRequestObjectSigningAlg());
Assert.assertNotNull(response.getClientSecret());
// Test Keycloak representation
ClientRepresentation kcClient = getClient(response.getClientId());
OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient);
Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.RS256);
+ Assert.assertEquals(config.getRequestObjectSignatureAlg(), Algorithm.RS256);
}
// Client auth with signedJWT - helper methods
- private String getClientSignedJWT(String clientId) {
+ private String getClientSignedJWT(String clientId, String privateKeyPem) {
String realmInfoUrl = KeycloakUriBuilder.fromUri(getAuthServerRoot()).path(ServiceUrlConstants.REALM_INFO_PATH).build(REALM_NAME).toString();
- PrivateKey privateKey = KeycloakModelUtils.getPrivateKey(PRIVATE_KEY);
+ PrivateKey privateKey = KeycloakModelUtils.getPrivateKey(privateKeyPem);
// Use token-endpoint as audience as OIDC conformance testsuite is using it too.
JWTClientCredentialsProvider jwtProvider = new JWTClientCredentialsProvider() {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
index 6abaf97..43c6fa9 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java
@@ -75,11 +75,11 @@ public class ExportImportTest extends AbstractExportImportTest {
@Test
public void testDirFullExportImport() throws Throwable {
- testingClient.testing().setProvider(DirExportProviderFactory.PROVIDER_ID);
- String targetDirPath = testingClient.testing().getExportImportTestDirectory()+ File.separator + "dirExport";
+ testingClient.testing().exportImport().setProvider(DirExportProviderFactory.PROVIDER_ID);
+ String targetDirPath = testingClient.testing().exportImport().getExportImportTestDirectory()+ File.separator + "dirExport";
DirExportProvider.recursiveDeleteDir(new File(targetDirPath));
- testingClient.testing().setDir(targetDirPath);
- testingClient.testing().setUsersPerFile(ExportImportConfig.DEFAULT_USERS_PER_FILE);
+ testingClient.testing().exportImport().setDir(targetDirPath);
+ testingClient.testing().exportImport().setUsersPerFile(ExportImportConfig.DEFAULT_USERS_PER_FILE);
testFullExportImport();
@@ -89,11 +89,13 @@ public class ExportImportTest extends AbstractExportImportTest {
@Test
public void testDirRealmExportImport() throws Throwable {
- testingClient.testing().setProvider(DirExportProviderFactory.PROVIDER_ID);
- String targetDirPath = testingClient.testing().getExportImportTestDirectory() + File.separator + "dirRealmExport";
+ testingClient.testing()
+ .exportImport()
+ .setProvider(DirExportProviderFactory.PROVIDER_ID);
+ String targetDirPath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "dirRealmExport";
DirExportProvider.recursiveDeleteDir(new File(targetDirPath));
- testingClient.testing().setDir(targetDirPath);
- testingClient.testing().setUsersPerFile(3);
+ testingClient.testing().exportImport().setDir(targetDirPath);
+ testingClient.testing().exportImport().setUsersPerFile(3);
testRealmExportImport();
@@ -104,18 +106,18 @@ public class ExportImportTest extends AbstractExportImportTest {
@Test
public void testSingleFileFullExportImport() throws Throwable {
- testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
- String targetFilePath = testingClient.testing().getExportImportTestDirectory() + File.separator + "singleFile-full.json";
- testingClient.testing().setFile(targetFilePath);
+ testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
+ String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "singleFile-full.json";
+ testingClient.testing().exportImport().setFile(targetFilePath);
testFullExportImport();
}
@Test
public void testSingleFileRealmExportImport() throws Throwable {
- testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
- String targetFilePath = testingClient.testing().getExportImportTestDirectory() + File.separator + "singleFile-realm.json";
- testingClient.testing().setFile(targetFilePath);
+ testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
+ String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "singleFile-realm.json";
+ testingClient.testing().exportImport().setFile(targetFilePath);
testRealmExportImport();
}
@@ -126,14 +128,14 @@ public class ExportImportTest extends AbstractExportImportTest {
removeRealm("test-realm");
// Set the realm, which doesn't have builtin clients/roles inside JSON
- testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
+ testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
URL url = ExportImportTest.class.getResource("/model/testrealm.json");
String targetFilePath = new File(url.getFile()).getAbsolutePath();
- testingClient.testing().setFile(targetFilePath);
+ testingClient.testing().exportImport().setFile(targetFilePath);
- testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT);
+ testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT);
- testingClient.testing().runImport();
+ testingClient.testing().exportImport().runImport();
RealmResource testRealmRealm = adminClient.realm("test-realm");
@@ -158,14 +160,14 @@ public class ExportImportTest extends AbstractExportImportTest {
realm.components().add(component);
- testingClient.testing().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
+ testingClient.testing().exportImport().setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
- String targetFilePath = testingClient.testing().getExportImportTestDirectory() + File.separator + "singleFile-realm.json";
- testingClient.testing().setFile(targetFilePath);
- testingClient.testing().setAction(ExportImportConfig.ACTION_EXPORT);
- testingClient.testing().setRealmName("component-realm");
+ String targetFilePath = testingClient.testing().exportImport().getExportImportTestDirectory() + File.separator + "singleFile-realm.json";
+ testingClient.testing().exportImport().setFile(targetFilePath);
+ testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT);
+ testingClient.testing().exportImport().setRealmName("component-realm");
- testingClient.testing().runExport();
+ testingClient.testing().exportImport().runExport();
// Delete some realm (and some data in admin realm)
adminClient.realm("component-realm").remove();
@@ -173,9 +175,9 @@ public class ExportImportTest extends AbstractExportImportTest {
Assert.assertEquals(3, adminClient.realms().findAll().size());
// Configure import
- testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT);
+ testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT);
- testingClient.testing().runImport();
+ testingClient.testing().exportImport().runImport();
realmRep = realm.toRepresentation();
@@ -203,10 +205,10 @@ public class ExportImportTest extends AbstractExportImportTest {
}
private void testFullExportImport() throws LifecycleException {
- testingClient.testing().setAction(ExportImportConfig.ACTION_EXPORT);
- testingClient.testing().setRealmName("");
+ testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT);
+ testingClient.testing().exportImport().setRealmName("");
- testingClient.testing().runExport();
+ testingClient.testing().exportImport().runExport();
removeRealm("test");
removeRealm("test-realm");
@@ -218,9 +220,9 @@ public class ExportImportTest extends AbstractExportImportTest {
assertNotAuthenticated("test", "user3", "password");
// Configure import
- testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT);
+ testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT);
- testingClient.testing().runImport();
+ testingClient.testing().exportImport().runImport();
// Ensure data are imported back
Assert.assertEquals(3, adminClient.realms().findAll().size());
@@ -232,10 +234,10 @@ public class ExportImportTest extends AbstractExportImportTest {
}
private void testRealmExportImport() throws LifecycleException {
- testingClient.testing().setAction(ExportImportConfig.ACTION_EXPORT);
- testingClient.testing().setRealmName("test");
+ testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_EXPORT);
+ testingClient.testing().exportImport().setRealmName("test");
- testingClient.testing().runExport();
+ testingClient.testing().exportImport().runExport();
// Delete some realm (and some data in admin realm)
adminClient.realm("test").remove();
@@ -248,9 +250,9 @@ public class ExportImportTest extends AbstractExportImportTest {
assertNotAuthenticated("test", "user3", "password");
// Configure import
- testingClient.testing().setAction(ExportImportConfig.ACTION_IMPORT);
+ testingClient.testing().exportImport().setAction(ExportImportConfig.ACTION_IMPORT);
- testingClient.testing().runImport();
+ testingClient.testing().exportImport().runImport();
// Ensure data are imported back, but just for "test" realm
Assert.assertEquals(3, adminClient.realms().findAll().size());
@@ -273,27 +275,4 @@ public class ExportImportTest extends AbstractExportImportTest {
Assert.assertEquals(expectedResult, testingClient.testing().validCredentials(realmName, username, password));
}
- private static String getExportImportTestDirectory() {
- String dirPath = null;
- String relativeDirExportImportPath = "testsuite" + File.separator +
- "integration-arquillian" + File.separator +
- "tests" + File.separator +
- "base" + File.separator +
- "target" + File.separator +
- "export-import";
-
- if (System.getProperties().containsKey("maven.home")) {
- dirPath = System.getProperty("user.dir").replaceFirst("testsuite.integration.*", Matcher.quoteReplacement(relativeDirExportImportPath));
- } else {
- for (String c : System.getProperty("java.class.path").split(File.pathSeparator)) {
- if (c.contains(File.separator + "testsuite" + File.separator + "integration-arquillian" + File.separator)) {
- dirPath = c.replaceFirst("testsuite.integration-arquillian.*", Matcher.quoteReplacement(relativeDirExportImportPath));
- }
- }
- }
-
- String absolutePath = new File(dirPath).getAbsolutePath();
- return absolutePath;
- }
-
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index accbd37..5bef3d2 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -480,6 +480,40 @@ public class LoginTest extends TestRealmKeycloakTest {
setRememberMe(false);
}
}
+
+ @Test
+ // KEYCLOAK-3181
+ public void loginWithEmailUserAndRememberMe() {
+ setRememberMe(true);
+
+ try {
+ loginPage.open();
+ loginPage.setRememberMe(true);
+ assertTrue(loginPage.isRememberMeChecked());
+ loginPage.login("login@test.com", "password");
+
+ Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+ Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+ EventRepresentation loginEvent = events.expectLogin().user(userId)
+ .detail(Details.USERNAME, "login@test.com")
+ .detail(Details.REMEMBER_ME, "true")
+ .assertEvent();
+ String sessionId = loginEvent.getSessionId();
+
+ // Expire session
+ testingClient.testing().removeUserSession("test", sessionId);
+
+ // Assert rememberMe checked and username/email prefilled
+ loginPage.open();
+ assertTrue(loginPage.isRememberMeChecked());
+
+ Assert.assertEquals("login@test.com", loginPage.getUsername());
+
+ loginPage.setRememberMe(false);
+ } finally {
+ setRememberMe(false);
+ }
+ }
// KEYCLOAK-1037
@Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
index 78d4faa..1864324 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java
@@ -17,69 +17,52 @@
package org.keycloak.testsuite.migration;
import java.util.List;
-import static org.junit.Assert.*;
-import org.junit.Ignore;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
-import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.RealmResource;
-import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.arquillian.migration.Migration;
/**
* @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
*/
public class MigrationTest extends AbstractKeycloakTest {
-
+
+ private RealmResource realmResource;
+ private RealmRepresentation realmRep;
+
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
log.info("Adding no test realms for migration test. Test realm should be migrated from previous vesrion.");
}
- @Test
- @Migration(versionFrom = "1.6.1.Final")
- public void migration16Test() {
- RealmResource realmResource = adminClient.realms().realm("Migration");
- RealmRepresentation realmRep = realmResource.toRepresentation();
- assertEquals("Migration", realmRep.getRealm());
-
- List<RoleRepresentation> realmRoles = realmResource.roles().list();
- assertEquals(1, realmRoles.size());
- assertEquals("offline_access", realmRoles.get(0).getName());
-
- for (ClientRepresentation client : realmResource.clients().findAll()) {
- final String clientId = client.getClientId();
- switch (clientId) {
- case "realm-management":
- assertEquals(13, realmResource.clients().get(client.getId()).roles().list().size());
- break;
- case "security-admin-console":
- assertEquals(0, realmResource.clients().get(client.getId()).roles().list().size());
- break;
- case "broker":
- assertEquals(1, realmResource.clients().get(client.getId()).roles().list().size());
- break;
- case "account":
- assertEquals(2, realmResource.clients().get(client.getId()).roles().list().size());
- break;
- default:
- fail("Migrated realm contains unexpected client " + clientId);
- break;
- }
- }
+ @Before
+ public void beforeMigrationTest() {
+ realmResource = adminClient.realms().realm("Migration");
+ realmRep = realmResource.toRepresentation();
}
@Test
- @Migration(versionFrom = "1.5.1.Final")
- @Ignore
- public void migration15Test() {
- for (RealmRepresentation realm : adminClient.realms().findAll()) {
- System.out.println(realm.getRealm());
- }
+ @Migration(versionFrom = "1.9.8.Final")
+ public void migration198Test() {
+ Assert.assertNames(realmResource.roles().list(), "offline_access", "uma_authorization");
+ Assert.assertNames(realmResource.clients().findAll(), "admin-cli", "realm-management", "security-admin-console", "broker", "account");
//TODO
}
-
+
+ /**
+ * Assumed that there is only one migration test for each version and *remove*
+ * 'Migration' realm from Keycloak after test to be able to run the rest
+ * of the testsuite isolated afterward.
+ */
+ @After
+ public void afterMigrationTest() {
+ log.info("removing '" + realmRep.getRealm() + "' realm");
+ removeRealm(realmRep);
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
index 191581e..e01057d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
@@ -19,30 +19,42 @@ package org.keycloak.testsuite.oidc;
import java.util.List;
-
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.OAuthErrorException;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
import org.keycloak.common.util.Time;
import org.keycloak.events.Details;
+import org.keycloak.jose.jws.Algorithm;
import org.keycloak.models.Constants;
+import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.IDToken;
+import org.keycloak.representations.idm.CertificateRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.util.CertificateInfoHelper;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.TestRealmKeycloakTest;
import org.keycloak.testsuite.admin.AbstractAdminTest;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
+import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
+import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -68,6 +80,9 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest {
@Page
protected OAuthGrantPage grantPage;
+ @Page
+ protected ErrorPage errorPage;
+
@Override
public void configureTestRealm(RealmRepresentation testRealm) {
@@ -308,29 +323,98 @@ public class OIDCAdvancedRequestParamsTest extends TestRealmKeycloakTest {
// REQUEST & REQUEST_URI
@Test
- public void requestParam() {
- driver.navigate().to(oauth.getLoginFormUrl() + "&request=abc");
+ public void requestParamUnsigned() throws Exception {
+ String validRedirectUri = oauth.getRedirectUri();
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
- assertFalse(loginPage.isCurrent());
+ // Send request object with invalid redirect uri.
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", "http://invalid", null, Algorithm.none.toString());
+ String requestStr = oidcClientEndpointsResource.getOIDCRequest();
+
+ oauth.request(requestStr);
+ oauth.openLoginForm();
+ Assert.assertTrue(errorPage.isCurrent());
+ assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
+
+ // Assert the value from request object has bigger priority then from the query parameter.
+ oauth.redirectUri("http://invalid");
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString());
+ requestStr = oidcClientEndpointsResource.getOIDCRequest();
+
+ oauth.request(requestStr);
+ OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
+ Assert.assertNotNull(response.getCode());
+ Assert.assertEquals("mystate", response.getState());
assertTrue(appPage.isCurrent());
+ }
- // Assert error response was sent because not logged in
- OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth);
- Assert.assertNull(resp.getCode());
- Assert.assertEquals(OAuthErrorException.REQUEST_NOT_SUPPORTED, resp.getError());
+ @Test
+ public void requestUriParamUnsigned() throws Exception {
+ String validRedirectUri = oauth.getRedirectUri();
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+
+ // Send request object with invalid redirect uri.
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", "http://invalid", null, Algorithm.none.toString());
+
+ oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
+ oauth.openLoginForm();
+ Assert.assertTrue(errorPage.isCurrent());
+ assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
+
+ // Assert the value from request object has bigger priority then from the query parameter.
+ oauth.redirectUri("http://invalid");
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString());
+
+ OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
+ Assert.assertNotNull(response.getCode());
+ Assert.assertEquals("mystate", response.getState());
+ assertTrue(appPage.isCurrent());
}
@Test
- public void requestUriParam() {
- driver.navigate().to(oauth.getLoginFormUrl() + "&request_uri=https%3A%2F%2Flocalhost%3A60784%2Fexport%2FqzHTG11W48.jwt");
+ public void requestUriParamSigned() throws Exception {
+ String validRedirectUri = oauth.getRedirectUri();
+ TestOIDCEndpointsApplicationResource oidcClientEndpointsResource = testingClient.testApp().oidcClientEndpoints();
+
+ // Set required signature for request_uri
+ ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app");
+ ClientRepresentation clientRep = clientResource.toRepresentation();
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(Algorithm.RS256);
+ clientResource.update(clientRep);
+
+ // Verify unsigned request_uri will fail
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.none.toString());
+ oauth.requestUri(TestApplicationResourceUrls.clientRequestUri());
+ oauth.openLoginForm();
+ Assert.assertTrue(errorPage.isCurrent());
+ assertEquals("Invalid Request", errorPage.getError());
- assertFalse(loginPage.isCurrent());
+ // Generate keypair for client
+ String clientPublicKeyPem = oidcClientEndpointsResource.generateKeys().get(TestingOIDCEndpointsApplicationResource.PUBLIC_KEY);
+
+ // Verify signed request_uri will fail due to failed signature validation
+ oidcClientEndpointsResource.setOIDCRequest("test", "test-app", validRedirectUri, "10", Algorithm.RS256.toString());
+ oauth.openLoginForm();
+ Assert.assertTrue(errorPage.isCurrent());
+ assertEquals("Invalid Request", errorPage.getError());
+
+
+ // Update clientModel with publicKey for signing
+ clientRep = clientResource.toRepresentation();
+ CertificateRepresentation cert = new CertificateRepresentation();
+ cert.setPublicKey(clientPublicKeyPem);
+ CertificateInfoHelper.updateClientRepresentationCertificateInfo(clientRep, cert, JWTClientAuthenticator.ATTR_PREFIX);
+ clientResource.update(clientRep);
+
+ // Check signed request_uri will pass
+ OAuthClient.AuthorizationEndpointResponse response = oauth.doLogin("test-user@localhost", "password");
+ Assert.assertNotNull(response.getCode());
+ Assert.assertEquals("mystate", response.getState());
assertTrue(appPage.isCurrent());
- // Assert error response was sent because not logged in
- OAuthClient.AuthorizationEndpointResponse resp = new OAuthClient.AuthorizationEndpointResponse(oauth);
- Assert.assertNull(resp.getCode());
- Assert.assertEquals(OAuthErrorException.REQUEST_URI_NOT_SUPPORTED, resp.getError());
+ // Revert requiring signature for client
+ OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setRequestObjectSignatureAlg(null);
+ clientResource.update(clientRep);
}
// LOGIN_HINT
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
index 91ccc7e..e1877b8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java
@@ -36,6 +36,8 @@ import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentatio
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.clientregistration.ClientRegistrationService;
+import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.Assert;
@@ -79,14 +81,24 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
Assert.assertEquals(oidcConfig.getUserinfoEndpoint(), OIDCLoginProtocolService.userInfoUrl(UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT)).build("test").toString());
Assert.assertEquals(oidcConfig.getJwksUri(), oauth.getCertsUrl("test"));
+ String registrationUri = UriBuilder
+ .fromUri(OAuthClient.AUTH_SERVER_ROOT)
+ .path(RealmsResource.class)
+ .path(RealmsResource.class, "getClientsService")
+ .path(ClientRegistrationService.class, "provider")
+ .build("test", OIDCClientRegistrationProviderFactory.ID)
+ .toString();
+ Assert.assertEquals(oidcConfig.getRegistrationEndpoint(), registrationUri);
+
// Support standard + implicit + hybrid flow
assertContains(oidcConfig.getResponseTypesSupported(), OAuth2Constants.CODE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token");
assertContains(oidcConfig.getGrantTypesSupported(), OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT);
assertContains(oidcConfig.getResponseModesSupported(), "query", "fragment");
- Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "public");
+ Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "pairwise", "public");
Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.RS256.toString());
Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), Algorithm.RS256.toString());
+ Assert.assertNames(oidcConfig.getRequestObjectSigningAlgValuesSupported(), Algorithm.none.toString(), Algorithm.RS256.toString());
// Client authentication
Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt");
@@ -101,8 +113,8 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest {
Assert.assertNames(oidcConfig.getScopesSupported(), OAuth2Constants.SCOPE_OPENID, OAuth2Constants.OFFLINE_ACCESS);
// Request and Request_Uri
- Assert.assertFalse(oidcConfig.getRequestParameterSupported());
- Assert.assertFalse(oidcConfig.getRequestUriParameterSupported());
+ Assert.assertTrue(oidcConfig.getRequestParameterSupported());
+ Assert.assertTrue(oidcConfig.getRequestUriParameterSupported());
} finally {
client.close();
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/jetty-web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..8c59313
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/jetty-web.xml
@@ -0,0 +1,46 @@
+<?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.
+ -->
+
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+ <Get name="securityHandler">
+ <Set name="authenticator">
+ <New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
+ <!--
+ <Set name="adapterConfig">
+ <New class="org.keycloak.representations.adapters.config.AdapterConfig">
+ <Set name="realm">tomcat</Set>
+ <Set name="resource">customer-portal</Set>
+ <Set name="authServerUrl">http://localhost:8180/auth</Set>
+ <Set name="sslRequired">external</Set>
+ <Set name="credentials">
+ <Map>
+ <Entry>
+ <Item>secret</Item>
+ <Item>password</Item>
+ </Entry>
+ </Map>
+ </Set>
+ <Set name="realmKey">MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB</Set>
+ </New>
+ </Set>
+ -->
+ </New>
+ </Set>
+ </Get>
+</Configure>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak.json
new file mode 100644
index 0000000..e00b8fc
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak.json
@@ -0,0 +1,11 @@
+{
+ "realm" : "demo",
+ "resource" : "basic-auth-service",
+ "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url": "/auth",
+ "ssl-required" : "external",
+ "enable-basic-auth" : "true",
+ "credentials": {
+ "secret": "password"
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak-relative.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak-relative.json
new file mode 100644
index 0000000..e00b8fc
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/keycloak-relative.json
@@ -0,0 +1,11 @@
+{
+ "realm" : "demo",
+ "resource" : "basic-auth-service",
+ "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url": "/auth",
+ "ssl-required" : "external",
+ "enable-basic-auth" : "true",
+ "credentials": {
+ "secret": "password"
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/web.xml
new file mode 100644
index 0000000..0ea56f4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/basic-auth/WEB-INF/web.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <module-name>basic-auth</module-name>
+
+ <security-constraint>
+ <web-resource-collection>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <!-- <user-data-constraint>
+ <transport-guarantee>CONFIDENTIAL</transport-guarantee>
+ </user-data-constraint> -->
+ <auth-constraint>
+ <role-name>user</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+ <login-config>
+ <auth-method>KEYCLOAK</auth-method>
+ <realm-name>demo</realm-name>
+ </login-config>
+
+ <security-role>
+ <role-name>user</role-name>
+ </security-role>
+</web-app>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json
index b75c1d5..32703f9 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/customer-portal/WEB-INF/keycloak.json
@@ -1,10 +1,10 @@
{
"realm": "demo",
"resource": "customer-portal",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
- "auth-server-url": "http://localhost:8180/auth",
+ "auth-server-url": "http://localhostt:8180/auth",
"ssl-required" : "external",
"expose-token": true,
+ "min-time-between-jwks-requests": 120,
"credentials": {
"secret": "password"
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
index 9a4a7f6..0272584 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
@@ -226,6 +226,15 @@
"/oauth-client-cdi/*"
],
"secret": "password"
+ },
+ {
+ "clientId": "basic-auth-service",
+ "standardFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "enabled": true,
+ "adminUrl": "/basic-auth",
+ "baseUrl": "/basic-auth",
+ "secret": "password"
}
]
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json
index a934e97..eecc24b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/input-portal/WEB-INF/keycloak.json
@@ -1,9 +1,9 @@
{
"realm" : "demo",
"resource" : "input-portal",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://${my.host.name}:8180/auth",
"ssl-required" : "external",
+ "min-time-between-jwks-requests": 120,
"credentials" : {
"secret": "password"
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json
index 85eb9a7..b86e018 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/keycloak.json
@@ -1,11 +1,11 @@
{
"realm": "demo",
"resource": "token-min-ttl",
- "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "http://localhost:8180/auth",
"ssl-required" : "external",
"credentials": {
"secret": "password"
},
- "token-minimum-time-to-live": 120
+ "token-minimum-time-to-live": 120,
+ "min-time-between-jwks-requests": 120
}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml
index f8110bc..86d07f5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/token-min-ttl/WEB-INF/web.xml
@@ -68,6 +68,12 @@
<url-pattern>/error.html</url-pattern>
</web-resource-collection>
</security-constraint>
+ <security-constraint>
+ <web-resource-collection>
+ <web-resource-name>Unsecured</web-resource-name>
+ <url-pattern>/unsecured/*</url-pattern>
+ </web-resource-collection>
+ </security-constraint>
<login-config>
<auth-method>KEYCLOAK</auth-method>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json
index bc5b2a7..b20eb5d 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/testrealm.json
@@ -95,5 +95,11 @@
"roles": ["customer-user"]
}
]
+ },
+ "attributes": {
+ "string-attr": "foo",
+ "int-attr": "123",
+ "long-attr": "1234567890123456",
+ "bool-attr": "true"
}
}
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 215da8e..8c996d6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -150,11 +150,11 @@
</configuration>
</container>
- <!-- PREVIOUS VERSIONS OF KEYCLOAK FOR MIGRATION TESTS -->
+ <!-- PREVIOUS VERSION OF KEYCLOAK FOR MIGRATION TESTS -->
- <container qualifier="auth-server-jboss-kc16" mode="manual" >
+ <container qualifier="auth-server-jboss-migration" mode="manual" >
<configuration>
- <property name="enabled">${auth.server.jboss.kc16}</property>
+ <property name="enabled">${auth.server.jboss.migration}</property>
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${keycloak.migration.home}</property>
<property name="javaVmArguments">
@@ -164,62 +164,10 @@
-Dkeycloak.migration.strategy=OVERWRITE_EXISTING
-Dkeycloak.migration.realmName=Migration
-Djboss.socket.binding.port-offset=${auth.server.port.offset}
- -Xms64m -Xmx512m -XX:MaxPermSize=256m
- </property>
- <property name="managementPort">${auth.server.management.port}</property>
- <property name="startupTimeoutInSeconds">${auth.server.startup.timeout}</property>
- </configuration>
- </container>
-
- <container qualifier="auth-server-jboss-kc15" mode="manual" >
- <configuration>
- <property name="enabled">${auth.server.jboss.kc15}</property>
- <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
- <property name="jbossHome">${keycloak.migration.home}</property>
- <property name="javaVmArguments">
- -Dkeycloak.migration.action=import
- -Dkeycloak.migration.provider=singleFile
- -Dkeycloak.migration.file=${keycloak.migration.file}
- -Dkeycloak.migration.strategy=OVERWRITE_EXISTING
- -Dkeycloak.migration.realmName=Migration
- -Djboss.socket.binding.port-offset=${auth.server.port.offset}
- -Xms64m -Xmx512m -XX:MaxPermSize=256m
+ ${auth.server.memory.settings}
</property>
<property name="managementPort">${auth.server.management.port}</property>
- <property name="startupTimeoutInSeconds">${auth.server.startup.timeout}</property>
- </configuration>
- </container>
-
- <container qualifier="auth-server-jboss-kc14" mode="manual" >
- <configuration>
- <property name="enabled">${auth.server.jboss.kc14}</property>
- <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
- <property name="jbossHome">${keycloak.migration.home}</property>
- <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m</property>
- <property name="managementPort">${auth.server.management.port}</property>
- <property name="startupTimeoutInSeconds">${auth.server.startup.timeout}</property>
- </configuration>
- </container>
-
- <container qualifier="auth-server-jboss-kc13" mode="manual" >
- <configuration>
- <property name="enabled">${auth.server.jboss.kc13}</property>
- <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
- <property name="jbossHome">${keycloak.migration.home}</property>
- <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m</property>
- <property name="managementPort">${auth.server.management.port}</property>
- <property name="startupTimeoutInSeconds">${auth.server.startup.timeout}</property>
- </configuration>
- </container>
-
- <container qualifier="auth-server-jboss-kc12" mode="manual" >
- <configuration>
- <property name="enabled">${auth.server.jboss.kc12}</property>
- <property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
- <property name="jbossHome">${keycloak.migration.home}</property>
- <property name="javaVmArguments">-Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m</property>
- <property name="managementPort">${auth.server.management.port}</property>
- <property name="startupTimeoutInSeconds">${auth.server.startup.timeout}</property>
+ <property name="startupTimeoutInSeconds">${auth.server.jboss.startup.timeout}</property>
</configuration>
</container>
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 04c79fe..ce33acf 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/log4j.properties
@@ -61,3 +61,5 @@ log4j.logger.org.hibernate=off
log4j.logger.org.jboss.resteasy=warn
log4j.logger.org.apache.directory.api=warn
log4j.logger.org.apache.directory.server.core=warn
+
+log4j.logger.org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator=trace
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPDefaultAuthzConfigAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPDefaultAuthzConfigAdapterTest.java
new file mode 100644
index 0000000..6d63c38
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPDefaultAuthzConfigAdapterTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.adapter.example.authorization;
+
+import org.keycloak.testsuite.adapter.example.authorization.AbstractDefaultAuthzConfigAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-eap")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class EAPDefaultAuthzConfigAdapterTest extends AbstractDefaultAuthzConfigAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPPhotozExampleAdapterTest.java
new file mode 100644
index 0000000..07b9844
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPPhotozExampleAdapterTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.adapter.example.authorization;
+
+import org.keycloak.testsuite.adapter.example.authorization.AbstractPhotozExampleAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@AppServerContainer("app-server-eap")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class EAPPhotozExampleAdapterTest extends AbstractPhotozExampleAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.java
new file mode 100644
index 0000000..3c789a4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EAPServletAuthzAdapterTest.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.testsuite.adapter.example.authorization;
+
+import org.jboss.arquillian.container.test.api.RunAsClient;
+import org.keycloak.testsuite.adapter.example.authorization.AbstractDefaultAuthzConfigAdapterTest;
+import org.keycloak.testsuite.adapter.example.authorization.AbstractServletAuthzAdapterTest;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+@RunAsClient
+@AppServerContainer("app-server-eap")
+//@AdapterLibsLocationProperty("adapter.libs.wildfly")
+public class EAPServletAuthzAdapterTest extends AbstractServletAuthzAdapterTest {
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
index 15fc1c4..5a2a2f3 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
@@ -53,7 +53,10 @@
<app.server.memory.settings>-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m</app.server.memory.settings>
<app.server.ssl.required>false</app.server.ssl.required>
-
+
+ <settings.path></settings.path>
+ <repo.url></repo.url>
+
<adapter.test.props>
-Dapp.server.base.url=http://localhost:${app.server.http.port}
-Dauth.server.base.url=http://localhost:${auth.server.http.port}
@@ -63,11 +66,14 @@
-Dauth.server.ssl.required=${auth.server.ssl.required}
-Dmy.host.name=localhost
-Djava.security.krb5.conf=${project.build.directory}/dependency/kerberos/test-krb5.conf
+ -Dkie.maven.settings.custom=${settings.path}
+ -Drepo.url=${repo.url}
</adapter.test.props>
<app.server.home>${containers.home}/app-server-${app.server}</app.server.home>
<adapter.config.bundled>true</adapter.config.bundled>
-
+ <examples.basedir>${main.basedir}/examples</examples.basedir>
+
</properties>
<modules>
@@ -88,6 +94,23 @@
<build>
<plugins>
<plugin>
+ <groupId>org.commonjava.maven.plugins</groupId>
+ <artifactId>directory-maven-plugin</artifactId>
+ <version>0.1</version>
+ <executions>
+ <execution>
+ <id>directories</id>
+ <goals>
+ <goal>highest-basedir</goal>
+ </goals>
+ <phase>initialize</phase>
+ <configuration>
+ <property>main.basedir</property>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>xml-maven-plugin</artifactId>
<executions>
@@ -311,7 +334,7 @@
</configuration>
</execution>
<execution>
- <id>example-realms</id>
+ <id>test-apps-realms</id>
<phase>generate-test-resources</phase>
<goals>
<goal>unpack</goal>
@@ -319,13 +342,6 @@
<configuration>
<artifactItems>
<artifactItem>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-examples-dist</artifactId>
- <version>${project.version}</version>
- <type>zip</type>
- <includes>**/*realm.json,**/testsaml.json</includes>
- </artifactItem>
- <artifactItem>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-test-apps-dist</artifactId>
<version>${project.version}</version>
@@ -336,9 +352,35 @@
<outputDirectory>${examples.home}</outputDirectory>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
- </execution>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>example-realms</id>
+ <phase>generate-test-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${examples.home}/example-realms</outputDirectory>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ <resources>
+ <resource>
+ <directory>${examples.basedir}</directory>
+ <filtering>true</filtering>
+ <includes>
+ <include>**/*realm.json</include>
+ <include>**/testsaml.json</include>
+ </includes>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
</executions>
- </plugin>
+ </plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientAuthorizationServicesAvailableTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientAuthorizationServicesAvailableTest.java
new file mode 100644
index 0000000..0dcf08b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/clients/ClientAuthorizationServicesAvailableTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.clients;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Test;
+import org.keycloak.common.Profile;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.testsuite.ProfileAssume;
+import org.keycloak.testsuite.console.page.clients.settings.ClientSettings;
+import org.openqa.selenium.By;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.auth.page.login.Login.OIDC;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class ClientAuthorizationServicesAvailableTest extends AbstractClientTest {
+
+ private ClientRepresentation newClient;
+
+ @Page
+ private ClientSettings clientSettingsPage;
+
+ @Test
+ public void authzServicesAvailable() {
+ ProfileAssume.assumePreview();
+
+ newClient = createClientRep("oidc-public", OIDC);
+ createClient(newClient);
+ assertEquals("oidc-public", clientSettingsPage.form().getClientId());
+
+ assertTrue(driver.findElement(By.xpath("//*[@for='authorizationServicesEnabled']")).isDisplayed());
+ }
+
+ @Test
+ public void authzServicesUnavailable() throws InterruptedException {
+ ProfileAssume.assumePreviewDisabled();
+
+ newClient = createClientRep("oidc-public", OIDC);
+ createClient(newClient);
+ assertEquals("oidc-public", clientSettingsPage.form().getClientId());
+
+ assertFalse(driver.findElement(By.xpath("//*[@for='authorizationServicesEnabled']")).isDisplayed());
+
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/pom.xml b/testsuite/integration-arquillian/tests/other/pom.xml
index 6b0f83c..b280525 100644
--- a/testsuite/integration-arquillian/tests/other/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/pom.xml
@@ -38,8 +38,9 @@
<modules>
<module>adapters</module>
+ <module>sssd</module>
</modules>
-
+
<build>
<pluginManagement>
<plugins>
@@ -65,7 +66,7 @@
</configuration>
</execution>
</executions>
- </plugin>
+ </plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
diff --git a/testsuite/integration-arquillian/tests/other/sssd/pom.xml b/testsuite/integration-arquillian/tests/other/sssd/pom.xml
new file mode 100644
index 0000000..3388822
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/sssd/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>integration-arquillian-tests-other</artifactId>
+ <groupId>org.keycloak.testsuite</groupId>
+ <version>2.2.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>integration-arquillian-tests-sssd</artifactId>
+
+ <name>SSSD tests</name>
+
+ <properties>
+ <exclude.sssd>**/sssd/**/*Test.java</exclude.sssd>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.2</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>${exclude.sssd}</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+
+ </plugins>
+
+ </build>
+
+ <profiles>
+ <profile>
+ <id>sssd-testing</id>
+ <properties>
+ <exclude.sssd>-</exclude.sssd>
+ </properties>
+ </profile>
+ </profiles>
+
+</project>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/sssd/README.md b/testsuite/integration-arquillian/tests/other/sssd/README.md
new file mode 100644
index 0000000..03d73a0
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/sssd/README.md
@@ -0,0 +1,27 @@
+What is this module about?
+-------------------------
+
+This module containes integration tests for testing the SSSD features of Keycloak.
+
+Prerequisites
+-------------
+
+To run tests inside this module, one needs to have a linux machine configured as an `IPA` client having sssd
+ service started with infopipe support.
+
+How does one run the tests?
+--------------------------
+
+*All the commands are intended to be run from the root `keycloak` project directory.*
+
+First build the distribution of keycloak:
+`mvn clean install -B -DskipTests -Pdistribution`
+
+It may fail in the end, but it's not a problem as far as it creates a zip distribution of Keycloak inside
+distribution/server-dist/target.
+
+Then build the integration-arquillian-servers-auth-server-wildfly artifact:
+`mvn clean install -B -Pauth-server-wildfly -f testsuite/integration-arquillian/servers/pom.xml`
+
+And then, finally, it's possible to run the tests:
+`mvn test -f testsuite/integration-arquillian/tests/other/sssd/ -Pauth-server-wildfly -Psssd-testing`
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java b/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java
new file mode 100644
index 0000000..81ffa98
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/sssd/src/test/java/org/keycloak/testsuite/sssd/SSSDTest.java
@@ -0,0 +1,174 @@
+package org.keycloak.testsuite.sssd;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.pages.AccountPasswordPage;
+import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.LoginPage;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SSSDTest extends AbstractKeycloakTest {
+
+ private static final String DISPLAY_NAME = "Test user federation";
+ private static final String PROVIDER_NAME = "sssd";
+ private static final String REALM_NAME = "test";
+
+ private static final String USERNAME = "emily";
+ private static final String PASSWORD = "emily123";
+ private static final String DISABLED_USER = "david";
+ private static final String DISABLED_USER_PASSWORD = "emily123";
+
+ private static final String DEFINITELY_NOT_PASSWORD = "not" + PASSWORD;
+
+ private static final String ADMIN_USERNAME = "admin";
+ private static final String ADMIN_PASSWORD = "password";
+
+ @Page
+ protected LoginPage accountLoginPage;
+
+ @Page
+ protected AccountPasswordPage changePasswordPage;
+
+ @Page
+ protected AccountUpdateProfilePage profilePage;
+
+ @Rule
+ public AssertEvents events = new AssertEvents(this);
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ RealmRepresentation realm = new RealmRepresentation();
+
+ realm.setRealm(REALM_NAME);
+ realm.setEnabled(true);
+
+ testRealms.add(realm);
+ }
+
+ @Before
+ public void createUserFederation() {
+ UserFederationProviderRepresentation userFederation = new UserFederationProviderRepresentation();
+
+ Map<String, String> config = new HashMap<>();
+ userFederation.setConfig(config);
+
+ userFederation.setDisplayName(DISPLAY_NAME);
+ userFederation.setPriority(0);
+ userFederation.setProviderName(PROVIDER_NAME);
+
+ adminClient.realm(REALM_NAME).userFederation().create(userFederation);
+ }
+
+ @Test
+ public void testWrongUser() {
+ log.debug("Testing wrong password for user " + USERNAME);
+
+ driver.navigate().to(getAccountUrl());
+ Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
+ accountLoginPage.login(USERNAME, DEFINITELY_NOT_PASSWORD);
+
+ Assert.assertEquals("Invalid username or password.", accountLoginPage.getError());
+ }
+
+ @Test
+ public void testDisabledUser() {
+ log.debug("Testing disabled user " + USERNAME);
+
+ driver.navigate().to(getAccountUrl());
+ Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
+ accountLoginPage.login(DISABLED_USER, DISABLED_USER_PASSWORD);
+
+ Assert.assertEquals("Invalid username or password.", accountLoginPage.getError());
+ }
+
+ @Test
+ public void testAdmin() {
+ log.debug("Testing wrong password for user " + ADMIN_USERNAME);
+
+ driver.navigate().to(getAccountUrl());
+ Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
+ accountLoginPage.login(ADMIN_USERNAME, ADMIN_PASSWORD);
+
+ Assert.assertEquals("Unexpected error when handling authentication request to identity provider.", accountLoginPage.getInstruction());
+ }
+
+ @Test
+ public void testExistingUserLogIn() {
+ log.debug("Testing correct password");
+
+ driver.navigate().to(getAccountUrl());
+ Assert.assertEquals("Browser should be on login page now", "Log in to " + REALM_NAME, driver.getTitle());
+ accountLoginPage.login(USERNAME, PASSWORD);
+ Assert.assertEquals("Browser should be on account page now, logged in", "Keycloak Account Management", driver.getTitle());
+
+ testUserGroups();
+ }
+
+ @Test
+ public void changeReadOnlyProfile() throws Exception {
+
+ profilePage.open();
+ accountLoginPage.login(USERNAME, PASSWORD);
+
+ Assert.assertEquals("emily", profilePage.getUsername());
+ Assert.assertEquals("Emily", profilePage.getFirstName());
+ Assert.assertEquals("Jones", profilePage.getLastName());
+ Assert.assertEquals("emily@jones.com", profilePage.getEmail());
+
+ profilePage.updateProfile("New first", "New last", "new@email.com");
+
+ Assert.assertEquals("You can't update your account as it is read only.", profilePage.getError());
+ }
+
+ @Test
+ public void changeReadOnlyPassword() {
+ changePasswordPage.open();
+ accountLoginPage.login(USERNAME, PASSWORD);
+
+ changePasswordPage.changePassword(PASSWORD, "new-password", "new-password");
+ Assert.assertEquals("You can't update your password as your account is read only.", profilePage.getError());
+ }
+
+
+ private void testUserGroups() {
+ log.debug("Testing user groups");
+
+ List<UserRepresentation> users = adminClient.realm(REALM_NAME).users().search(USERNAME, 0, 1);
+
+ Assert.assertTrue("There must be at least one user", users.size() > 0);
+ Assert.assertEquals("Exactly our test user", USERNAME, users.get(0).getUsername());
+
+ List<GroupRepresentation> groups = adminClient.realm(REALM_NAME).users().get(users.get(0).getId()).groups();
+
+ Assert.assertEquals("User must have exactly two groups", 2, groups.size());
+ boolean wrongGroup = false;
+ for (GroupRepresentation group : groups) {
+ if (!group.getName().equalsIgnoreCase("ipausers") && !group.getName().equalsIgnoreCase("testgroup")) {
+ wrongGroup = true;
+ break;
+ }
+ }
+
+ Assert.assertFalse("There exists some wrong group", wrongGroup);
+ }
+
+ private String getAccountUrl() {
+ return getAuthRoot() + "/auth/realms/" + REALM_NAME + "/account";
+ }
+
+ private String getAuthRoot() {
+ return suiteContext.getAuthServerInfo().getContextRoot().toString();
+ }
+}
testsuite/integration-arquillian/tests/pom.xml 190(+17 -173)
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index b2ee848..3e97fc1 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -37,7 +37,7 @@
<module>base</module>
<module>other</module>
</modules>
-
+
<properties>
<auth.server>undertow</auth.server>
<auth.server.undertow>true</auth.server.undertow>
@@ -54,7 +54,6 @@
<auth.server.management.port>10090</auth.server.management.port>
<auth.server.management.port.jmx>10099</auth.server.management.port.jmx>
<auth.server.ssl.required>false</auth.server.ssl.required>
- <auth.server.jboss.startup.timeout>60</auth.server.jboss.startup.timeout>
<auth.server.memory.settings>-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m</auth.server.memory.settings>
<auth.server.config.property.name>serverConfig</auth.server.config.property.name>
<auth.server.adapter.impl.class>org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</auth.server.adapter.impl.class>
@@ -76,6 +75,7 @@
<testsuite.constants>${project.build.directory}/dependency/test-constants.properties</testsuite.constants>
+ <skip.add.user.json>false</skip.add.user.json>
</properties>
<build>
@@ -116,6 +116,7 @@
<goal>copy-resources</goal>
</goals>
<configuration>
+ <skip>${skip.add.user.json}</skip>
<outputDirectory>${auth.server.config.dir}</outputDirectory>
<resources>
<resource>
@@ -178,7 +179,7 @@
<properties>
<property>
<name>listener</name>
- <value>org.keycloak.testsuite.util.TestEventsLogger</value>
+ <value>org.keycloak.testsuite.util.TestEventsLogger,org.keycloak.testsuite.util.junit.AggregateResultsReporter</value>
</property>
</properties>
</configuration>
@@ -402,7 +403,7 @@
</build>
</profile>
- <!-- Profiles for migration tests-->
+ <!-- Profile for migration tests-->
<profile>
<id>migration</id>
@@ -411,31 +412,9 @@
<name>migrated.auth.server.version</name>
</property>
</activation>
- <properties>
- <!--diable exclusion pattern for migration tests, which is enabled by default in the base/pom.xml-->
- <exclude.migration>-</exclude.migration>
- </properties>
<build>
<plugins>
<plugin>
- <artifactId>maven-enforcer-plugin</artifactId>
- <executions>
- <execution>
- <id>enforce-properties</id>
- <goals>
- <goal>enforce</goal>
- </goals>
- <configuration>
- <rules>
- <requireProperty>
- <property>migrated.auth.server.version</property>
- </requireProperty>
- </rules>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
@@ -448,7 +427,7 @@
<artifactItems>
<artifactItem>
<groupId>org.keycloak.testsuite</groupId>
- <artifactId>${migrated.auth.server.jboss.artifactId}</artifactId>
+ <artifactId>integration-arquillian-migration-server</artifactId>
<version>${project.version}</version>
<type>zip</type>
</artifactItem>
@@ -464,158 +443,16 @@
<configuration>
<systemPropertyVariables>
<migrated.auth.server.version>${migrated.auth.server.version}</migrated.auth.server.version>
+ <auth.server.jboss.migration>true</auth.server.jboss.migration>
+ <keycloak.migration.home>${containers.home}/keycloak-${migrated.auth.server.version}</keycloak.migration.home>
+ <keycloak.migration.file>src/test/resources/migration-test/migration-realm-${migrated.auth.server.version}.json</keycloak.migration.file>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
-
- <profile>
- <id>migration-kc16</id>
- <activation>
- <property>
- <name>migrated.auth.server.version</name>
- <value>1.6.1.Final</value>
- </property>
- </activation>
- <properties>
- <migrated.auth.server.jboss.artifactId>integration-arquillian-server-wildfly-kc16</migrated.auth.server.jboss.artifactId>
- </properties>
- <build>
- <pluginManagement>
- <plugins>
- <plugin>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <systemPropertyVariables>
- <auth.server.jboss.kc16>true</auth.server.jboss.kc16>
- <keycloak.migration.home>${containers.home}/keycloak-${migrated.auth.server.version}</keycloak.migration.home>
- <keycloak.migration.file>src/test/resources/migration-test/migration-realm-16.json</keycloak.migration.file>
- </systemPropertyVariables>
- </configuration>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
- </profile>
-
- <profile>
- <id>migration-kc15</id>
- <activation>
- <property>
- <name>migrated.auth.server.version</name>
- <value>1.5.1.Final</value>
- </property>
- </activation>
- <properties>
- <migrated.auth.server.jboss.artifactId>integration-arquillian-server-wildfly-kc15</migrated.auth.server.jboss.artifactId>
- </properties>
- <build>
- <pluginManagement>
- <plugins>
- <plugin>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <systemPropertyVariables>
- <auth.server.jboss.kc15>true</auth.server.jboss.kc15>
- <keycloak.migration.home>${containers.home}/keycloak-${migrated.auth.server.version}</keycloak.migration.home>
- <keycloak.migration.file>src/test/resources/migration-test/migration-realm-15.json</keycloak.migration.file>
- </systemPropertyVariables>
- </configuration>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
- </profile>
-
- <profile>
- <id>migration-kc14</id>
- <activation>
- <property>
- <name>migrated.auth.server.version</name>
- <value>1.4.0.Final</value>
- </property>
- </activation>
- <properties>
- <migrated.auth.server.jboss.artifactId>integration-arquillian-server-wildfly-kc14</migrated.auth.server.jboss.artifactId>
- </properties>
- <build>
- <pluginManagement>
- <plugins>
- <plugin>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <systemPropertyVariables>
- <auth.server.jboss.kc14>true</auth.server.jboss.kc14>
- <keycloak.migration.home>${containers.home}/keycloak-${migrated.auth.server.version}</keycloak.migration.home>
- </systemPropertyVariables>
- </configuration>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
- </profile>
-
- <profile>
- <id>migration-kc13</id>
- <activation>
- <property>
- <name>migrated.auth.server.version</name>
- <value>1.3.1.Final</value>
- </property>
- </activation>
- <properties>
- <migrated.auth.server.jboss.artifactId>integration-arquillian-server-wildfly-kc13</migrated.auth.server.jboss.artifactId>
- </properties>
- <build>
- <pluginManagement>
- <plugins>
- <plugin>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <systemPropertyVariables>
- <auth.server.jboss.kc13>true</auth.server.jboss.kc13>
- <keycloak.migration.home>${containers.home}/keycloak-${migrated.auth.server.version}</keycloak.migration.home>
- </systemPropertyVariables>
- </configuration>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
- </profile>
-
- <profile>
- <id>migration-kc12</id>
- <activation>
- <property>
- <name>migrated.auth.server.version</name>
- <value>1.2.0.Final</value>
- </property>
- </activation>
- <properties>
- <migrated.auth.server.jboss.artifactId>integration-arquillian-server-wildfly-kc12</migrated.auth.server.jboss.artifactId>
- </properties>
- <build>
- <pluginManagement>
- <plugins>
- <plugin>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <systemPropertyVariables>
- <auth.server.jboss.kc12>true</auth.server.jboss.kc12>
- <keycloak.migration.home>${containers.home}/keycloak-${migrated.auth.server.version}</keycloak.migration.home>
- </systemPropertyVariables>
- </configuration>
- </plugin>
- </plugins>
- </pluginManagement>
- </build>
- </profile>
-
-
-
-
+
<profile>
<id>no-account</id>
<properties>
@@ -645,6 +482,13 @@
</file>
</activation>
<dependencies>
+
+ <dependency>
+ <groupId>org.keycloak.testsuite</groupId>
+ <artifactId>integration-arquillian-test-utils</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<!-- TEST DEPENDENCIES -->
<dependency>
<groupId>junit</groupId>
diff --git a/testsuite/integration-arquillian/test-utils/pom.xml b/testsuite/integration-arquillian/test-utils/pom.xml
new file mode 100644
index 0000000..c03c56b
--- /dev/null
+++ b/testsuite/integration-arquillian/test-utils/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>integration-arquillian</artifactId>
+ <groupId>org.keycloak.testsuite</groupId>
+ <version>2.2.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>integration-arquillian-test-utils</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Test utils</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-configuration</groupId>
+ <artifactId>commons-configuration</artifactId>
+ <version>1.10</version>
+ </dependency>
+ </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java b/testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java
new file mode 100644
index 0000000..2b1bf6c
--- /dev/null
+++ b/testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java
@@ -0,0 +1,280 @@
+package org.keycloak.testsuite.util.junit;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+
+import org.jboss.logging.Logger;
+
+import org.junit.Ignore;
+import org.junit.runner.Description;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Aggregates jUnit test results into a single report - XML file.
+ */
+public class AggregateResultsReporter extends RunListener {
+
+ private static final Logger LOGGER = Logger.getLogger(AggregateResultsReporter.class);
+
+ private final Document xml;
+ private final File reportFile;
+ private final boolean working;
+
+ private final AtomicInteger tests = new AtomicInteger(0);
+ private final AtomicInteger errors = new AtomicInteger(0);
+ private final AtomicInteger failures = new AtomicInteger(0);
+ private final AtomicInteger ignored = new AtomicInteger(0);
+ private final AtomicLong suiteStartTime = new AtomicLong(0L);
+
+ private final AtomicReference<Element> testsuite = new AtomicReference<Element>();
+
+ private final Map<String, Long> testTimes = new HashMap<String, Long>();
+
+ public AggregateResultsReporter() {
+ boolean working = true;
+ Document xml = null;
+ try {
+ xml = createEmptyDocument();
+ } catch (ParserConfigurationException ex) {
+ LOGGER.error("Failed to create XML DOM - reporting will not be done", ex);
+ working = false;
+ }
+
+ File reportFile = null;
+ try {
+ reportFile = createReportFile();
+ } catch (Exception ex) {
+ LOGGER.error("Failed to create log file - reporting will not be done", ex);
+ working = false;
+ }
+
+ this.working = working;
+ this.xml = xml;
+ this.reportFile = reportFile;
+ }
+
+ private Document createEmptyDocument() throws ParserConfigurationException {
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ return builder.newDocument();
+ }
+
+ private File createReportFile() throws Exception {
+ String logDirPath = null;
+
+ try {
+ PropertiesConfiguration config = new PropertiesConfiguration(System.getProperty("testsuite.constants"));
+ config.setThrowExceptionOnMissing(true);
+ logDirPath = config.getString("log-dir");
+ } catch (Exception e) {
+ logDirPath = System.getProperty("project.build.directory");
+ if (logDirPath == null) {
+ throw new RuntimeException("Could not determine the path to the log directory.");
+ }
+ logDirPath += File.separator + "surefire-reports";
+ }
+
+ final File logDir = new File(logDirPath);
+ logDir.mkdirs();
+
+ final File reportFile = new File(logDir, "junit-single-report.xml").getAbsoluteFile();
+ reportFile.createNewFile();
+
+ return reportFile;
+ }
+
+ @Override
+ public void testRunStarted(Description description) throws Exception {
+ if (working) {
+ suiteStartTime.set(System.currentTimeMillis());
+
+ Element testsuite = xml.createElement("testsuite");
+
+ if (description.getChildren().size() == 1) {
+ testsuite.setAttribute("name", safeString(description.getChildren().get(0).getDisplayName()));
+ }
+
+ xml.appendChild(testsuite);
+ this.testsuite.set(testsuite);
+ writeXml();
+ }
+ }
+
+ @Override
+ public void testStarted(Description description) throws Exception {
+ if (working) {
+ testTimes.put(description.getDisplayName(), System.currentTimeMillis());
+ }
+ }
+
+ @Override
+ public void testFinished(Description description) throws Exception {
+ if (working) {
+ if (testTimes.containsKey(description.getDisplayName())) {
+ testsuite.get().appendChild(createTestCase(description));
+ writeXml();
+ }
+ }
+ }
+
+ @Override
+ public void testAssumptionFailure(Failure failure) {
+ if (working) {
+ ignored.incrementAndGet();
+
+ Element testcase = createTestCase(failure.getDescription());
+ Element skipped = xml.createElement("skipped");
+ skipped.setAttribute("message", safeString(failure.getMessage()));
+
+ testcase.appendChild(skipped);
+
+ testsuite.get().appendChild(testcase);
+ writeXml();
+ }
+ }
+
+ @Override
+ public void testFailure(Failure failure) throws Exception {
+ if (working) {
+ if (failure.getDescription().getMethodName() == null) {
+ // before class failed
+ for (Description child : failure.getDescription().getChildren()) {
+ // mark all methods failed
+ testFailure(new Failure(child, failure.getException()));
+ }
+ } else {
+ // normal failure
+ Element testcase = createTestCase(failure.getDescription());
+
+ Element element;
+ if (failure.getException() instanceof AssertionError) {
+ failures.incrementAndGet();
+ element = xml.createElement("failure");
+ } else {
+ errors.incrementAndGet();
+ element = xml.createElement("error");
+ }
+
+ testcase.appendChild(element);
+
+ element.setAttribute("type", safeString(failure.getException().getClass().getName()));
+ element.setAttribute("message", safeString(failure.getMessage()));
+ element.appendChild(xml.createCDATASection(safeString(failure.getTrace())));
+
+ testsuite.get().appendChild(testcase);
+ writeXml();
+ }
+ }
+ }
+
+ @Override
+ public void testIgnored(Description description) throws Exception {
+ if (working) {
+ ignored.incrementAndGet();
+
+ Element testcase = createTestCase(description);
+
+ Element skipped = xml.createElement("skipped");
+ skipped.setAttribute("message", safeString(description.getAnnotation(Ignore.class).value()));
+
+ testcase.appendChild(skipped);
+
+ testsuite.get().appendChild(testcase);
+ writeXml();
+ }
+ }
+
+ @Override
+ public void testRunFinished(Result result) throws Exception {
+ if (working) {
+ writeXml();
+ }
+ }
+
+ private void writeXml() {
+ Element testsuite = this.testsuite.get();
+
+ testsuite.setAttribute("tests", Integer.toString(tests.get()));
+ testsuite.setAttribute("errors", Integer.toString(errors.get()));
+ testsuite.setAttribute("skipped", Integer.toString(ignored.get()));
+ testsuite.setAttribute("failures", Integer.toString(failures.get()));
+ testsuite.setAttribute("time", computeTestTime(suiteStartTime.get()));
+
+ try {
+ Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(reportFile, false), Charset.forName("UTF-8")));
+ try {
+ Transformer t = TransformerFactory.newInstance().newTransformer();
+ t.setOutputProperty(OutputKeys.INDENT, "yes");
+ t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+ t.transform(new DOMSource(xml), new StreamResult(writer));
+ } catch (TransformerConfigurationException ex) {
+ LOGGER.error("Misconfigured transformer", ex);
+ } catch (TransformerException ex) {
+ LOGGER.error("Unable to save XML file", ex);
+ } finally {
+ writer.close();
+ }
+ } catch (IOException ex) {
+ LOGGER.warn("Unable to open report file", ex);
+ }
+ }
+
+ private String computeTestTime(Long startTime) {
+ if (startTime == null) {
+ return "0";
+ } else {
+ long amount = System.currentTimeMillis() - startTime;
+ return String.format("%.3f", amount / 1000F);
+ }
+ }
+
+ private Element createTestCase(Description description) {
+ tests.incrementAndGet();
+
+ Element testcase = xml.createElement("testcase");
+
+ testcase.setAttribute("name", safeString(description.getMethodName()));
+ testcase.setAttribute("classname", safeString(description.getClassName()));
+ testcase.setAttribute("time", computeTestTime(testTimes.remove(description.getDisplayName())));
+
+ return testcase;
+ }
+
+ private String safeString(String input) {
+ if (input == null) {
+ return "null";
+ }
+
+ return input
+ // first remove color coding (all of it)
+ .replaceAll("\u001b\\[\\d+m", "")
+ // then remove control characters that are not whitespaces
+ .replaceAll("[\\p{Cntrl}&&[^\\p{Space}]]", "");
+ }
+}
diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
index bdcf43a..1f42008 100755
--- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -158,5 +158,7 @@ locale_es=Espa\u00F1ol
locale_fr=Fran\u00e7ais
locale_it=Italian
locale_ja=\u65E5\u672C\u8A9E
+locale_no=Norsk
+locale_lt=Lietuvi\u0173
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/account/messages/messages_lt.properties b/themes/src/main/resources/theme/base/account/messages/messages_lt.properties
new file mode 100644
index 0000000..f9edf13
--- /dev/null
+++ b/themes/src/main/resources/theme/base/account/messages/messages_lt.properties
@@ -0,0 +1,153 @@
+doSave=Saugoti
+doCancel=At\u0161aukti
+
+doLogOutAllSessions=Atjungti visas sesijas
+doRemove=\u0160alinti
+doAdd=Prid\u0117ti
+doSignOut=Atsijungti
+
+editAccountHtmlTitle=Redaguoti paskyr\u0105
+federatedIdentitiesHtmlTitle=Susietos paskyros
+accountLogHtmlTitle=Paskyros \u017Eurnalas
+changePasswordHtmlTitle=Keisti slapta\u017Eod\u012F
+sessionsHtmlTitle=Prisijungimo sesijos
+accountManagementTitle=Keycloak Naudotoj\u0173 Administravimas
+authenticatorTitle=Autentifikatorius
+applicationsHtmlTitle=Programos
+
+authenticatorCode=Vienkartinis kodas
+email=El. pa\u0161tas
+firstName=Vardas
+givenName=Pavard\u0117
+fullName=Pilnas vardas
+lastName=Pavard\u0117
+familyName=Pavard\u0117
+password=Slapta\u017Eodis
+passwordConfirm=Pakartotas slapta\u017Eodis
+passwordNew=Naujas slapta\u017Eodis
+username=Naudotojo vardas
+address=Adresas
+street=Gatv\u0117
+locality=Miestas arba vietov\u0117
+region=Rajonas
+postal_code=Pa\u0161to kodas
+country=\u0160alis
+emailVerified=El. pa\u0161to adresas patvirtintas
+gssDelegationCredential=GSS prisijungimo duomen\u0173 delegavimas
+
+role_admin=Administratorius
+role_realm-admin=Srities administravimas
+role_create-realm=Kurti srit\u012F
+role_view-realm=Per\u017Ei\u016Br\u0117ti srit\u012F
+role_view-users=Per\u017Ei\u016Br\u0117ti naudotojus
+role_view-applications=Per\u017Ei\u016Br\u0117ti programas
+role_view-clients=Per\u017Ei\u016Br\u0117ti klientines programas
+role_view-events=Per\u017Ei\u016Br\u0117ti \u012Fvyki\u0173 \u017Eurnal\u0105
+role_view-identity-providers=Per\u017Ei\u016Br\u0117ti tapatyb\u0117s teik\u0117jus
+role_manage-realm=Valdyti sritis
+role_manage-users=Valdyti naudotojus
+role_manage-applications=Valdyti programas
+role_manage-identity-providers=Valdyti tapatyb\u0117s teik\u0117jus
+role_manage-clients=Valdyti programas
+role_manage-events=Valdyti \u012Fvykius
+role_view-profile=Per\u017Ei\u016Br\u0117ti paskyr\u0105
+role_manage-account=Valdyti paskyr\u0105
+role_read-token=Skaityti prieigos rak\u0161\u0105
+role_offline-access=Darbas neprisijungus
+role_uma_authorization=\u012Egauti UMA autorizavimo teises
+client_account=Paskyra
+client_security-admin-console=Saugumo administravimo konsol\u0117
+client_admin-cli=Administravimo CLI
+client_realm-management=Srities valdymas
+client_broker=Tarpininkas
+
+
+requiredFields=Privalomi laukai
+allFieldsRequired=Visi laukai yra privalomi
+
+backToApplication=« Gr\u012F\u017Eti \u012F program\u0105
+backTo=Atgal \u012F {0}
+
+date=Data
+event=\u012Evykis
+ip=IP
+client=Klientas
+clients=Klientai
+details=Detaliau
+started=Suk\u016Brimo laikas
+lastAccess=V\u0117liausia prieiga
+expires=Galioja iki
+applications=Programos
+
+account=Paskyra
+federatedIdentity=Susieta tapatyb\u0117
+authenticator=Autentifikatorius
+sessions=Sesijos
+log=\u012Evykiai
+
+application=Programa
+availablePermissions=Galimos teis\u0117s
+grantedPermissions=\u012Egalintos teis\u0117s
+grantedPersonalInfo=\u012Egalinta asmenin\u0117 informacija
+additionalGrants=Papildomi \u012Fgaliojimai
+action=Veiksmas
+inResource=yra
+fullAccess=Pilna prieiga
+offlineToken=Re\u017Eimo neprisijungus raktas (token)
+revoke=At\u0161aukti \u012Fgaliojim\u0105
+
+configureAuthenticators=Sukonfig\u016Bruotas autentifikatorius
+mobile=Mobilus
+totpStep1=\u012Ediekite <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> arba Google Authenticator savo \u012Frenginyje. Program\u0117l\u0117s prieinamos <a href="https://play.google.com">Google Play</a> ir Apple App Store.
+totpStep2=Atidarykite program\u0117l\u0119 ir nuskenuokite barkod\u0105 arba \u012Fveskite kod\u0105.
+totpStep3=\u012Eveskite program\u0117l\u0117je sugeneruot\u0105 vien\u0105 kart\u0105 galiojant\u012F kod\u0105 ir paspauskite Saugoti nor\u0117dami prisijungti.
+
+missingUsernameMessage=Pra\u0161ome \u012Fvesti naudotojo vard\u0105.
+missingFirstNameMessage=Pra\u0161ome \u012Fvesti vard\u0105.
+invalidEmailMessage=Neteisingas el. pa\u0161to adresas.
+missingLastNameMessage=Pra\u0161ome \u012Fvesti pavard\u0119.
+missingEmailMessage=Pra\u0161ome \u012Fvesti el. pa\u0161to adres\u0105.
+missingPasswordMessage=Pra\u0161ome \u012Fvesti slapta\u017Eod\u012F.
+notMatchPasswordMessage=Slapta\u017Eod\u017Eiai nesutampa.
+
+missingTotpMessage=Pra\u0161ome \u012Fvesti autentifikacijos kod\u0105.
+invalidPasswordExistingMessage=Neteisingas dabartinis slapta\u017Eodis.
+invalidPasswordConfirmMessage=Pakartotas slapta\u017Eodis nesutampa.
+invalidTotpMessage=Neteisingas autentifikacijos kodas.
+
+usernameExistsMessage=Toks naudotojas jau egzistuoja.
+emailExistsMessage=El. pa\u0161to adresas jau egzistuoja.
+
+readOnlyUserMessage=Tik skaitymui sukonfig\u016Bruotos paskyros duomen\u0173 atnaujinti neleid\u017Eiama.
+readOnlyPasswordMessage=Tik skaitymui sukonfig\u016Bruotos paskyros slapta\u017Eod\u017Eio atnaujinti neleid\u017Eiama.
+
+successTotpMessage=Mobilus autentifikatorius sukonfig\u016Bruotas.
+successTotpRemovedMessage=Mobilus autentifikatorius pa\u0161alintas.
+
+successGrantRevokedMessage=\u012Egalinimas pa\u0161alintas s\u0117kmingai.
+
+accountUpdatedMessage=J\u016Bs\u0173 paskyros duomenys s\u0117kmingai atnaujinti.
+accountPasswordUpdatedMessage=J\u016Bs\u0173 paskyros slapta\u017Eodis pakeistas.
+
+missingIdentityProviderMessage=Nenurodytas tapatyb\u0117s teik\u0117jas.
+invalidFederatedIdentityActionMessage=Neteisingas arba ne\u017Einomas veiksmas.
+identityProviderNotFoundMessage=Nurodytas tapatyb\u0117s teik\u0117jas nerastas.
+federatedIdentityLinkNotActiveMessage=Nurodyta susieta tapatyb\u0117 neaktyvi.
+federatedIdentityRemovingLastProviderMessage=J\u016Bs negalite pa\u0161alinti paskutinio tapatyb\u0117s teik\u0117jo s\u0105sajos, nes J\u016Bs neturite nusistat\u0119 paskyros slapta\u017Eod\u017Eio.
+identityProviderRedirectErrorMessage=Klaida nukreipiant \u012F tapatyb\u0117s teik\u0117jo puslap\u012F.
+identityProviderRemovedMessage=Tapatyb\u0117s teik\u0117jas s\u0117kmingai pa\u0161alintas.
+identityProviderAlreadyLinkedMessage=Susieta tapatyb\u0117 i\u0161 {0} jau susieta su kita paskyra.
+staleCodeAccountMessage=Puslapio galiojimas baig\u0117si. Bandykite dar kart\u0105.
+consentDenied=Prieiga draud\u017Eiama.
+
+accountDisabledMessage=Paskyros galiojimas sustabdytas, kreipkit\u0117s \u012F administratori\u0173.
+
+accountTemporarilyDisabledMessage=Paskyros galiojimas laikinai sustabdytas. Kreipkit\u0117s \u012F administratori\u0173 arba pabandykite v\u0117liau.
+invalidPasswordMinLengthMessage=Per trumpas slapta\u017Eodis: ma\u017Eiausias ilgis {0}.
+invalidPasswordMinLowerCaseCharsMessage=Neteisingas slapta\u017Eodis: privaloma \u012Fvesti {0} ma\u017E\u0105j\u0105 raid\u0119.
+invalidPasswordMinDigitsMessage=Neteisingas slapta\u017Eodis: privaloma \u012Fvesti {0} skaitmen\u012F.
+invalidPasswordMinUpperCaseCharsMessage=Neteisingas slapta\u017Eodis: privaloma \u012Fvesti {0} did\u017Ei\u0105j\u0105 raid\u0119.
+invalidPasswordMinSpecialCharsMessage=Neteisingas slapta\u017Eodis: privaloma \u012Fvesti {0} special\u0173 simbol\u012F.
+invalidPasswordNotUsernameMessage=Neteisingas slapta\u017Eodis: slapta\u017Eodis negali sutapti su naudotojo vardu.
+invalidPasswordRegexPatternMessage=Neteisingas slapta\u017Eodis: slapta\u017Eodis netenkina regex taisykl\u0117s(i\u0173).
+invalidPasswordHistoryMessage=Neteisingas slapta\u017Eodis: slapta\u017Eodis negali sutapti su prie\u0161 tai buvusiais {0} slapta\u017Eod\u017Eiais.
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/account/messages/messages_no.properties b/themes/src/main/resources/theme/base/account/messages/messages_no.properties
new file mode 100644
index 0000000..3cae31e
--- /dev/null
+++ b/themes/src/main/resources/theme/base/account/messages/messages_no.properties
@@ -0,0 +1,163 @@
+doSave=Lagre
+doCancel=Avbryt
+doLogOutAllSessions=Logg ut av alle sesjoner
+doRemove=Fjern
+doAdd=Legg til
+doSignOut=Logg ut
+
+editAccountHtmlTitle=Rediger konto
+federatedIdentitiesHtmlTitle=Federerte identiteter
+accountLogHtmlTitle=Kontologg
+changePasswordHtmlTitle=Endre passord
+sessionsHtmlTitle=Sesjoner
+accountManagementTitle=Keycloak kontoadministrasjon
+authenticatorTitle=Autentikator
+applicationsHtmlTitle=Applikasjoner
+
+authenticatorCode=Engangskode
+email=E-post
+firstName=Fornavn
+givenName=Fornavn
+fullName=Fullt navn
+lastName=Etternavn
+familyName=Etternavn
+password=Passord
+passwordConfirm=Bekreftelse
+passwordNew=Nytt passord
+username=Brukernavn
+address=Adresse
+street=Gate-/veinavn + husnummer
+locality=By
+region=Fylke
+postal_code=Postnummer
+country=Land
+emailVerified=E-post bekreftet
+gssDelegationCredential=GSS legitimasjonsdelegering
+
+role_admin=Administrator
+role_realm-admin=Administrator for sikkerhetsdomene
+role_create-realm=Opprette sikkerhetsdomene
+role_view-realm=Se sikkerhetsdomene
+role_view-users=Se brukere
+role_view-applications=Se applikasjoner
+role_view-clients=Se klienter
+role_view-events=Se hendelser
+role_view-identity-providers=Se identitetsleverand\u00F8rer
+role_manage-realm=Administrere sikkerhetsdomene
+role_manage-users=Administrere brukere
+role_manage-applications=Administrere applikasjoner
+role_manage-identity-providers=Administrere identitetsleverand\u00F8rer
+role_manage-clients=Administrere klienter
+role_manage-events=Administrere hendelser
+role_view-profile=Se profil
+role_manage-account=Administrere konto
+role_read-token=Lese token
+role_offline-access=Frakoblet tilgang
+role_uma_authorization=Skaffe tillatelser
+client_account=Konto
+client_security-admin-console=Sikkerhetsadministrasjonskonsoll
+client_admin-cli=Kommandolinje-grensesnitt for administrator
+client_realm-management=Sikkerhetsdomene-administrasjon
+client_broker=Broker
+
+
+requiredFields=Obligatoriske felt
+allFieldsRequired=Alle felt m\u00E5 fylles ut
+
+backToApplication=« Tilbake til applikasjonen
+backTo=Tilbake til {0}
+
+date=Dato
+event=Hendelse
+ip=IP
+client=Klient
+clients=Klienter
+details=Detaljer
+started=Startet
+lastAccess=Sist benyttet
+expires=Utl\u00F8per
+applications=Applikasjoner
+
+account=Konto
+federatedIdentity=Federert identitet
+authenticator=Autentikator
+sessions=Sesjoner
+log=Logg
+
+application=Applikasjon
+availablePermissions=Tilgjengelige rettigheter
+grantedPermissions=Innvilgede rettigheter
+grantedPersonalInfo=Innvilget personlig informasjon
+additionalGrants=Ekstra rettigheter
+action=Handling
+inResource=i
+fullAccess=Full tilgang
+offlineToken=Offline token
+revoke=Opphev rettighet
+
+configureAuthenticators=Konfigurerte autentikatorer
+mobile=Mobiltelefon
+totpStep1=Installer <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> eller Google Authenticator p\u00E5 din enhet. Begge applikasjoner er tilgjengelige p\u00E5 <a href="https://play.google.com">Google Play</a> og Apple App Store.
+totpStep2=\u00C5pne applikasjonen og skann strekkoden eller skriv inn koden.
+totpStep3=Skriv inn engangskoden gitt av applikasjonen og klikk Lagre for \u00E5 fullf\u00F8re.
+
+missingUsernameMessage=Vennligst oppgi brukernavn.
+missingFirstNameMessage=Vennligst oppgi fornavn.
+invalidEmailMessage=Ugyldig e-postadresse.
+missingLastNameMessage=Vennligst oppgi etternavn.
+missingEmailMessage=Vennligst oppgi e-postadresse.
+missingPasswordMessage=Vennligst oppgi passord.
+notMatchPasswordMessage=Passordene er ikke like.
+
+missingTotpMessage=Vennligst oppgi engangskode.
+invalidPasswordExistingMessage=Ugyldig eksisterende passord.
+invalidPasswordConfirmMessage=Passordene er ikke like.
+invalidTotpMessage=Ugyldig engangskode.
+
+usernameExistsMessage=Brukernavnet finnes allerede.
+emailExistsMessage=E-postadressen finnes allerede.
+
+readOnlyUserMessage=Du kan ikke oppdatere kontoen din ettersom den er skrivebeskyttet.
+readOnlyPasswordMessage=Du kan ikke oppdatere passordet ditt ettersom kontoen din er skrivebeskyttet.
+
+successTotpMessage=Autentikator for mobiltelefon er konfigurert.
+successTotpRemovedMessage=Autentikator for mobiltelefon er fjernet.
+
+successGrantRevokedMessage=Vellykket oppheving av rettighet.
+
+accountUpdatedMessage=Kontoen din har blitt oppdatert.
+accountPasswordUpdatedMessage=Ditt passord har blitt oppdatert.
+
+missingIdentityProviderMessage=Identitetsleverand\u00F8r er ikke spesifisert.
+invalidFederatedIdentityActionMessage=Ugyldig eller manglende handling.
+identityProviderNotFoundMessage=Spesifisert identitetsleverand\u00F8r ikke funnet.
+federatedIdentityLinkNotActiveMessage=Denne identiteten er ikke lenger aktiv.
+federatedIdentityRemovingLastProviderMessage=Du kan ikke fjerne siste federerte identitet ettersom du ikke har et passord.
+identityProviderRedirectErrorMessage=Redirect til identitetsleverand\u00F8r feilet.
+identityProviderRemovedMessage=Fjerning av identitetsleverand\u00F8r var vellykket.
+identityProviderAlreadyLinkedMessage=Federert identitet returnert av {0} er allerede koblet til en annen bruker.
+staleCodeAccountMessage=Siden har utl\u00F8pt. Vennligst pr\u00F8v en gang til.
+consentDenied=Samtykke avsl\u00E5tt.
+
+accountDisabledMessage=Konto er deaktivert, kontakt administrator.
+
+accountTemporarilyDisabledMessage=Konto er midlertidig deaktivert, kontakt administrator eller pr\u00F8v igjen senere.
+invalidPasswordMinLengthMessage=Ugyldig passord: minimum lengde {0}.
+invalidPasswordMinLowerCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sm\u00E5 bokstaver.
+invalidPasswordMinDigitsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sifre.
+invalidPasswordMinUpperCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} store bokstaver.
+invalidPasswordMinSpecialCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} spesialtegn.
+invalidPasswordNotUsernameMessage=Ugyldig passord: kan ikke v\u00E6re likt brukernavn.
+invalidPasswordRegexPatternMessage=Ugyldig passord: tilfredsstiller ikke kravene for passord-m\u00F8nster.
+invalidPasswordHistoryMessage=Ugyldig passord: kan ikke v\u00E6re likt noen av de {0} foreg\u00E5ende passordene.
+
+locale_ca=Catal\u00E0
+locale_de=Deutsch
+locale_en=English
+locale_es=Espa\u00F1ol
+locale_fr=Fran\u00e7ais
+locale_it=Italian
+locale_ja=\u65E5\u672C\u8A9E
+locale_no=Norsk
+locale_pt-BR=Portugu\u00EAs (Brasil)
+locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439
diff --git a/themes/src/main/resources/theme/base/account/theme.properties b/themes/src/main/resources/theme/base/account/theme.properties
index 2f853e8..2cf724d 100644
--- a/themes/src/main/resources/theme/base/account/theme.properties
+++ b/themes/src/main/resources/theme/base/account/theme.properties
@@ -1 +1 @@
-locales=ca,de,en,es,fr,it,ja,pt-BR,ru
\ No newline at end of file
+locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties
index 02a307a..0025c88 100755
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties
@@ -218,8 +218,8 @@ 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-redirect-binding-url=Assertion Consumer Service Redirect Binding URL
assertion-consumer-redirect-binding-url.tooltip=Assertion Consumer Service Redirect Binding URL
-logout-service-binding-post-url=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3
-logout-service-binding-post-url.tooltip=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3 \u00FAnica del client. Pots deixar-ho en blanc si est\u00E0s fent servir un enlla\u00E7 diferent.
+logout-service-post-binding-url=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3
+logout-service-post-binding-url.tooltip=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3 \u00FAnica del client. Pots deixar-ho en blanc si est\u00E0s fent servir un enlla\u00E7 diferent.
logout-service-redir-binding-url=URL d''enlla\u00E7 SAML de redirecci\u00F3 per a la desconnexi\u00F3
logout-service-redir-binding-url.tooltip=URL d''enlla\u00E7 SAML de redirecci\u00F3 per a la desconnexi\u00F3 \u00FAnica del client. Pots deixar-ho en blanc si est\u00E0s fent servir un enlla\u00E7 diferent.
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 4f15d6d..d3cdd9e 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
@@ -164,6 +164,12 @@ usermodel.clientRoleMapping.rolePrefix.label=Client Role prefix
usermodel.clientRoleMapping.rolePrefix.tooltip=A prefix for each client role (optional).
usermodel.realmRoleMapping.rolePrefix.label=Realm Role prefix
usermodel.realmRoleMapping.rolePrefix.tooltip=A prefix for each Realm Role (optional).
+sectorIdentifierUri.label=Sector Identifier URI
+sectorIdentifierUri.tooltip=Providers that use pairwise sub values and support Dynamic Client Registration SHOULD use the sector_identifier_uri parameter. It provides a way for a group of websites under common administrative control to have consistent pairwise sub values independent of the individual domain names. It also provides a way for Clients to change redirect_uri domains without having to reregister all of their users.
+pairwiseSubAlgorithmSalt.label=Salt
+pairwiseSubAlgorithmSalt.tooltip=Salt used when calculating the pairwise subject identifier. If left blank, a salt will be generated.
+
+
# 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.
@@ -240,14 +246,16 @@ fine-oidc-endpoint-conf=Fine Grain OpenID Connect Configuration
fine-oidc-endpoint-conf.tooltip=Expand this section to configure advanced settings of this client related to OpenID Connect protocol
user-info-signed-response-alg=User Info Signed Response Algorithm
user-info-signed-response-alg.tooltip=JWA algorithm used for signed User Info Endpoint response. If set to 'unsigned', then User Info Response won't be signed and will be returned in application/json format.
+request-object-signature-alg=Request Object Signature Algorithm
+request-object-signature-alg.tooltip=JWA algorithm, which client needs to use when sending OIDC request object specified by 'request' or 'request_uri' parameters. If set to 'any', then Request object can be signed by any algorithm (including 'none' ).
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-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.
-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-post-binding-url=Logout Service POST Binding URL
+logout-service-post-binding-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.
@@ -316,7 +324,7 @@ available-roles=Available Roles
add-selected=Add selected
associated-roles=Associated Roles
composite.associated-realm-roles.tooltip=Realm level roles associated with this composite role.
-composite.available-realm-roles.tooltip=Realm level roles associated with this composite role.
+composite.available-realm-roles.tooltip=Realm level roles that you can associate to this composite role.
remove-selected=Remove selected
client-roles=Client Roles
select-client-to-view-roles=Select client to view roles for client
@@ -564,7 +572,7 @@ realm-default-roles.tooltip=Realm level roles assigned to new users.
default.available-roles-client.tooltip=Roles from this client that are assignable as a default.
client-default-roles=Client Default Roles
client-default-roles.tooltip=Roles from this client assigned as a default role.
-composite.available-roles.tooltip=Realm level roles associated with this composite role.
+composite.available-roles.tooltip=Realm level roles that you can associate to this composite role.
composite.associated-roles.tooltip=Realm level roles associated with this composite role.
composite.available-roles-client.tooltip=Roles from this client that you can associate to this composite role.
composite.associated-roles-client.tooltip=Client roles associated with this composite role.
@@ -806,6 +814,7 @@ top-level-flow-type.tooltip=What kind of top level flow is it? Type 'client' is
create-execution-flow=Create Execution Flow
flow-type=Flow Type
flow.form.type=form
+flow.generic.type=generic
flow-type.tooltip=What kind of form is it
form-provider=Form Provider
default-groups.tooltip=Newly created or registered users will automatically be added to these groups
@@ -842,6 +851,7 @@ include-representation=Include Representation
include-representation.tooltip=Include JSON representation for create and update requests.
clear-admin-events.tooltip=Deletes all admin events in the database.
server-version=Server Version
+server-profile=Server Profile
info=Info
providers=Providers
server-time=Server Time
@@ -983,13 +993,13 @@ authz-required=Required
authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server.
authz-policy-enforcement-mode=Policy Enforcement Mode
-authz-policy-enforcement-mode.tooltip=The policy enforcement mode dictates how policies are enforced when evaluating authorization requests. 'Enforcing' means requests are denied by default even when there is no policy associated with a given resource. 'Permissive' means requests are allowed even when there is no policy associated with a given resource. 'Disabled' completely disables the evaluation of policies and allow access to any resource.
+authz-policy-enforcement-mode.tooltip=The policy enforcement mode dictates how policies are enforced when evaluating authorization requests. 'Enforcing' means requests are denied by default even when there is no policy associated with a given resource. 'Permissive' means requests are allowed even when there is no policy associated with a given resource. 'Disabled' completely disables the evaluation of policies and allows access to any resource.
authz-policy-enforcement-mode-enforcing=Enforcing
authz-policy-enforcement-mode-permissive=Permissive
authz-policy-enforcement-mode-disabled=Disabled
authz-remote-resource-management=Remote Resource Management
-authz-remote-resource-management.tooltip=Should resources be managed remotely by the resource server? If false, resources can only be managed from this admin console.
+authz-remote-resource-management.tooltip=Should resources be managed remotely by the resource server? If false, resources can be managed only from this admin console.
authz-export-settings=Export Settings
authz-export-settings.tooltip=Export and download all authorization settings for this resource server.
@@ -1032,7 +1042,7 @@ authz-policy-logic.tooltip=The logic dictates how the policy decision should be
authz-policy-apply-policy=Apply Policy
authz-policy-apply-policy.tooltip=Specifies all the policies that must be applied to the scopes defined by this policy or permission.
authz-policy-decision-strategy=Decision Strategy
-authz-policy-decision-strategy.tooltip=The decision strategy dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order to the overall decision be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order to the overall decision be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative.
+authz-policy-decision-strategy.tooltip=The decision strategy dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order for the final decision to be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order for the final decision to be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative.
authz-policy-decision-strategy-affirmative=Affirmative
authz-policy-decision-strategy-unanimous=Unanimous
authz-policy-decision-strategy-consensus=Consensus
@@ -1056,15 +1066,15 @@ authz-policy-time-not-before.tooltip=Defines the time before which the policy MU
authz-policy-time-not-on-after=Not On or After
authz-policy-time-not-on-after.tooltip=Defines the time after which the policy MUST NOT be granted. Only granted if current date/time is before or equal to this value.
authz-policy-time-day-month=Day of Month
-authz-policy-time-day-month.tooltip=Defines the day of month before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the day of month before/equal which the policy MUST be granted. In this case, the policy would be granted if current day of month is between/equal the two values you provided.
+authz-policy-time-day-month.tooltip=Defines the day of month which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current day of month is between or equal to the two values you provided.
authz-policy-time-month=Month
-authz-policy-time-month.tooltip=Defines the month before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the month before/equal which the policy MUST be granted. In this case, the policy would be granted if current month is between/equal the two values you provided.
+authz-policy-time-month.tooltip=Defines the month which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current month is between or equal to the two values you provided.
authz-policy-time-year=Year
-authz-policy-time-year.tooltip=Defines the year before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the year before/equal which the policy MUST be granted. In this case, the policy would be granted if current year is between/equal the two values you provided.
+authz-policy-time-year.tooltip=Defines the year which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current year is between or equal to the two values you provided.
authz-policy-time-hour=Hour
-authz-policy-time-hour.tooltip=Defines the hour before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the hour before/equal which the policy MUST be granted. In this case, the policy would be granted if current hour is between/equal the two values you provided.
+authz-policy-time-hour.tooltip=Defines the hour which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current hour is between or equal to the two values you provided.
authz-policy-time-minute=Minute
-authz-policy-time-minute.tooltip=Defines the minute before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the minute before/equal which the policy MUST be granted. In this case, the policy would be granted if current minute is between/equal the two values you provided.
+authz-policy-time-minute.tooltip=Defines the minute which the policy MUST be granted. You can also provide a range by filling the second field. In this case, permission is granted only if current minute is between or equal to the two values you provided.
# Authz Drools Policy Detail
authz-add-drools-policy=Add Drools Policy
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
index 2a1807c..fb17ff6 100755
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties
@@ -218,8 +218,8 @@ 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-redirect-binding-url=Assertion Consumer Service Redirect Binding URL
assertion-consumer-redirect-binding-url.tooltip=Assertion Consumer Service Redirect Binding URL
-logout-service-binding-post-url=URL de enlace SAML POST para la desconexi\u00F3n
-logout-service-binding-post-url.tooltip=URL de enlace SAML POST para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto.
+logout-service-post-binding-url=URL de enlace SAML POST para la desconexi\u00F3n
+logout-service-post-binding-url.tooltip=URL de enlace SAML POST para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto.
logout-service-redir-binding-url=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n
logout-service-redir-binding-url.tooltip=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto.
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties
index 93e7b14..b923234 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties
@@ -27,7 +27,7 @@ sslRequired.option.all=toutes les requ\u00eates
sslRequired.option.external=les requ\u00eates externes
sslRequired.option.none=aucun
sslRequired.tooltip=Si le HTTPS est requis ? ''aucun'' signifie que le HTTPS n''est requis pour aucune adresse IP cliente. ''les requ\u00eates externes'' signifie que localhost et les adresses IP priv\u00e9es peuvent acc\u00e9der sans HTTPS. ''toutes les requ\u00eates'' signifie que le protocole HTTPS est obligatoire pour toutes les adresses IP.
-publicKey=Clef publique key
+publicKey=Clef publique
gen-new-keys=Cr\u00e9ation de nouvelle clef
certificate=Certificat
host=H\u00f4te
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_lt.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_lt.properties
new file mode 100644
index 0000000..b65bb7c
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_lt.properties
@@ -0,0 +1,1131 @@
+consoleTitle=Keycloak administravimo konsol\u0117
+
+# Common messages
+enabled=\u012Egalintas
+name=Pavadinimas
+displayName=Rodomas pavadinimas
+displayNameHtml=Rodomas pavadinimas HTML formatu
+save=Saugoti
+cancel=At\u0161aukti
+onText=ON
+offText=OFF
+client=Klientas
+clients=Klientai
+clear=I\u0161valyti
+selectOne=Pasirinkite vien\u0105...
+
+true=Taip
+false=Ne
+
+endpoints=Prieigos adresai
+
+# Realm settings
+realm-detail.enabled.tooltip=Naudotojai ir programos prie srities gali prieiti tik tuomet, kai ji \u012Fgalinta
+realm-detail.oidc-endpoints.tooltip=Atidaromas langas su OpenID Connect prieigos URL adresais
+registrationAllowed=Naudotoj\u0173 registracija
+registrationAllowed.tooltip=\u012Egalina naudotoj\u0173 registravimosi s\u0105saj\u0105. Prisijungimo lange rodoma nuoroda \u012F registravimosi puslap\u012F.
+registrationEmailAsUsername=El. pa\u0161tas kaip naudojo vardas
+registrationEmailAsUsername.tooltip=Jei \u012Fgalintas tuomet naudotojo vardo laukas registravimosi lange yra slepiamas ir naujai besiregistruojantiems naudotojams el. pa\u0161to adresas naudojamas kaip naudotojo vardas.
+editUsernameAllowed=Naudotojo vardo redagavimas
+editUsernameAllowed.tooltip=Jei \u012Fgalintas, tuomet naudotojas gali keisti savo naudotojo vard\u0105.
+resetPasswordAllowed=Slapta\u017Eod\u017Eio priminimas
+resetPasswordAllowed.tooltip=Prisijungimo lange rodoma nuoroda pamir\u0161to slapta\u017Eod\u017Eio atk\u016Brimui.
+rememberMe=Prisiminti mane
+rememberMe.tooltip=Prisijungimo lange rodyti pasirinkim\u0105 leid\u017Eiant\u012F naudotojui likti prisijungus netgi tuomet, kai nar\u0161ykl\u0117 yra i\u0161jungiama/\u012Fjungiama tol, kol nepasibaigia prisijungimo sesija.
+verifyEmail=El. pa\u0161to patvirtinimas
+verifyEmail.tooltip=Reikalauti naudotojo patvirtinti el. pa\u0161to adres\u0105 pirmojo prisijungimo metu.
+sslRequired=Reikalauti SSL
+sslRequired.option.all=visoms u\u017Eklausoms
+sslRequired.option.external=i\u0161orin\u0117ms u\u017Eklausoms
+sslRequired.option.none=niekada
+sslRequired.tooltip=Ar HTTPS privalomas? 'niekada' - HTTPS nereikalaujamas. 'i\u0161orin\u0117ms u\u017Eklausoms' - jungiantis i\u0161 localhost ar serverio IP adres\u0173 galima prieiti ir per HTTP. 'visoms u\u017Eklausoms' - HTTPS reikalaujamas jungiantis i\u0161 vis\u0173 IP adres\u0173.
+publicKey=Vie\u0161as raktas
+privateKey=Privatus raktas
+gen-new-keys=Generuoti naujus raktus
+certificate=Sertifikatas
+host=Serveris
+smtp-host=SMTP serveris
+port=Prievadas
+smtp-port=SMTP prievadas (numatyta reik\u0161m\u0117 25)
+from=Nuo
+sender-email-addr=Siunt\u0117jo el. pa\u0161to adresas
+enable-ssl=\u012Egalinti SSL
+enable-start-tls=\u012Egalinti StartTLS
+enable-auth=\u012Egalinti autentifikacij\u0105
+username=Naudotojo vardas
+login-username=Prisijungimui naudojamas naudotojo vardas
+password=Slapta\u017Eodis
+login-password=Prisijungimui naudojamas slapta\u017Eodis
+login-theme=Prisijungimo lango tema
+login-theme.tooltip=Pasirinkite kaip atrodys J\u016Bs\u0173 prisijungimo, TOTP, teisi\u0173 suteikimo, naudotoj\u0173 registracijos ir slapta\u017Eod\u017Ei\u0173 priminimo langai.
+account-theme=Naudotojo profilio tema
+account-theme.tooltip=Pasirinkite kaip atrodys naudotojo profilio valdymo langai.
+admin-console-theme=Administravimo konsol\u0117s tema
+select-theme-admin-console=Pasirinkite kaip atrodys administravimo konsol\u0117s langai.
+email-theme=El. pa\u0161to tema
+select-theme-email=Pasirinkite kaip atrodys siun\u010Diami el. pa\u0161to lai\u0161kai.
+i18n-enabled=Daugiakalbyst\u0117s palaikymas
+supported-locales=Palaikomos kalbos
+supported-locales.placeholder=Pasirinkite arba \u012Fra\u0161ykite kalbos pavadinim\u0105
+default-locale=Numatyta kalba
+realm-cache-clear=Srities pod\u0117lis
+realm-cache-clear.tooltip=I\u0161 vis\u0173 sri\u010Di\u0173 pa\u0161alinama visa pod\u0117lyje (cache) esanti informacija
+user-cache-clear=Naudotoj\u0173 pod\u0117lis
+user-cache-clear.tooltip=I\u0161 vis\u0173 sri\u010Di\u0173 pa\u0161alinama visa naudotoj\u0173 pod\u0117lyje (cache) esanti informacija
+revoke-refresh-token=Prieigos rakt\u0105 naudoti tik kart\u0105
+revoke-refresh-token.tooltip=Jei \u012Fgalintas, tuomet atnaujinimo raktai (Refresh Token) gali b\u016Bti naudojami tik vien\u0105 kart\u0105. Kitu atveju - atnaujinimo raktai gali b\u016Bti pernaudojami daugel\u012F kart\u0173.
+sso-session-idle=SSO sesijos neveikimo laikas
+seconds=Sekund\u0117s
+minutes=Minut\u0117s
+hours=Valandos
+days=Dienos
+sso-session-max=SSO sesijos maksimalus laikas
+sso-session-idle.tooltip=Laikas, po kurio neaktyvi sesija bus u\u017Ebaigta. Sesijos pasibaigimo metu visi raktai (Tokens) ir nar\u0161ykli\u0173 sesijos sunaikinamos.
+sso-session-max.tooltip=Laikas, po kurio prisijungimo sesija yra sunaikinama. Sesijos pasibaigimo metu visi raktai (Tokens) ir nar\u0161ykli\u0173 sesijos sunaikinamos.
+offline-session-idle=Neprisijungusios sesijos neveikimo laikas
+offline-session-idle.tooltip=Darbo neprisijungus sesijos neveikimo laikas, po kurio neaktyvi sesija bus u\u017Ebaigta. Darbo neprisijungus metu, prisijungimo raktai turi b\u016Bti atnaujinami bent kart\u0105 per nurodyt\u0105 period\u0105. Kitu atveju sesijos galiojmas bus sustabdytas.
+access-token-lifespan=Prisijungimo rakto galiojimo laikas
+access-token-lifespan.tooltip=Laikas, po kurio prisijungimui naudojamas raktas (Access Token) nustoja galioti. Rekomenduojama, kad \u0161ios reik\u0161m\u0117s galiojimas b\u016Bt\u0173 reliatyviai trumpas palyginus su SSO galiojimo laiku.
+access-token-lifespan-for-implicit-flow=Prisijungimo rakto galiojimo laikas (Implicit Flow)
+access-token-lifespan-for-implicit-flow.tooltip=Laikas, po kurio prisijungimui naudojamas OpenID Connect Implicit Flow raktas nustoja galioti. Rekomenduojama, kad \u0161ios reik\u0161m\u0117s galiojimas b\u016Bt\u0173 reliatyviai trumpas palyginus su SSO galiojimo laiku. \u0160is parametras skiriasi nuo 'Prisijungimo rakto galiojimo laikas', nes n\u0117ra galimyb\u0117s atnaujinti prieigos rakto naudojant OpenID Connect Implicit Flow.
+client-login-timeout=Kliento prisijungimui skirtas laikas
+client-login-timeout.tooltip=Laikas, per kur\u012F klientas turi u\u017Ebaigti prisijungimo proces\u0105. Normaliu atveju reik\u0161m\u0117 tur\u0117t\u0173 b\u016Bti 1 minut\u0117.
+login-timeout=Naudotojo prisijungimui skirtas laikas
+login-timeout.tooltip=Laikas, per kur\u012F naudotojas turi u\u017Ebaigti prisijungimo proces\u0105. Rekomenduojamas pakankamai ilgas laiko tarpas. Pvz. 30 minu\u010Di\u0173 ar daugiau.
+login-action-timeout=Naudotojo prisijungimo veiksmui skirtas laikas
+login-action-timeout.tooltip=Laikas, per kur\u012F naudotojas turi u\u017Ebaigti su prisijungimu susijus\u012F veiksm\u0105. Pavyzd\u017Eiui atnaujinti slapta\u017Eod\u012F ar sukonfig\u016Bruoti TOTP. Rekomenduojamas laikas - 5 minut\u0117s ar daugiau.
+headers=Antra\u0161t\u0117s
+brute-force-detection=Grubios j\u0117gos ataka
+x-frame-options=X-Frame-Options
+x-frame-options-tooltip=Numatyta reik\u0161m\u0117 draud\u017Eia puslapius naudoti kitose svetain\u0117se per iframe (paspauskite antra\u0161t\u0119 nor\u0117dami gauti daugiau informacijos)
+content-sec-policy=Content-Security-Policy
+content-sec-policy-tooltip=Numatyta reik\u0161m\u0117 draud\u017Eia puslapius naudoti kitose svetain\u0117se per iframe (paspauskite antra\u0161t\u0119 nor\u0117dami gauti daugiau informacijos)
+content-type-options=X-Content-Type-Options
+content-type-options-tooltip=Numatyta reik\u0161m\u0117 draud\u017Eia Internet Explorer ir Google Chrome atlikti priimti kitokias MIME reik\u0161mes (MIME-sniffing) nei deklaruotas turinio tipas (content-type) (paspauskite antra\u0161t\u0119 nor\u0117dami gauti daugiau informacijos)
+max-login-failures=Maksimalus bandym\u0173 prisijungim\u0173 skai\u010Dius
+max-login-failures.tooltip=Pasiekus maksimal\u0173 nes\u0117kming\u0173 bandym\u0173 prisijungti skai\u010Di\u0173 \u012Fjungiamas specialus r\u0117\u017Eimas, kuomet laukimo intervalas yra didinamas po kiekvieno sekan\u010Dio neteisingo bandymo.
+wait-increment=Laukimo laiko didinimas po
+wait-increment.tooltip=Laikas, kur\u012F naudotojo prisijungimai yra draud\u017Eiami, kai n\u0117s\u0117kming\u0173 bandym\u0173 skai\u010Dius pasiekia nustatyt\u0105 rib\u0105
+quick-login-check-millis=Per greito bandymo prisijungti laikas milisekund\u0117mis
+quick-login-check-millis.tooltip=Jei n\u0117s\u0117kmingi bandymai prisijungti seka vienas kit\u0105 per greitai, tuomet naudotojo paskyra yra u\u017Erakinama.
+min-quick-login-wait=Per greito bandymo prisijungti u\u017Erakinimo laikas
+min-quick-login-wait.tooltip=Laikas, kur\u012F naudotojo prisijungimai yra draud\u017Eiami, kai n\u0117s\u0117kmingi bandymai prisijungti seka vienas kit\u0105 per greitai.
+max-wait=Maksimalus u\u017Erakinimo laikas
+max-wait.tooltip=Maksimalus laikas, kuomet naudotojo paskyra yra u\u017Erakinama po nes\u0117kming\u0173 bandym\u0173 prisijungti.
+failure-reset-time=Pamir\u0161ti nepavykusius prisijungimus po
+failure-reset-time.tooltip=Laikas, po kurio nepavyk\u0119 prisijungimai bus pamir\u0161ti
+realm-tab-login=Prisijungimas
+realm-tab-keys=Raktai
+realm-tab-email=El. pa\u0161tas
+realm-tab-themes=Temos
+realm-tab-cache=Pod\u0117lis
+realm-tab-tokens=Raktai
+realm-tab-client-initial-access=Pradiniai prieigos raktai
+realm-tab-security-defenses=Saugos priemon\u0117s
+realm-tab-general=Bendra informacija
+add-realm=Prid\u0117ti srit\u012F
+
+#Session settings
+realm-sessions=Srities sesijos
+revocation=At\u0161aukimai
+logout-all=Atjungti visus
+active-sessions=Aktyvios sesijos
+sessions=Sesijos
+not-before=Ne anks\u010Diau
+not-before.tooltip=At\u0161aukti visus raktus i\u0161duotus prie\u0161 nurodyt\u0105 dat\u0105.
+set-to-now=Parinkti dabartin\u0119 dat\u0105
+push=Informuoti apie at\u0161aukim\u0105
+push.tooltip=Visus klientus, kurie turi administravimo URL, informuoti apie nauj\u0105 rakt\u0173 at\u0161aukimo taisykl\u0119.
+
+#Protocol Mapper
+usermodel.prop.label=Atributas
+usermodel.prop.tooltip=S\u0105sajos UserModel atributo metodo pavadinimas. Pavyzd\u017Eiui reik\u0161m\u0117 'email' atitinka UserMode.getEmail() metod\u0105.
+usermodel.attr.label=Naudotojo atributas
+usermodel.attr.tooltip=I\u0161saugoto naudotojo atributo pavadinimas kuris naudojamas UserModel.attribute rinkinyje.
+userSession.modelNote.label=Naudotojo sesijos pastaba
+userSession.modelNote.tooltip=I\u0161saugotos naudotojo sesijos pastaba, kuri saugoma UserSessionModel.note rinkinyje.
+multivalued.label=Daugiareik\u0161mis
+multivalued.tooltip=Nurodo, kad atributas gali tur\u0117ti daugiau nei vien\u0105 reik\u0161m\u0119. Jei pa\u017Eym\u0117tas, tuomet visos reik\u0161m\u0117s nustatomos kaip privalomos. Kitu atveju privaloma tik pirmoji reik\u0161m\u0117.
+selectRole.label=Parinkite rol\u0119
+selectRole.tooltip=Kair\u0117je pus\u0117je esan\u010Diame laukelyje \u012Fveskite rol\u0117s pavadinim\u0105 arba paspauskite Rinktis nor\u0117dami nurodyti pageidaujam\u0105 rol\u0119.
+tokenClaimName.label=Reikalaujamo rakto pavadinimas
+tokenClaimName.tooltip=\u012E rakt\u0105 \u012Fterpiamas privalomas atributas. Galite nurodyte piln\u0105 keli\u0105 iki atributo, pavyzd\u017Eiui 'address.street'. Pateiktu atveju bus sukuriamas sud\u0117tinis (nested) JSON objektas.
+jsonType.label=Privalomo atributo JSON tipas
+jsonType.tooltip=Naudojamas JSON lauko tipas, kuris turi b\u016Bti u\u017Epildomas rakto privalomoje JSON informacijoje. Galimi tipai: long, int, boolean ir String.
+includeInIdToken.label=Prid\u0117ti prie ID rakto
+includeInIdToken.tooltip=Ar privaloma informacija turi b\u016Bti pridedama prie ID rakto?
+includeInAccessToken.label=Prid\u0117ti prie prieigos rakto
+includeInAccessToken.tooltip=Ar privaloma informacija turi b\u016Bti pridedama prie prieigos rakto?
+includeInUserInfo.label=Prid\u0117ti prie naudotojo informacijos
+includeInUserInfo.tooltip=Ar privaloma informacija turi b\u016Bti pridedama prie naudotojo informacijos?
+usermodel.clientRoleMapping.clientId.label=Kliento ID
+usermodel.clientRoleMapping.clientId.tooltip=Kliento ID naudojamas roli\u0173 atribut\u0173 susiejime
+usermodel.clientRoleMapping.rolePrefix.label=Kliento rol\u0117s prefiksas
+usermodel.clientRoleMapping.rolePrefix.tooltip=Prefiksas, pridedamas prie\u0161 kiekvien\u0105 kliento rol\u0119 (neprivalomas)
+usermodel.realmRoleMapping.rolePrefix.label=Srities rol\u0117s prefiksas
+usermodel.realmRoleMapping.rolePrefix.tooltip=Prefiksas, pridedamas prie\u0161 kiekvien\u0105 srities rol\u0119 (neprivalomas)
+
+# client details
+clients.tooltip=Klientai - tai srities nar\u0161ykl\u0117s program\u0117l\u0117s arba tinklin\u0117s paslaugos, kuriomis pasitikima. Klientai gali jungtis prie sistemos. Klientams galima nurodyti specifines roles.
+search.placeholder=Ie\u0161koti...
+create=Sukurti
+import=Importuoti
+client-id=Kliento ID
+base-url=Pagrindinis URL
+actions=Veiksmai
+not-defined=Nenurodyta
+edit=Redaguoti
+delete=Trinti
+no-results=Rezultat\u0173 n\u0117ra
+no-clients-available=N\u0117ra sukonfig\u016Bruot\u0173 klient\u0173
+add-client=Prid\u0117ti klient\u0105
+select-file=Parinkti rinkmen\u0105
+view-details=Per\u017Ei\u016Br\u0117ti detaliau
+clear-import=I\u0161valyti importuojamas rinkmenas
+client-id.tooltip=Identifikatorius, naudojamas URI adresuose ir prieigos raktuose. Pavyzd\u017Eiui 'my-client'. SAML protokolo atveju, \u0161i\u0105 reik\u0161m\u0119 tikimasi gauti kaip authn u\u017Eklausos siunt\u0117j\u0105
+client.name.tooltip=Reik\u0161m\u0117, kuri rodoma naudotojams. Pavyzd\u017Eiui 'My Client'. Galimos lokalizuotos reik\u0161m\u0117s - pavyzd\u017Eiui\: ${my_client}
+client.enabled.tooltip=Klientai, kurie n\u0117ra \u012Fgalinti, negali inicijuoti prisijungimo arba gauti prieigos raktus.
+consent-required=Reikalingas patvirtinimas
+consent-required.tooltip=Jei \u012Fgalinta, tuomet naudotojai privalo patvirtinti, kad pageidauja prisijungti prie kliento (programos).
+client-protocol=Kliento protokolas
+client-protocol.tooltip='OpenID connect' leid\u017Eia klientams tikrinti galutinio naudotojo tapatyb\u0119 remiantis autorizacijos serverio atlikta autentifikacija. 'SAML' \u012Fgalina \u017Einiatinklio, \u012Fskaitant skirting\u0173 domen\u0173 atvejus, vieningos autentifikacijos ir autorizacijos scenarijus perduodant informacij\u0105 saugiose \u017Einut\u0117se.
+access-type=Prieigos tipas
+access-type.tooltip='Konfidencialus' klientai nor\u0117dami inicijuoti prisijungimo protokol\u0105 privalo perduoti slapt\u0105 kod\u0105. 'Vie\u0161as' klientai neprivalo perduoti slapto kodo. 'Tik ne\u0161\u0117jas' klientai - tai tinklin\u0117s paslaugos, kurios niekada neinicijuoja prisijungimo.
+standard-flow-enabled=\u012Egalinta standartin\u0117 seka
+standard-flow-enabled.tooltip=\u012Egalina standartin\u012F OpenID Connect nukreipim\u0105, kuomet autentifikacijos metu yra perduodamas autorizacijos kodas. OpenID Connect arba OAuth2 specifikacijos terminais tai rei\u0161kia 'Authorization Code Flow' \u012Fgalinim\u0105 \u0161iam klientui.
+implicit-flow-enabled=\u012Egalinta i\u0161reik\u0161tin\u0117 seka
+implicit-flow-enabled.tooltip=\u012Egalina OpenID Connect nukreipim\u0105, kuomet autentifikacijos metu n\u0117ra perduodamas autorizacijos kodas. OpenID Connect arba OAuth2 specifikacijos terminais tai rei\u0161kia 'Implicit Flow' \u012Fgalinim\u0105 \u0161iam klientui.
+direct-access-grants-enabled=\u012Egalintas tiesiogin\u0117s prieigos suteikimas
+direct-access-grants-enabled.tooltip=\u012Egalina tiesiogin\u012F prieigos suteikim\u0105, kuomet klientas turi prieig\u0105 prie naudotojo vardo ir slapta\u017Eod\u017Eio ir prieigos rakt\u0173 gavimui \u0161iais duomenimis gali tiesiogiai apsikeisti su Keycloak serveriu. OAuth2 specifikacijos terminais, \u0161iam klientui \u012Fgalinimas 'Resource Owner Password Credentials Grant'.
+service-accounts-enabled=\u012Egalintas paslaugos naudotojas
+service-accounts-enabled.tooltip=\u012Egalina klient\u0105 autentifikuotis su Keycloak serveriu ir gauti dedikuot\u0105 prieigos rakt\u0105 skirt\u0105 \u0161iam klientui. OAuth2 specifikacijos terminais, tai rei\u0161kia 'Client Credentials Grant' teis\u0119 \u0161iam klientui.
+include-authnstatement=\u012Etraukti AuthnStatement
+include-authnstatement.tooltip=Ar prisijungimo b\u016Bdas ir laikas \u0161ur\u0117t\u0173 b\u016Bti \u012Ftraukiami \u012F prisijungimo operacijos atsakym\u0105?
+sign-documents=Pasira\u0161yti dokumentus
+sign-documents.tooltip=Ar SAML dokumentai turi b\u016Bt\u012F pasira\u0161omi \u0161ios srities?
+sign-assertions=Pasira\u0161yti sprendinius
+sign-assertions.tooltip=Ar SAML sprendiniai SAML dokumentuose turi b\u016Bti pasira\u0161omi? \u0160is nustatymas neb\u016Btinas, kuomet naudojamas viso dokumento pasira\u0161ymas.
+signature-algorithm=Para\u0161o algoritmas
+signature-algorithm.tooltip=Para\u0161o algoritmas naudojamas dokument\u0173 pasira\u0161ymui.
+canonicalization-method=Standartizavimo metodas
+canonicalization-method.tooltip=XML para\u0161o metodas.
+encrypt-assertions=U\u017Ekoduoti sprendinius
+encrypt-assertions.tooltip=Ar SAML sprendiniai turi b\u016Bti u\u017Ekoduojami kliento vie\u0161uoju raktu naudojant AES?
+client-signature-required=Privalomas kliento para\u0161as
+client-signature-required.tooltip=Ar kliento siun\u010Diamos SAML u\u017Eklausos ir atsakymai bus pasira\u0161yti? Jei taip, tuomet ar juos privaloma tikrinti?
+force-post-binding=Priverstinai naudoti POST s\u0105ry\u0161\u012F
+force-post-binding.tooltip=Visuomet naudoti POST s\u0105ry\u0161\u012F siun\u010Diant atsakymus.
+front-channel-logout=I\u0161registravimas per nar\u0161ykl\u0119
+front-channel-logout.tooltip=Jei \u012Fgalinta, tuomet atsijungimas atliekamas nar\u0161ykl\u0117s nukreipimu \u012F kliento puslap\u012F. Kitu atveju, atsijungimas atliekamas perduodant serveris-serveris u\u017Eklaus\u0105.
+force-name-id-format=Priverstinai naudoti NameID format\u0105
+force-name-id-format.tooltip=Ignoruoti NameID tapatyb\u0117s identifikatoriaus format\u0105, naudojant administratoriaus konsol\u0117je nurodyt\u0105 format\u0105.
+name-id-format=NameID formatas
+name-id-format.tooltip=Koks tapatyb\u0117s identifikatoriaus formatas turi b\u016Bti naudojamas.
+root-url=\u0160akninis URL
+root-url.tooltip=Prie reliatyvi\u0173 nuorod\u0173 pridedamas \u0161akninis URL
+valid-redirect-uris=Leid\u017Eiamos nukreipimo nuorodos
+valid-redirect-uris.tooltip=Nukreipimo URI \u0161ablonas, kuomet nar\u0161yklei leid\u017Eiama nukreipti naudotoj\u0105 po s\u0117kmingos autentifikacijos ar atsijungimo metu. Leid\u017Eiami pakaitos simboliai, pvz. 'http://pavyzdys.lt/*'. Leid\u017Eiami reliatyv\u016Bs keliai pvz. /mano/reliatyvus/kelias/*. Reliatyvumas skai\u010Diuojamas nuo kliento \u0161akninio URL (jei nurodyta) arba nuo autentifikacijos serverio \u0161akninio adreso. SAML atveju, kuomet tikimasi gav\u0117jo paslaugos URL \u012Ftraukimo \u012F prisijungimo u\u017Eklaus\u0105, privaloma nurodyti teisingus URI \u0161ablonus.
+base-url.tooltip=Numatytas URL, kuris turi b\u016Bti naudojamas naudotojo nukreipimui atgal \u012F klient\u0105.
+admin-url=Administravimo URL
+admin-url.tooltip=Kliento administravimo tinklin\u0117s s\u0105sajos URL. \u012Era\u0161yti tuomet, kai klientas palaiko adapterio REST API. \u0160is REST API leid\u017Eia autentifikacijos serveriui perduoti at\u0161aukimo ir kitas su administravimu susijusias taisykles. Da\u017Eniausiai \u0161is URL sutampa su kliento pagrindiniu URL.
+master-saml-processing-url=\u0160akninis SAML apdorojimo URL
+master-saml-processing-url.tooltip=Kuomet sukonfig\u016Bruotas, \u0161is URL bus naudojamas visoms, 'SP's Assertion Consumer' ir 'Single Logout Services' u\u017Eklausoms. Detalioje SAML prieigos adres\u0173 konfig\u016Bravimo skyriuje \u0161ios reik\u0161m\u0117s gali b\u016Bti atskirai pakeistos.
+idp-sso-url-ref=IDP inicijuojamo SSO URL fragmento pavadinimas
+idp-sso-url-ref.tooltip=Pavadinimas, kuris IDP inicijuoto SSO prisijungimo metu, perduodamas klientui per URL fragment\u0105. Palikus tu\u0161\u010Di\u0105 reik\u0161m\u0119 IDP inicjuojam\u0105 SSO prisijungimo funkcionalumas i\u0161jungiamas. \u0160is fragmentas buv naudojamas formuojant \u0161i\u0105 nuorod\u0105: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
+idp-sso-relay-state=IDP inicijuotos SSO b\u016Bsenos perdavimas
+idp-sso-relay-state.tooltip=SSO b\u016Bsenos parametro (RelayState) perdavimas kartu su IDP inicijuota SSO SAML u\u017Eklausa.
+web-origins=\u0160aknin\u0117s nuorodos
+web-origins.tooltip=Leid\u017Eiamos CORS nuorodos. Nor\u0117dami leisti nukreipim\u0105 \u012F teisingas nuorodas, naudokite '+'. Nor\u0117dami leisti visas nuorodas, naudokite '*'.
+fine-oidc-endpoint-conf=Detalioji OpenID prisijungimo konfig\u016Bracija
+fine-oidc-endpoint-conf.tooltip=Nor\u0117dami konfig\u016Bruoti kliento s\u0105sajos su OpenID prisijungimo protokolu i\u0161pl\u0117stines nuostatas, i\u0161skleiskite \u0161\u012F skyri\u0173.
+user-info-signed-response-alg=Naudotojo informacijos pasira\u0161yto atsako algoritmas
+user-info-signed-response-alg.tooltip=JWA algoritmas naudojamas pasira\u0161yti naudotojo informacijos prieigos ta\u0161ko atsak\u0105. Jei nustatyta 'unsigned', tuomet naudotojo informacijos atsakas nebus pasira\u0161ytas ir bus gr\u0105\u017Eintas application/json formatu.
+request-object-signature-alg=U\u017Eklausos objekto para\u0161o algoritmas
+request-object-signature-alg.tooltip=JWA algoritmas, kur\u012F klientas naudoja siun\u010Diant OIDC u\u017Eklausos objekt\u0105, nusakyt\u0105 'request' arba 'request_uri' parameterais. Jei nustatyta 'any', tuomet u\u017Eklausos objektas gali b\u016Bti nepasira\u0161ytas arba pasira\u0161ytas bet kuriuo algoritmu.
+fine-saml-endpoint-conf=Detalioji SAML prieigos ta\u0161k\u0173 konfig\u016Bracija
+fine-saml-endpoint-conf.tooltip=Nor\u0117dami konfig\u016Bruoti sprendini\u0173 pri\u0117mimo ir vieningo atsijungimo paslaugas, i\u0161skleiskite \u0161\u012F skyri\u0173.
+assertion-consumer-post-binding-url=Sprendini\u0173 naudotojo paslaugos POST jungties URL
+assertion-consumer-post-binding-url.tooltip=Kliento sprendini\u0173 pri\u0117mimo paslaugos (prisijungimo rezultat\u0173) SAML POST jungties URL. Jei toki\u0173 jung\u010Di\u0173 neturite, tuomet palikite tu\u0161\u010Dias reik\u0161mes.
+assertion-consumer-redirect-binding-url=Sprendini\u0173 pri\u0117mimo paslaugos nukreipimo jungties URL
+assertion-consumer-redirect-binding-url.tooltip=Kliento sprendinio pri\u0117mimo paslaugos SAML nukreipimo jungties URL (prisijungimo atsakymams). Jei toki\u0173 jung\u010Di\u0173 neturite, tuomet palikite tu\u0161\u010Dias reik\u0161mes.
+logout-service-binding-post-url=Atsijungimo paslaugos POST jungties URL
+logout-service-binding-post-url.tooltip=Kliento vieningo atsijungimo SAML POST jungties URL. Jei naudojate kitas jungtis, tuomet \u0161ias reik\u0161mes galite palikti neu\u017Epildytas.
+logout-service-redir-binding-url=Atsijungimo paslaugos nukreipimo jungties URL
+logout-service-redir-binding-url.tooltip=Kliento vieningo atsijungimo paslaugos SAML nukreipimo jungties. Jei naudojate kitas jungtis, tuomet \u0161ias reik\u0161mes galite palikti neu\u017Epildytas.
+
+# client import
+import-client=\u012Ediegti programos nustatymus
+format-option=Formato pasirinkimas
+select-format=Pasirinkite format\u0105
+import-file=Importuoti rinkmen\u0105
+
+# client tabs
+settings=Nustatymai
+credentials=Prisijungimo duomenys
+saml-keys=SAML raktai
+roles=Rol\u0117s
+mappers=Atribut\u0173 atitikmenys
+mappers.tooltip=Protokolo atribut\u0173 susiejimas atlieka rakt\u0173 ir dokument\u0173 transformacijas. Naudotojo duomenys gali b\u016Bti ver\u010Diami \u012F protokolo teiginius, arba tiesiog transformuoti bet kurias u\u017Eklausas perduodamas tarp kliento ir autentifikacijos serverio.
+scope=Apimtis
+scope.tooltip=Apimties atitikmen\u0173 parinkimas leid\u017Eia apriboti, kurios naudotojo rol\u0117s kartu su raktu bus perduodamos klientui.
+sessions.tooltip=Per\u017Ei\u016Br\u0117ti \u0161io kliento aktyvias sesijas. Matysite \u0161iuo metu prisijungusius naudotojus bei j\u0173 prisijungimo laikus.
+offline-access=Darbas neprisijungus
+offline-access.tooltip=Per\u017Ei\u016Br\u0117ti \u0161io kliento darbo neprisijungus r\u0117\u017Eimo aktyvias sesijas. Matysite naudotojus, kuriems yra i\u0161duoti darbo neprisijungus raktai bei j\u0173 i\u0161davimo laikus. Nor\u0117dami at\u0161aukti visus \u0161iam klientui i\u0161duotus raktus, eikite \u012F at\u0161aukim\u0173 kortel\u0119 ir pasirinkite 'Parinkti dabartin\u0119 dat\u0105'
+clustering=Klasteriai
+installation=Diegimas
+installation.tooltip=Klient\u0173 konfig\u016Bravimo pagalbin\u0117 priemon\u0117, padedanti sugeneruoti klient\u0173 adapteri\u0173 konfig\u016Bracijas, kurias galima atsisi\u0173sti, kopijuoti ar \u012Fkelti i\u0161 i\u0161karpin\u0117s
+service-account-roles=Paslaugos paskyros rol\u0117s
+service-account-roles.tooltip=Dedikuot\u0173 roli\u0173 priskyrimas \u0161ios paslaugos naudotojui
+
+# client credentials
+client-authenticator=Kliento autentifikavimo priemon\u0117s
+client-authenticator.tooltip=Kliento autentifikavimo priemon\u0117s naudojamos kliento autentifikavimuisi \u012F Keycloak server\u012F
+certificate.tooltip=Kliento sertifikatas naudojamas kliento i\u0161duot\u0173 ir priva\u010Diu raktu pasira\u0161yt\u0173 JWT prieigos rakt\u0173 tikrinimui.
+publicKey.tooltip=Kliento i\u0161duotas vie\u0161asis raktas pasira\u0161ytas kliento priva\u010Diu raktu ir skirtas JWT tikrinimui.
+no-client-certificate-configured=Nesukonfig\u016Bruotas nei vienas kliento sertifikatas
+gen-new-keys-and-cert=Nauj\u0173 rakt\u0173 ir sertifikat\u0173 generavimas
+import-certificate=Importuoti sertifikat\u0105
+gen-client-private-key=Generuoti kliento privat\u0173 rakt\u0105
+generate-private-key=Generuoti privat\u0173 rakt\u0105
+archive-format=Archyvo formatas
+archive-format.tooltip=Java rakt\u0173 saugykla (keystore) arba PKCS12 formato rinkmena.
+key-alias=Rakto pseudonimas
+key-alias.tooltip=Privataus rakto ir sertifikato rinkmenos pseudonimas.
+key-password=Rakto slapta\u017Eodis
+key-password.tooltip=Slapta\u017Eod\u017Ei\u0173 saugykloje esan\u010Dio privataus rakto slapta\u017Eodis
+store-password=Saugyklos slapta\u017Eodis
+store-password.tooltip=Slapta\u017Eodis, reikalingas norint atidaryti slapta\u017Eod\u017Ei\u0173 saugykl\u0105
+generate-and-download=Generuoti ir atsisi\u0173sti
+client-certificate-import=Kliento sertifikato importavimas
+import-client-certificate=Importuoti kliento sertifikatus
+jwt-import.key-alias.tooltip=Slapta\u017Eod\u017Ei\u0173 saugyklos pseudonimas
+secret=Slaptas kodas
+regenerate-secret=Pergeneruoti slapt\u0105 kod\u0105
+registrationAccessToken=Registracijos prieigos raktas
+registrationAccessToken.regenerate=Pergeneruoti registracijos prieigos rakt\u0105
+registrationAccessToken.tooltip=Registracijos prieigos raktas klientams suteikia prieig\u0105 prie klient\u0173 registracijos paslaugos
+add-role=Prid\u0117ti rol\u0119
+role-name=Rol\u0117s pavadinimas
+composite=Sud\u0117tinis
+description=Apra\u0161ymas
+no-client-roles-available=Kliento rol\u0117s nesukonfig\u016Bruotos
+scope-param-required=Privalomas taikymo srities parametras
+scope-param-required.tooltip=\u0160i rol\u0117 suteikiama tik tuo atveju, kai taikymo srities parametras su rol\u0117s vardu panaudotas autorizacijos u\u017Eklausoje ar rakte.
+composite-roles=Sud\u0117tin\u0117s rol\u0117s
+composite-roles.tooltip=Visos susietos rol\u0117s bus automati\u0161kai priskiriamos naudotojui prisikiriant \u0161i\u0105 sud\u0117tin\u0119 rol\u0119.
+realm-roles=Srities rol\u0117s
+available-roles=Galimos rol\u0117s
+add-selected=Prid\u0117ti pa\u017Eym\u0117tas
+associated-roles=Priskirtos rol\u0117s
+composite.associated-realm-roles.tooltip=Srities apmities rol\u0117s susietos su \u0161ia sud\u0117tine role.
+composite.available-realm-roles.tooltip=Srities apmities rol\u0117s, kurias galima susieti su \u0161ia sud\u0117tine role.
+remove-selected=Pa\u0161alinti pa\u017Eym\u0117tas
+client-roles=Kliento rol\u0117s
+select-client-to-view-roles=Nor\u0117dami pamatyti priskirtas roles pa\u017Eym\u0117kite klient\u0105
+available-roles.tooltip=\u0160io kliento rol\u0117s, kurios gali b\u016Bti priskiritos \u0161iai kompozicinei rolei.
+client.associated-roles.tooltip=Su \u0161iuo klientu susietos sud\u0117tin\u0117s rol\u0117s.
+add-builtin=Prid\u0117ti numatytuosius
+category=Kategorija
+type=Tipas
+no-mappers-available=N\u0117ra susiet\u0173 atribut\u0173
+add-builtin-protocol-mappers=Prid\u0117ti numatytuosius protokolo atribut\u0173 susiejimus
+add-builtin-protocol-mapper=Prid\u0117ti numatytuosius protokolo atribut\u0173 susiejimus
+scope-mappings=atribut\u0173 susiejimo taikymo sritis
+full-scope-allowed=Taikymas pilna apimtimi
+full-scope-allowed.tooltip=\u012Egalinimo atveju visi apribojimai i\u0161jungiami
+scope.available-roles.tooltip=Srities lygio rol\u0117s, kurios gali b\u016Bti priskiriamos \u0161iai taikymo sri\u010Diai.
+assigned-roles=Priskirtos rol\u0117s
+assigned-roles.tooltip=Srities lygio rol\u0117s, kurios yra priskirtos \u0161iai taikymo sri\u010Diai.
+effective-roles=Aktyvios rol\u0117s
+realm.effective-roles.tooltip=Priskirtos srities lygio rol\u0117s, kurios gali gali b\u016Bti paveld\u0117tos i\u0161 sud\u0117tini\u0173 roli\u0173.
+select-client-roles.tooltip=Nor\u0117dami pamatyti visas roles pa\u017Eym\u0117kite klient\u0105
+assign.available-roles.tooltip=Kliento rol\u0117s, kurias galima priskirti.
+client.assigned-roles.tooltip=Priskirtos kliento rol\u0117s.
+client.effective-roles.tooltip=Priskirtos kliento rol\u0117s, kurios gali gali b\u016Bti paveld\u0117tos i\u0161 sud\u0117tini\u0173 roli\u0173.
+basic-configuration=Pagrindin\u0117 konfig\u016Bracija
+node-reregistration-timeout=Mazgo persiregistravimui skirtas laikas
+node-reregistration-timeout.tooltip=Nurodykite maksimal\u0173 laiko interval\u0105, per kur\u012F mazgai privalo i\u0161 naujo prisiregistruoti. Jei mazgas neatsi\u0173s persiregistravimo u\u017Eklausos per nurodyt\u0105 laik\u0105, tuomet \u0161is mazgas bus i\u0161registruojamas i\u0161 Keycloak
+registered-cluster-nodes=Registruoti klasterio mazgus
+register-node-manually=Registruoti mazg\u0105 rankiniu b\u016Bdu
+test-cluster-availability=Tikrinti ar mazgas prieinamas
+last-registration=V\u0117liausia registracija
+node-host=Mazgo serveris
+no-registered-cluster-nodes=Nepriregistuotas nei vienas klasterio mazgas
+cluster-nodes=Klasterio mazgai
+add-node=Prid\u0117ti mazg\u0105
+active-sessions.tooltip=\u0160io kliento aktyvi\u0173 naudotoj\u0173 sesij\u0173 skai\u010Dius.
+show-sessions=Rodyti sesijas
+show-sessions.tooltip=D\u0117mesio, \u0161is veiksmas gali ilgai u\u017Etrukti priklausomai nuo aktyvi\u0173 sesij\u0173 skai\u010Diaus.
+user=Naudotojas
+from-ip=Prisijungimo IP
+session-start=Sesijos prad\u017Eios laikas
+first-page=Pirmas puslapis
+previous-page=Atgalinis puslapis
+next-page=Sekantis puslapis
+client-revoke.not-before.tooltip=At\u0161aukti visus \u0161io kliento raktus i\u0161duotus prie\u0161 nurodyt\u0105 dat\u0105.
+client-revoke.push.tooltip=Kuomet nurodytas administravimo URL, taisykl\u0117 perduodama klientui.
+select-a-format=Formato parinkimas
+download=Atsisi\u0173sti
+offline-tokens=Darbo neprisijungus raktai
+offline-tokens.tooltip=Rakt\u0173 skai\u010Dius, kurie naudojami darbui neprisijungus
+show-offline-tokens=Rodyti raktus
+show-offline-tokens.tooltip=D\u0117mesio, \u0161is veiksmas gali ilgai u\u017Etrukti priklausomai nuo aktyvi\u0173 darbo neprisijungus sesij\u0173 skai\u010Diaus.
+token-issued=Rakto i\u0161davimo laikas
+last-access=V\u0117liausios prieigos laikas
+last-refresh=V\u0117liausio atnaujinimo laikas
+key-export=Eksportuoti rakt\u0105
+key-import=Importuoti rakt\u0105
+export-saml-key=Eksportuoti SAML rakt\u0105
+import-saml-key=Importuoti SAML rakt\u0105
+realm-certificate-alias=Srities sertifikato pseudonimas
+realm-certificate-alias.tooltip=Srities sertifikato, kuris taip pat saugomas saugykloje, pseudonimas.
+signing-key=Pasira\u0161ymo raktas
+saml-signing-key=SAML pasira\u0161ymo raktas.
+private-key=Privatus raktas
+generate-new-keys=Generuoti naujus raktus
+export=Eksportuoti
+encryption-key=U\u017Ekodavimo raktas
+saml-encryption-key.tooltip=SAML u\u017Ekodavimo raktas.
+service-accounts=Paslaugos naudotojo profiliai
+service-account.available-roles.tooltip=\u0160ios paslaugos paskyrai galimos priskirti srities rol\u0117s
+service-account.assigned-roles.tooltip=Paslaugos paskyrai priskirtos srities rol\u0117s.
+service-account-is-not-enabled-for=Kliento {{client}} paslaugos paskyra ne\u012Fgalinta
+create-protocol-mappers=Protokolo atitkmen\u0173 susiejimas
+create-protocol-mapper=Protokolo atitkmenens susiejimas
+protocol=Protokolas
+protocol.tooltip=Protokolas...
+id=ID
+mapper.name.tooltip=Atitikmens susiejimo vardas.
+mapper.consent-required.tooltip=Ar teikiant laikin\u0105 prieig\u0105 naudotojas privalo pateikti sutikim\u0105 d\u0117l duomen\u0173 perdavimo?
+consent-text=Sutikimo tekstas
+consent-text.tooltip=Tekstas, kuris rodomas naudotojo sutikimo puslapyje.
+mapper-type=Atitikmens tipas
+mapper-type.tooltip=Atitikmens tipas
+# realm identity providers
+identity-providers=Tapatyb\u0117s teik\u0117jai
+table-of-identity-providers=Tapatyb\u0117s teik\u0117j\u0173 s\u0105ra\u0161as
+add-provider.placeholder=Prid\u0117ti teik\u0117j\u0105...
+provider=Teik\u0117jas
+gui-order=GUI eili\u0161kumas
+first-broker-login-flow=Pirmojo prisijungimo eiga
+post-broker-login-flow=Sekan\u010Di\u0173 prisijungim\u0173 eiga
+redirect-uri=Nukreipimo URI
+redirect-uri.tooltip=Tapatyb\u0117s teik\u0117jo konfig\u016Bravimo nuoroda.
+alias=Pseudonimas
+identity-provider.alias.tooltip=Pseudonimas, kuris vienareik\u0161mi\u0161kai identifikuoja tapatyb\u0117s teik\u0117j\u0105 ir yra naudojamas konstruojant nukreipimo nuorod\u0105.
+identity-provider.enabled.tooltip=\u012Egalinti \u0161\u012F tapatyb\u0117s teik\u0117j\u0105.
+authenticate-by-default=Autentifikuoti i\u0161 karto
+identity-provider.authenticate-by-default.tooltip=Jei \u012Fgalinta, tuomet bus bandoma autentifikuoti naudotoj\u0105 prie\u0161 parodant prisijungimo lang\u0105.
+store-tokens=Saugoti raktus
+identity-provider.store-tokens.tooltip=Jei \u012Fgalinta, tuomet po naudotoj\u0173 prisijungimo, prieigos raktai bus i\u0161saugoti.
+stored-tokens-readable=Saugoti raktus skaitomame formate
+identity-provider.stored-tokens-readable.tooltip=Jei \u012Fgalinta, tuomet naudotojai gali per\u017Ei\u016Br\u0117ti i\u0161saugotus prieigos raktus. \u012Egalinama broker.read-token rol\u0117.
+update-profile-on-first-login=Profilio duomen\u0173 atnaujinimas pirmojo prisijungimo metu
+on=On
+off=Off
+on-missing-info=Kuomet tr\u016Bksta informacijos
+update-profile-on-first-login.tooltip=Nurodykite s\u0105lygas, kuomet naudotojas privalo atnaujinti savo profil\u012F pirmojo prisijungimo metu.
+trust-email=El. pa\u0161tas patikimas
+trust-email.tooltip=Jei \u012Fgalintas, tuomet \u0161io tapatyb\u0117s teik\u0117jo pateiktas el. pa\u0161to adresas laikomas patikimu ir, nepaisant bendr\u0173j\u0173 srities nustatym\u0173, n\u0117ra papildomai tikrinamas.
+gui-order.tooltip=Eili\u0161kum\u0105 GUI lange (pvz. Prisijungimo langas) nurodantis skai\u010Dius
+first-broker-login-flow.tooltip=Autentifikacijos eigos pseudonimas, kuris bus su\u017Eadintas \u0161io tapatyb\u0117s teik\u0117jo naudotojui prisijungus pirm\u0105 kart\u0105. Terminas 'pirmas kartas' rei\u0161kia, kad Keycloak sistemoje nebuvo saugomas naudotojo profilis susietas su autentifikuotu \u0161io tapatyb\u0117s teik\u0117jo naudotoju.
+post-broker-login-flow.tooltip=Autentifikacijos eigos pseudonimas, kuris bus su\u017Eadintas po kiekvieno prisijungimo naudojant \u0161\u012F tapatyb\u0117s teik\u0117j\u0105. Naudingas tuomet, kai atlikti papildomus tikrinimus (pvz. OTP). Palikite tu\u0161\u010Di\u0105 reik\u0161m\u0119 jei nenorite su\u017Eadinti papildom\u0173 tikrinim\u0173 autentifikatoriumi jungiantis per \u0161\u012F tapatyb\u0117s teik\u0117j\u0105. Tur\u0117kite omenyje, kad autentifikatoriaus realizacijos turi daryti prielaid\u0105, kad ClientSession naudotojas yra tapatyb\u0117s teik\u0117jo nustatytas.
+openid-connect-config=OpenID prisijungimo konfig\u016Bracija
+openid-connect-config.tooltip=OIDC SP ir i\u0161orinio IDP konfig\u016Bracija.
+authorization-url=Autorizacijos URL
+authorization-url.tooltip=Autorizacijos URL adresas.
+token-url=Prieigos rakt\u0173 URL
+token-url.tooltip=Prieigos rakt\u0173 URL.
+logout-url=Atsijungimo URL
+identity-provider.logout-url.tooltip=Adresas, kuris turi b\u016Bti naudojamas norint atjungti naudotoj\u0105 nuo i\u0161orinio tapatyb\u0117s teik\u0117jo.
+backchannel-logout=Foninis atjungimas
+backchannel-logout.tooltip=Ar i\u0161orinis tapatyb\u0117s teik\u0117jas palaiko serveris-serveris naudotojo atjungimo b\u016Bd\u0105?
+user-info-url=Naudotojo informacijos URL
+user-info-url.tooltip=Naudotojo informacijos URL. Neprivalomas.
+identity-provider.client-id.tooltip=Kliento identifikatorius u\u017Eregistruotas tapatyb\u0117s teik\u0117jo sistemoje.
+client-secret=Kliento slaptas kodas
+show-secret=Rodysi slapt\u0105 kod\u0105
+hide-secret=Sl\u0117pti slapt\u0105 kod\u0105
+client-secret.tooltip=Kliento slaptas kodas u\u017Eregistruotas tapatyb\u0117s teik\u0117jo sistemoje.
+issuer=I\u0161dav\u0117jas
+issuer.tooltip=I\u0161dav\u0117jo identifikatorius perduodamas i\u0161dav\u0117jo atsakyme. Tikrinimas nebus atliekamas jei reik\u0161m\u0117 tu\u0161\u010Dia.
+default-scopes=Numatytosios taikymo sritys
+identity-provider.default-scopes.tooltip=Taikymos sritys, kurios siun\u010Diamos autorizavimo u\u017Eklausoje. Reik\u0161m\u0117s turi b\u016Bti atskirtos tarpo simboliu. Numatyta reik\u0161m\u0117 - 'openid'.
+prompt=Raginimas
+unspecified.option=nenurodyta
+none.option=jokio
+consent.option=sutikimo tekstas
+login.option=prisijungimas
+select-account.option=paskyros pasirinkimas
+prompt.tooltip=Nurodo, ar autorizacijos serveris galutini\u0173 naudotoj\u0173 reikalauja pakartotinai patvirtinti sutikim\u0105 ar prisijungti.
+validate-signatures=Para\u0161o tikrinimas
+identity-provider.validate-signatures.tooltip=\u012Egalinamas i\u0161orini\u0173 IDP para\u0161\u0173 tikrinimas.
+validating-public-key=Vie\u0161as raktas para\u0161o tikrinimui
+identity-provider.validating-public-key.tooltip=PEM formato vie\u0161asis raktas, kuris turi b\u016Bti naudojamas i\u0161orinio IDP para\u0161t\u0173 tikrinimui.
+import-external-idp-config=Importuoti i\u0161orinio IDP konfig\u016Bracij\u0105
+import-external-idp-config.tooltip=Leid\u017Eia \u012Fkelti konfig\u016Bracin\u0119 rinkmen\u0105 arba nurodyti atsisiuntimo URL su i\u0161orinio IDP metaduomenimis.
+import-from-url=Importuoti i\u0161 URL
+identity-provider.import-from-url.tooltip=Importuoti metaduomenis i\u0161 nutolusio IDP aptikimo apra\u0161o (IDP discovery descriptor).
+import-from-file=Importuoti i\u0161 rinkmenos
+identity-provider.import-from-file.tooltip=Importuoti metaduomenis i\u0161 rinkmenos, kuri\u0105 atsisiunt\u0117te i\u0161 IDP aptikimo apra\u0161o (IDP discovery descriptor).
+saml-config=SAML konfig\u016Bracija
+identity-provider.saml-config.tooltip=SAML SP ir i\u0161oriniu IDP konfig\u016Bracija.
+single-signon-service-url=Vieningo prisijungimo paslaugos URL
+saml.single-signon-service-url.tooltip=Adresas, kuriuo turi b\u016Bti siun\u010Diamos autentifikacijos u\u017Eklausos (SAML AuthnRequest).
+single-logout-service-url=Vieningo atsijungimo paslaugos URL
+saml.single-logout-service-url.tooltip=Adresas, kuriuo turi b\u016Bti siun\u010Diamos naudotojo atjungimo u\u017Eklausos.
+nameid-policy-format=NameID taisykli\u0173 formatas
+nameid-policy-format.tooltip=Nurodykite URI nuorod\u0105 atitinkan\u010Di\u0105 vardo identifikatoriaus format\u0105. Numatyta reik\u0161m\u0117 urn:oasis:names:tc:SAML:2.0:nameid-format:persistent.
+http-post-binding-response=Si\u0173sti atsakymus HTTP-POST
+http-post-binding-response.tooltip=Jei \u012Fgalinta, tuomet atsakymai siun\u010Diami HTTP-POST saistymu. Kitu atveju bus naudojamas HTTP-REDIRECT.
+http-post-binding-for-authn-request=Si\u0173sti AuthnRequest HTTP-POST
+http-post-binding-for-authn-request.tooltip=Jei \u012Fgalinta, tuomet AuthnRequest siun\u010Diami HTTP-POST saistymu. Kitu atveju bus naudojamas HTTP-REDIRECT.
+want-authn-requests-signed=Reikalaujami pasira\u0161yt\u0173 AuthnRequests
+want-authn-requests-signed.tooltip=Nurodykite, ar tapatyb\u0117s teik\u0117jas tikisi pasira\u0161yt\u0173 AuthnRequest u\u017Eklaus\u0173.
+force-authentication=Priverstin\u0117 autentifikacija
+identity-provider.force-authentication.tooltip=Jei \u012Fgalinta, tuomet tapatyb\u0117s teik\u0117jas privalo autentifikuoti naudotoj\u0105 i\u0161 naujo nepasitikint ankstesniu prisijungimu.
+validate-signature=Para\u0161o tikrinimas
+saml.validate-signature.tooltip=\u012Ejungti/i\u0161jungti SAML atsakym\u0173 para\u0161o tikrinim\u0105.
+validating-x509-certificate=X509 sertifikatas tikrinimui
+validating-x509-certificate.tooltip=PEM formato sertifikatas, kuris turi b\u016Bti naudojamas para\u0161\u0173 tikrinimui.
+saml.import-from-url.tooltip=Importuoti metaduomenis i\u0161 nutolusio IDP SAML subjekto apra\u0161o.
+social.client-id.tooltip=Kliento identifikatorius u\u017Eregistruotas tapatyb\u0117s teik\u0117jo sistemoje.
+social.client-secret.tooltip=Kliento saugos kodas u\u017Eregistruotas tapatyb\u0117s teik\u0117jo sistemoje.
+social.default-scopes.tooltip=Autorizacijos metu siun\u010Diamos taikymo sritys. Galim\u0173 reik\u0161mi\u0173 s\u0105ra\u0161o, skirtuko ir numatytos reik\u0161m\u0117s ie\u0161kokite tapatyb\u0117s teik\u0117jo sistemos dokumentacijoje..
+key=Raktas
+stackoverflow.key.tooltip=Stack Overflow kliento registracijos metu gautas raktas.
+
+# User federation
+sync-ldap-roles-to-keycloak=Sinchronizuoti LDAP roles \u012F Keycloak
+sync-keycloak-roles-to-ldap=Sinchronizuoti Keycloak roles \u012F LDAP
+sync-ldap-groups-to-keycloak=Sinchronizuoti LDAP grupes \u012F Keycloak
+sync-keycloak-groups-to-ldap=Sinchronizuoti Keycloak grupes \u012F LDAP
+
+realms=Sritys
+realm=Sritis
+
+identity-provider-mappers=Tapatyb\u0117s teik\u0117jo atitikmen\u0173 susiejimai
+create-identity-provider-mapper=Sukurti tapatyb\u0117s teik\u0117jo atitikmens susiejim\u0105
+add-identity-provider-mapper=Prid\u0117ti tapatyb\u0117s teik\u0117jo atitikmens susiejim\u0105
+client.description.tooltip=Nurodomas kliento apra\u0161as. Pavyzd\u017Eiui 'Mano laiko lenteli\u0173 klientas'. Palaikomos lokalizuotos reik\u0161m\u0117s. Pavyzd\u017Eiui\: ${my_client_description}
+
+expires=Galioja iki
+expiration=Galiojimas
+expiration.tooltip=Nurodykite kiek laiko galios prieigos raktas
+count=Kiekis
+count.tooltip=Nurodykite kiek klient\u0173 gali b\u016Bti sukurti naudojant prieigos rakt\u0105
+remainingCount=Lik\u0119s kiekis
+created=Sukurta
+back=Atgal
+initial-access-tokens=Pradiniai prieigos raktai
+add-initial-access-tokens=Prid\u0117ti pradin\u012F prieigos rakt\u0105
+initial-access-token=Pradinis prieigos raktas
+initial-access.copyPaste.tooltip=Nukopijuokite ir \u012Fklijuokite prieigos rakt\u0105 prie\u0161 i\u0161eidami i\u0161 \u0161io puslapio. V\u0117liau negal\u0117site kopijuoti \u0161i\u0173 prieigos rakt\u0173.
+continue=T\u0119sti
+initial-access-token.confirm.title=Kopijuoti pradinius prieigos raktus
+initial-access-token.confirm.text=Pra\u0161ome \u012Fsitikinti, kad nusikopijavote pradinius prieigos raktus nes v\u0117liau prie rakt\u0173 nebegal\u0117site prieiti
+no-initial-access-available=N\u0117ra galim\u0173 pradini\u0173 prieigos rak\u0161\u0173
+
+trusted-hosts-legend=Patikimi kliento registracijos serveriai
+trusted-hosts-legend.tooltip=Serveri\u0173 vardai, kuriais pasitikima kliento registracijos metu. Klient\u0173 registravimo u\u017Eklausos i\u0161 \u0161i\u0173 serveri\u0173 gali b\u016Bti siun\u010Diamos be pradini\u0173 prieigos rakt\u0173. Klient\u0173 registracijos skai\u010Dius ribojamas pagal nurodyt\u0105 kiekvieno serverio limit\u0105.
+no-client-trusted-hosts-available=N\u0117ra galim\u0173 patikim\u0173 serveri\u0173
+add-client-reg-trusted-host=Prid\u0117ti patikim\u0105 server\u012F
+hostname=Serverio vardas
+client-reg-hostname.tooltip=Pilnas serverio vardas arba IP adresas. Klient\u0173 registracijomis su \u0161iuo serverio vardu arba IP adresu bus pasitikima ir leid\u017Eiama nauj\u0173 klient\u0173 registracija.
+client-reg-count.tooltip=Limitas, kiek registravimo u\u017Eklaus\u0173 galima atsi\u0173sti i\u0161 kiekvieno serverio. Limitas bus atkurtas tik po atk\u016Brimo.
+client-reg-remainingCount.tooltip=I\u0161 \u0161io serverio lik\u0119s galim\u0173 registracijos u\u017Eklaus\u0173 skai\u010Dius. Limitas bus atkurtas tik po atk\u016Brimo.
+reset-remaining-count=Atk\u016Brimo limit\u0105
+
+client-templates=Klient\u0173 \u0161ablonai
+client-templates.tooltip=Klient\u0173 \u0161ablonai leid\u017Eia nurodyti bendr\u0105 vis\u0173 klient\u0173 konfig\u016Bracij\u0105
+
+groups=Grup\u0117s
+
+group.add-selected.tooltip=Grupei galimos priskirti srities rol\u0117s.
+group.assigned-roles.tooltip=Su \u0161ia grupe susietos srities roles
+group.effective-roles.tooltip=Visos srities susietos rol\u0117s. \u0160iame s\u0105ra\u0161e taip pat rodomos visos rol\u0117s, kurios priskirtos sud\u0117tin\u0117ms rol\u0117ms.
+group.available-roles.tooltip=\u0160io kliento galimos susieti rol\u0117s.
+group.assigned-roles-client.tooltip=Susietos \u0161io kliento rol\u0117s.
+group.effective-roles-client.tooltip=Visos \u0161io kliento susietos rol\u0117s. \u0160iame s\u0105ra\u0161e taip pat rodomos visos rol\u0117s, kurios priskirtos sud\u0117tin\u0117ms rol\u0117ms.
+
+default-roles=Numatytosios rol\u0117s
+no-realm-roles-available=Sritis neturi roli\u0173
+
+users=Naudotojai
+user.add-selected.tooltip=Naudotojui galimos priskirti srities rol\u0117s.
+user.assigned-roles.tooltip=Su \u0161iuo naudotoju susietos srities rol\u0117s
+user.effective-roles.tooltip=Visos srities susietos rol\u0117s. \u0160iame s\u0105ra\u0161e taip pat rodomos visos rol\u0117s, kurios priskirtos sud\u0117tin\u0117ms rol\u0117ms.
+user.available-roles.tooltip=\u0160io kliento galimos susieti rol\u0117s.
+user.assigned-roles-client.tooltip=Susietos \u0161io kliento rol\u0117s.
+user.effective-roles-client.tooltip=Visos \u0161io kliento susietos rol\u0117s. \u0160iame s\u0105ra\u0161e taip pat rodomos visos rol\u0117s, kurios priskirtos sud\u0117tin\u0117ms rol\u0117ms.
+default.available-roles.tooltip=Galimos priskirti srities rol\u0117s.
+realm-default-roles=Numatytosios srities rol\u0117s
+realm-default-roles.tooltip=Srities rol\u0117s, kurios automati\u0161kai priskiriamos naujiems naudotojams.
+default.available-roles-client.tooltip=\u0160io kliento rol\u0117s, kurios automati\u0161kai gali b\u016Bti priskiriamos naudotojams.
+client-default-roles=Numatytosios kliento rol\u0117s
+client-default-roles.tooltip=Kliento rol\u0117s, kurios automati\u0161kai priskiriamos naujiems naudotojams.
+composite.available-roles.tooltip=Srities rol\u0117s, kurias galima susieti su \u0161ia sud\u0117tine role.
+composite.associated-roles.tooltip=Srities rol\u0117s, kurios susietos su \u0161ia sud\u0117tine role.
+composite.available-roles-client.tooltip=Kliento rol\u0117s, kurias galima susieti su \u0161ia sud\u0117tine role.
+composite.associated-roles-client.tooltip=Kliento rol\u0117s, kurios susietos su \u0161ia sud\u0117tine role.
+partial-import=Dalinis duomen\u0173 importavimas
+partial-import.tooltip=Dalinis duomen\u0173 importavimas leid\u017Eia \u012Fkelti prie\u0161 tai eksportuot\u0105 JSON rinkmen\u0105 su naudotojais, klientais ir kitais resursais.
+
+file=Rinkmena
+exported-json-file=Eksportuota JSON rinkmena
+import-from-realm=\u012Ekelti i\u0161 srities
+import-users=\u012Ekelti naudotojus
+import-groups=\u012Ekelti grupes
+import-clients=\u012Ekelti klientus
+import-identity-providers=\u012Ekelti tapatyb\u0117s teik\u0117jus
+import-realm-roles=\u012Ekelti srities roles
+import-client-roles=\u012Ekelti klient\u0173 roles
+if-resource-exists=Jei resursas egzistuoja
+fail=Nevykdyti
+skip=Praleisti
+overwrite=Perra\u0161yti
+if-resource-exists.tooltip=Nurodykite k\u0105 daryti kuomet bandoma \u012Fkelti jau egzistuojant\u012F resurs\u0105.
+
+action=Veiksmas
+role-selector=Roli\u0173 parinkimas
+realm-roles.tooltip=Srities rol\u0117s, kurias galima pasirinkti.
+
+select-a-role=Pasirinkti rol\u0119
+select-realm-role=Pasirinkti srities rol\u0119
+client-roles.tooltip=Kliento rol\u0117s, kurias galite pa\u017Eym\u0117ti.
+select-client-role=Pasirinkti kliento rol\u0119
+
+client-template=Kliento \u0161ablonas
+client-template.tooltip=Kliento \u0161ablonas, i\u0161 kurio paveldima konfig\u016Bracija
+client-saml-endpoint=Kliento SAML adresas
+add-client-template=Kliento \u0161ablono k\u016Brimas
+
+manage=Valdyti
+authentication=Autentifikavimas
+user-federation=Naudotoj\u0173 federavimas
+user-storage=Naudotoj\u0173 saugykla
+events=\u012Evykiai
+realm-settings=Srities nustatymai
+configure=Konfig\u016Bruoti
+select-realm=Pasirinkite srit\u012F
+add=Prid\u0117ti
+
+client-template.name.tooltip=Kliento \u0161ablono pavadinimas. Privalo b\u016Bti unikalus \u0161ioje srityje
+client-template.description.tooltip=Kliento \u0161ablono apra\u0161ymas
+client-template.protocol.tooltip=Kurio SSO protokolo konfig\u016Bracija teikia \u0161is \u0161ablonas
+
+add-user-federation-provider=Prid\u0117ti naudotoj\u0173 federacijos teik\u0117ja
+required-settings=Privalomi nustatymai
+provider-id=Teik\u0117jo ID
+console-display-name=Konsol\u0117je rodomas pavadinimas
+console-display-name.tooltip=Administravimo konsol\u0117je rodomas teik\u0117jo pavadinimas.
+priority=Prioritetas
+priority.tooltip=Skai\u010Dius nurodantis naudotojo paie\u0161kos \u0161iame federacijos teik\u0117juje prioritet\u0105. Pirmiausia imama su ma\u017Eesniu skai\u010Diumi.
+sync-settings=Sinchronizuoti nustatymus
+periodic-full-sync=Pilnas periodinis sinchronizavimas
+periodic-full-sync.tooltip=Ar turi b\u016Bti atliekamas periodinis pilnas teik\u0117jo naudotoj\u0173 sinchronizavimas \u012F Keycloak?
+full-sync-period=Pilno sinchronizavimo intervalas
+full-sync-period.tooltip=Laikas sekund\u0117mis, kas kur\u012F atliekamas pilnas naudotoj\u0173 sinchronizavimas \u012F Keycloak sistem\u0105
+periodic-changed-users-sync=Periodinis pakeitim\u0173 sinchronizavimas
+periodic-changed-users-sync.tooltip=Ar turi b\u016Bti atliekamas naujai u\u017Eregistruot\u0173 naudotoj\u0173 ar naudotoj\u0173 su redaguotais profilio duomenimis periodinis sinchronizavimas \u012F Keycloak?
+changed-users-sync-period=Periodinis sinchronizavimo intervalas
+changed-users-sync-period.tooltip=Laikas sekund\u0117mis, kas kur\u012F atliekamas naujai u\u017Eregistruot\u0173 naudotoj\u0173 ar naudotoj\u0173 su redaguotais profilio duomenimis sinchronizavimas \u012F Keycloak
+synchronize-changed-users=Sinchronizuoti naudotoj\u0173 pakeitimus
+synchronize-all-users=Sinchronizuoti visus naudotojus
+kerberos-realm=Kerberos sritis
+kerberos-realm.tooltip=Kerberos srities pavadinimas. Pavyzd\u017Eiui FOO.ORG
+server-principal=Pagrindinis serveris
+server-principal.tooltip=Pilnas HTTP paslaugai skirtas pagrindinio serverio su domenu pavadinimas. Pavyzd\u017Eiui HTTP/host.foo.org@FOO.ORG
+keytab=KeyTab
+keytab.tooltip=Kelias iki Kerberos KeyTab rinkmenos talpinan\u010Dios prisijungimo prie pagrindinio serverio duomenis. Pavyzd\u017Eiui /etc/krb5.keytab
+debug=Derinti
+debug.tooltip=Ar \u012Fgalinti Krb5LoginModule veikimo prane\u0161im\u0173 ra\u0161ym\u0105 \u012F standarin\u0119 i\u0161vest\u012F derinimo r\u0117\u017Eimu?
+allow-password-authentication=Leisti autentifikacij\u0105 naudojant slapta\u017Eod\u012F
+allow-password-authentication.tooltip=Ar suteikti galimyb\u0119 naudotojui prisijungti prie Kerberos naudojant naudotojo vard\u0105 ir slapta\u017Eod\u012F?
+edit-mode=Pakeitim\u0173 r\u0117\u017Eimas
+edit-mode.tooltip=READ_ONLY rei\u0161kia, kad naudotojui neleid\u017Eiama keisti slapta\u017Eod\u017Eio ir autentifikacija visuomet bus atliekama Kerberos. UNSYNCED rei\u0161kia, kad naudotojui leid\u017Eiama keisti slapta\u017Eod\u012F saugom\u0105 Keycloak duomen\u0173 baz\u0117je ir kuris bus naudojamas autentifikacijos metu vietoj Kerberos slapta\u017Eod\u017Eio.
+ldap.edit-mode.tooltip=READ_ONLY rei\u0161kia, kad LDAP saugykla bus naudojama vien tik skaitymo r\u0117\u017Eimu. WRITABLE rei\u0161kia, kad duomenys sinchronizuojami atgal \u012F LDAP pagal poreik\u012F. UNSYNCED rei\u0161kia, kad naudotoj\u0173 duomenys bus importuoti, ta\u010Diau niekuomet nesinchronizuojami atgal \u012F LDAP.
+update-profile-first-login=Pirmojo prisijungimo metu atnaujinti duomenis
+update-profile-first-login.tooltip=Pirmojo prisijungimo metu atnaujinti naudotojo profilio duomenis
+sync-registrations=Sinchronizuoti registracijas
+ldap.sync-registrations.tooltip=Ar naujai u\u017Esiregistrav\u0119 naudotojai tur\u0117t\u0173 b\u016Bti sinchonizuojami \u012F LDAP saugykl\u0105? Federavimo teik\u0117jas, \u012F kur\u012F sinchronizuojami nauji naudotojai, parenkamas pagal prioritet\u0105. Pirmiausia imami su ma\u017Eiausiu skai\u010Diumi.
+vendor=Gamintojas
+ldap.vendor.tooltip=LDAP gamintojas (teik\u0117jas)
+username-ldap-attribute=Prisijungimo vardo LDAP atributas
+ldap-attribute-name-for-username=LDAP atributo pavadinimas, kuriame saugomas naudotojo prisijungimo vardas
+username-ldap-attribute.tooltip=LDAP atributas, kuris turi b\u016Bti susietas su Keycloak naudotojo prisijungimo vardu. Su daugeliu LDAP serveri\u0173 gali b\u016Bti naudojamas 'uid' atributas. ActiveDirectory gali b\u016Bti 'sAMAccountName' arba 'cn'. Reikalaujama, kad nurodytas LDAP atributas b\u016Bt\u0173 u\u017Epildytas visiems naudotojams kurie importuojami i\u0161 LDAP \u012F Keycloak.
+rdn-ldap-attribute=RDN LDAP atributas
+ldap-attribute-name-for-user-rdn=LDAP atributo pavadinimas, kuriame saugomas naudotojo RDN
+rdn-ldap-attribute.tooltip=LDAP atributas, kuris naudojamas kaip RDN (Relative Distinguished Name) vietoj tipinio naudotojo DN (Distinguished Name). Da\u017Eniausiai reik\u0161m\u0117 sutampa su prisijungimo vardo LDAP atributu, ta\u010Diau pastarasis n\u0117ra privalomas. Pavyzd\u017Eiui ActiveDirectory da\u017Eniausiai kaip RDN atributas naudojamas 'cn' nors prisijungimo vardo atributas b\u016Bna 'sAMAccountName'.
+uuid-ldap-attribute=UUID LDAP atributas
+ldap-attribute-name-for-uuid=LDAP atributo pavadinimas, kuriame saugomas UUID
+uuid-ldap-attribute.tooltip=LDAP atributas, kuris naudojamas kaip unikalus LDAP objekt\u0173 identifikatorius (UUID). Daugelis LDAP serveri\u0173 naudoja 'entryUUID', ta\u010Diau pasitaiko ir i\u0161im\u010Di\u0173. Pavyzd\u017Eiui ActiveDirectory naudojamas 'objectGUID' atributas. Jei j\u016Bs\u0173 LDAP serveris nepalaiko UUID atribut\u0173, tuomet galite naudoti bet kur\u012F kit\u0105 atribut\u0105 kuris u\u017Etikrina LDAP naudotoj\u0173 unikalum\u0105. Pavyzd\u017Eiui 'uid' arba 'entryDN'.
+user-object-classes=Naudotoj\u0173 objekt\u0173 klas\u0117s
+ldap-user-object-classes.placeholder=LDAP naudotoj\u0173 objekt\u0173 klas\u0117s (skiriamos kableliu)
+ldap.user-object-classes.tooltip=LDAP atributo objectClass atskirtos kableliu reik\u0161m\u0117s skirtos LDAP naudotojo objektui. Pavyzd\u017Eiui: 'inetOrgPerson, organizationalPerson' . Naujai registruoti Keycloak naudotojai bus \u012Fra\u0161yti \u0161 LDAP su visomis nurodytomis objekt\u0173 klas\u0117mis. Egzistuojantys LDAP naudotoj\u0173 objektai randami tik tuomet, kai jie turi visas \u0161ias nurodytas objekto klases.
+
+ldap-connection-url=LDAP jungties URL
+ldap-users-dn=LDAP naudotoj\u0173 DN
+ldap-bind-dn=LDAP prisijungimo DN
+ldap-bind-credentials=LDAP prisijungimo slapta\u017Eodis
+ldap-filter=LDAP filtras
+
+connection-url=Jungties URL
+ldap.connection-url.tooltip=Jungties \u012F LDAP server\u012F URL
+test-connection=Tikrinti jungt\u012F
+users-dn=Naudotoj\u0173 DN
+ldap.users-dn.tooltip=\u0160aknin\u0117 LDAP med\u017Eio DN (Distinguished Name) kuriame saugomi naudotojai. Jei pavyzd\u017Eiui tipinio naudotojo DN yra 'uid=john,ou=users,dc=example,dc=com' tuomet \u0161io atributo reik\u0161m\u0117 tur\u0117t\u0173 b\u016Bti 'ou=users,dc=example,dc=com'
+authentication-type=Autentifikacijos tipas
+ldap.authentication-type.tooltip=LDAP autentifikacijos tipas. Galimi 'none' (anonimin\u0117 LDAP prieiga) arba 'simple' (Prisijungimo DN + slapta\u017Eodis autentifikacijai) autentifikacijos b\u016Bdai
+bind-dn=Prisijungimo DN
+ldap.bind-dn.tooltip=LDAP administratoriaus DN (Distinguished Name), kuris turi b\u016Bti naudojamas Keycloak prieiti prie LDAP serverio
+bind-credential=Prisijungimo slapta\u017Eodis
+ldap.bind-credential.tooltip=LDAP administratoriaus slapta\u017Eodis
+test-authentication=Tikrinti autentifikacij\u0105
+custom-user-ldap-filter=Papildomas naudotoj\u0173 LDAP filtras
+ldap.custom-user-ldap-filter.tooltip=Papildomas LDAP filtras, kuris turi b\u016Bti naudojamas surast\u0173 naudotoj\u0173 nufiltravimui. Palikite tu\u0161\u010Di\u0105 lauk\u0105 jei papildomas filtravimas nereikalingas. \u012Esitikinkite, kad filtras prasideda '(' ir baigiasi ')' simboliais
+search-scope=Paie\u0161kos apimtis
+ldap.search-scope.tooltip=Jei pasirinkta vieno lygio paie\u0161ka, tuomet naudotoj\u0173 ie\u0161koma vien tik nurodytame naudotoj\u0173 DN. Kai pasirinkta paie\u0161ka medyje, tuomet naudotoj\u0173 ie\u0161koma visose med\u017Eio \u0161akose. I\u0161samesn\u0117s informacijos ie\u0161kokite LDAP dokumentacij\u0105
+use-truststore-spi=Naudoti rakt\u0173 saugyklos SPI
+ldap.use-truststore-spi.tooltip=Nurodykite, kuomet LDAP jungtis naudos standalone.xml/domain.xml sukonfig\u016Bruot\u0105 patikim\u0173 liudijim\u0173/rakt\u0173 saugyklos SPI. 'Visada' rei\u0161kia, kad bus naudojama visada. 'Niekada' rei\u0161kia, kad sukonfig\u016Bruota liudijim\u0173 saugykla nebus naudojama. 'Tik LDAP' rei\u0161kia, kad saugykla bus naudojama tik su LDAP jungtimis. Pastaba: jei standalone.xml/domain.xml nesukonfig\u016Bruotas, tuomet bus naudojama standartin\u0117 Java cacerts arba 'javax.net.ssl.trustStore' parametre nurodyta liudijim\u0173 saugykla.
+connection-pooling=Jung\u010Di\u0173 buferizavimas
+ldap.connection-pooling.tooltip=Ar Keycloak tur\u0117t\u0173 naudoti jung\u010Di\u0173 telkin\u012F jungiantis prie LDAP serverio?
+ldap.pagination.tooltip=Ar LDAP serveris palaiko puslapiavim\u0105?
+kerberos-integration=Kerberos intergacija
+allow-kerberos-authentication=Leisti Kerberos autentifikacij\u0105
+ldap.allow-kerberos-authentication.tooltip=\u012Egalina HTTP naudotoj\u0173 autentifikacij\u0105 naudojant SPNEGO/Kerberos raktus. Duomenys apie prisijungus\u012F naudotoj\u0105 bus teikiama \u0161io LDAP serverio
+use-kerberos-for-password-authentication=Naudoti Kerberos autentifikacijai su slapta\u017Eod\u017Eiu
+ldap.use-kerberos-for-password-authentication.tooltip=Ar jungiantis su naudotojo vardu ir slapta\u017Eod\u017Eiu naudoti Kerberos server\u012F vietoj LDAP serverio Directory Service API
+batch-size=Paketo dydis
+ldap.batch-size.tooltip=Vienos tranzacijos metu \u012F Keycloak importuojam\u0173 LDAP naudotoj\u0173 skai\u010Dius.
+ldap.periodic-full-sync.tooltip=Ar \u012Fgalinti periodin\u0119 piln\u0105 LDAP naudotoj\u0173 sinchronizacij\u0105 \u012F Keycloak?
+ldap.periodic-changed-users-sync.tooltip=Ar \u012Fgalinti periodin\u0119 naujai registruot\u0173 arba su pakeistais duomenimis LDAP naudotoj\u0173 sinchronizacij\u0105 \u012F Keycloak?
+ldap.changed-users-sync-period.tooltip=Intervalas sekund\u0117mis, kas kur\u012F atliekamas periodinis naujai registruot\u0173 arba su pakeistais duomenimis LDAP naudotoj\u0173 sinchronizavimas \u012F Keycloak
+user-federation-mappers=Federuoto naudotojo atribut\u0173 atitikmenys
+create-user-federation-mapper=Sukurti federuoto naudotojo atributo atitikmen\u012F
+add-user-federation-mapper=Prid\u0117ti federuoto naudotojo atributo atitikmen\u012F
+provider-name=Teik\u0117jo pavadinimas
+no-user-federation-providers-configured=Nesukonfig\u016Bruotas nei vienas naudotoj\u0173 federacijos teik\u0117jas
+no-user-storage-providers-configured=Nesukonfig\u016Bruota nei viena naudotoj\u0173 saugykla
+add-identity-provider=Prid\u0117ti tapatyb\u0117s teik\u0117j\u0105
+add-identity-provider-link=Prid\u0117ti s\u0105saj\u0105 su tapatyb\u0117s teik\u0117ju
+identity-provider=Tapatyb\u0117s teik\u0117jas
+identity-provider-user-id=Tapatyb\u0117s teik\u0117jo naudotojo ID
+identity-provider-user-id.tooltip=Unikalus, tapatyb\u0117s teik\u0117jo saugomas, naudotojo ID
+identity-provider-username=Tapatyb\u0117s teik\u0117jo naudotojo vardas
+identity-provider-username.tooltip=Tapatyb\u0117s teik\u0117jo sistemoje saugomas naudotojo vardas
+pagination=Puslapiavimas
+
+browser-flow=Autentifikacijos seka
+browser-flow.tooltip=Pasirinkite autentifikacijos nar\u0161ykl\u0117je sek\u0105
+registration-flow=Registracijos seka
+registration-flow.tooltip=Pasirinkite registracijos nar\u0161ykl\u0117je sek\u0105.
+direct-grant-flow=Tiesiogini\u0173 teisi\u0173 seka
+direct-grant-flow.tooltip=Pasirinkite tiesiogini\u0173 teisi\u0173 sek\u0105 (direct grant authentication).
+reset-credentials=Prisijungimo duomen\u0173 atk\u016Brimo seka
+reset-credentials.tooltip=Pasirinkite prisijungimo duomen\u0173 priminimo nar\u0161ykl\u0117je sek\u0105
+client-authentication=Klient\u0173 autentifikacijos seka
+client-authentication.tooltip=Pasirinkite klient\u0173 autentifikacijos sek\u0105.
+new=Naujas
+copy=Kopijuoti
+add-execution=Prid\u0117ti i\u0161imt\u012F
+add-flow=Prid\u0117ti sek\u0105
+auth-type=Autentifikacijos tipas
+requirement=Privalomumas
+config=Konfig\u016Bruoti
+no-executions-available=N\u0117ra sukonfig\u016Bruot\u0173 i\u0161im\u010Di\u0173
+authentication-flows=Autentifikacijos sekos
+create-authenticator-config=Sukurti autentifikatoriaus konfig\u016Bracij\u0105
+authenticator.alias.tooltip=Konfig\u016Bracijos pavadinimas
+otp-type=OTP tipas
+time-based=Paremtas laiku
+counter-based=Paremtas skaitliuku
+otp-type.tooltip='totp' paremtas ribot\u0105 laik\u0105 galiojan\u010Diu vienkartiniu slapta\u017Eod\u017Eiu. 'hotp' - ribot\u0105 kart\u0173 galiojan\u010Diu vienkartiniu slapta\u017Eod\u017Eiu.
+otp-hash-algorithm=OTP mai\u0161os algoritmas
+otp-hash-algorithm.tooltip=Kuris mai\u0161os algoritmas turi b\u016Bti naudojamas OTP generavimui.
+number-of-digits=Skaitmen\u0173 skai\u010Dius
+otp.number-of-digits.tooltip=Kiek OTP tur\u0117t\u0173 tur\u0117ti skaitmen\u0173?
+look-ahead-window=Neatitikimo langas
+otp.look-ahead-window.tooltip=Koks intervalas yra leid\u017Eiamas tuo atveju, kai prieigos rakt\u0173 generatoriaus ir serverio laikai arba skaitliukai nesutampa.
+initial-counter=Pradin\u0117 skaitliuko reik\u0161m\u0117
+otp.initial-counter.tooltip=Kokia turi b\u016Bti pradin\u0117 skaitliuko reik\u0161m\u0117?
+otp-token-period=OTP rakto galiojimo intervalas
+otp-token-period.tooltip=Kiek sekund\u017Eiu galios OTP prieigos raktas? Numatyta reik\u0161m\u0117 30 sekund\u017Ei\u0173.
+table-of-password-policies=Slapta\u017Eod\u017Eio taisykli\u0173 lentel\u0117
+add-policy.placeholder=Prid\u0117ti taisykl\u0119...
+policy-type=Taisykl\u0117s tipas
+policy-value=Taisykl\u0117s reik\u0161m\u0117
+admin-events=Administravimo \u012Fvykiai
+admin-events.tooltip=Rodomi srities administravimo \u012Fvykiai susij\u0119 su administratoriaus paskyra, pvz. srities k\u016Brimas. Pasirinkite konfig\u016Bravimo skilt\u012F nor\u0117dami kad \u012Fvykiai b\u016Bt\u0173 saugomi.
+login-events=Prisijungimo \u012Fvykiai
+filter=Filtruoti
+update=Atnaujinti
+reset=I\u0161valyti
+resource-types=Resurso tipas
+operation-types=Veiksmas
+select-operations.placeholder=Pasirinkite veiksmus...
+resource-path=Resurso kelias
+resource-path.tooltip=Filtravimas pagal resurso keli\u0105. Palaikomas pakaitos simbolis '*' atitinkantis vien\u0105 kelio element\u0105 ir '**' daugiau nei vien\u0105 element\u0105. Pavyzd\u017Eiui 'realms/*/clients/asbc' visose sritise randa klient\u0105 su identifikatoriumi 'asbc'. Kitas pavyzdys 'realms/master/**' randa visus veiksmus 'master' srityje.
+date-(from)=Data (Nuo)
+date-(to)=Data (Iki)
+authentication-details=Autentifikacijos informacija
+ip-address=IP adresas
+time=Laikas
+operation-type=Veiksmo tipas
+auth=Autentifikacijos informacija
+representation=Reprezentacija
+register=Registracijos
+required-action=Privalomi veiksmai
+default-action=Numatytas veiksmas
+auth.default-action.tooltip=Jei \u012Fgalintas, tuomet visi nauji naudotojai prival\u0117s atlikti pa\u017Eym\u0117tus veiksmus.
+no-required-actions-configured=N\u0117ra nei vieno sukonfig\u016Bruoto privalomo veiksmo
+defaults-to-id=Nenurod\u017Eius bus naudojamas identifikatorius
+flows=Sekos
+bindings=S\u0105ry\u0161iai
+required-actions=Privalomi veiksmai
+password-policy=Slapta\u017Eod\u017Ei\u0173 taisykl\u0117s
+otp-policy=OTP taisykl\u0117s
+user-groups=Naudotoj\u0173 grup\u0117s
+default-groups=Numatytos grup\u0117s
+groups.default-groups.tooltip=Nurodykite grupes, \u012F kurias automati\u0161kai \u012Ftraukiami nauji naudotojai.
+cut=I\u0161kirpti
+paste=\u012Eklijuoti
+
+create-group=Sukurti grup\u0119
+create-authenticator-execution=Sukurti autentifikatoriaus veiksmo vykdym\u0105
+create-form-action-execution=Sukurti formos veiksmo vykdym\u0105
+create-top-level-form=Sukurti auk\u0161\u010Diausio lygio form\u0105
+flow.alias.tooltip=\u012Era\u0161ykite sekos rodom\u0105 pavadinim\u0105.
+top-level-flow-type=Auk\u0161\u010Diausio lygio sekos tipas
+flow.generic=generic
+flow.client=client
+top-level-flow-type.tooltip=Kokio tipo \u0161i auk\u0161\u010Diausio lygio sritis? 'client' tipas naudojamas klient\u0173 (program\u0173) autentifikacijai. 'generic' naudojamas visais kitais atvejais.
+create-execution-flow=Sukurti vykdymo sek\u0105
+flow-type=Sekos tipas
+flow.form.type=form
+flow-type.tooltip=Kokios r\u016B\u0161ies \u0161i forma?
+form-provider=Formos teik\u0117jas
+default-groups.tooltip=Naujai sukurti ar u\u017Eregistruoti naudotojai automati\u0161kai priskiriami \u0161ioms grup\u0117ms
+select-a-type.placeholder=pasirinkite tip\u0105
+available-groups=Galimos grup\u0117s
+available-groups.tooltip=Nurodykite grup\u0119, kuri bus numatytoji.
+value=Reik\u0161m\u0117
+table-of-group-members=Grup\u0117s nari\u0173 lentel\u0117
+last-name=Pavard\u0117
+first-name=Vardas
+email=El. pa\u0161tas
+toggle-navigation=Perjungti navigacij\u0105
+manage-account=Valdyti paskyr\u0105
+sign-out=Atsijungti
+server-info=Serverio informacija
+resource-not-found=Resuras <strong>nerastas</strong>...
+resource-not-found.instruction=Negalime rasti j\u016Bs\u0173 ie\u0161komo resurso. \u012Esitikinkite, kad \u012Fved\u0117te teising\u0105 URL.
+go-to-the-home-page=Eiti \u012F pradin\u012F puslap\u012F »
+page-not-found=Puslapis <strong>nerastas</strong>...
+page-not-found.instruction=Negalime rasti j\u016Bs\u0173 ie\u0161komo puslapio. \u012Esitikinkite, kad \u012Fved\u0117te teising\u0105 URL.
+events.tooltip=Rodomi srities i\u0161saugoti \u012Fvykiai. Rodomi \u012Fvykiai susij\u0119 su naudotoj\u0173 paskyromis, pavyzd\u017Eiui naudotojo prisijungimas. Nor\u0117dami keisti nustatymus pasirinkite 'Konfig\u016Bruoti'
+select-event-types.placeholder=Pasirinkite \u012Fvykiu tipus...
+events-config.tooltip=Rodoma naudotoj\u0173 ir administravimo \u012Fvyki\u0173 konfig\u016Bracija.
+select-an-action.placeholder=Pasirinkite veiksm\u0105...
+event-listeners.tooltip=Nurodykite srities \u012Fvyki\u0173 gav\u0117jus.
+login.save-events.tooltip=Jei \u012Fgalinta, tuomet su prisijungimu susij\u0119 veiksmai saugomi duomen\u0173 baz\u0117je ir tampa prieinami per administravimo bei naudotojo paskyros valdymo skydus.
+clear-events.tooltip=I\u0161trinti visus \u012Fvykius i\u0161 duomen\u0173 baz\u0117s.
+events.expiration.tooltip=Nustato \u012Fvyki\u0173 galiojimo laik\u0105. Nebegaliojantys \u012Fvykiai periodi\u0161kai i\u0161trinami i\u0161 duomen\u0173 baz\u0117s.
+admin-events-settings=Administravimo veiksm\u0173 nustatymai
+save-events=Saugoti \u012Fvykius
+admin.save-events.tooltip=Jei \u012Fgalinta, tuomet administravimo veiksmai saugomi duomen\u0173 baz\u0117je ir tampa prieinami per administravimo valdymo skyd\u0105.
+saved-types.tooltip=Nurodykite veiksm\u0173 tipus, kurie tur\u0117t\u0173 b\u016Bti i\u0161saugoti.
+include-representation=I\u0161saugoti reprezentacij\u0105
+include-representation.tooltip=I\u0161saugoti kur\u016Bmo ir redagavimo u\u017Eklaus\u0173 JSON reprezentacij\u0105.
+clear-admin-events.tooltip=I\u0161trina visus su administravimu susijusius veiksmus i\u0161 duomen\u0173 baz\u0117s.
+server-version=Serverio versija
+info=Informacija
+providers=Teik\u0117jai
+server-time=Serverio laikas
+server-uptime=Serverio veikimo laikas
+memory=Atmintis
+total-memory=Viso atminties
+free-memory=Laisva atmintis
+used-memory=Naudojama atmintis
+system=Sistema
+current-working-directory=Darbinis katalogas
+java-version=Java Version
+java-vendor=Java Vendor
+java-runtime=Java Runtime
+java-vm=Java VM
+java-vm-version=Java VM Version
+java-home=Java Home
+user-name=User Name
+user-timezone=User Timezone
+user-locale=User Locale
+system-encoding=System Encoding
+operating-system=Operating System
+os-architecture=OS Architecture
+spi=SPI
+granted-roles=Suteiktos rol\u0117s
+granted-protocol-mappers=Suteiktos protokolo atitikmen\u0173 s\u0105sajos
+additional-grants=Papildomai suteikta
+revoke=At\u0161aukti
+new-password=Naujas slapta\u017Eodis
+password-confirmation=Pakartotas slapta\u017Eodis
+reset-password=Pakeisti slapta\u017Eod\u012F
+credentials.temporary.tooltip=Jei \u012Fgalinta, tuomet naudotojas prival\u0117s pasikeisti slapta\u017Eod\u012F sekan\u010Dio prisijungimo metu
+remove-totp=\u0160alinti TOTP
+credentials.remove-totp.tooltip=\u0160alinti vienkartin\u012F naudotojo slapta\u017Eod\u017Ei\u0173 generatori\u0173.
+reset-actions=Atkurti veiksmus
+credentials.reset-actions.tooltip=Nurodykite naudotojui el. pa\u0161tu siun\u010Diamus privalomus atlikti veiksmus. 'Patvirtinti el. pa\u0161to adres\u0105' \u012F naudotojo el. pa\u0161to adres\u0105 siun\u010Dia patvirtinimo nuorod\u0105. 'Atnaujinti profilio informacij\u0105' reikalauja naudotojo per\u017Ei\u016Br\u0117ti ir atnaujinti profilio informacij\u0105. 'Atnaujinti slapta\u017Eod\u012F' reikalauja naudotojo pasikeisti slapta\u017Eod\u012F. 'Konfig\u016Bruoti TOTP' reikalauja atnaujinti mobilaus slapta\u017Eod\u017Ei\u0173 generatoriaus konfig\u016Bracij\u0105.
+reset-actions-email=Atk\u016Brimo veiksm\u0173 siuntimas
+send-email=Si\u0173sti el. pa\u0161to lai\u0161k\u0105
+credentials.reset-actions-email.tooltip=Naudotojui siun\u010Diamas el. pa\u0161to lai\u0161kas su nuorodomis leid\u017Eian\u010Diomis atlikti pasirinktus veiksmus. Naudotojas atidar\u0119s siun\u010Diam\u0105 nuorod\u0105 gal\u0117s atlikti atk\u016Brimo veiksmus. Veism\u0173 atlikimui naudotoj\u0173 nebus reikalaujama prisijungti. Pavyzd\u017Eiui parinkus slapta\u017Eod\u017Eio atk\u016Brimo veiksm\u0105, naudotojas gal\u0117s neprisijung\u0119s nurodyti nauj\u0105 slapta\u017Ed\u012F.
+add-user=Prid\u0117ti naudotoj\u0105
+created-at=Suk\u016Brimo data
+user-enabled=Naudotojas \u012Fgalintas
+user-enabled.tooltip=Ne\u012Fgalintam naudotojai neleid\u017Eiama prisijungti prie sistemos.
+user-temporarily-locked=Naudotojas laikinai u\u017Erakintas
+user-temporarily-locked.tooltip=Naudotojas laikintai u\u017Erakintas, nes per daug klydo prisijungiant prie sistemos.
+unlock-user=Atrakinti naudotoj\u0105
+federation-link=Federacijos s\u0105saja
+email-verified=El. pa\u0161tas patvirtintas
+email-verified.tooltip=Ar naudotojo el. pa\u0161to adresas yra patvirtintas?
+required-user-actions=Privalomi veiksmai naudotojui
+required-user-actions.tooltip=Nurodykite kuriuos veiksmus po prisijungimo naudotojas privalo atlikti. 'Patvirtinti el. pa\u0161to adres\u0105' \u012F naudotojo el. pa\u0161to adres\u0105 siun\u010Dia patvirtinimo nuorod\u0105. 'Atnaujinti profilio informacij\u0105' reikalauja naudotojo per\u017Ei\u016Br\u0117ti ir atnaujinti profilio informacij\u0105. 'Atnaujinti slapta\u017Eod\u012F' reikalauja naudotojo pasikeisti slapta\u017Eod\u012F. 'Konfig\u016Bruoti TOTP' reikalauja atnaujinti mobilaus slapta\u017Eod\u017Ei\u0173 generatoriaus konfig\u016Bracij\u0105.
+locale=Lokal\u0117
+select-one.placeholder=Pasirinkite...
+impersonate=\u012Ek\u016Bnyti
+impersonate-user=\u012Ek\u016Bnyti naudotoj\u0105
+impersonate-user.tooltip=Prisijungti kaip \u0161is naudotojas. Jei j\u016Bs\u0173 sritis sutampa su naudotojo sritimi, tuomet j\u016Bs\u0173 sesija bus baigta prie\u0161 prisijungiant \u0161iuo naudotoju.
+identity-provider-alias=Tapatyb\u0117s teik\u0117jo pseudonimas
+provider-user-id=Teik\u0117jo naudotojo ID
+provider-username=Teik\u0117jo naudotojo vardas
+no-identity-provider-links-available=N\u0117ra nei vienos tapatyb\u0117s teik\u0117jo s\u0105sajos
+group-membership=Naryst\u0117 grup\u0117se
+group-membership.tooltip=Visos grup\u0117s, kuri\u0173 narys yra \u0161is naudotojas. Pa\u017Eym\u0117kite grup\u0119 ir paspauskite 'Palikti' nor\u0117dami pa\u0161alinti naudotoj\u0105 i\u0161 grup\u0117s.
+leave=Palikti
+membership.available-groups.tooltip=Grup\u0117s, \u012F kurias galima \u012Ftraukti naudotoj\u0105. Pa\u017Eym\u0117kite grup\u0119 ir paspauskite \u012Ftraukti.
+table-of-realm-users=Srities naudotoj\u0173 s\u0105ra\u0161as
+view-all-users=Rodyti visus naudotojus
+unlock-users=Atrakinti naudotojus
+no-users-available=Naudotoj\u0173 n\u0117ra
+users.instruction=\u012Eveskite paie\u0161kos kriterij\u0173 arba paspauskite rodyti visus naudotojus
+consents=Sutikimai
+started=Prad\u0117ta
+logout-all-sessions=Atjungti visas sesijas
+logout=Seanso pabaiga
+new-name=Naujas pavadinimas
+ok=Gerai
+attributes=Atributai
+role-mappings=Roli\u0173 susiejimas
+members=Nariai
+details=Detaliau
+identity-provider-links=S\u0105sajos su tapatyb\u0117s teik\u0117jais
+register-required-action=Registruoti privalom\u0105 atlikti veiksm\u0105
+gender=Lytis
+address=Adresas
+phone=Telefonas
+profile-url=Profilio URL
+picture-url=Nuotraukos URL
+website=Internetin\u0117 svetain\u0117
+import-keys-and-cert=Importuoti raktus ir sertifikatus
+import-keys-and-cert.tooltip=\u012Ekelti kliento rakt\u0173 por\u0105 ir sertifikat\u0105.
+upload-keys=\u012Ekelti raktus
+download-keys-and-cert=Atsisi\u0173sti raktus ir sertifikat\u0105
+no-value-assigned.placeholder=N\u0117ra priskirtos reik\u0161m\u0117s
+remove=\u0160alinti
+no-group-members=Grup\u0117 neturi nari\u0173
+temporary=Laikinas
+join=Prijungti
+event-type=\u012Evykio tipas
+events-config=\u012Evyki\u0173 konfig\u016Bracija
+event-listeners=\u012Evyki\u0173 gav\u0117jai
+login-events-settings=Prisijungimo \u012Fvyki\u0173 nustatymai
+clear-events=I\u0161valyti \u012Fvykius
+saved-types=Saugomi tipai
+clear-admin-events=I\u0161valyti administravimo \u012Fvykius
+clear-changes=I\u0161valyti pasikeitimus
+error=Klaida
+
+# Authz
+# Authz Common
+authz-authorization=Autorizacija
+authz-owner=Savininkas
+authz-uri=URI
+authz-scopes=Taikymo sritys
+authz-resource=Resursas
+authz-resource-type=Resurso tipas
+authz-resources=Resursai
+authz-scope=Taikymo sritis
+authz-authz-scopes=Autorizacijos taikymo sritys
+authz-policies=Taisykl\u0117s
+authz-permissions=Leidimai
+authz-evaluate=I\u0161bandyti
+authz-icon-uri=Ikonos URI
+authz-icon-uri.tooltip=Ikonos paveiksliuko URI.
+authz-select-scope=Parinkite taikymo srit\u012F
+authz-select-resource=Parinkite resurs\u0105
+authz-associated-policies=Susietos taisykl\u0117s
+authz-any-resource=Bet kuris resursas
+authz-any-scope=Bet kuri taikymo sritis
+authz-any-role=Bet kuri rol\u0117
+authz-policy-evaluation=I\u0161bandyti taisykl\u0119
+authz-select-client=Parinkite klient\u0105
+authz-select-user=Parinkite naudotoj\u0105
+authz-entitlements=Teis\u0117s
+authz-no-resources=Resurs\u0173 n\u0117ra
+authz-result=Rezultatas
+authz-authorization-services-enabled=Autorizacija \u012Fgalinta
+authz-authorization-services-enabled.tooltip=\u012Egalinti detal\u0173 kliento autorizacijos palaikym\u0105
+authz-required=Privalimas
+
+# Authz Settings
+authz-import-config.tooltip=Importuoti \u0161io resurs\u0173 serverio autorizacijos nustatym\u0173 JSON rinkmen\u0105.
+
+authz-policy-enforcement-mode=Taisykli\u0173 vykdymo r\u0117\u017Eimas
+authz-policy-enforcement-mode.tooltip=Taisykli\u0173 vykdymo r\u0117\u017Eimas nusako kaip turi b\u016Bti tenkinamos autorizacijos u\u017Eklaus\u0173 taisykl\u0117s. 'Taikyti' rei\u0161kia, kad tuo atveju kai n\u0117ra sukonfig\u016Bruota nei viena su resursu susijusi taisykl\u0117, prieiga draud\u017Eiama. 'Liberalus' rei\u0161kia, kad tuo atveju kai n\u0117ra sukonfig\u016Bruota nei viena su resursu susijusi taisykl\u0117, prieiga leid\u017Eiama. 'I\u0161jungta' rei\u0161kia, kad neatliekamas taisykli\u0173 tikrinimas ir prieiga leid\u017Eiama prie vis\u0173 resurs\u0173.
+authz-policy-enforcement-mode-enforcing=Taikyti
+authz-policy-enforcement-mode-permissive=Liberalus
+authz-policy-enforcement-mode-disabled=I\u0161jungta
+
+authz-remote-resource-management=Nuotolinis resurs\u0173 valdymas
+authz-remote-resource-management.tooltip=Ar leid\u017Eiama nuotoliniu b\u016Bdu resurs\u0173 serveriui valdyti resursus? Jei ne\u012Fgalinta, tuomet resursai gali b\u016Bti valdomi tik per \u0161i\u0105 administravimo konsol\u0119.
+
+authz-export-settings=Eksportuoti nustatymus
+authz-export-settings.tooltip=Eksportuoti ir atsisi\u0173sti visus \u0161io resurs\u0173 serverio autorazacijos nustatymus.
+
+# Authz Resource List
+authz-no-resources-available=N\u0117ra galim\u0173 resurs\u0173.
+authz-no-scopes-assigned=N\u0117ra susiet\u0173 taikymo sri\u010Di\u0173.
+authz-no-type-defined=N\u0117ra nurodyt\u0173 tip\u0173.
+authz-no-permission-assigned=Nera susiet\u0173 leidim\u0173.
+authz-no-policy-assigned=N\u0117ra susiet\u0173 taisykli\u0173.
+authz-create-permission=Sukurti leidim\u0105
+
+# Authz Resource Detail
+authz-add-resource=Prid\u0117ti resurs\u0105
+authz-resource-name.tooltip=Unikalus resurso vardas. Vardas turi unikaliai identifikuoti resurs\u0105. Naudingas, kuomet ie\u0161koma specifini\u0173 resurs\u0173.
+authz-resource-owner.tooltip=\u0160io resurso savininkas.
+authz-resource-type.tooltip=\u0160io resurso tipas. Reik\u0161m\u0117 leid\u017Eia sugrupuoti skirtingus resursus turin\u010Dius t\u0105 pat\u012F tip\u0105.
+authz-resource-uri.tooltip=URI kuris taip pat gali b\u016Bti naudojamas vienareik\u0161mi\u0161kam resurso identifikavimui.
+authz-resource-scopes.tooltip=Su \u0161iuo resursu susietos taikymo sritys.
+
+# Authz Scope List
+authz-add-scope=Pri\u0117ti taikymo srit\u012F
+authz-no-scopes-available=N\u0117ra galim\u0173 taikymo sri\u010Di\u0173.
+
+# Authz Scope Detail
+authz-scope-name.tooltip=Unikalus taikymo srities pavadinimas. \u0160is pavadinimas gali vienareik\u0161mi\u0161kai identifikuoti taikymo srit\u012F. Naudingas kuomet ie\u0161koma \u0161ios tam tikros srities.
+
+# Authz Policy List
+authz-all-types=Visi tipai
+authz-create-policy=Sukurti taisykl\u0119
+authz-no-policies-available=N\u0117ra galim\u0173 taisykli\u0173.
+
+# Authz Policy Detail
+authz-policy-name.tooltip=\u0160ios taisykl\u0117s pavadinimas.
+authz-policy-description.tooltip=\u0160ios taisykl\u0117s apra\u0161ymas.
+authz-policy-logic=Logika
+authz-policy-logic-positive=Teigiama
+authz-policy-logic-negative=Neigiama
+authz-policy-logic.tooltip=Logika nurodo kaip turi b\u016Bti tenkinama taisykl\u0117. Jei nurodyta 'Teigiama', tuomet \u0161ios taisykl\u0117s vykdymo metu gautas rezultatas (leisti arba drausti) bus naudojamas sprendinio pri\u0117mimui. Jei nurodyta 'Neigiama', tuomet \u0161ios taisykl\u0117s vykdymo rezultatas bus paneigtas, t.y. leid\u017Eiama taps draud\u017Eiama ir atvirk\u0161\u010Diai.
+authz-policy-apply-policy=Pritaikyti taisykl\u0119
+authz-policy-apply-policy.tooltip=Nurodo visas taisykles, kurios turi b\u016Bti \u012Fvertintos \u0161ios taisykl\u0117s ar leidimo taikymo sri\u010Diai.
+authz-policy-decision-strategy=Sprendimo strategija
+authz-policy-decision-strategy.tooltip=Sprendimo strategija nurodo kaip priimamas galutinis sprendimas, kuomet yra vykdomos visos \u0161io leidimo taisykl\u0117s. 'Pozityvi' rei\u0161kia, kad galutiniam teigiamam sprendimui turi b\u016Bti tenkinama bent viena taisykl\u0117. 'Vienbals\u0117' rei\u0161kia, kad galutiniam teigiamam sprendimui visos taisykl\u0117s turi b\u016Bti teigiamos. 'Daugumos' rei\u0161kia, kad galutinis teigiamas sprendimas bus priimtas tuomet, kai teigiam\u0173 taisykli\u0173 bus daugiau nei neigiam\u0173. Jei teigiam\u0173 ir neigiam\u0173 taisykli\u0173 skai\u010Dius yra vienodas, tuomet galutinis rezultatas bus neigiamas.
+authz-policy-decision-strategy-affirmative=Pozityvi
+authz-policy-decision-strategy-unanimous=Vienbals\u0117
+authz-policy-decision-strategy-consensus=Daugumos
+authz-select-a-policy=Parinkite taisykl\u0119
+
+# Authz Role Policy Detail
+authz-add-role-policy=Prid\u0117ti rol\u0117s taisykl\u0119
+authz-no-roles-assigned=N\u0117ra susiet\u0173 roli\u0173.
+authz-policy-role-realm-roles.tooltip=Nurodo kurios *srities* rol\u0117(s) tenkina \u0161i\u0105 taisykl\u0119.
+authz-policy-role-clients.tooltip=Parinkite klien\u0105 nor\u0117dami rodyti tik \u0161io kliento roles.
+authz-policy-role-client-roles.tooltip=Nurodo *kliento* rol\u0117(\u012Fs) kurios tenkina \u0161i\u0105 taisykl\u0119.
+
+# Authz User Policy Detail
+authz-add-user-policy=Prid\u0117ti naudotojo taisykl\u0119
+authz-no-users-assigned=N\u0117ra susiet\u0173 naudotoj\u0173.
+authz-policy-user-users.tooltip=Nurodo kurie naudotojai tenkina \u0161i\u0105 taisykl\u0119.
+
+# Authz Time Policy Detail
+authz-add-time-policy=Prid\u0117ti laiko taisykl\u0119
+authz-policy-time-not-before.tooltip=Nurodykite laik\u0105 iki kurio \u0161i taisykl\u0117 NETENKINAMA. Teigiamas rezultatas duodamas tik tuo atveju, kuomet dabartin\u0117 data ir laikas yra v\u0117lesn\u0117 arba lygi \u0161iai reik\u0161mei.
+authz-policy-time-not-on-after=Ne v\u0117liau
+authz-policy-time-not-on-after.tooltip=Nurodykite laik\u0105 po kurio \u0161i taisykl\u0117 NETENKINAMA. Teigiamas rezultatas duodamas tik tuo atveju, kuomet dabartin\u0117 data ir laikas yra ankstesni arba lygi \u0161iai reik\u0161mei.
+authz-policy-time-day-month=M\u0117nesio diena
+authz-policy-time-day-month.tooltip=Nurodykite m\u0117nesio dien\u0105 iki kurios \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei diena patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai.
+authz-policy-time-month=M\u0117nesis
+authz-policy-time-month.tooltip=Nurodykite m\u0117nes\u012F iki kurio \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei m\u0117nesis patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai.
+authz-policy-time-year=Metai
+authz-policy-time-year.tooltip=Nurodykite metus iki kuri\u0173 \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei metai patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai.
+authz-policy-time-hour=Valanda
+authz-policy-time-hour.tooltip=Nurodykite valand\u0105 iki kurios \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei valanda patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai.
+authz-policy-time-minute=Minut\u0117
+authz-policy-time-minute.tooltip=Nurodykite minut\u0119 iki kurios \u0161i taisykl\u0117 TENKINAMA. U\u017Epild\u017Eius antr\u0105j\u012F laukel\u012F, taisykl\u0117 bus TENKINAMA jei minut\u0117 patenka \u012F nurodyt\u0105 interval\u0105. Reik\u0161m\u0117s nurodomos imtinai.
+
+# Authz Drools Policy Detail
+authz-add-drools-policy=Prid\u0117ti Drools taisykl\u0119
+authz-policy-drools-maven-artifact-resolve=I\u0161spr\u0119sti
+authz-policy-drools-maven-artifact=Maven taisykl\u0117s artefaktas
+authz-policy-drools-maven-artifact.tooltip=Nuoroda \u012F Maven GAV artifakt\u0105 kuriame apra\u0161ytos taisykl\u0117s. Kai tik nurodysite GAV, galite paspausti *I\u0161spr\u0119sti* tam kad \u012Fkelti *Modulis* ir *Sesija* laukus.
+authz-policy-drools-module=Modulis
+authz-policy-drools-module.tooltip=\u0160ioje taisykl\u0117je naudojamas modulis. Privalote nurodyti modul\u012F tam, kad gal\u0117tum\u0117te pasirinkti specifin\u0119 sesij\u0105 taisykli\u0173 \u012Fk\u0117limui.
+authz-policy-drools-session=Sesija
+authz-policy-drools-session.tooltip=\u0160ioje taisykl\u0117je naudojama sesija. Sesija teikia taisykles reikalingas \u0161ios taisykl\u0117s vykdymui.
+authz-policy-drools-update-period=Atnaujinimo intervalas
+authz-policy-drools-update-period.tooltip=Nurodykite laiko interval\u0105, kas kur\u012F turi b\u016Bti ie\u0161koma artefakto atnaujinim\u0173.
+
+# Authz JS Policy Detail
+authz-add-js-policy=Prid\u0117ti JavaScript taisykl\u0119
+authz-policy-js-code=Programinis kodas
+authz-policy-js-code.tooltip=JavaScript kodas kuriame apra\u0161ytos \u0161ios taisykl\u0117s s\u0105lygos.
+
+
+# Authz Aggregated Policy Detail
+authz-aggregated=Agreguota
+authz-add-aggregated-policy=Prid\u0117ti agreguot\u0105 taisykl\u0119
+
+# Authz Permission List
+authz-no-permissions-available=N\u0117ra galim\u0173 leidim\u0173.
+
+# Authz Permission Detail
+authz-permission-name.tooltip=\u0160io leidimo pavadinimas.
+authz-permission-description.tooltip=\u0160io leidimo apra\u0161ymas.
+
+# Authz Resource Permission Detail
+authz-add-resource-permission=Prid\u0117ti resurso leidim\u0105
+authz-permission-resource-apply-to-resource-type=Pritaikyti resurso tipui
+authz-permission-resource-apply-to-resource-type.tooltip=Nurodykite ar \u0161is leidimas turi b\u016Bti pritaikomas visiems \u0161io tipo resursams. Jei \u012Fgalinta, tuomet leidimo tikrinimas bus atliekamas visiems nurodyto tipo resursams.
+authz-permission-resource-resource.tooltip=Nurodykite, kad \u0161is leidimas turi b\u016Bti taikomas tik tam tikriems resursams.
+authz-permission-resource-type.tooltip=Nurodykite, kad \u0161i taisykl\u0117 turi b\u016Bti taikoma visiems \u0161io tipo resursams.
+
+# Authz Scope Permission Detail
+authz-add-scope-permission=Prid\u0117ti taikymo srities leidim\u0105
+authz-permission-scope-resource.tooltip=Pasirinkdami resur\u0105 apribosite taikymo sri\u010Di\u0173 s\u0105ra\u0161\u0105. Jei nepasirinkta, tuomet matysite visas galimas taikymo sritis.
+authz-permission-scope-scope.tooltip=Nurodo, kad \u0161is leidimas turi b\u016Bti pritaikytas vienai ar daugiau taikymo sri\u010Di\u0173.
+
+# Authz Evaluation
+authz-evaluation-identity-information=Tapatyb\u0117s informacija
+authz-evaluation-identity-information.tooltip=Nurodykite tapatyb\u0117s informacij\u0105, kuri bus naudojama taisykli\u0173 vertinime.
+authz-evaluation-client.tooltip=Nurodykite klient\u0105, kuris atlieka autorizacijos u\u017Eklausas. Nei nenurodyta, tuomet autorizacijos u\u017Eklausa bus vertinama naudojant dabartin\u012F klient\u0105.
+authz-evaluation-user.tooltip=Nurodykite naudotoj\u0105, kurio vardu atliekamas teisi\u0173 serveryje filtravimas.
+authz-evaluation-role.tooltip=Nurodykite pasirinkto naudotojo roles.
+authz-evaluation-new=Papildyti u\u017Eklaus\u0105
+authz-evaluation-re-evaluate=Vertinti pakartotinai
+authz-evaluation-previous=Prie\u0161 tai buv\u0119s bandymas
+
+authz-evaluation-contextual-info=Kontekstin\u0117 informacija
+authz-evaluation-contextual-info.tooltip=Nurodykite kontekstin\u0119 informacij\u0105, kuri bus naudojama taisykli\u0173 vertinime.
+authz-evaluation-contextual-attributes=Kontekstiniai atributai
+authz-evaluation-contextual-attributes.tooltip=Galite pateikti vykdymo aplinkos arba vykdymo konteksto atributus.
+authz-evaluation-permissions.tooltip=Nurodykite leidimus, kuriems bus taikomos taisykl\u0117s.
+authz-evaluation-evaluate=Vertinti
+authz-evaluation-any-resource-with-scopes=Bet kuris resursas su \u0161ia taikymo sritimi (sritimis)
+authz-evaluation-no-result=Vertinant autorizacijos u\u017Eklaus\u0105 rezultat\u0173 nerasta. Patikrinkite ar egzistuoja resursai ar taikymo sritys susietos su taisykl\u0117mis.
+authz-evaluation-no-policies-resource=\u0160iam resursui taisykl\u0117s nerastos.
+authz-evaluation-result.tooltip=Leidim\u0173 u\u017Eklausos bendras rezultatas.
+authz-evaluation-scopes.tooltip=Leid\u017Eiam\u0173 taikymo sri\u010Di\u0173 s\u0105ra\u0161as.
+authz-evaluation-policies.tooltip=Informacija apie vertinime dalyvavusias taisykles ir sprendimus.
+authz-evaluation-authorization-data=Atsakymas
+authz-evaluation-authorization-data.tooltip=Autorizavimo u\u017Eklausos apdorojimo rezultatas su autorizacijos duomenimis. Rezultatas parodo k\u0105 Keycloak gr\u0105\u017Eina klientui pra\u0161an\u010Diam leidimo. Per\u017Ei\u016Br\u0117kite 'authorization' teigin\u012F su leidimais, kurie buvo suteikti \u0161iai autorizacijos u\u017Eklausai.
+authz-show-authorization-data=Rodyti autorizacijos duomenis
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties
new file mode 100644
index 0000000..574eb91
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties
@@ -0,0 +1,1118 @@
+consoleTitle=Keycloak administrasjonskonsoll
+
+# Common messages
+enabled=Aktivert
+name=Navn
+displayName=Vis navn
+displayNameHtml=HTML vis navn
+save=Lagre
+cancel=Avbryt
+onText=P\u00C5
+offText=AV
+client=Klient
+clients=Klienter
+clear=T\u00F8m
+selectOne=Velg en...
+
+true=True
+false=False
+
+endpoints=Endepunkter
+
+# Realm settings
+realm-detail.enabled.tooltip=Brukere og klienter har kun tilgang til et sikkerhetsdomene hvis det er aktivert
+realm-detail.oidc-endpoints.tooltip=Viser konfigurasjonen av endepunkter for OpenID Connect
+registrationAllowed=Registrering av bruker
+registrationAllowed.tooltip=Aktiver/deaktiver registreringssiden. En lenke for registrering vil v\u00E6re synlig p\u00E5 innloggingssiden.
+registrationEmailAsUsername=E-postadresse som brukernavn
+registrationEmailAsUsername.tooltip=Dersom registreringssiden er aktivert, vil feltet for brukernavn v\u00E6re skjult fra registreringsskjema og e-postadresse vil bli brukt som brukernavn for nye brukere.
+editUsernameAllowed=Rediger brukernavn
+editUsernameAllowed.tooltip=Dersom aktivert, er feltet for brukernavn redigerbart, ellers kun lesbart.
+resetPasswordAllowed=Glemt passord
+resetPasswordAllowed.tooltip=Vis en lenke p\u00E5 innloggingssiden som brukere kan klikke p\u00E5 om de har glemt sine innloggingsdetaljer.
+rememberMe=Husk meg
+rememberMe.tooltip=Vis en avkryssingsboks p\u00E5 innloggingssiden som lar brukere forbli innlogget mellom omstart av nettleser og inntil sesjonen utl\u00F8per.
+verifyEmail=Bekreft e-postadresse
+verifyEmail.tooltip=Krev at bruker verifiserer sin e-postadresse f\u00F8rste gang de logger inn.
+sslRequired=Krev SSL
+sslRequired.option.all=Alle foresp\u00F8rsler
+sslRequired.option.external=Eksterne foresp\u00F8rsler
+sslRequired.option.none=Ingen
+sslRequired.tooltip=Kreves HTTPS? 'Ingen' betyr at HTTPS ikke kreves for noen klienters IP-adresse. 'Ekstern foresp\u00F8rsel' betyr at localhost og private IP-adresser kan f\u00E5 tilgang uten HTTPS. 'Alle foresp\u00F8rsler' betyr at HTTPS kreves for alle IP-adresser.
+publicKey=Offentlig n\u00F8kkel
+privateKey=Privat n\u00F8kkel
+gen-new-keys=Generer nye n\u00F8kler
+certificate=Sertifikat
+host=Vert
+smtp-host=SMTP Vert
+port=Port
+smtp-port=SMTP Port (standard er 25)
+from=Fra
+sender-email-addr=Senders e-postadresse
+enable-ssl=Aktiver SSL
+enable-start-tls=Aktiver StartTLS
+enable-auth=Aktiver autentisering
+username=Brukernavn
+login-username=Innloggingsbrukernavn
+password=Passord
+login-password=Innloggingspassord
+login-theme=Innloggingstema
+login-theme.tooltip=Velg tema for sidene: innlogging, TOTP, rettigheter, registrering, glemt passord.
+account-theme=Kontotema
+account-theme.tooltip=Velg tema for brukerkontoadministrasjonssider.
+admin-console-theme=Administrasjonskonsolltema
+select-theme-admin-console=Velg et tema for administrasjonskonsollen.
+email-theme=E-posttema
+select-theme-email=Velg tema for e-post sendt av server.
+i18n-enabled=Internasjonalisering aktivert
+supported-locales=St\u00F8ttede lokaliteter
+supported-locales.placeholder=Skriv inn en lokalitet og klikk enter
+default-locale=Standard lokalitet
+realm-cache-clear=Cache for sikkerhetsdomenet
+realm-cache-clear.tooltip=T\u00F8m sikkerhetsdomenecache (Dette vil fjerne oppf\u00F8ringer for alle sikkerhetsdomener)
+user-cache-clear=Brukercache
+user-cache-clear.tooltip=T\u00F8m brukercache (Dette vil fjerne oppf\u00F8ringer for alle sikkerhetsdomener)
+revoke-refresh-token=Fjern refresh token
+revoke-refresh-token.tooltip=Hvis aktivert kan refresh token kun bli brukt en gang. Ellers vil refresh tokens kunne bli brukt flere ganger.
+sso-session-idle=Inaktiv SSO sesjon
+seconds=Sekunder
+minutes=Minutter
+hours=Timer
+days=Dager
+sso-session-max=Maksimum SSO sesjon
+sso-session-idle.tooltip=Tiden en sesjon er tillatt \u00E5 v\u00E6re inaktiv f\u00F8r den utl\u00F8per. Tokens og nettlesersesjoner vil bli ugyldig n\u00E5r en sesjon utl\u00F8per.
+sso-session-max.tooltip=Maksimum tid f\u00F8r en sesjon utl\u00F8per. Tokens og nettlesersesjoner vil bli ugyldig n\u00E5r en sesjon utl\u00F8per.
+offline-session-idle=Inaktiv sesjon i frakoblet modus
+offline-session-idle.tooltip=Tiden en sesjon i frakoblet modus er tillatt \u00E5 v\u00E6re inaktiv f\u00F8r den utl\u00F8per. Du m\u00E5 bruke tokens for frakoblet modus for \u00E5 oppdatere sesjonen minst en gang i denne perioden, ellers vil sesjonen utl\u00F8pe.
+access-token-lifespan=Levetid for access token
+access-token-lifespan.tooltip= Maksimum tid f\u00F8r et access token utl\u00F8per. Det anbefales at denne verdien er kort i forhold til SSO timeout.
+access-token-lifespan-for-implicit-flow=Access token-levetid for implicit flow
+access-token-lifespan-for-implicit-flow.tooltip=Maksimum tid f\u00F8r et access token utstedt under OpenID Connect implicit flow utl\u00F8per. Det anbefales at denne er kortere enn SSO timeout. Det er ingen mulighet til \u00E5 oppdatere tokenet i l\u00F8pet av en implicit flow, derfor er det satt en separat timeout som er forskjellig fra 'Levetid for Access Token'.
+client-login-timeout=Timeout av klientinnlogging
+client-login-timeout.tooltip=Maksimum tid en klient har for \u00E5 fullf\u00F8re access token protokollen. Dette burde normalt v\u00E6re 1 minutt.
+login-timeout=Timeout for innlogging
+login-timeout.tooltip=Maksimum tid en bruker har til \u00E5 fullf\u00F8re en innlogging. Det anbefales at denne er relativt lang. 30 minutter eller mer.
+login-action-timeout=Timeout for innloggingshandling.
+login-action-timeout.tooltip=Maksimum tid en bruker har til \u00E5 fullf\u00F8re handlinger relatert til innlogging, som \u00E5 oppdatere passord eller konfigurere TOTP. Det anbefales at denne er relativt lang. 5 minutter eller mer.
+headers=Headere
+brute-force-detection=Deteksjon av Brute Force
+x-frame-options=Alternativer for X-Frame
+x-frame-options-tooltip=Standardverdi hindrer sider fra \u00E5 bli inkludert via non-origin iframes. (Klikk p\u00E5 etikett for mer informasjon)
+content-sec-policy=Sikkerhetspolicy for innhold
+content-sec-policy-tooltip=Standardverdi hindrer sider fra \u00E5 bli inkludert via non-origin iframes. (Klikk p\u00E5 etikett for mer informasjon)
+content-type-options=Alternativer for X-innholdstyper
+content-type-options-tooltip=Standardverdi som forhindrer Internet Explorer og Google Chrome fra \u00E5 MIME-sniffe et svar vekk fra den deklarerte innholdstypen (content-type) (Klikk p\u00E5 etikett for mer informasjon)
+max-login-failures=Maksimum antall innloggingsfeil
+max-login-failures.tooltip=Hvor mange feil f\u00F8r ventetid blir aktivert.
+wait-increment=\u00F8kning av ventetid
+wait-increment.tooltip=N\u00E5r terskelen for feil er n\u00E5dd, hvor lenge skal brukeren stenges ute?
+quick-login-check-millis=Hurtig innlogging - kontroll av millisekunder
+quick-login-check-millis.tooltip=Hvis en feil skjer for raskt samtidig, steng brukeren ute.
+min-quick-login-wait=Minimum ventetid for hurtig innlogging
+min-quick-login-wait.tooltip=Ventetid etter en hurtig innloggingsfeil.
+max-wait=Maksimum ventetid
+max-wait.tooltip=Maksimum tid en bruker vil v\u00E6re stengt ute.
+failure-reset-time=Tid for tilbakestilling av feil.
+failure-reset-time.tooltip=N\u00E5r vil teller for feil nullstilles?
+realm-tab-login=Innlogging
+realm-tab-keys=N\u00F8kler
+realm-tab-email=E-post
+realm-tab-themes=Tema
+realm-tab-cache=Cache
+realm-tab-tokens=Tokens
+realm-tab-client-initial-access=F\u00F8rste access token
+realm-tab-security-defenses=Sikkerhetsmekanismer
+realm-tab-general=Generelt
+add-realm=Legg til sikkerhetsdomene
+
+#Session settings
+realm-sessions=Sikkerhetsdomenesesjoner
+revocation=Oppheving
+logout-all=Logg ut alle
+active-sessions=Aktive sesjoner
+sessions=Sesjoner
+not-before=Ikke f\u00F8r
+not-before.tooltip=Opphev alle tokens utstedt f\u00F8r denne datoen.
+set-to-now=Sett til n\u00E5
+push=Send
+push.tooltip=For enhver klient som har en administratorURL, send dem beskjed om den nye opphevingspolicyen.
+
+#Protocol Mapper
+usermodel.prop.label=Egenskap
+usermodel.prop.tooltip=Navn p\u00E5 egenskapsmetoden i UserModel-grensesnittet. For eksempel, en verdi av 'e-post' vil referere til metoden UserModel.getEmail().
+usermodel.attr.label=Brukerattributt
+usermodel.attr.tooltip=Navn p\u00E5 lagret brukerattributt som er navnet p\u00E5 en attributt innenfor UserModel.attribute map.
+userSession.modelNote.label=Brukersesjonsmerknad
+userSession.modelNote.tooltip=Navn p\u00E5 lagret brukersesjonsmerknad innenfor UserSessionModel.note map.
+multivalued.label=Flere verdier
+multivalued.tooltip=Angir om en attributt st\u00F8tter flere verdier. Hvis true, vil listen med alle verdier for dette attributtet bli satt som claims. Hvis false, vil bare den f\u00F8rste verdien bli satt som claim.
+selectRole.label=Velg rolle
+selectRole.tooltip=Skriv inn rolle i tekstboksen til venstre, eller klikk p\u00E5 denne knappen for \u00E5 bla gjennom og velge rollen du \u00F8nsker.
+tokenClaimName.label=Navn p\u00E5 token claim
+tokenClaimName.tooltip=Navn p\u00E5 claim som skal legges inn i token. Denne kan v\u00E6re et fullt kvalifisert navn som 'address.street'. I dette tilfellet vil et nestet jsonobjekt bli laget.
+jsonType.label=JSON-type for claims
+jsonType.tooltip=JSON-type som burde bli brukt for \u00E5 fylle json claimet i tokenet. long, int, boolean og String er gyldige verdier.
+includeInIdToken.label=Legg til i ID token
+includeInIdToken.tooltip=Burde claim bli lagt til i ID token?
+includeInAccessToken.label=Legg til i access token
+includeInAccessToken.tooltip=Burde claim bli lagt til i access token?
+includeInUserInfo.label=Legg til i brukerinfo
+includeInUserInfo.tooltip=Burde claim bli lagt til i brukerinfo?
+usermodel.clientRoleMapping.clientId.label=Klient-ID
+usermodel.clientRoleMapping.clientId.tooltip=Klient-ID for \u00E5 mappe roller
+usermodel.clientRoleMapping.rolePrefix.label=Prefiks for klientrolle
+usermodel.clientRoleMapping.rolePrefix.tooltip=Prefiks for hver klientrolle (valgfri).
+usermodel.realmRoleMapping.rolePrefix.label=Prefiks for sikkerhetsdomenerolle
+usermodel.realmRoleMapping.rolePrefix.tooltip=Prefiks for hver sikkerhetsdomenerolle (valgfri).
+
+# client details
+clients.tooltip=Klienter er betrodde nettleserapplikasjoner og web-tjenester i et sikkerhetsdomene. Disse klientene kan be om en innlogging. Du kan ogs\u00E5 definere klientspesifikke roller.
+search.placeholder=S\u00F8k...
+create=Opprett
+import=Importer
+client-id=Klient-ID
+base-url=Base URL
+actions=Handlinger
+not-defined=Ikke definert
+edit=Rediger
+delete=Slett
+no-results=Ingen resultater
+no-clients-available=Ingen klienter er tilgjengelige
+add-client=Legg til klient
+select-file=Velg fil
+view-details=Se detaljer
+clear-import=T\u00F8m import
+client-id.tooltip=Angir ID referert i URI og tokens. For eksempel 'min-klient'. For SAML er dette ogs\u00E5 forventet utgiververdi fra authn-foresp\u00F8rsler
+client.name.tooltip=Angir klientnavnet som blir vist. For eksempel, 'Min klient'. St\u00F8tter n\u00F8kler for lokaliserte verdier. For eksempel\: ${my_client}
+client.enabled.tooltip=Deaktiverte klienter kan ikke initiere en innlogging eller motta access tokens.
+consent-required=Samtykke p\u00E5krevd
+consent-required.tooltip=Hvis aktivert m\u00E5 brukere gi samtykke for at klienten skal f\u00E5 tilgang.
+client-protocol=Klientprotokoll
+client-protocol.tooltip='OpenID connect' tillater klienter \u00E5 verifisere identiteten til sluttbrukeren basert p\u00E5 autentisering utf\u00F8rt av en autorisasjonsserver. 'SAML' aktiverer en web-basert autentisering og autoriseringsscenarier som inkluderer cross-domain single sign-on (SSO) og som bruker security tokens som inneholder assertions for \u00E5 dele informasjon videre.
+access-type=Tilgangstype
+access-type.tooltip='Confidential' klienter krever en secret for \u00E5 initiere innloggingsprotokoll. 'Public' klienter krever ikke en secret. 'Bearer-only' klienter er webtjenester som aldri initierer en innlogging.
+standard-flow-enabled=Standard flow aktivert
+standard-flow-enabled.tooltip=Dette aktiverer standard OpenID Connect redirect-basert autentisering med autorisasjonskode. I forhold til OpenID Connect eller OAuth2 spesifikasjoner aktiverer dette st\u00F8tte for 'Authorization Code Flow' for denne klienten.
+implicit-flow-enabled=Implicit flow aktivert
+implicit-flow-enabled.tooltip=Dette aktiverer st\u00F8tte for OpenID Connect redirect-basert autentisering uten autorisasjonskode. I forhold til OpenID Connect eller OAuth2 spesifikasjoner aktiverer dette st\u00F8tte for 'Implicit Flow' for denne klienten.
+direct-access-grants-enabled=Direct access grants aktivert
+direct-access-grants-enabled.tooltip=Dette gir st\u00F8tte for Direct Access Grants, som betyr at klienten har tilgang til brukerens brukernavn/passord og kan bytte dette direkte med Keycloak-serveren for access token. I f\u00F8lge OAuth2 spesifikasjonen, aktiverer dette st\u00F8tte for 'Resource Owner Password Credentials Grant' for denne klienten.
+service-accounts-enabled=Tjenestekonto aktivert
+service-accounts-enabled.tooltip=Lar deg autentisere denne klienten til Keycloak og hente access token dedikert til denne klienten. I f\u00F8lge OAuth2 spesifikasjonen, aktiverer dette st\u00F8tte for 'Client Credentials Grant' for denne klienten.
+include-authnstatement=Inkluder AuthnStatement
+include-authnstatement.tooltip=Skal et statement som spesifiserer metoden for tidsstempel inng\u00E5 i innloggingssvaret?
+sign-documents=Signer dokumenter
+sign-documents.tooltip=Skal SAML dokumenter bli signert av sikkerhetsdomenet?
+sign-assertions=Signer assertions
+sign-assertions.tooltip=Skal assertions i SAML dokumenter bli signert? Denne innstillingen er ikke n\u00F8dvendig hvis et dokument allerede har blitt signert.
+signature-algorithm=Signaturalgoritme
+signature-algorithm.tooltip=Signaturalgoritmen som brukes for \u00E5 signere et dokument.
+canonicalization-method=Kanoniseringsmetode
+canonicalization-method.tooltip=Kanoniseringsmetode for XML signaturer.
+encrypt-assertions=Krypter assertions
+encrypt-assertions.tooltip=Skal SAML assertions bli kryptert med klientens offentlige n\u00F8kkel ved \u00E5 bruke AES?
+client-signature-required=Klientens signatur er p\u00E5krevd
+client-signature-required.tooltip=Skal klienten signere sine SAML foresp\u00F8rsler og svar? Og skal de valideres?
+force-post-binding=Force POST binding
+force-post-binding.tooltip=Bruk alltid POST binding for svar.
+front-channel-logout=Front channel utlogging
+front-channel-logout.tooltip=Hvis satt til true, krever utlogging en redirect i nettleser til klient. Hvis satt til false, vil server utf\u00F8re en bakgrunnskall for utlogging.
+force-name-id-format=Force navn-ID format
+force-name-id-format.tooltip=Ignorer forespurt format p\u00E5 Navn-ID emnet og bruk den som er konfigurert i administrasjonskonsollen.
+name-id-format=Navn-ID format
+name-id-format.tooltip=Navn-ID formatet som skal brukes for emnet.
+root-url=Root URL
+root-url.tooltip=Root URL lagt til relative URLer
+valid-redirect-uris=Gyldig redirect URIer
+valid-redirect-uris.tooltip=Gyldig URI m\u00F8nster som en nettleser kan redirecte til etter en vellykket innlogging eller utlogging. Enkle jokertegn er tillatt, for eksempel 'http://example.com/*'. Relativ sti kan ogs\u00E5 spesifiseres, for eksempel /my/relative/path/*. Relative stier er relative til klientens root URL, eller hvis ingen er spesifisert brukes root URL for autorisasjonsserveren. For SAML m\u00E5 du sette et gyldig URI m\u00F8nster hvis du er avhengig av at URL for forbrukertjenesten er integrert med foresp\u00F8rselen for p\u00E5logging.
+base-url.tooltip=Standard URL som kan brukes n\u00E5r autorisasjonsserveren trenger \u00E5 redirecte eller lenke tilbake til klienten.
+admin-url=Admin URL
+admin-url.tooltip=URL til administratorgrensesnitt for klienten. Sett denne hvis klienten st\u00F8tter adapter REST API. Dette REST APIet tillater autorisasjonsserveren til \u00E5 sende tilbakekallingsregler og andre administrative oppgaver. Vanligvis er dette satt til klientens base URL.
+master-saml-processing-url=Master SAML prosesserings URL
+master-saml-processing-url.tooltip=Hvis konfigurert vil denne URLen bli brukt for hver binding til b\u00E5de SPs Assertion Consumer og Single Logout-tjenester. Denne kan bli individuelt overstyrt for hver binding og tjenester i konfigurasjonen for finkornet SAML endepunkt.
+idp-sso-url-ref=IDP initiert SSO URL navn
+idp-sso-url-ref.tooltip=Navn p\u00E5 URL-fragment som refererer til klienten n\u00E5r du vil gj\u00F8re en IDP initiert SSO. La denne st\u00E5 tom om du \u00F8nsker \u00E5 deaktivere IDP initiert SSO. URLen vil v\u00E6re: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
+idp-sso-relay-state=IDP initiert SSO relay state
+idp-sso-relay-state.tooltip=Relay state du \u00F8nsker \u00E5 sende med SAML foresp\u00F8rselen n\u00E5r du vil utf\u00F8re en IDP initiert SSO.
+web-origins=Web origins
+web-origins.tooltip=Tillat CORS origins. For \u00E5 tillate alle origins med gyldig Redirect URIer legg til '+'. For \u00E5 tillate alle origins legg til '*'.
+fine-saml-endpoint-conf=Finkornet SAML endepunktskonfigurasjon
+fine-saml-endpoint-conf.tooltip=Utvid denne delen til \u00E5 konfigurere n\u00F8yaktige URLer for Assertion Consumer og Single Logout-tjeneste.
+assertion-consumer-post-binding-url=Assertion consumer service POST binding URL
+assertion-consumer-post-binding-url.tooltip=SAML POST binding URL for klientens assertion customer service (innloggingsrespons). Du kan la denne st\u00E5 tom om du ikke \u00F8nsker en URL for denne bindingen.
+assertion-consumer-redirect-binding-url=Assertion Consumer Service redirect binding URL
+assertion-consumer-redirect-binding-url.tooltip=SAML redirect for klientens assertion consumer service (innloggingsrespons). Du kan la denne st\u00E5 tom om du ikke \u00F8nsker en URL for denne bindingen.
+logout-service-binding-post-url=logout-tjeneste POST binding URL
+logout-service-binding-post-url.tooltip=SAML POST binding URL for klientens single logout-tjeneste. Du kan la dette st\u00E5 tomt om du bruker en annen binding.
+logout-service-redir-binding-url=Logout-tjeneste redirect binding URL
+logout-service-redir-binding-url.tooltip=SAML redirect binding URL for klientens single logout-tjeneste. Du kan la dette st\u00E5 tomt om du bruker en annen binding.
+
+# client import
+import-client=Importer klient
+format-option=Formatalternativer
+select-format=Velg et format
+import-file=Importer fil
+
+# client tabs
+settings=Innstillinger
+credentials=Innloggingsdetaljer
+saml-keys=SAML n\u00F8kler
+roles=Roller
+mappers=Mappere
+mappers.tooltip=Protokollmappere som utf\u00F8rer endringer av tokens og dokumenter. De kan utf\u00F8re handlinger som \u00E5 mappe brukerdata til protokollclaims, eller bare endre foresp\u00F8rsler som blir sendt mellom klienten og autorisasjonsserver.
+scope=Scope
+scope.tooltip=Mapping av scope lar deg begrense hvilke rollemappinger som er inkludert i access token som klienten har bedt om.
+sessions.tooltip=Viser aktive sesjoner for denne klienten. Tillater deg \u00E5 se hvilke brukere som er aktive og n\u00E5r de logget inn.
+offline-access=Frakoblet tilgang
+offline-access.tooltip=Viser frakoblede sesjoner for denne klienten. Tillater deg \u00E5 se hvilke brukere som henter tokens for frakoblet modus og n\u00E5r de henter den. For \u00E5 tilbakekalle alle tokens for klienten, g\u00E5 til fanen Opphev og sett verdien for Ikke f\u00F8r til n\u00E5.
+clustering=Clustering
+installation=Installasjon
+installation.tooltip=Verkt\u00F8y for \u00E5 generere ulike konfigurasjonsformater for klientadapter som du kan laste ned eller klippe og lime inn for \u00E5 konfigurere klientene dine.
+service-account-roles=Tjenestekonto-roller
+service-account-roles.tooltip=Tillater deg \u00E5 autentisere rollemappinger for tjenestekontoen som er dedikert til denne klienten.
+
+# client credentials
+client-authenticator=Klientautentikator
+client-authenticator.tooltip=Klientautentikator som blir brukt for \u00E5 autentisere denne klienten mot keycloak-server
+certificate.tooltip=Klientsertifikat for \u00E5 validere JWT utstedt av klienten og signert av privatn\u00F8kkel til klient fra din keystore.
+publicKey.tooltip=Offentlig n\u00F8kkel for \u00E5 validere JWT utstedt av klient og signert av klientens privatn\u00F8kkel.
+no-client-certificate-configured=Ingen klientsertifikat er konfigurert
+gen-new-keys-and-cert=Generer nye n\u00F8kler og sertifikater
+import-certificate=Importer sertifikat
+gen-client-private-key=Generer privatn\u00F8kkel for klient
+generate-private-key=Generer privatn\u00F8kkel
+archive-format=Arkivformat
+archive-format.tooltip=Java keystore eller PKCS12 arkivformat.
+key-alias=N\u00F8kkelalias
+key-alias.tooltip=Arkiv-alias for din privatn\u00F8kkel og sertifikater.
+key-password=N\u00F8kkelpassord
+key-password.tooltip=Passord for \u00E5 f\u00E5 tilgang til privatn\u00F8kler i arkivet
+store-password=Lagre passord
+store-password.tooltip=Passord for \u00E5 f\u00E5 tilgang til arkivet
+generate-and-download=Generer og Last ned
+client-certificate-import=Import av klientsertifikat
+import-client-certificate=Importer klientsertifikat
+jwt-import.key-alias.tooltip=Arkiv-alias for sertifikatet ditt.
+secret=Secret
+regenerate-secret=Regenere secret
+registrationAccessToken=Access token for registrering
+registrationAccessToken.regenerate=Regenerer access token for registrering
+registrationAccessToken.tooltip=Access token for registrering gir klienter tilgang til registreringstjenesten for klienter.
+add-role=Legg til rolle
+role-name=Rollenavn
+composite=Sammensatt
+description=Beskrivelse
+no-client-roles-available=Ingen klientroller er tilgjengelig
+scope-param-required=Scope parameter p\u00E5krevd
+scope-param-required.tooltip=Denne rollen vil kun bli gitt hvis parameter for scope med rollenavn blir brukt under foresp\u00F8rsel av autorisasjon/token.
+composite-roles=Sammensatte roller
+composite-roles.tooltip=N\u00E5r denne rollen er tildelt/ikke tildelt til en bruker, vil hvilken som helst rolle assosiert med denne bli implisitt tildelt/ikke tildelt.
+realm-roles=Sikkerhetsdomeneroller
+available-roles=Tilgjengelig roller
+add-selected=Legg til valgte
+associated-roles=Assosierte roller
+composite.associated-realm-roles.tooltip=Sikkerhetsdomeneniv\u00E5-rolle knyttet til denne sammensatte rollen.
+composite.available-realm-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller knyttet til denne sammensatte rollen.
+remove-selected=Fjern valgte
+client-roles=Klientroller
+select-client-to-view-roles=Velg klient for \u00E5 se roller for klient
+available-roles.tooltip=Roller fra denne klienten som du kan knytte til denne sammensatte rollen.
+client.associated-roles.tooltip=Klientrolle assosiert med denne sammensatte rollen.
+add-builtin=Legg til Builtin
+category=Kategori
+type=Type
+no-mappers-available=Ingen mappere er tilgjengelig
+add-builtin-protocol-mappers=Legg til Builtin protokollmappere
+add-builtin-protocol-mapper=Legg til Builtin protokollmapper
+scope-mappings=Scopemapping
+full-scope-allowed=Tillatt med fullt scope
+full-scope-allowed.tooltip=Lar deg \u00E5 deaktivere alle restriksjoner.
+scope.available-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller som kan bli tildelt til scope.
+assigned-roles=Tildelte roller
+assigned-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller tildelt til scope.
+effective-roles=Effektive roller
+realm.effective-roles.tooltip=Tildelte sikkerhetsdomeneniv\u00E5-roller som kan ha blitt arvet fra en sammensatt rolle.
+select-client-roles.tooltip=Velg en klient for \u00E5 se roller for klient
+assign.available-roles.tooltip=Klientroller som er tilgjengelige til \u00E5 bli tildelt.
+client.assigned-roles.tooltip=Tildelte klientroller.
+client.effective-roles.tooltip=Tildelte klientroller som kan ha blitt arvet fra en sammensatt rolle.
+basic-configuration=Basiskonfigurasjon
+node-reregistration-timeout=Timeout for re-registrering av node
+node-reregistration-timeout.tooltip=Intervall for \u00E5 angi maksimum tid for registrerte klienters clusternoder for \u00E5 re-registreres. Hvis en clusternode ikke sender re-regisreringsforesp\u00F8rsel til Keycloak innen dette intervallet, vil den bli uregistrert fra Keycloak.
+registered-cluster-nodes=Registrerte clusternoder
+register-node-manually=Register node manuelt
+test-cluster-availability=Test cluster tilgjengelighet
+last-registration=Siste registrering
+node-host=Nodevert
+no-registered-cluster-nodes=Ingen registrerte clusternoder tilgjengelig
+cluster-nodes=Clusternoder
+add-node=Legg til node
+active-sessions.tooltip=Totalt antall aktive brukersesjoner for denne klienten.
+show-sessions=Vis sesjoner
+show-sessions.tooltip=Advarsel, dette er en potensielt kostbar operasjon avhengig av antall aktive sesjoner.
+user=Bruker
+from-ip=Fra IP
+session-start=Start av sesjon
+first-page=F\u00F8rste side
+previous-page=Forrige side
+next-page=Neste side
+client-revoke.not-before.tooltip=Opphev alle token utstedt f\u00F8r denne datoen for denne klienten.
+client-revoke.push.tooltip=Hvis administrator URL er konfigurert for denne klienten, dytt denne policyen p\u00E5 denne klienten.
+select-a-format=Velg et format
+download=Last ned
+offline-tokens=Offline tokens
+offline-tokens.tooltip=Totalt antall offline tokens for denne klienten.
+show-offline-tokens=Vis offline tokens
+show-offline-tokens.tooltip=Advarsel, dette er en potensielt kostbar operasjon avhengig av antall offline tokens.
+token-issued=Utgitt token
+last-access=Sist aksessert
+last-refresh=Siste refresh
+key-export=Eksporter n\u00F8kkel
+key-import=Importer n\u00F8kkel
+export-saml-key=Eksporter SAML n\u00F8kkel
+import-saml-key=Importer SAML n\u00F8kkel
+realm-certificate-alias=Alias for sikkerhetsdomenesertifikat
+realm-certificate-alias.tooltip=Sertifikat for sikkerhetsdomenet er ogs\u00E5 lagret i arkivet. Dette er aliaset.
+signing-key=Signeringsn\u00F8kkel
+saml-signing-key=SAML signeringsn\u00F8kkel
+private-key=Privatn\u00F8kkel
+generate-new-keys=Generer nye n\u00F8kler
+export=Eksporter
+encryption-key=Krypteringsn\u00F8kkel
+saml-encryption-key.tooltip=SAML krypteringsn\u00F8kkel.
+service-accounts=Tjenestekonto-konto
+service-account.available-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller som kan bli tildelt til tjeneste-konto.
+service-account.assigned-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller tildelt service-konto.
+service-account-is-not-enabled-for=Tjeneste-konto er ikke aktivert for {{client}}
+create-protocol-mappers=Opprett protokollmappere
+create-protocol-mapper=Opprett protokollmapper
+protocol=Protokoll
+protocol.tooltip=Protokoll...
+id=ID
+mapper.name.tooltip=Navn p\u00E5 mapper.
+mapper.consent-required.tooltip=Ved tildeling av midlertidig tilgang m\u00E5 brukeren samtykke til \u00E5 gi denne informasjonen til klienten?
+consent-text=Samtykketekst
+consent-text.tooltip=Tekst som blir vist p\u00E5 side for samtykke.
+mapper-type=Mappertype
+mapper-type.tooltip=Type mapper
+# realm identity providers
+identity-providers=Identitetsleverand\u00F8r
+table-of-identity-providers=Liste over identitetsleverand\u00F8rer
+add-provider.placeholder=Legg til leverand\u00F8r...
+provider=Leverand\u00F8r
+gui-order=Rekkef\u00F8lge for brukergrensesnitt
+first-broker-login-flow=Flyt for f\u00F8rste innlogging
+post-broker-login-flow=Post-p\u00E5loggingsflyt
+redirect-uri=Redirect URI
+redirect-uri.tooltip=Redirect URI som skal brukes n\u00E5r du konfigurerer identitetsleverand\u00F8ren.
+alias=Alias
+identity-provider.alias.tooltip=Aliaset identifiserer en identitetsleverand\u00F8r og kan brukes for \u00E5 bygge en redirect uri.
+identity-provider.enabled.tooltip=Aktiver/deaktiver denne identitetsleverand\u00F8ren.
+authenticate-by-default=Autentiser som standard
+identity-provider.authenticate-by-default.tooltip=Indikerer om en leverand\u00F8r burde fors\u00F8kes \u00E5 brukes om standard for autentisering selv om innloggingssiden enda ikke har blitt vist.
+store-tokens=Lagre Tokens
+identity-provider.store-tokens.tooltip=Aktiver/deaktiver hvis tokens m\u00E5 bli lagret etter at brukere har blitt autentisert.
+stored-tokens-readable=Lagrede lesbare tokens
+identity-provider.stored-tokens-readable.tooltip=Aktiver/deaktiver hvis nye brukere kan lese lagrede tokens. Dette tildeles broker.read-token rollen.
+update-profile-on-first-login=Oppdater profil ved f\u00F8rste innlogging
+on=P\u00E5
+on-missing-info=Ved manglende informasjon
+off=Av
+update-profile-on-first-login.tooltip=Definer vilk\u00E5rene en bruker m\u00E5 oppfylle for \u00E5 oppdatere sin profil ved f\u00F8rste innlogging.
+trust-email=Stol p\u00E5 e-post
+trust-email.tooltip=Hvis aktivert vil ikke e-post levert av denne leverand\u00F8ren bli verifisert selv om verifisering er aktivert for sikkerhetsdomenet.
+gui-order.tooltip=Antall som angir rekkef\u00F8lgen av leverand\u00F8rer i brukergrensesnittet (For eksempel p\u00E5 innloggingssiden).
+first-broker-login-flow.tooltip=Alias for autentiseringsflyt, som trigges etter f\u00F8rste innlogging med denne identitetsleverand\u00F8ren. Begrepet 'F\u00F8rste innlogging' betyr at det enn\u00E5 ikke eksisterer en Keycloak-konto koblet til den autentiserte kontoen til identitetsleverand\u00F8ren.
+post-broker-login-flow.tooltip=Alias for autentiseringsflyt, som trigges etter hver innlogging med denne identitetsleverand\u00F8ren. Nyttig om man \u00F8nsker tilleggsverifikasjon av hver bruker autentisert med denne identitetsleverand\u00F8ren (for eksempel med engangskode/engangspassord). La denne st\u00E5 tom om du ikke \u00F8nsker at tilleggautentisering skal bli trigget etter en innlogging med denne identitetsleverand\u00F8ren. Merk ogs\u00E5 at implementasjonen av denne autentikatoren m\u00E5 anta at bruker allerede er satt i ClientSession ettersom identitetsleverand\u00F8ren allerede setter det.
+openid-connect-config=OpenID Connect konfigurasjon
+openid-connect-config.tooltip=OIDC SP og ekstern IDP-konfigurasjon.
+authorization-url=Autorisasjons URL
+authorization-url.tooltip=Autorisasjons URLen.
+token-url=Token URL
+token-url.tooltip=Token URLen.
+logout-url=Utloggings URL
+identity-provider.logout-url.tooltip=Endepunkt for avsluttende sesjon som brukes for \u00E5 logge ut bruker fra ekstern IDP.
+backchannel-logout=Backchannel utlogging
+backchannel-logout.tooltip=St\u00F8tter ekstern IDP backchannel utlogging?
+user-info-url=Brukerinfo URL
+user-info-url.tooltip=Brukerinfo URLen. Denne er valgfri.
+identity-provider.client-id.tooltip=Klienten eller klientidentifikator registrert hos identitetsleverand\u00F8ren.
+client-secret=Klient secret
+show-secret=Vis secret
+hide-secret=Skjul secret
+client-secret.tooltip=Klienten eller klient secret registrert hos identitetsleverand\u00F8ren.
+issuer=Utgiver
+issuer.tooltip=Identifikator for utgiver av foresp\u00F8rselen. Hvis dette ikke er oppgitt vil ingen validering utf\u00F8res.
+default-scopes=Standard Scopes
+identity-provider.default-scopes.tooltip=Scopes som sendes n\u00E5r du ber om autorisasjon. Dette kan v\u00E6re en liste med scopes separert med mellomrom. Standard er satt til 'openid'.
+prompt=Prompt
+unspecified.option=uspesifisert
+none.option=Ingen
+consent.option=samtykke
+login.option=Innlogging
+select-account.option=velg_konto (select_account)
+prompt.tooltip=Spesifiserer om autorisasjonsserver skal be sluttbruker om re-autentisering og samtykke.
+validate-signatures=Valider signaturer
+identity-provider.validate-signatures.tooltip=Aktiver/deaktiver signaturvalidering av eksterne IDP signaturer.
+validating-public-key=Valider offentlig n\u00F8kkel
+identity-provider.validating-public-key.tooltip=PEM format for offentlig n\u00F8kkel som m\u00E5 brukes for \u00E5 kontrollere eksterne IDP signaturer.
+import-external-idp-config=Importer ekstern IDP konfigurasjon
+import-external-idp-config.tooltip=Lar deg laste inn ekstern IDP metadata fra en konfigurasjonsfil eller ved \u00E5 laste det ned fra en URL.
+import-from-url=Importer fra URL
+identity-provider.import-from-url.tooltip=Importer metadata fra et eksternt IDP discovery descriptor.
+import-from-file=Importer fra fil
+identity-provider.import-from-file.tooltip=Importer metadata fra en nedlastet IDP discovery descriptor.
+saml-config=SAML konfigurasjon
+identity-provider.saml-config.tooltip=SAML SP og ekstern IDP konfigurasjon.
+single-signon-service-url=Single sign-on service URL
+saml.single-signon-service-url.tooltip=URL som m\u00E5 brukes for \u00E5 sende autentiseringsforesp\u00F8rsler (SAML AuthnRequest).
+single-logout-service-url=Single utloggingstjeneste URL
+saml.single-logout-service-url.tooltip=URL som m\u00E5 brukes for \u00E5 sende utloggingsforesp\u00F8rsler.
+nameid-policy-format=Policy for nameid-format
+nameid-policy-format.tooltip=Angir URI-referanse som tilsvarer et format for identifikator-navn. Standard er satt til urn:oasis:names:tc:SAML:2.0:nameid-format:persistent.
+http-post-binding-response=HTTP-POST binding svar
+http-post-binding-response.tooltip=Indikerer om man skal svare p\u00E5 foresp\u00F8rsler som bruker HTTP-POST binding eller ikke. Hvis satt til false, vil HTTP-REDIRECT binding bli brukt.
+http-post-binding-for-authn-request=HTTP-POST binding for AuthnRequest
+http-post-binding-for-authn-request.tooltip=Indikerer om AuthnRequests m\u00E5 bli sendt ved \u00E5 bruke en HTTP-POST binding. Hvis satt til false, vil HTTP-REDIRECT binding bli brukt.
+want-authn-requests-signed=Vil ha AuthnRequests signert
+want-authn-requests-signed.tooltip=Indikerer om identitetsleverand\u00F8r forventer en signert AuthnRequest.
+force-authentication=Force autentisering
+identity-provider.force-authentication.tooltip=Indikerer om identitetsleverand\u00F8r m\u00E5 autentisere presentat\u00F8ren direkte i stedet for \u00E5 stole p\u00E5 en tidligere sikkerhetskontekst.
+validate-signature=Valider signatur
+saml.validate-signature.tooltip=Aktiver/deaktiver signaturvalidering av SAML svar.
+validating-x509-certificate=Validerer X509 sertifikat
+validating-x509-certificate.tooltip=Sertifikatet i PEM format som m\u00E5 brukes for \u00E5 se etter signaturer.
+saml.import-from-url.tooltip=Importer metadata fra et eksternt IDP SAML entity descriptor.
+social.client-id.tooltip=Identifikator for klient registrert hos identitetsleverand\u00F8r.
+social.client-secret.tooltip=Klient secret registrert hos identitetsleverand\u00F8r.
+social.default-scopes.tooltip=Scopes som sendes n\u00E5r tillatelse for autorisasjon blir etterspurt. Se dokumentasjon for mulige verdier, separator og standardverdi.
+key=N\u00F8kkel
+stackoverflow.key.tooltip=N\u00F8kkelen er hentet fra klientregistrering p\u00E5 Stack Overflow.
+
+# User federation
+sync-ldap-roles-to-keycloak=Synkroniser LDAP-roller til Keycloak
+sync-keycloak-roles-to-ldap=Synkroniser Keycloak-roller til LDAP
+sync-ldap-groups-to-keycloak=Synkroniser LDAP-grupper til Keycloak
+sync-keycloak-groups-to-ldap=Synkroniser Keycloak-grupper til LDAP
+
+realms=Sikkerhetsdomener
+realm=Sikkerhetsdomene
+
+identity-provider-mappers=Identitetsleverand\u00F8rmappere
+create-identity-provider-mapper=Opprett identitetsleverand\u00F8rmappere
+add-identity-provider-mapper=Legg til identitetsleverand\u00F8rmappere
+client.description.tooltip=Angir beskrivelse av klienten. For eksempel\: 'Min klient for timelister'. St\u00F8tter n\u00F8kler for lokaliserte verdier. For eksempel\: ${my_client_description}
+
+expires=Utl\u00F8per
+expiration=Holdbarhet
+expiration.tooltip=Angir hvor lenge et token skal v\u00E6re gyldig
+count=Teller
+count.tooltip=Angir hvor mange klienter som kan bli opprettet ved \u00E5 bruke token.
+remainingCount=Resterende antall
+created=Opprettet
+back=Tilbake
+initial-access-tokens=F\u00F8rste access tokens
+initial-access-tokens.tooltip=F\u00F8rste access tokens for dynamisk registrering av klienter. Foresp\u00F8rsler med disse tokens kan bli sent fra alle verter.
+add-initial-access-tokens=Legg til f\u00F8rste access token
+initial-access-token=F\u00F8rste access token
+initial-access.copyPaste.tooltip=Kopier/lim inn f\u00F8rste access token f\u00F8r du navigerer vekk fra denne siden ettersom det ikke er mulig \u00E5 hente den senere.
+continue=Fortsett
+initial-access-token.confirm.title=Kopier f\u00F8rste access token
+initial-access-token.confirm.text=Vennligst kopier og lim inn f\u00F8rste access token f\u00F8r du bekrefter ettersom den ikke kan hentes senere
+no-initial-access-available=F\u00F8rste access token er ikke tilgjengelig
+
+trusted-hosts-legend=Betrodde verter for klientregistrering
+trusted-hosts-legend.tooltip=Verter, som er betrodd for klientregistrering. klientregistreringsforesp\u00F8rsler fra disse vertene kan bli sent selv uten f\u00F8rste access token. Antall klientregistreringer fra denne spesifikke verten kan bli begrenset til et spesifisert antall.
+no-client-trusted-hosts-available=Ingen betrodde verter er tilgjengelig
+add-client-reg-trusted-host=Legg til betrodd vert
+hostname=Vertsnavn
+client-reg-hostname.tooltip=Vertsnavn eller IP-adresse. Klientregistreringsforesp\u00F8rsler fra denne verten/adressen vil bli betrodd og tillat til \u00E5 registrere en ny klient.
+client-reg-count.tooltip=Tillat antall klientregistreringsforesp\u00F8rsler fra en spesifikk vert. Du m\u00E5 restarte denne n\u00E5r grensen er n\u00E5dd.
+client-reg-remainingCount.tooltip=Gjenv\u00E6rende antall klientregistreringsforesp\u00F8rsler fra denne verten. Du m\u00E5 restarte denne n\u00E5r grensen er n\u00E5dd.
+reset-remaining-count=Tilbakestill gjenst\u00E5ende antall
+
+client-templates=Klientmaler
+client-templates.tooltip=Klientmaler tillater deg \u00E5 definere felles konfigurasjon som er delt av flere klienter.
+
+groups=Grupper
+
+group.add-selected.tooltip=Sikkerhetsdomene-roller som kan bli tildelt gruppen.
+group.assigned-roles.tooltip=Sikkerhetsdomene-roller mappet til gruppen.
+group.effective-roles.tooltip=Alle sikkerhetsdomene-rollemappinger. Noen roller kan ha blitt arvet fra en sammensatt rolle.
+group.available-roles.tooltip=Roller som kan tildeles fra denne klienten.
+group.assigned-roles-client.tooltip=Rollemapping for denne klienten.
+group.effective-roles-client.tooltip=Rollemapping for denne klienten. Noen roller kan ha blitt arvet fra en mappet sammensatt rolle.
+
+default-roles=Standardroller
+no-realm-roles-available=Ingen sikkerhetsdomener er tilgjengelig
+
+users=Brukere
+user.add-selected.tooltip=Sikkerhetsdomene-roller som kan bli tildelt bruker.
+user.assigned-roles.tooltip=Sikkerhetsdomene-roller mappet til bruker
+user.effective-roles.tooltip=Alle sikkerhetsdomene-rollemappinger. Noen roller kan ha blitt arvet fra en sammensatt rolle.
+user.available-roles.tooltip=Roller som kan tildeles fra denne klienten.
+user.assigned-roles-client.tooltip=Rollemapping for denne klienten.
+user.effective-roles-client.tooltip=Rollemapping for denne klienten. Noen roller kan ha blitt arvet fra en mappet sammensatt rolle.
+default.available-roles.tooltip=Sikkerhetsdomene-roller som kan tildeles.
+realm-default-roles=Standardroller for sikkerhetsdomene
+realm-default-roles.tooltip=Sikkerhetsdomene-roller tildelt nye brukere.
+default.available-roles-client.tooltip=Roller fra denne klienten som blir tildelt som standard.
+client-default-roles=Standard klientroller
+client-default-roles.tooltip=Roller fra denne klienten blir tildelt som standardrolle.
+composite.available-roles.tooltip=Sikkerhetsdomene-roller knyttet til denne sammensatte rollen.
+composite.associated-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller knyttet til denne sammensatte rollen.
+composite.available-roles-client.tooltip=Roller fra denne klienten kan du assosiere med denne sammensatte rollen.
+composite.associated-roles-client.tooltip=Klientroller knyttet til denne sammensatte rollen.
+partial-import=Delvis import
+partial-import.tooltip=Delvis import lar deg importere brukere, klienter og andre ressurser fra en tidligere eksportert json-fil.
+
+file=File
+exported-json-file=Eksporter json-fil
+import-from-realm=Importer fra sikkerhetsdomene
+import-users=Importer brukere
+import-groups=Importer grupper
+import-clients=Importer klienter
+import-identity-providers=Importer identitetsleverand\u00F8rer
+import-realm-roles=Importer roller for sikkerhetsdomene
+import-client-roles=Importer klientroller
+if-resource-exists=Hvis en ressurs eksisterer
+fail=Mislykkes
+skip=Hopp over
+overwrite=Skriv over
+if-resource-exists.tooltip=Spesifiser hva som skal gj\u00F8res om du fors\u00F8ker \u00E5 importere en ressurs som allerede eksisterer.
+
+action=Handling
+role-selector=Rollevelger
+realm-roles.tooltip=Rolle for sikkerhetsdomene som kan velges.
+
+select-a-role=Velg en rolle
+select-realm-role=Velg en rolle for sikkerhetsdomenet
+client-roles.tooltip=Klientroller som kan velges.
+select-client-role=Velg klientrolle
+
+client-template=Klientmal
+client-template.tooltip=Klientmal som denne klienten arver konfigurasjonen fra
+client-saml-endpoint=Endepunkt for klient-SAML
+add-client-template=Legg til klientmal
+
+manage=H\u00E5ndter
+authentication=Autentisering
+user-storage=Brukerlagring
+user-federation=Brukerfederering
+events=Hendelser
+realm-settings=Innstillinger for sikkerhetsdomene
+configure=Konfigurer
+select-realm=Velg sikkerhetsdomene
+add=Legg til
+
+client-template.name.tooltip=Navn p\u00E5 klientmal. M\u00E5 v\u00E6re unik i sikkerhetsdomenet.
+client-template.description.tooltip=Beskrivelse av klientmal
+client-template.protocol.tooltip=Hvilken SSO protokoll-konfigurasjon som blir levert av denne klientmalen
+
+add-user-federation-provider=Legg til leverand\u00F8r for brukerfederering
+add-user-storage-provider=Legg til brukerlagringsleverand\u00F8r
+required-settings=P\u00E5krevde innstillinger
+provider-id=Leverand\u00F8r-ID
+console-display-name=Konsoll vis navn
+console-display-name.tooltip=Viser navn p\u00E5 leverand\u00F8r n\u00E5r den er lenket i administratorkonsollen.
+priority=Prioritet
+priority.tooltip=Prioritet av leverand\u00F8r n\u00E5r et oppslag av bruker utf\u00F8res. Lavest f\u00F8rst.
+sync-settings=Innstillinger for synkronisering
+periodic-full-sync=Fullstendig periodisk synkronisering
+periodic-full-sync.tooltip=Skal fullstendig periodisk synkronisering av leverand\u00F8r-brukere til Keycloak v\u00E6re aktivert eller deaktivert
+full-sync-period=Fullstendig synkroniseringsperiode
+full-sync-period.tooltip=Periode for fullstendig synkronisering i sekunder
+periodic-changed-users-sync=Periodisk synkronisering av endrede brukere
+periodic-changed-users-sync.tooltip=Skal periodisk synkronisering av endrede eller nylig opprettede leverand\u00F8r-brukere til Keycloak v\u00E6re aktivert eller deaktivert
+changed-users-sync-period=Synkroniseringsperiode for endrede brukere
+changed-users-sync-period.tooltip=Periode for synkronisering av endrede eller nylig opprettede leverand\u00F8r-brukere i sekunder.
+synchronize-changed-users=Synkroniser endrede brukere
+synchronize-all-users=Synkroniser alle brukere
+kerberos-realm=Sikkerhetsdomene for Kerberos
+kerberos-realm.tooltip=Navn p\u00E5 kerberos-sikkerhetsdomene. For eksempel FOO.ORG
+server-principal=Server principal
+server-principal.tooltip=Fullstendig navn p\u00E5 server principal for HTTP-service som inkluderer server og domenenavn. For eksempel HTTP/host.foo.org@FOO.ORG
+keytab=KeyTab
+keytab.tooltip=Plassering av Kerberos-keytab fil som inneholder legitimasjonsdetaljer for server principal. For eksempel /etc/krb5.keytab
+debug=Feils\u00F8king
+debug.tooltip=Aktiver/deaktiver logging av feils\u00F8king til standard output for Krb5LoginModule.
+allow-password-authentication=Tillat autentisering med passord
+allow-password-authentication.tooltip=Aktiver/deaktivert muligheten for autentisering med brukernavn/passord mot databasen til Kerberos
+edit-mode=Redigeringsmodus
+edit-mode.tooltip=READ_ONLY betyr at passordoppdateringer ikke er tillatt, og at bruker alltid autentiseres med et Kerberos-passord. UNSYNCED betyr at bruker kan endre sitt passord i databasen til Keycloak og at denne vil bli brukt i stedet for Kerberos-passordet.
+ldap.edit-mode.tooltip=READ_ONLY er et skrivebeskyttet LDAP-lager. WRITABLE betyr at data vil bli synkronisert tilbake til LDAP p\u00E5 foresp\u00F8rsel. UNSYNCED betyr at brukerdata vil bli importert, men vil ikke bli synkronisert tilbake til LDAP.
+update-profile-first-login=Oppdater profil ved f\u00F8rste innlogging
+update-profile-first-login.tooltip=Oppdater profil ved f\u00F8rste innlogging
+sync-registrations=Synkroniser registreringer
+ldap.sync-registrations.tooltip=Skal nylig opprettede brukere bli opprettet innenfor et LDAP-lager? Prioritet p\u00E5virker hvilken leverand\u00F8r som er valgt for \u00E5 synkronisere den nye brukeren.
+vendor=Leverand\u00F8r
+ldap.vendor.tooltip=LDAP leverand\u00F8r (provider)
+username-ldap-attribute=Brukernavn LDAP-attributt
+ldap-attribute-name-for-username=LDAP-attributtnavn for brukernavn
+username-ldap-attribute.tooltip=Navn p\u00E5 LDAP-attributt, som er kartlagt som et Keycloak-brukernavn. For mange LDAP-serverleverand\u00F8rer kan dette v\u00E6re 'uid'. For Active directory kan dette v\u00E6re 'sAMAccountName' eller 'cn'. Attributtet burde v\u00E6re fylt for alle LDAP-brukeroppf\u00F8ringer om du vil importere fra LDAP til Keycloak.
+rdn-ldap-attribute=RDN LDAP-attributt
+ldap-attribute-name-for-user-rdn=LDAP-attributtnavn for RDN-bruker
+rdn-ldap-attribute.tooltip=Navn p\u00E5 LDAP-attributt, som brukes som RDN (topp-attributt) for en typisk DN-bruker. Vanligvis er dette det samme som LDAP-attributtet for brukernavn, men det er ikke p\u00E5krevd. For eksempel for Active directory er det vanlig \u00E5 bruke cn' som RDN-attributt n\u00E5r attributtet for brukernavn kan v\u00E6re 'sAMAccountName'.
+uuid-ldap-attribute=UUID LDAP-attributt
+ldap-attribute-name-for-uuid=LDAP-attributtnavn for UUID.
+uuid-ldap-attribute.tooltip=Navn p\u00E5 LDAP-attributtet, som brukes som en unik objekt-identifikator (UUID) for objekter i LDAP. For mange LDAP-serverleverand\u00F8rer er det 'entryUUID', men noen er annerledes. For eksempel for Active directory b\u00F8r det v\u00E6re 'objectGUID'. Hvis din LDAP-server ikke st\u00F8tter UUID, kan du bruke hvilket som helst annet attributt, som er ment \u00E5 v\u00E6re unikt blant LDAP-brukere i treet. For eksempel 'uid' eller 'entryDN'.
+user-object-classes=Brukerobjektklasser
+ldap-user-object-classes.placeholder=Objektklasser for LDAP-bruker (separert med komma)
+
+ldap-connection-url=LDAP tilkoblings URL
+ldap-users-dn=LDAP brukere DN
+ldap-bind-dn=LDAP bind DN
+ldap-bind-credentials=LDAP bind innloggingsdetaljer
+ldap-filter=LDAP filter
+ldap.user-object-classes.tooltip=Alle verdier av LDAP objectClass-attributtet for brukere i LDAP er separert med komma. For eksempel, 'inetOrgPerson, organizationalPerson'. Nylig opprettede Keycloak-brukere vil bli skrevet til LDAP med alle disse objektklassene og eksisterende LDAP-brukeroppf\u00F8ringer vil bli funnet om de inneholder de samme objektklassene.
+
+connection-url=Tilkoblings URL
+ldap.connection-url.tooltip=Tilkoblings URL din til LDAP-server
+test-connection=Testkobling
+users-dn=DN-brukere
+ldap.users-dn.tooltip=Fullstendig DN av LDAP-tre hvor dine brukere befinner seg. Denne spesifikke DN er forelder til LDAP-brukere. Den kan for eksempel v\u00E6re 'ou=users,dc=example,dc=com' hvis din typiske bruker vil ha en DN som 'uid=john,ou=users,dc=example,dc=com'
+authentication-type=Autentiseringstype
+ldap.authentication-type.tooltip=LDAP Autentiseringstype. For \u00F8yeblikket er kun mekanismene 'ingen' (anonym LDAP autentisering) eller 'enkel' (bind innloggingsdetaljer) tilgjengelig.
+bind-dn=Bind DN
+ldap.bind-dn.tooltip=DN av LDAP-administrator, som kan brukes av Keycloak for \u00E5 aksessere LDAP-server
+bind-credential=Bind innloggingsdetaljer
+ldap.bind-credential.tooltip=Passord for LDAP administrator
+test-authentication=Testautentisering
+custom-user-ldap-filter=Egendefinert filter for LDAP-bruker
+ldap.custom-user-ldap-filter.tooltip=TilleggsLDAP-filter for \u00E5 filtrere s\u00F8kte brukere. La filteret v\u00E6re tomt om du ikke trenger et ekstra filter. Pass p\u00E5 at den starter med '(' og slutter med ')'
+search-scope=Scope for s\u00F8k
+ldap.search-scope.tooltip=For et niv\u00E5 s\u00F8ker vi etter brukere kun i DNser spesifisert av bruker-DNser. For subtre s\u00F8ker vi i hele subtreet. Se LDAP dokumentasjon for mer informasjon.
+use-truststore-spi=Bruk Truststore SPI
+ldap.use-truststore-spi.tooltip=Spesifiserer om LDAP-koblingen vil bruke truststore SPI med truststore konfigurert i keycloak-server.json. 'Alltid' betyr at den alltid vil brukes. 'Aldri' betyr at den ikke brukes. 'Kun for ldaps' betyr at den vil brukes hvis din koblings URL bruker ldaps. Merk at selv om keycloak-server.json ikke er konfigurert, vil default Java cacerts eller sertifikat spesifisert i 'javax.net.ssl.trustStore' bli brukt.
+connection-pooling=Connection Pooling
+ldap.connection-pooling.tooltip=Burde Keycloak bruke connection pooling for \u00E5 aksessere LDAP-serveren?
+ldap.pagination.tooltip=St\u00F8tter LDAP-serveren paginering?
+kerberos-integration=Kerberos Integrasjon
+allow-kerberos-authentication=Tillat autentisering med Kerberos
+ldap.allow-kerberos-authentication.tooltip=Aktiver/deaktiver HTTP autentisering av brukere med SPNEGO/Kerberos tokens. Informasjonen om autentiserte brukere vil bli klargjort fra denne LDAP-serveren.
+use-kerberos-for-password-authentication=Bruk Kerberos for autentisering av passord
+ldap.use-kerberos-for-password-authentication.tooltip=Bruk Kerberos-innloggingsmodul for \u00E5 autentisere brukernavn/passord mot Kerberos-server i stedet for autentisering mot LDAP-server med Directory Service API
+batch-size=Batch st\u00F8rrelse
+ldap.batch-size.tooltip=Antall LDAP-brukere som vil bli importert fra LDAP til Keycloak innen en enkelt transaksjon.
+ldap.periodic-full-sync.tooltip=Om fullstendig periodisk synkronisering av LDAP-brukere til Keycloak vil v\u00E6re aktivert eller deaktivert.
+ldap.periodic-changed-users-sync.tooltip=Om periodisk synkronisering av endret eller nylig opprettede LDAP-brukere til Keycloak vil v\u00E6re aktivert eller deaktivert.
+ldap.changed-users-sync-period.tooltip=Tidsperiode for synkronisering av endrede eller nylig opprettede LDAP-brukere i sekunder.
+user-federation-mappers=Mappere for brukerfederering
+create-user-federation-mapper=Opprett mapper for brukerfederering
+add-user-federation-mapper=Legg til mapper for brukerfederering
+provider-name=Leverand\u00F8rnavn
+no-user-federation-providers-configured=Ingen leverand\u00F8r for brukerfederering er konfigurert
+no-user-storage-providers-configured=Ingen leverand\u00F8r for brukerlagring er konfigurert
+add-identity-provider=Legg til identitetsleverand\u00F8r
+add-identity-provider-link=Legg til lenke til identitetsleverand\u00F8r
+identity-provider=Identitetsleverand\u00F8r
+identity-provider-user-id=Bruker-ID for identitetsleverand\u00F8r
+identity-provider-user-id.tooltip=Unik ID for brukeren p\u00E5 identitetsleverand\u00F8rens side
+identity-provider-username=Brukernavn til identitetsleverand\u00F8r
+identity-provider-username.tooltip=Brukernavn p\u00E5 identitetsleverand\u00F8rens side
+pagination=Paginering
+
+browser-flow=Nettleserflyt
+browser-flow.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke for nettleser-autentisering.
+registration-flow=Registreringsflyt
+registration-flow.tooltip=Velg flyten du \u00F8nsker for registrering.
+direct-grant-flow=Direct Grant Flyt
+direct-grant-flow.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke for direct grant autentisering.
+reset-credentials=Tilbakestill innloggingsdetaljer
+reset-credentials.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke n\u00E5r brukeren har glemt sine p\u00E5loggingsdetaljer.
+client-authentication=Autentisering av klient
+client-authentication.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke for autentisering av klienter.
+new=Ny
+copy=Kopi
+add-execution=Legg til eksekvering
+add-flow=Legg til flyt
+auth-type=Type auth
+requirement=Krav
+config=Konfig
+no-executions-available=Ingen tilgjengelig eksekvering
+authentication-flows=Autentiseringsflyt
+create-authenticator-config=Opprett konfig for autentikator
+authenticator.alias.tooltip=Navn p\u00E5 konfigurasjonen
+otp-type=Type engangskode
+time-based=Tidsbasert
+counter-based=Tellerbasert
+otp-type.tooltip=Totp er et tidsbasert engangspassord. 'hotp' er et teller basert engangspassord hvor serveren f\u00F8lger med p\u00E5 en teller som den kan hashe mot.
+otp-hash-algorithm=OTP hash-algoritme
+otp-hash-algorithm.tooltip=Hva slags hashing algoritme skal brukes for \u00E5 generere OTP.
+number-of-digits=Antall siffer
+otp.number-of-digits.tooltip=Hvor mange sifre skal OTP ha?
+look-ahead-window=Look Ahead Window
+otp.look-ahead-window.tooltip=Hvor langt frem b\u00F8r serveren se i tilfelle token generator og server er ute av tidssynkronisering eller tellersynkronisering?
+initial-counter=Initiell teller
+otp.initial-counter.tooltip=Hva b\u00F8r den initielle tellerverdien v\u00E6re?
+otp-token-period=Engangskode token
+otp-token-period.tooltip=Hvor mange sekunder burde et engangskode token v\u00E6re gyldig? Standard er satt til 30 sekunder.
+table-of-password-policies=Liste over policy for passord
+add-policy.placeholder=Legg til policy...
+policy-type=Type policy
+policy-value=Verdi for policy
+admin-events=administratorhendelser
+admin-events.tooltip=Viser lagrede administratorhendelser for sikkerhetsdomenet. Hendelser relaterer til administratorkontoen, for eksempel opprettelse av et sikkerhetsdomene. For \u00E5 aktivere persistente hendelser g\u00E5 til konfig.
+login-events=innloggingshendelser
+filter=Filtrer
+update=Oppdater
+reset=Tilbakestill
+operation-types=Operasjonstyper
+resource-types=Ressurstyper
+select-operations.placeholder=Velg operasjoner...
+select-resource-types.placeholder=Velg ressursyper...
+resource-path=Filsti for ressurs
+resource-path.tooltip=Sorter etter filsti for ressurs. St\u00F8tter jokertegn '*' for \u00E5 sjekke om den er lik en del av stien, og '**' for \u00E5 sjekke flere deler. For eksempel 'realms/*/clients/asbc' vil v\u00E6re lik klient-ID asbc i alle sikkerhetsdomener, mens 'realms/master/**' er lik alt i mastersikkerhetsdomenet.
+date-(from)=Dato (Fra)
+date-(to)=Dato (Til)
+authentication-details=Autentiseringsdetaljer
+ip-address=IP-adresse
+time=Tid
+operation-type=Operasjonstype
+resource-type=Ressurstype
+auth=Auth
+representation=Representasjon
+register=Registrer
+required-action=P\u00E5krevd handling
+default-action=Standard handling
+auth.default-action.tooltip=Hvis aktivert, vil enhver ny bruker bli tilordnet denne p\u00E5krevde handlingen.
+no-required-actions-configured=Ingen p\u00E5krevde handlinger er konfigurert
+defaults-to-id=Standardverdi er id
+flows=Flyt
+bindings=Bindinger
+required-actions=P\u00E5krevde handlinger
+password-policy=Passordpolicy
+otp-policy=Policy for engangskode
+user-groups=Brukergruppe
+default-groups=Standardgrupper
+groups.default-groups.tooltip=Sett med grupper som nye brukere automatisk vil bli medlem av.
+cut=Klipp
+paste=Lim inn
+
+create-group=Opprett gruppe
+create-authenticator-execution=Opprett autentiseringsutf\u00F8relse
+create-form-action-execution=Opprett skjema for handlingsutf\u00F8relse
+create-top-level-form=Opprett skjema for toppniv\u00E5
+flow.alias.tooltip=Spesifiserer visningsnavn for flyten.
+top-level-flow-type=Flytstype for toppniv\u00E5
+flow.generic=generisk
+flow.client=klient
+top-level-flow-type.tooltip=Hvilken type toppniv\u00E5 flyt er det? Type 'klient' brukes for autentisering av klienter (applikasjoner) n\u00E5r generisk brukes for brukere og alt annet
+create-execution-flow=Opprett eksekveringsflyt
+flow-type=Type av flyt
+flow.form.type=skjema
+flow-type.tooltip=Hva slags skjema det er
+form-provider=Skjemaleverand\u00F8r
+default-groups.tooltip=Nyopprettede eller registrerte brukere vil automatisk bli lagt til disse gruppene
+select-a-type.placeholder=velg en type
+available-groups=Tilgjengelige grupper
+available-groups.tooltip=Velg en gruppe du \u00F8nsker \u00E5 legge til som standard.
+value=Verdi
+table-of-group-members=Liste over gruppemedlemmer
+last-name=Etternavn
+first-name=Fornavn
+email=E-postadresse
+toggle-navigation=Toggle navigasjon
+manage-account=Administrer konto
+sign-out=Logg ut
+server-info=Serverinformasjon
+resource-not-found=Ressurs <strong>ikke funnet</strong>...
+resource-not-found.instruction=Vi kunne ikke finne ressursen du leter etter. Vennligst kontroller at nettadressen du oppga er riktig.
+go-to-the-home-page=G\u00E5 til hjemmeside »
+page-not-found=Side <strong>ikke funnet</strong>...
+page-not-found.instruction=Vi kunne ikke finne siden du ser etter. Vennligst kontroller at nettadressen du skrev inn er riktig.
+events.tooltip=Viser lagrede hendelser for sikkerhetsdomenet. Hendelser er relatert til brukerkontoer, for eksempel innlogging av bruker. For \u00E5 aktivere persistente hendelser g\u00E5 til konfig.
+select-event-types.placeholder=Velg hendelsestyper...
+events-config.tooltip=Viser konfigurasjonsalternativer for \u00E5 muliggj\u00F8re persistente bruker- og administratorhendelser.
+select-an-action.placeholder=Velg en handling...
+event-listeners.tooltip=Konfigurer hvilke lyttere som skal motta eventer fra sikkerhetsdomenet.
+login.save-events.tooltip=Hvis aktivert vil innloggingshendelser bli lagret i databasen, noe som gj\u00F8r hendelsene tilgjengelige for administrator og kontoadministrasjonskonsoll.
+clear-events.tooltip=Sletter alle hendelser fra databasen.
+events.expiration.tooltip=Setter utl\u00F8pstid for hendelser. Utl\u00F8pte hendelser vil med jevne mellomrom bli slettet fra databasen.
+admin-events-settings=Innstillinger for administratorhendelser
+save-events=Lagre hendelser
+admin.save-events.tooltip=Hvis aktivert vil administratorhendelser bli lagret i databasen, som vil gj\u00F8re hendelsene tilgjengelige i administrasjonskonsollen.
+saved-types.tooltip=Konfigurer hvilke eventtyper som lagres.
+include-representation=Inkluder representasjon
+include-representation.tooltip=Inkluder JSON-representasjon for \u00E5 skape og oppdatere foresp\u00F8rsler.
+clear-admin-events.tooltip=Sletter alle administratorhendelser i databasen.
+server-version=Serverversjon
+info=Info
+providers=Leverand\u00F8rer
+server-time=Servertid
+server-uptime=Oppetid for server
+memory=Minne
+total-memory=Totalt minne
+free-memory=Ledig minne
+used-memory=Brukt minne
+system=System
+current-working-directory=Gjeldende arbeidskatalog
+java-version=Java versjon
+java-vendor=Java leverand\u00F8r
+java-runtime=Java Runtime
+java-vm=Java VM
+java-vm-version=Java VM versjon
+java-home=Java hjem
+user-name=Brukers navn
+user-timezone=Tidssone for bruker
+user-locale=Lokalitet for bruker
+system-encoding=Systemenkoding
+operating-system=Operativsystem (OS)
+os-architecture=OS arkitektur
+spi=SPI
+granted-roles=Tildelte roller
+granted-protocol-mappers=Innvilgede protokollmappere
+additional-grants=Tillegsrettigheter
+revoke=Opphev
+new-password=Nytt passord
+password-confirmation=Passord bekreftelse
+reset-password=Tilbakestill passord
+credentials.temporary.tooltip=Hvis aktivert, er brukeren p\u00E5krevd til \u00E5 endre passordet ved neste innlogging
+remove-totp=Fjern TOTP
+credentials.remove-totp.tooltip=Fjern generator for engangspassord for bruker.
+reset-actions=Tilbakestill handlinger
+credentials.reset-actions.tooltip=Sett med handlinger som kan utf\u00F8res ved \u00E5 sende en bruker en Tilbakestillingshandling for E-post. 'Verifiser e-post' sender en e-post til brukeren for \u00E5 verifisere e-postadresse. 'Oppdater profil' krever at bruker legger inn personlig informasjon. 'Oppdater passord' krever at bruker skriver inn et nytt passord. 'Konfigurer TOTP' krever installasjon av en passordgenerator for mobil.
+reset-actions-email=Tilbakestillingshandling for E-post.
+send-email=Send e-post
+credentials.reset-actions-email.tooltip=Sender en e-post til en bruker med en lenke. Ved \u00E5 klikke p\u00E5 denne lenken vil brukeren f\u00E5 lov til \u00E5 utf\u00F8re tilbakestillingshandlinger. Brukeren trenger ikke logge inn f\u00F8r dette. For eksempel, sett handlingen for \u00E5 oppdatere passord, klikk p\u00E5 denne knappen, og brukeren vil kunne endre deres passord uten \u00E5 logge inn.
+add-user=Legg til bruker
+created-at=Opprettet ved
+user-enabled=Bruker aktivert
+user-enabled.tooltip=En deaktivert bruker kan ikke logge inn.
+user-temporarily-locked=Bruker er midlertidig l\u00E5st.
+user-temporarily-locked.tooltip=Brukeren kan ha blitt l\u00E5st p\u00E5 grunn av at innloggingsfors\u00F8k har feilet for mange ganger.
+unlock-user=L\u00E5s opp bruker
+federation-link=Federeringslenke
+email-verified=E-post verifisert
+email-verified.tooltip=Har brukerens e-post blitt verifisert?
+required-user-actions=P\u00E5krevde brukerhandlinger
+required-user-actions.tooltip=Krev en handling n\u00E5r brukeren logger inn. 'Verifiser e-post' sender en e-post til brukeren for \u00E5 verifisere deres e-postadresse. 'Oppdater profil' krever at bruker legger inn personlig informasjon. 'Oppdater passord' krever at bruker skriver inn et nytt passord. 'Konfigurer TOTP' krever installasjon av en passordgenerator for mobil.
+locale=Lokalitet
+select-one.placeholder=Velg en...
+impersonate=Utgi deg for \u00E5 v\u00E6re bruker
+impersonate-user=Utgi deg for \u00E5 v\u00E6re bruker
+impersonate-user.tooltip=Logg inn som denne brukeren. Hvis bruker er i samme sikkerhetsdomene som deg, vil din n\u00E5v\u00E6rende innloggede sesjon bli logget ut f\u00F8r du blir logget inn som denne brukeren.
+identity-provider-alias=Alias for identitetsleverand\u00F8r
+provider-user-id=Bruker-ID for leverand\u00F8r
+provider-username=Brukernavn for leverand\u00F8r
+no-identity-provider-links-available=Ingen lenker for identitetsleverand\u00F8r er tilgjengelig
+group-membership=Gruppemedlemskap
+leave=Forlat
+group-membership.tooltip=Gruppen som brukeren er medlem av. Velg en gruppe p\u00E5 listen og klikk p\u00E5 'Forlat' for \u00E5 forlate gruppen.
+membership.available-groups.tooltip=Grupper som brukere kan bli medlem av. Velg en gruppe og klikk p\u00E5 'Bli med' knappen.
+table-of-realm-users=Liste over sikkerhetsdomenebrukere
+view-all-users=Se alle brukere
+unlock-users=L\u00E5s opp brukere
+no-users-available=Ingen brukere tilgjengelig
+users.instruction=Vennligst skriv inn et s\u00F8k, eller klikk p\u00E5 Se alle brukere
+consents=Samtykke
+started=Startet
+logout-all-sessions=Logg ut av alle sesjoner
+logout=Logg ut
+new-name=Nytt navn
+ok=Ok
+attributes=Attributter
+role-mappings=Mapping av roller
+members=Medlemmer
+details=Detaljer
+identity-provider-links=Lenker til identitetsleverand\u00F8r
+register-required-action=Registrer p\u00E5krevd handling
+gender=Kj\u00F8nn
+address=Adresse
+phone=Telefon
+profile-url=Profil URL
+picture-url=Bilde URL
+website=Nettsted
+import-keys-and-cert=Importer n\u00F8kler og sertifikat
+import-keys-and-cert.tooltip=Last opp klientens n\u00F8kkelpar og sertifikat.
+upload-keys=Last opp n\u00F8kler
+download-keys-and-cert=Last ned n\u00F8kler og sertifikat
+no-value-assigned.placeholder=Ingen tilordnet verdi
+remove=Fjern
+no-group-members=Ingen gruppemedlemmer
+temporary=Midlertidig
+join=Bli med
+event-type=Hendelsestype
+events-config=Hendelseskonfigurasjon
+event-listeners=Hendelseslyttere
+login-events-settings=Innstillinger for innloggingshendelser
+clear-events=Fjern hendelser
+saved-types=Lagrede typer
+clear-admin-events=Fjern administratorhendelser
+clear-changes=Fjern endringer
+error=Feil
+
+# Authz
+ # Authz Common
+authz-authorization=Autorisasjon
+authz-owner=Eier
+authz-uri=URI
+authz-scopes=Scope
+authz-resource=Ressurs
+authz-resource-type=Ressurstype
+authz-resources=Ressurser
+authz-scope=Scope
+authz-authz-scopes=Autorisasjonsscopes
+authz-policies=Policier
+authz-permissions=Tillatelser
+authz-evaluate=Evaluer
+authz-icon-uri=Ikon URI
+authz-icon-uri.tooltip=En URI som peker til et ikon.
+authz-select-scope=Velg et scope
+authz-select-resource=Velg en ressurs
+authz-associated-policies=Assosierte policier
+authz-any-resource=Enhver ressurs
+authz-any-scope=Ethvert scope
+authz-any-role=Enhver rolle
+authz-policy-evaluation=Evaluering av policy
+authz-select-client=Velg en klient
+authz-select-user=Velg en bruker
+authz-entitlements=Rettigheter
+authz-no-resources=Ingen ressurser
+authz-result=Resultat
+authz-authorization-services-enabled=Autorisasjon aktivert
+authz-authorization-services-enabled.tooltip=Aktiver/deaktiver finkornet autorisasjonssupport for en klient
+authz-required=P\u00E5krevd
+
+# Authz Settings
+authz-import-config.tooltip=Importer en JSON-fil som inneholder innstillinger for autorisasjon for denne ressursserveren.
+
+authz-policy-enforcement-mode=Modus for h\u00E5ndhevelse av policy
+authz-policy-enforcement-mode.tooltip=Modus for h\u00E5ndhevelse av policy dikterer hvordan policier blir h\u00E5ndhevet n\u00E5r autorisasjonsforesp\u00F8rsler blir evaluert. 'H\u00E5ndhevende' betyr at foresp\u00F8rsler blir nektet som standard selv om det ikke er en policy knyttet til en gitt ressurs. 'Ettergivende' betyr at foresp\u00F8rsler blir tillatt selv om det ikke er en policy knyttet til en gitt ressurs. 'Deaktivert' deaktiverer fullstendig evalueringen av policier og tillater tilgang til enhver ressurs.
+authz-policy-enforcement-mode-enforcing=H\u00E5ndhevende
+authz-policy-enforcement-mode-permissive=Ettergivende
+authz-policy-enforcement-mode-disabled=Deaktivert
+
+authz-remote-resource-management=H\u00E5ndtering av ekstern ressurs
+authz-remote-resource-management.tooltip=Skal ressursene bli h\u00E5ndtert eksternt av ressursserveren? Hvis satt til false kan ressursene kun bli h\u00E5ndtert fra denne administratorkonsollen.
+
+authz-export-settings=Eksporter innstillinger
+authz-export-settings.tooltip=Eksporter og last ned alle innstillinger for autorisasjon for denne ressursserveren.
+
+ # Authz Resource List
+authz-no-resources-available=Ingen tilgjengelige ressurser.
+authz-no-scopes-assigned=Ingen tildelte scopes.
+authz-no-type-defined=Ingen definert type.
+authz-no-permission-assigned=Ingen tillatelse er tildelt.
+authz-no-policy-assigned=Ingen tildelt policy.
+authz-create-permission=Opprett tillatelse
+
+ # Authz Resource Detail
+authz-add-resource=Legg til ressurs
+authz-resource-name.tooltip=Et unikt navn for denne ressursen. Navnet kan bli brukt til \u00E5 identifisere en ressurs og er nyttig i sp\u00F8rringer for en bestemt ressurs.
+authz-resource-owner.tooltip=Eieren av denne ressursen.
+authz-resource-type.tooltip=Ressurstype. Den kan brukes til \u00E5 gruppere ulike ressursinstanser av samme type.
+authz-resource-uri.tooltip=En URI som ogs\u00E5 kan brukes for \u00E5 identifisere denne ressursen.
+authz-resource-scopes.tooltip=Scopes assosiert med denne ressursen.
+
+ # Authz Scope List
+authz-add-scope=Legg til scope
+authz-no-scopes-available=Ingen tilgjengelige scopes.
+
+ # Authz Scope Detail
+authz-scope-name.tooltip=Et unikt navn for dette scopet. Navnet kan bli brukt for \u00E5 identifisere et scope, og er nyttig i sp\u00F8rringer for en bestemt ressurs.
+
+ # Authz Policy List
+authz-all-types=Alle typer
+authz-create-policy=Opprett policy
+authz-no-policies-available=Ingen tilgjengelige policier.
+
+ # Authz Policy Detail
+authz-policy-name.tooltip=Navnet p\u00E5 denne policien.
+authz-policy-description.tooltip=En beskrivelse av denne policien.
+authz-policy-logic=Logikk
+authz-policy-logic-positive=Positiv
+authz-policy-logic-negative=Negativ
+authz-policy-logic.tooltip=Logikken som dikterer hvordan beslutningspolicien skal utf\u00F8rres. Hvis 'Positiv', vil resulterende effekt (tillate eller nekte) oppn\u00E5dd under evalueringen av denne policien bli brukt for \u00E5 ta en beslutning. Hvis 'Negativ', vil resulterende effekt bli opphevet, med andre ord blir en tillatelse til et avslag og motsatt.
+authz-policy-apply-policy=Anvend policy
+authz-policy-apply-policy.tooltip=Spesifiserer alle policies som m\u00E5 bli anvendt for scopes definert av denne policien eller tillatelsen.
+authz-policy-decision-strategy=Beslutningsstrategi
+authz-policy-decision-strategy.tooltip=Beslutningsstrategi som dikterer hvordan policies knyttet til en gitt policy blir evaluert og hvordan endelig avgj\u00F8relse oppn\u00E5s. 'Bekreftende' betyr at minst en policy m\u00E5 evalueres til en positiv beslutning for at den samlede avgj\u00F8relsen kan bli positiv. 'Enstemmig' betyr at alle policies m\u00E5 evalueres til en positiv beslutning for at den samlede avgj\u00F8relsen kan bli positiv. 'Konsensus' betyr at antall positive beslutninger m\u00E5 v\u00E6re h\u00F8yere enn antall negative beslutninger. Hvis antallet av positive og negative er likt, blir den samlede avgj\u00F8relsen negativ.
+authz-policy-decision-strategy-affirmative=Bekreftende
+authz-policy-decision-strategy-unanimous=Enstemmig
+authz-policy-decision-strategy-consensus=Konsensus
+authz-select-a-policy=Velg en policy
+
+ # Authz Role Policy Detail
+authz-add-role-policy=Legg til policy for rolle
+authz-no-roles-assigned=Ingen tildelte roller.
+authz-policy-role-roles.tooltip=Spesifiser sikkerhetsdomenerolle(r) som tillates av denne policien.
+authz-policy-role-clients.tooltip=Velger en klient for \u00E5 filtrere klientroller som kan bli tatt i bruk av denne policien.
+authz-policy-role-client-roles.tooltip=Spesifiserer klientroller tillatt av denne policien.
+
+ # Authz User Policy Detail
+authz-add-user-policy=Legg til policy for bruker
+authz-no-users-assigned=Ingen tildelte brukere.
+authz-policy-user-users.tooltip=Spesifiser bruker(e) som tillates av denne policien.
+
+ # Authz Time Policy Detail
+authz-add-time-policy=Legg til policy for tid
+authz-policy-time-not-before.tooltip=Definerer tiden f\u00F8r policien M\u00C5 IKKE innvilges. Denne innvilges kun om gjeldende dato/tid er f\u00F8r eller lik denne verdien.
+authz-policy-time-not-on-after=Ikke p\u00E5 eller etter
+authz-policy-time-not-on-after.tooltip=Definerer tiden etter en policy M\u00C5 IKKE innvilges. Denne innvilges kun om gjeldende dato/tid er f\u00F8r eller lik denne verdien.
+
+ # Authz Drools Policy Detail
+authz-add-drools-policy=Legg til Drools policy
+authz-policy-drools-maven-artifact-resolve=L\u00F8s
+authz-policy-drools-maven-artifact=Policy for Maven artefakt.
+authz-policy-drools-maven-artifact.tooltip=Et Maven GAV som peker til et artefakt hvor reglene vil bli lastet fra. Med en gang du har gitt GAV kan du klikke *L\u00F8s* for \u00E5 laste felter for b\u00E5de *Modul* og *Sesjon*
+authz-policy-drools-module=Modul
+authz-policy-drools-module.tooltip=Modulen som brukes av denne policien. Du m\u00E5 oppgi en modul for \u00E5 velge en bestemt \u00F8kt der reglene vil bli lastet fra.
+authz-policy-drools-session=Sesjon
+authz-policy-drools-session.tooltip=Sesjonen brukt av denne policien. Sesjonen vil gi alle regler for evaluering ved prosessering av policien.
+authz-policy-drools-update-period=Oppdater periode
+authz-policy-drools-update-period.tooltip=Spesifiserer et intervall for \u00E5 skanne etter oppdateringer for artefakter.
+
+ # Authz JS Policy Detail
+authz-add-js-policy=Legg til policy for JavaScript
+authz-policy-js-code=Kode
+authz-policy-js-code.tooltip=JavaScript-koden angir betingelsene for denne politikken.
+
+
+ # Authz Aggregated Policy Detail
+authz-aggregated=Aggregert
+authz-add-aggregated-policy=Legg til policy for aggregering.
+
+ # Authz Permission List
+authz-no-permissions-available=Ingen tilgjengelige tillatelser.
+
+ # Authz Permission Detail
+authz-permission-name.tooltip=Navnet p\u00E5 denne tillatelsen.
+authz-permission-description.tooltip=En beskrivelse av denne tillatelsen.
+
+ # Authz Resource Permission Detail
+authz-add-resource-permission=Legg til tillatelse for ressurs.
+authz-permission-resource-apply-to-resource-type=Bruk p\u00E5 ressurstype
+authz-permission-resource-apply-to-resource-type.tooltip=Spesifiserer om denne tillatelsen skal gjelde for alle ressurser med en gitt type. I dette tilfellet vil tillatelsen bli evaluert for alle instanser av gitt ressurstype.
+authz-permission-resource-resource.tooltip=Spesifiserer at denne tillatelsen m\u00E5 bli brukt for en spesifikk ressursinstans.
+authz-permission-resource-type.tooltip=Spesifiserer at denne tillatelsen m\u00E5 bli anvendt for alle ressursinstanser for en gitt type.
+
+ # Authz Scope Permission Detail
+authz-add-scope-permission=Legg til tillatelse for scope
+authz-permission-scope-resource.tooltip=Begrens scopes til de som er tilknyttet den valgte ressursen. Hvis dette ikke er valgt vil alle scopes v\u00E6re tilgjengelige.
+authz-permission-scope-scope.tooltip=Spesifiserer at denne tillatelse m\u00E5 anvendes p\u00E5 en eller flere scopes.
+
+ # Authz Evaluation
+authz-evaluation-identity-information=Identitetsinformasjon
+authz-evaluation-identity-information.tooltip=De tilgjengelige alternativene for \u00E5 konfigurere identitesinformasjon som vil bli brukt ved evaluering av policier.
+authz-evaluation-client.tooltip=Velg klienten som vil utf\u00F8re denne autorisasjonsforesp\u00F8rselen.
+authz-evaluation-user.tooltip=Velg en bruker hvis identitet vil bli brukt for \u00E5 s\u00F8ke tillatelser fra serveren.
+authz-evaluation-role.tooltip=Velg en rolle som du vil knytte til den valgte brukeren.
+authz-evaluation-new=Ny evaluering
+authz-evaluation-re-evaluate=Re-evaluering
+authz-evaluation-previous=Forrige evaluering
+authz-evaluation-contextual-info=Kontekstuell informasjon
+authz-evaluation-contextual-info.tooltip=Tilgjengelige valg for \u00E5 konfigurere enhver kontekstuell informasjon som vil bli brukt ved evaluering av policier.
+authz-evaluation-contextual-attributes=Kontekstuelle attributter
+authz-evaluation-contextual-attributes.tooltip=Ethvert attributt gitt av et kj\u00F8rende milj\u00F8 eller ved utf\u00F8relseskontekst.
+authz-evaluation-permissions.tooltip=De tilgjengelige alternativene for \u00E5 konfigurere tillatelsene for hvilke policies som skal anvendes.
+authz-evaluation-evaluate=Evaluer
+authz-evaluation-any-resource-with-scopes=Enhver ressurs med scope(s)
+authz-evaluation-no-result=Kunne ikke f\u00E5 et resultat for den gitte autorisasjonsforesp\u00F8rselen. Sjekk om de tilgjengelige ressursene er tilknyttet en policy.
+authz-evaluation-no-policies-resource=Ingen policies ble funnet for denne ressursen.
+authz-evaluation-result.tooltip=Det samlede resultatet for denne foresp\u00F8rselen for tillatelse.
+authz-evaluation-scopes.tooltip=Liste over tillatte scopes.
+authz-evaluation-policies.tooltip=Detaljer om hvilke policies som ble evaluert og deres avgj\u00F8relser.
+authz-evaluation-authorization-data=Respons
+authz-evaluation-authorization-data.tooltip=Representerer et token som b\u00E6rer autorisasjonsdata som et resultat av prosesseringen av en autorisasjonsforesp\u00F8rsel. Denne representasjonen er hva Keycloak sender ut til klienter som ettersp\u00F8r tillatelser. Sjekk autorisasjonsclaim for tillatelsene som ble gitt basert p\u00E5 n\u00E5v\u00E6rende autorisasjonsforesp\u00F8rsel.
+authz-show-authorization-data=Vis autorisasjonsdata
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_pt_BR.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_pt_BR.properties
index e69de29..5130658 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_pt_BR.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_pt_BR.properties
@@ -0,0 +1,1094 @@
+#encoding: utf-8
+consoleTitle=Console de Administração do Keycloak
+
+# Common messages
+enabled=Habilitado
+name=Nome
+displayName=Nome de exibição
+displayNameHtml=Nome de exibição HTML
+save=Salvar
+cancel=Cancelar
+onText=Sim
+offText=Não
+client=Cliente
+clients=Clientes
+clear=Limpar
+selectOne=Selecione Um...
+
+true=Sim
+false=Não
+
+endpoints=Endpoints
+
+# Realm settings
+realm-detail.enabled.tooltip=Usuários e clientes somente podem acessar um Realm se ele estiver habilitado
+realm-detail.oidc-endpoints.tooltip=Exibe a configuração dos endpoints do OpenID Connect
+registrationAllowed=Cadastro de usuário
+registrationAllowed.tooltip=Habilita/desabilita a página de cadastro. Um link para a página de cadastro também será exibido na tela de login.
+registrationEmailAsUsername=Email como nome de usuário
+registrationEmailAsUsername.tooltip=Se habilitado o campo 'nome de usuário' será ocultado no formulário de cadastro e o e-mail será usado como nome de usuário para o novo cadastro.
+editUsernameAllowed=Editar nome de usuário
+editUsernameAllowed.tooltip=Se habilitado, o campo nome de usuário é editável, senão será apenas leitura.
+resetPasswordAllowed=Esqueci a senha
+resetPasswordAllowed.tooltip=Exibe um link na página de login para o usuário clicar quando houver esquecido suas credenciais.
+rememberMe=Lembrar me
+rememberMe.tooltip=Exibe um checkbox na página de login para permitir ao usuário continuar logado entre restarts do browser até que a sessão expire.
+verifyEmail=Verificar e-mail
+verifyEmail.tooltip=Requer que o usuário verifique seu endereço de e-mail na primeira vez que efetuar login.
+sslRequired=SSL requerido
+sslRequired.option.all=todas requisições
+sslRequired.option.external=requisições externas
+sslRequired.option.none=nunca
+sslRequired.tooltip=É necessário SSL? 'Nunca' significa que HTTPS não é requerido para nenhum endereço IP cliente. 'Requisições externas' significa que localhost e IPs privados podem acessar sem HTTPS. 'Todas requisições' significa que HTTPS é requerido para todos os endereços IPs.
+publicKey=Chave pública
+privateKey=Chave privada
+gen-new-keys=Gerar novas chaves
+certificate=Certificado
+host=Host
+smtp-host=Host SMTP
+port=Porta
+smtp-port=Porta SMTP (valor padrão: 25)
+from=Remetente
+sender-email-addr=Endereço de e-mail do remetente
+enable-ssl=Habilitar SSL
+enable-start-tls=Habilitar StartTLS
+enable-auth=Habilitar autenticação
+username=Usuário
+login-username=Nome de usuário para login
+password=Senha
+login-password=Senha para login
+login-theme=Tema de login
+login-theme.tooltip=Selecione o tema para páginas de login, TOTP, grant, cadastro e recuperar senha.
+account-theme=Tema para conta
+account-theme.tooltip=Selecione o tema para as páginas de administração de conta do usuário.
+admin-console-theme=Tema para console de administração
+select-theme-admin-console=Selecione o tema para o console de administração.
+email-theme=Tema de e-mail
+select-theme-email=Selecione o tema para os e-mail que são enviados pelo servidor.
+i18n-enabled=Habilitar internacionalização
+supported-locales=Locais disponíveis
+supported-locales.placeholder=Digite um local e pressione Enter
+default-locale=Local padrão
+realm-cache-clear=Realm Cache
+realm-cache-clear.tooltip=Remove todas as entradas do cache de realm (isto irá remover as entradas para todos os realms)
+user-cache-clear=Cache de usuário
+user-cache-clear.tooltip=Remove todas as entradas do cache de usuário (isto irá remover as entradas de todos os realms)
+revoke-refresh-token=Revogar Token de Atualização
+revoke-refresh-token.tooltip=Se habilitado os tokens de atualização podem ser utilizados somente uma vez. Caso contrário os tokens de atualização não são revogados quando utilizados e podem ser utilizados várias vezes.
+sso-session-idle=Sessão SSO inativa
+sso-session-idle.tooltip=Tempo que uma sessão pode ficar inativa antes de expirar. Tokens e sessões de navegador são invalidados quando uma sessão é expirada.
+seconds=Segundos
+minutes=Minutos
+hours=Horas
+days=Dias
+sso-session-max=Sessão SSO Máxima
+sso-session-max.tooltip=Tempo máximo antes que uma sessão seja expirada. Tokens e sessões de navegador são invalidados quando uma sessão é expirada.
+offline-session-idle=Sessão Offline Inativa
+offline-session-idle.tooltip=Tempo que uma sessão offline pode ficar inativa antes de expirar. Você precisa utilizar um token de atualização offline pelo menos uma vez neste período, caso contrário a sessão offline será expirada.
+access-token-lifespan=Duração do Token de Acesso
+access-token-lifespan.tooltip=Tempo máximo antes que um token de acesso expire. Recomenda-se que este valor seja menor em relação ao tempo de inativação do inativação do SSO.
+access-token-lifespan-for-implicit-flow=Duração do token de acesso para fluxos Implícitos
+access-token-lifespan-for-implicit-flow.tooltip=Tempo máximo antes que um token de acesso emitido durante o Fluxo Implícito do OpenID Connect expire. Recomenda-se que este valor seja menor em relação ao tempo de inativação do SSO. Não há posibilidade de atualizar este token durante o fluxo implícito, sendo este o motivo de existir um tempo limite diferente para a 'Duração do Token de Acesso'.
+client-login-timeout=Tempo limite para login do Cliente
+client-login-timeout.tooltip=Tempo máximo que um cliente tem para finalizar o procolo do token de acesso. Normalmente deve ser 1 minuto.
+login-timeout=Tempo máximo do Login
+login-timeout.tooltip=Tempo máximo que um usuário tempo para completar o login. É recomendado que seja relativamente longo - 30 minutos ou mais.
+login-action-timeout=Tempo limite da ação de Login
+login-action-timeout.tooltip=Tempo máximo que um usuário tem para completar as ações relacionadas ao login como atualizar senhas ou configurar totp. É recomendado que seja relativamente longo - 5 minutos ou mais.
+headers=Cabeçalhos
+brute-force-detection=Detecção de ataque de Força Bruta
+x-frame-options=X-Frame-Options
+x-frame-options-tooltip=O valor padrão impede páginas de serem incluídas via non-origin iframes (clique no label para mais informações)
+content-sec-policy=Content-Security-Policy
+content-sec-policy-tooltip=O valor padrão impede páginas de serem incluídas via non-origin iframes (clique no label para mais informações)
+content-type-options=X-Content-Type-Options
+content-type-options-tooltip=O valor padrão impede Internet Explorer and Google Chrome de realizarem MIME-sniffing em uma resposta diferente do content-type declarado (clique no label para mais informações)
+max-login-failures=Falhas de login
+max-login-failures.tooltip=Quantas falhas de login antes que a espera seja habilitada.
+wait-increment=Incremento de Espera
+wait-increment.tooltip=Quando a quantidade de falhas for alcançada, quanto tempo o usuário deve aguardar antes de tentar novamente?
+quick-login-check-millis=Verificação de Quick Login em Milli Seconds
+quick-login-check-millis.tooltip=Se uma falha ocorre concorrentemente neste período, travar a conta do usuário.
+min-quick-login-wait=Espera mínima após Quick Login
+min-quick-login-wait.tooltip=Quanto tempo aguardar após uma falha de quick login.
+max-wait=Espera máxima
+max-wait.tooltip=Tempo máximo que um usuário deverá aguardar após uma falha de quick login.
+failure-reset-time=Tempo para zerar falhas
+failure-reset-time.tooltip=Quando o contador de falhas será resetado?
+realm-tab-login=Login
+realm-tab-keys=Chaves
+realm-tab-email=E-mail
+realm-tab-themes=Temas
+realm-tab-cache=Cache
+realm-tab-tokens=Tokens
+realm-tab-client-initial-access=Tokens de Acesso inicial
+realm-tab-security-defenses=Defesas
+realm-tab-general=Geral
+add-realm=Adicionar realm
+
+#Session settings
+realm-sessions=Sessões do Realm
+revocation=Revogação
+logout-all=Deslogar todos
+active-sessions=Sessões Ativas
+sessions=Sessões
+not-before=Não antes de
+not-before.tooltip=Revogar qualquer token emitido antes desta data.
+set-to-now=Definir como agora
+push=Enviar
+push.tooltip=Para cada cliente que possui uma URL de administrador, notificá-los da nova política de revogação.
+
+#Protocol Mapper
+usermodel.prop.label=Propriedade
+usermodel.attr.label=Atributo do usuário
+userSession.modelNote.label=Nota da sessão de usuário
+multivalued.label=Múltiplos valores
+selectRole.label=Selecione o Role
+tokenClaimName.label=Nome do Token Claim
+jsonType.label=Tipo JSON do Claim
+includeInIdToken.label=Adicionar ao token de ID
+includeInAccessToken.label=Adicionar ao token de acesso
+includeInUserInfo.label=Adicionar à informação do usuário
+usermodel.clientRoleMapping.clientId.label=ID do cliente
+usermodel.clientRoleMapping.rolePrefix.label=Prefixo para o role de Cliente
+usermodel.realmRoleMapping.rolePrefix.label=Prefixo do Realm Role
+
+# client details
+search.placeholder=Pesquisar...
+create=Criar
+import=Importar
+client-id=ID do cliente
+base-url=URL Base
+actions=Ações
+not-defined=Não definido
+edit=Editar
+delete=Excluir
+no-results=Sem resultados
+no-clients-available=Nenhum cliente disponível
+add-client=Adicionar cliente
+select-file=Selecionar arquivo
+view-details=Ver detalhes
+clear-import=Cancelar importação
+client-id.tooltip=Especifica o ID referenciado em URI e tokens. Por exemplo 'meu-cliente'. Para SAML também representa o valor do emissor esperado dos authn requests
+client.name.tooltip=Especifica o nome de exibição do cliente. Por exemplo 'Meu Cliente'. Também aceita chaves para valores localizados. Por exemplo: ${meu_cliente}
+client.description.tooltip=Especifica a descrição do cliente. Por exemplo 'Meu cliente para TimeSheets'. Também aceita chaves para valores localizados. Por exemplo: ${meu_cliente_descricao}
+client.enabled.tooltip=Clientes desabilitados não podem realizar login ou obter tokens de acesso.
+consent-required=Consentimento exigido
+consent-required.tooltip=Se habilitado os usuários devem consentir com o acesso ao cliente.
+client-protocol=Protocolo cliente
+client-protocol.tooltip='OpenID connect' permite aos Clientes verificarem a identidade do usuário final baseado na autenticação realizada por um servidor de Autorização. 'SAML' permite cenários de autenticação e autorização web-based incluindo cross-domain single sign-on (SSO) e utiliza tokens de segurança contendo assertions para trafegar informações.
+access-type=Tipo de acesso
+access-type.tooltip=Clientes 'Confidential' requerem um secret para iniciar o protocolo de login. Clientes 'Public' não necessitam de secret. Clientes 'Bearer-only' são web services que nunca iniciam um login.
+standard-flow-enabled=Fluxo padrão habilitado
+standard-flow-enabled.tooltip=Isto habilita a autenticação baseada em redirecionamento com código de autorização padrão do OpenID Connect. Em termos de especificações OpenID Connect ou OAuth2, isto habilita suporte ao 'Fluxo de Código de Autorização' para este cliente.
+implicit-flow-enabled=Fluxo implícito habilitado
+implicit-flow-enabled.tooltip=Isto habilita suporte a autenticação baseada em redirecionamento sem código de autorização. Em tempos de especificações OpenID Connect ou OAuth2, isto habilita suporte do 'Fluxo Implícito' para este cliente.
+direct-access-grants-enabled=Grants de Acesso direto habilitado
+service-accounts-enabled=Contas de serviço habilitadas
+include-authnstatement=Incluir AuthnStatement
+sign-documents=Assinar documentos
+sign-assertions=Assinar assertions
+signature-algorithm=Algoritmo de assinatura
+canonicalization-method=Método de Canonicalization
+encrypt-assertions=Encriptar Assertions
+client-signature-required=Assinatura do cliente requerida
+force-post-binding=Forçar Binding via POST
+front-channel-logout=Front Channel Logout
+force-name-id-format=Forçar formato do NameID
+name-id-format=Formato do NameID
+valid-redirect-uris=URIs de redirecionamento válidas
+admin-url=URL do administrador
+master-saml-processing-url=URL de processamento SAML principal
+idp-sso-url-ref=Nome de URL para SSO iniciado via IDP
+idp-sso-relay-state=Estado de relay para SSO iniciado via IDP
+fine-saml-endpoint-conf=Configuração de endpoint para configuração fina do SAML
+assertion-consumer-post-binding-url=URL para conexão post para o serviço consumidor de Assertions
+assertion-consumer-redirect-binding-url=URL para conexão de redirecionamento do serviço consumidor de Assertions
+logout-service-post-binding-url=URL de conexão POST para o serviço de logout
+logout-service-post-binding-url.tooltip=URL de conexão POST para o serviço de logout
+logout-service-redir-binding-url=URL de conexão para o redirecionamento do serviço de logout
+
+# client import
+import-client=Importar cliente
+format-option=Formato
+import-file=Importar arquivo
+
+# client tabs
+settings=Configurações
+credentials=Credenciais
+saml-keys=Chaves SAML
+roles=Roles
+mappers=Mapeamentos
+scope=Escopo
+offline-access=Acesso offline
+installation=Instalação
+service-account-roles=Roles de contas de serviço
+
+# client credentials
+client-authenticator=Autenticador do cliente
+no-client-certificate-configured=Nenhum certificado cliente configurado
+gen-new-keys-and-cert=Gerar novas chaves e certificados
+import-certificate=Importar certificado
+gen-client-private-key=Gerar chave privada do cliente
+generate-private-key=Gerar chave privada
+archive-format=Formato do arquivo
+key-alias=Alias da chave
+key-password=Senha da chave
+store-password=Salvar senha
+generate-and-download=Gerar e fazer download
+client-certificate-import=Importar certificado do cliente
+import-client-certificate=Importar certificado do cliente
+secret=Segredo
+regenerate-secret=Recriar segredo
+registrationAccessToken=Token de acesso para registro
+registrationAccessToken.regenerate=Regerar token de acesso para registro
+add-role=Adicionar Role
+role-name=Nome do Role
+composite=Composto
+description=Descrição
+no-client-roles-available=Nenhum role de cliente disponível
+scope-param-required=Parâmetro de escopo requerido
+composite-roles=Roles compostos
+realm-roles=Roles do Realm
+available-roles=Roles disponíveis
+add-selected=Adicionar selecionados
+associated-roles=Roles associados
+remove-selected=Remover selecionados
+client-roles=Roles de clientes
+select-client-to-view-roles=Selecione o cliente para ver os roles do cliente
+add-builtin=Adicionar Builtin
+category=Categoria
+type=Tipo
+no-mappers-available=Nenhum mapeamento disponível
+add-builtin-protocol-mappers=Adicionar mapeamentos de protocolo Builtin
+add-builtin-protocol-mapper=Adicionar mapeamentos de protocolo Builtin
+
+scope-mappings=Mapeamentos do Escopo
+full-scope-allowed=Permitir Escopo completo
+assigned-roles=Roles associados
+effective-roles=Roles efetivos
+basic-configuration=Configuração básica
+node-reregistration-timeout=Tempo limite para re-registro de nó
+registered-cluster-nodes=Nós de cluster registrados
+register-node-manually=Registrar nó manualmente
+test-cluster-availability=Testar disponibilidade do cluster
+last-registration=Último registro
+node-host=Host
+no-registered-cluster-nodes=Nenhum nó registrado disponível
+cluster-nodes=Nós do cluster
+add-node=Adicionar nó
+show-sessions=Exibir sessões
+user=Usuário
+from-ip=Do IP
+session-start=Início da sessão
+first-page=Primeira página
+previous-page=Página anterior
+next-page=Próxima página
+select-a-format=Selecione um formato
+download=Download
+offline-tokens=Tokens offline
+show-offline-tokens=Exibir tokens offline
+token-issued=Token emitido
+last-access=Último acesso
+last-refresh=Último refresh
+key-export=Exportar chave
+key-import=Importar chave
+export-saml-key=Exportar chave SAML
+import-saml-key=Importar chave SAML
+realm-certificate-alias=Alias do certificado do Realm
+signing-key=Chave de assinatura
+saml-signing-key=Chave de assinatura SAML
+private-key=Chave privada
+generate-new-keys=Gerar novas chaves
+export=Exportar
+encryption-key=Chave de encriptação
+service-accounts=Contas de serviço
+service-account-is-not-enabled-for=Contas de serviço não estão habilitadas para {{client}}
+create-protocol-mappers=Criar mapeamentos de protocolo
+create-protocol-mapper=Criar mapeamento de protocolo
+protocol=Protocolo
+protocol.tooltip=Protocolo...
+id=ID
+mapper.name.tooltip=Nome do mapeamento
+consent-text=Texto para consentimento
+mapper-type=Tipo de mapeamento
+# realm identity providers
+identity-providers=Provedores de identificação
+table-of-identity-providers=Tabela de provedores de identidade
+add-provider.placeholder=Adicionar provedor...
+provider=Provedor
+first-broker-login-flow=Fluxo do primeiro login
+post-broker-login-flow=Fluxo pós login
+redirect-uri=URI de redirecionamento
+alias=Alias
+authenticate-by-default=Autenticar por padrão
+store-tokens=Salvar Tokens
+stored-tokens-readable=Leitura de tokens salvos
+trust-email=Confiar no e-mail recebido
+gui-order=Ordem na tela
+gui-order.tooltip=Número definindo a ordem do provedor na GUI (ex na página de Login).
+openid-connect-config=Configuração OpenID Connect
+authorization-url=URL de autorização
+token-url=URL do Token
+logout-url=URL de logout
+backchannel-logout=Backchannel Logout
+user-info-url=URL de informações do usuário
+client-secret=Secret do Cliente
+show-secret=Exibir secret
+hide-secret=Esconder secret
+issuer=Emissor
+default-scopes=Escopos padrão
+prompt=Prompt
+unspecified.option=Não especificado
+none.option=Nenhum
+consent.option=Consentimento
+login.option=Login
+select-account.option=select_account
+validate-signatures=Validar assinaturas
+validating-public-key=Chave pública para validação
+import-external-idp-config=Importar configuração de IDP externo
+import-from-url=Importar de URL
+import-from-file=Importar de arquivo
+saml-config=Configuração SAML
+single-signon-service-url=URL de serviço do Single Sign On
+single-logout-service-url=URL de serviço de Single Logout
+nameid-policy-format=Política de formato NameID
+http-post-binding-response=Responder com HTTP-POST
+http-post-binding-for-authn-request=Utilizar HTTP-POST binding para AuthnRequest
+want-authn-requests-signed=Esperar AuthnRequests assinados
+force-authentication=Forçar autenticação
+validate-signature=Validar assinatura
+validating-x509-certificate=Validar certificados X509
+key=Chave
+
+# User federation
+sync-ldap-roles-to-keycloak=Sincronizar os roles do LDAP para o Keycloak
+sync-keycloak-roles-to-ldap=Sincronizar os roles do Keycloak para o LDAP
+sync-ldap-groups-to-keycloak=Sincronizar os grupos do LDAP para o Keycloak
+sync-keycloak-groups-to-ldap=Sincronizar os grupos do Keycloak para o LDAP
+
+realms=Realms
+realm=Realm
+
+identity-provider-mappers=Mapeamentos de provedores de identificação
+create-identity-provider-mapper=Criar mapeamento de provedores de identificação
+add-identity-provider-mapper=Adicionar mapeamento de provedor de identificação
+
+expires=Expira em
+expiration=Duração
+expiration.tooltip=Especifica por quanto tempo o token deve ser válido
+count=Quantidade
+count.tooltip=Especifica quantos clientes podem ser criados usando o token
+remainingCount=Quantidade restante
+created=Criado em
+back=Voltar
+initial-access-tokens=Tokens de acesso inicial
+add-initial-access-tokens=Adicionar token de acesso inicial
+initial-access-token=Token de acesso inicial
+initial-access.copyPaste.tooltip=Copie/cole o token de acesso inicial antes de sair desta página pois não é possível recuperá-lo depois
+continue=Continuar
+initial-access-token.confirm.title=Copiar o token de acesso inicial
+initial-access-token.confirm.text=Por favor copie e cole o token de acesso inicial antes de confirmar pois não é possível recuperá-lo depois
+
+client-templates=Modelos de cliente
+
+groups=Grupos
+
+default-roles=Roles padrão
+no-realm-roles-available=Nenhum role de realm disponível
+
+users=Usuários
+realm-default-roles=Roles padrão do Realm
+client-default-roles=Roles padrão do Cliente
+partial-import=Importação parcial
+
+file=Arquivo
+exported-json-file=Arquivo json exportado
+import-from-realm=Importar de realm
+import-users=Importar usuários
+import-groups=Importar grupos
+import-clients=Importar clientes
+import-identity-providers=Importar provedores de identificação
+import-realm-roles=Importar roles do realm
+import-client-roles=Importar roles de cliente
+if-resource-exists=Se um recurso já existir
+fail=Falhar
+skip=Pular
+overwrite=Sobrescrever
+
+action=Ações
+role-selector=Seletor de roles
+
+select-a-role=Selecione um role
+select-realm-role=Selecione um role de realm
+select-client-role=Selecione um role de cliente
+
+client-template=Modelos de Cliente
+client-saml-endpoint=Cliente SAML Endpoint
+add-client-template=Adicionar modelo de cliente
+
+manage=Administração
+authentication=Autenticação
+user-federation=Federação de usuários
+events=Eventos
+realm-settings=Configurações do Realm
+configure=Configuração
+select-realm=Selecione um realm
+add=Adicionar
+
+
+add-user-federation-provider=Adicionar provedor de federação de usuários
+required-settings=Configurações obrigatórias
+provider-id=ID do provedor
+console-display-name=Nome de exibição no console
+priority=Prioridade
+sync-settings=Configurações de sincronização
+periodic-full-sync=Syncronização completa periódica
+full-sync-period=Período
+periodic-changed-users-sync=Sincronização periódica de usuários alterados
+changed-users-sync-period=Período
+synchronize-changed-users=Sincronizar usuários alterados
+synchronize-all-users=Sincronizar todos os usuários
+kerberos-realm=Realm do Kerberos
+server-principal=Principal do servidor
+keytab=KeyTab
+debug=Debug
+allow-password-authentication=Permitir autenticação via senha
+edit-mode=Modo de edição
+update-profile-first-login=Atualizar Profile no primeiro login
+sync-registrations=Sincronizar contas
+vendor=Vendor
+username-ldap-attribute=Atributo LDAP para Username
+ldap-attribute-name-for-username=Atributo LDAP para Username
+rdn-ldap-attribute=Atributo LDAP para RDN
+ldap-attribute-name-for-user-rdn=Atributo LDAP para RDN
+uuid-ldap-attribute=Atributo LDAP para UUID
+ldap-attribute-name-for-uuid=Atributo LDAP para UUID
+user-object-classes=Classes do objeto User
+
+ldap-connection-url=URL de conexão ao LDAP
+ldap-users-dn=DN dos usuários no LDAP
+ldap-bind-dn=DN para bind no LDAP
+ldap-bind-credentials=Credenciais para conectar ao LDAP
+ldap-filter=Filtro do LDAP
+
+connection-url=URL de conexão
+test-connection=Testar conexão
+users-dn=Users DN
+authentication-type=Tipo de autenticação
+bind-dn=Bind DN
+bind-credential=Senha para conexão
+test-authentication=Testar autenticação
+custom-user-ldap-filter=Filtro de usuários LDAP customizado
+search-scope=Escopo de pesquisa
+use-truststore-spi=Utilizar Truststore SPI
+connection-pooling=Pooling de conexões
+kerberos-integration=Integração com Kerberos
+allow-kerberos-authentication=Permitir autenticação Kerberos
+use-kerberos-for-password-authentication=Utilizar Kerberos para autenticação via senha
+batch-size=Tamanho do lote
+user-federation-mappers=Mapeamentos de federação de usuário
+create-user-federation-mapper=Criar mapeamento de federação de usuário
+add-user-federation-mapper=Adicionar mapeamento de federação de usuário
+provider-name=Nome do provedor
+no-user-federation-providers-configured=Nenhum federação de usuários configurada.
+add-identity-provider=Adicionar provedor de identificação
+add-identity-provider-link=adicionar link para provedor de identificação
+identity-provider=Provedor de identificação
+identity-provider-user-id=ID de usuário do provedor de identificação
+identity-provider-username=Nome de usuário do provedor de identificação
+pagination=Paginação
+
+browser-flow=Fluxo de browser
+registration-flow=Fluxo de registro
+direct-grant-flow=Fluxo de Direct Grant
+reset-credentials=Reiniciar credenciais
+client-authentication=Autenticação do cliente
+new=Novo
+copy=Copiar
+add-execution=Adicionar execução
+add-flow=Adicionar fluxo
+auth-type=Tipo
+requirement=Condição
+config=Configuração
+no-executions-available=Nenhuma execução disponível
+authentication-flows=Fluxos de autenticação
+create-authenticator-config=Criar configuração de autenticação
+otp-type=Tipo OTP
+time-based=Baseado em tempo
+counter-based=Baseado em contador
+otp-hash-algorithm=Algoritmo de hash OTP
+number-of-digits=Quantidade de dígitos
+look-ahead-window=Look Ahead Window
+initial-counter=Contador inicial
+otp-token-period=Período de token OTP
+table-of-password-policies=Tabela de política de senhas
+add-policy.placeholder=Adicionar política...
+policy-type=Tipo da política
+policy-value=Valor da política
+admin-events=Eventos de adminstração
+login-events=Eventos de login
+filter=Filtro
+update=Atualizar
+reset=Reiniciar
+operation-types=Tipos de operações
+select-operations.placeholder=Selecionar operações...
+resource-path=Path do recurso
+date-(from)=Data (De)
+date-(to)=Data (Até)
+authentication-details=Detalhes para autenticação
+ip-address=Endereço IP
+time=Tempo
+operation-type=Tipo de operação
+auth=Autenticação
+representation=Representação
+register=Registro
+required-action=Ação requerida
+default-action=Ação padrão
+no-required-actions-configured=Nenhuma ação requerida configurada
+defaults-to-id=ID é o padrão
+flows=Fluxos
+bindings=Ligações
+required-actions=Ações requeridas
+password-policy=Política de senha
+otp-policy=Política OTP
+user-groups=Grupos de usuário
+default-groups=Grupos Padrão
+cut=Recortar
+paste=Colar
+
+create-group=Criar grupo
+create-authenticator-execution=Criar execução de autenticação
+create-form-action-execution=Criar execução de ação de formulário
+create-top-level-form=Criar formulário de nível superior
+top-level-flow-type=Tipo do fluxo de nível superior
+flow.generic=genérico
+flow.client=cliente
+create-execution-flow=Criar fluxo de execução
+flow-type=Flow Type
+flow.form.type=formulário
+flow.generic.type=genérico
+form-provider=Provedor de formulário
+select-a-type.placeholder=selecione um tipo
+available-groups=Grupos disponíveis
+value=Valor
+table-of-group-members=Tabela de membros do grupo
+last-name=Sobrenome
+first-name=Primeiro nome
+email=E-mail
+toggle-navigation=Alternar navegação
+manage-account=Administrar a conta
+sign-out=Sign Out
+server-info=Informação do servidor
+resource-not-found=Recurso <strong>não encontrado</strong>...
+resource-not-found.instruction=Não foi possível encontrar o recurso solicitado. Por favor verifique se a URL solicitada está correta.
+go-to-the-home-page=Ir para a página inicial »
+page-not-found=Página <strong>não encontrada</strong>...
+page-not-found.instruction=Não foi possível encontrar a página solicitada. Por favor verifique se a URL solicitada está correta.
+select-event-types.placeholder=Selecione os tipos de eventos...
+select-an-action.placeholder=Selecione uma ação...
+admin-events-settings=Configuração de eventos de administração
+save-events=Salvar eventos
+include-representation=Incluir representação
+server-version=Versão do servidor
+info=Informações
+providers=Provedores
+server-time=Hora do servidor
+server-uptime=Uptime do servidor
+memory=Memória
+total-memory=Memória total
+free-memory=Memória livre
+used-memory=Memória utilizada
+system=Sistema
+current-working-directory=Diretório de trabalho atual
+java-version=Versão do Java
+java-vendor=Java Vendor
+java-runtime=Java Runtime
+java-vm=Java VM
+java-vm-version=Versão da Java VM
+java-home=Java Home
+user-name=Usuário
+user-timezone=Zona horária do usuário
+user-locale=Locale do usuário
+system-encoding=Enconding do sistema
+operating-system=Sistema operacional
+os-architecture=Arquitetura do OS
+spi=SPI
+granted-roles=Roles concedidos
+granted-protocol-mappers=Protocol Mappers concedidos
+additional-grants=Concessões adicionais
+revoke=Revogar
+new-password=Nova senha
+password-confirmation=Confirmação de senha
+reset-password=Reiniciar senha
+remove-totp=Remover TOTP
+reset-actions=Ações para reiniciar
+reset-actions-email=Ações para reiniciar e-mail
+send-email=Enviar e-mail
+add-user=Adicionar usuário
+created-at=Criado em
+user-enabled=Usuário ativo
+user-temporarily-locked=Usuário temporariamente desativado
+unlock-user=Liberar usuário
+federation-link=Link para federação
+email-verified=E-mail verificado
+required-user-actions=Ações necessárias do usuário
+locale=Locale
+select-one.placeholder=Selecione um...
+impersonate=Personificar
+impersonate-user=Personificar usuário
+identity-provider-alias=Alias do Provedor de Identificação
+provider-user-id=Provider User ID
+provider-username=Provider Username
+no-identity-provider-links-available=Nenhum link para provedor de identificação disponível
+group-membership=Grupos associados
+leave=Sair
+table-of-realm-users=Tabela de usuários do Realm
+view-all-users=Exibir todos os usuários
+unlock-users=Liberar usuários
+no-users-available=Nenhum usuário disponível
+users.instruction=Por favor faça uma pesquisa, ou clique em Exibir todos os usuários
+consents=Consentimentos
+started=Iniciado
+logout-all-sessions=Logout todas as sessões
+logout=Logout
+new-name=Novo nome
+ok=Ok
+attributes=Atributos
+role-mappings=Mapeamento de roles
+members=Membros
+details=Detalhes
+identity-provider-links=Links de provedores de identificação.
+register-required-action=Registrar ação necessária
+gender=Gênero
+address=Endereço
+phone=Telefone
+profile-url=URL do perfil
+picture-url=URL da foto
+website=Website
+import-keys-and-cert=Importar chave e certificado
+upload-keys=Carregar chaves
+download-keys-and-cert=Download chave e certificado
+no-value-assigned.placeholder=Nenhum valor associado
+remove=Remover
+no-group-members=Nenhum membro
+temporary=Temporária
+join=Participar
+event-type=Tipo de evento
+events-config=Configurar eventos
+event-listeners=Listeners de eventos
+login-events-settings=Configuração de eventos de login
+clear-events=Limpar eventos
+saved-types=Tipos salvos
+clear-admin-events=Limpar eventos administrativos
+clear-changes=Cancelar mudanças
+error=Erro
+
+# Authz
+# Authz Common
+authz-authorization=Autorização
+authz-owner=Proprietário
+authz-uri=URI
+authz-scopes=Escopos
+authz-resource=Recurso
+authz-resource-type=Tipo de recurso
+authz-resources=Recursos
+authz-scope=Escopo
+authz-authz-scopes=Autorização de escopos
+authz-policies=Políticas
+authz-permissions=Permissões
+authz-evaluate=Avaliar
+authz-icon-uri=URI do ícone
+authz-select-scope=Selecione um escopo
+authz-select-resource=Selecione um recurso
+authz-associated-policies=Políticas associadas
+authz-any-resource=Qualquer recurso
+authz-any-scope=Qualquer escopo
+authz-any-role=Qualquer role
+authz-policy-evaluation=Avaliação da política
+authz-select-client=Selecione um cliente
+authz-select-user=Selecione um usuário
+authz-entitlements=Direitos
+authz-no-resources=Nenhum recurso
+authz-result=Resultado
+authz-authorization-services-enabled=Autorização habilitada
+authz-required=Obrigatório
+
+# Authz Settings
+
+authz-policy-enforcement-mode=Modo de execução da política
+authz-policy-enforcement-mode-enforcing=Restritiva
+authz-policy-enforcement-mode-permissive=Permissiva
+authz-policy-enforcement-mode-disabled=Desabilitada
+
+authz-remote-resource-management=Administração remota de recursos
+
+authz-export-settings=Exportar configurações
+
+# Authz Resource List
+authz-no-resources-available=Nenhum recurso disponível.
+authz-no-scopes-assigned=Nenhum escopo associado.
+authz-no-type-defined=Nenhum tipo definido.
+authz-no-permission-assigned=Nenhuma permissão associada.
+authz-no-policy-assigned=Nenhuma política associada.
+authz-create-permission=Criar permissão
+
+# Authz Resource Detail
+authz-add-resource=Adicionar recurso
+
+# Authz Scope List
+authz-add-scope=Adicionar escopo
+authz-no-scopes-available=Nenhum escopo disponível.
+
+# Authz Scope Detail
+
+# Authz Policy List
+authz-all-types=Todos os tipos
+authz-create-policy=Criar política
+authz-no-policies-available=Nenhuma política disponível
+
+# Authz Policy Detail
+authz-policy-logic=Lógica
+authz-policy-logic-positive=Positiva
+authz-policy-logic-negative=Negativa
+authz-policy-apply-policy=Aplicar política
+authz-policy-decision-strategy=Estratégia de decisão
+authz-policy-decision-strategy-affirmative=Afirmativa
+authz-policy-decision-strategy-unanimous=Unânime
+authz-policy-decision-strategy-consensus=Consensual
+authz-select-a-policy=Selecionar uma política
+
+# Authz Role Policy Detail
+authz-add-role-policy=Adicionar política de Role
+authz-no-roles-assigned=Nenhum role associado
+
+# Authz User Policy Detail
+authz-add-user-policy=Adicionar política de usuário
+authz-no-users-assigned=Nenhum usuário associado
+
+# Authz Time Policy Detail
+authz-add-time-policy=Adicionar política de tempo
+authz-policy-time-not-on-after=Não em ou depois
+
+# Authz Drools Policy Detail
+authz-add-drools-policy=Adicionar política Drools
+authz-policy-drools-maven-artifact-resolve=Resolver
+authz-policy-drools-maven-artifact=Artefato maven de política
+authz-policy-drools-module=Módulo
+authz-policy-drools-session=Sessão
+authz-policy-drools-update-period=Atualizar período
+
+# Authz JS Policy Detail
+authz-add-js-policy=Adicionar política Javascript
+authz-policy-js-code=Código
+
+
+# Authz Aggregated Policy Detail
+authz-aggregated=Agregado
+authz-add-aggregated-policy=Adicionar política agregada
+
+# Authz Permission List
+authz-no-permissions-available=Nenhuma permissão disponível
+
+# Authz Permission Detail
+
+# Authz Resource Permission Detail
+authz-add-resource-permission=Adicionar permissão para recurso
+authz-permission-resource-apply-to-resource-type=Aplicar ao tipo de recurso
+
+# Authz Scope Permission Detail
+authz-add-scope-permission=Adicionar permissão de escopo
+
+# Authz Evaluation
+authz-evaluation-identity-information=Informação de identidade
+authz-evaluation-new=Nova avaliação
+authz-evaluation-re-evaluate=Re-avaliar
+authz-evaluation-previous=Avaliação anterior
+authz-evaluation-contextual-info=Informação contextual
+authz-evaluation-contextual-attributes=Atributos contextuais
+authz-evaluation-evaluate=Avaliar
+authz-evaluation-any-resource-with-scopes=Qualquer recurso com escopo(s)
+authz-evaluation-no-result=Não foi possível obter nenhum resultado para o pedido de autorização provida. Verifique os recurso(s) ou escopo(s) providos estão associados com alguma política.
+authz-evaluation-no-policies-resource=Nenhma política foi encontrada para este recurso.
+authz-evaluation-authorization-data=Resposta
+authz-show-authorization-data=Exibir dados da autorização
+
+usermodel.prop.tooltip=Nome do método da propriedade na interface UserModel. Por exemplo, o valor 'email' iria referenciar o método UserModel.getEmail() .
+usermodel.attr.tooltip=Nome do atributo do usuário que é uma chave de atributo no mapa UserModel.attribute.
+userSession.modelNote.tooltip=Nome da nota de sessão do usuário salva no mapa UserSessionModel.note.
+multivalued.tooltip=Indica se um atributo suporta múltiplos valores. Se verdadeiro, então a lista de todos os valores desse atributo será definida como o claim. Se falso, então apenas o primeiro valor será utilizado.
+selectRole.tooltip=Entre com o role na caixa à esquerda, ou clique neste botão para navegar e selecionar o role desejado.
+tokenClaimName.tooltip=Nome do claim para inserir no token. Pode ser um nome completo (fully qualified) como 'address.street'. Neste caso, um objeto json aninhado será criado.
+jsonType.tooltip=Tipo JSON que deve ser utilizado para popular o claim json no token. Os valores válidos são Long, int boolean e String.
+includeInIdToken.tooltip=O claim deve ser adicionado ao token de ID?
+includeInAccessToken.tooltip=O claim deve ser adicionado ao token de acesso?
+includeInUserInfo.tooltip=O claim deve ser adicionado à informação do usuário?
+usermodel.clientRoleMapping.clientId.tooltip=ID do cliente para mapeamentos de roles
+usermodel.clientRoleMapping.rolePrefix.tooltip=Um prefixo para cada role do cliente (opcional)
+usermodel.realmRoleMapping.rolePrefix.tooltip=Um prefixo para cada Realm Role (opcional).
+clients.tooltip=Os clientes são aplicativos de browser e serviços web confiáveis em um realm. Esses clientes podem solicitar login. Você também pode definir roles específicos do cliente.
+authz-policy-role-clients.tooltip= Selecione um cliente a fim de filtrar os roles de cliente que podem ser aplicados a esta política.
+direct-access-grants-enabled.tooltip=Habilita o suporte para concessões de acesso direto (Direct Access Grants), o que significa que o cliente tem acesso ao nome de usuário/senha e negocia diretamente com o servidor Keycloak pelo token de acesso. Em termos de especificações OAuth2, habilita suporte de "Resource Owner Password Credentials Grant" para este cliente.
+service-accounts-enabled.tooltip=Permite autenticar este cliente no Keycloak e recuperar tokens de acesso dedicados para este cliente. Em termos da especificações OAuth2, habilita suporte para 'Client Credentials Grants' para este cliente.
+include-authnstatement.tooltip=Deve ser adicionado um statement especificando o método e timestamp nas respostas de login?
+sign-documents.tooltip=Devem os documentos SAML serem assinados pelo realm?
+sign-assertions.tooltip=Devem as asserções dentro dos documentos SAML serem assinadas? Esta configuração não é necessária se o documento já está sendo assinado.
+signature-algorithm.tooltip=O algoritmo de assinatura a ser utilizado para assinar documentos.
+canonicalization-method.tooltip=Canonicalization Method para assinaturas XML.
+encrypt-assertions.tooltip=Devem as asserções SAML serem encriptadas com a chave pública do cliente usando AES?
+client-signature-required.tooltip=O cliente irá assinar os pedidos e respostas saml? E eles devem ser validados?
+force-post-binding.tooltip=Sempre utilizar POST para respostas.
+front-channel-logout.tooltip=Quando marcado, o logout requer um redirecionamento do browser para o cliente. Caso contrário o servidor executo uma invocação em background para o logout.
+force-name-id-format.tooltip=Ignora o NameID de assunto solicitado e utiliza o configurado no console de administração.
+name-id-format.tooltip=O formato de Name ID para utilizar como assunto.
+root-url.tooltip=URL raiz adicionada à URLs relativas
+valid-redirect-uris.tooltip=Padrão de URI válido para onde um navegador pode redirecionar depois de um login bem-sucedido ou sair. Wildcards simples são permitidos, por exemplo 'http://example.com/*'. Caminhos relativos podem ser especificados também, ex: /my/relative/path/*. Caminhos relativos são relativos à URL raiz do cliente, ou se nenhum for especificado a URL raiz do servidor é usado. Para SAML, é necessário definir padrões de URI válidos se você está contando com a URL do serviço consumidor incorporada com a solicitação de login.
+base-url.tooltip=URL padrão para utilizar quando o servidor de autenticação necessita redirecionar ou linkar para o cliente.
+admin-url.tooltip=URL para a inteface administrativa do cliente. Defina este valor se o cliente suporta a API do adaptador REST. Esta API rest permite que o servidor de autenticação envie políticas de revogação e outras tarefas administrativas. Geralmente este valor é definido apontando para a URL base do cliente.
+master-saml-processing-url.tooltip=Se configurado, esta URL será utilizada para todos os bindings do "SP's Assertion Consumer" e "Single Logout Services". Ela pode ser sobreescriva idnvidualmente para cada ligação e serviço na Configuração Detalhada do Endpoint SAML.
+idp-sso-url-ref.tooltip=Nome do fragmento URL para referenciar o cliente quando você deseja um SSO iniciado por IDP. Deixar este campo vazio irá desabilitar SSO iniciado por IDP. A URL que você irá referenciar do seu browser será: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name}
+idp-sso-relay-state.tooltip=O estado de Relay que você deseja enviar com um pedido SAML quando você deseja realizar SSO iniciado por IDP.
+web-origins.tooltip=Permitir origens CORS. Para permitir todas as URIs de redirecionamento de origem válidas adicionar '+'. Para permitir todas as origens adicionar '*'.
+fine-saml-endpoint-conf.tooltip=Expanda esta sessão para configurar URLs específics para 'Assertion Consumer' e 'Single Logout Service'.
+assertion-consumer-post-binding-url.tooltip=URL de ligação SAML via post para as asserções de consumidor de serviços do cliente (respostas de login). Você pode deixar este campo em branco se você não tiver uma URL para esta ligação.
+assertion-consumer-redirect-binding-url.tooltip=URL de ligação SAML de redirecionamento para as asserções de consumidor de serviços do cliente (respostas de login). Você pode deixar este campo em branco se você não tiver uma URL para esta ligação.
+logout-service-binding-post-url.tooltip=URL de ligação SAML via post para o serviço de logout único do cliente. Voce pode deixar este campo em branco se estiver usando uma ligação diferente.
+logout-service-redir-binding-url.tooltip=URL de ligação SAML de redirecionamento para o serviço de logout único do cliente. Voce pode deixar este campo em branco s e estiver usando uma ligação diferente.
+mappers.tooltip=Mapeamentos de protocolo executam transformações em tokens e documentos. Eles podem realizar coisas como mapear dados de usuários para claims de protocolo, ou apenas transformar qualquer solicitação entre o cliente e o servidor de autenticação.
+scope.tooltip=Escopos de mapeamento permitem que você restrinja quais mapeamentos de roles de usuário são inclusos nos tokens de acesso solicitado pelo cliente.
+ldap.search-scope.tooltip=Para um nível nós pesquisamos somente os usuários nos DNs especificados pelo campo User DNs. Para subtree, nós pesquisamos na sub-árvore completa. Verifique a documentação do LDAP para mais detalhes.
+authz-permission-scope-scope.tooltip=Define que esta permissões deve ser aplicada para um ou mais escopos.
+sessions.tooltip=Exibir as sessões ativas para este cliente. Permite que você veja quais usuários estão ativos e quando eles logaram.
+active-sessions.tooltip=Total de sessões de usuário ativas para este cliente.
+show-sessions.tooltip=Atenção, esta é uma operação potencialmente cara dependendo do número de sessões ativas.
+offline-access.tooltip=Exibe as sessões offline para este cliente. Permite que você veja quantos usuários obtém tokens offline e quando eles os obtiveram. PAra revogar todos os tokens do cliente, vá para a aba Revogações e defina o valor do campo 'não antes de' para 'agora'.
+installation.tooltip=Ferramenta de auxílio para gerar vários formatos de adaptadores de cliente que você poderá fazer download depois ou copiar e colar para configurar seus cliente.
+service-account-roles.tooltip=Permite que você autentique mapeamentos de roles para as contas de serviço dedicadas à este cliente.
+client-authenticator.tooltip=Autenticador de Cliente usado para autenticar este cliente ao servidor Keycloak
+certificate.tooltip=Certificado do cliente para validar JWT emitidos pelo cliente e assinados pela chave privada do cliente da sua keystore.
+validating-x509-certificate.tooltip=O certificado em formato PEM que deve ser usado para verificar assinaturas.
+archive-format.tooltip=Keystore Java ou arquivo em formato PKCS12.
+key-alias.tooltip=Alias do arquivo para sua chave privada e certificado.
+key-password.tooltip=Senha para acessar a chave privada no certificado.
+store-password.tooltip=Senha para acessar o arquivo em si.
+jwt-import.key-alias.tooltip=Alias do arquivo para o seu certificado.
+registrationAccessToken.tooltip=O token de acesso para registro provê acesso aos cliente para o serviço de registro cliente.
+scope-param-required.tooltip=Este role somente será concedido se os parâmetros de escopo com os nomes dos roles forem utilizados durante a autorização/solicitação de token.
+composite-roles.tooltip=Quando este role é associado/removido de um usuário, qualquer role associado com ele também será adicionado/removido implicitamente.
+composite.associated-realm-roles.tooltip=Roles de nível de realm associados com este role composto.
+composite.available-realm-roles.tooltip=Roles de nível de realm disponíveis para este role composto.
+available-roles.tooltip=Roles para este cliente que você pode associar a este role composto.
+scope.available-roles.tooltip=Roles do Realm que podem ser associados a este escopo.
+service-account.available-roles.tooltip=Roles do Realm que podem ser associados a contas de serviço.
+client.associated-roles.tooltip=Roles do Cliente associados a este role composto.
+full-scope-allowed.tooltip=Permite a você desabilitar todas as restrições.
+assigned-roles.tooltip=Roles do Realm associados ao escopo.
+service-account.assigned-roles.tooltip=Roles do Realm associados a conta de serviço.
+group.assigned-roles.tooltip=Roles do Realm mapeados ao grupo.
+realm.effective-roles.tooltip=Roles do Realm associados que podem ter sido herdados de um role composto.
+select-client-roles.tooltip=Selecione o cliente para visualizar os roles de cliente.
+assign.available-roles.tooltip=Roles de Cliente disponíveis para associação.
+client.assigned-roles.tooltip=Roles de Cliente associados.
+client.effective-roles.tooltip=Roles de cliente associados que podem ter sido herdados de um role composto.
+node-reregistration-timeout.tooltip=Intervalo para especificar o tempo máximo para nós clientes de cluster registrados se re-registrarem. Se os nós do cluster não enviarem solicitações de re-registro dentro deste intervalo eles serão deregistrados do Keycloak.
+client-revoke.not-before.tooltip=Revocar qualquer token emitido antes desta data para este cliente.
+client-revoke.push.tooltip=Se a URL de administração estiver configurada para este cliente, envie esta política para este cliente.
+offline-tokens.tooltip=Número total de tokens offline para este cliente.
+show-offline-tokens.tooltip=Atenção, esta é uma operação potencialmente cara dependendo do número de tokens offline.
+realm-certificate-alias.tooltip=O certificado do Realm também é guardado em arquivo. Este é o alias para ele.
+saml-encryption-key.tooltip=Chave de encriptação SAML.
+mapper.consent-required.tooltip=Ao conceder acesso temporário, deve o usuário consentir em prover esta informação para o cliente?
+consent-text.tooltip=Texto para exibir na página de consentimento
+mapper-type.tooltip=Tipo do mapeamento
+redirect-uri.tooltip=A url de redirecionamento para usar quando da configuração do provedor de identidade.
+identity-provider.alias.tooltip=O alias é o identificador único de um provedor de identidade e também é utilizado para construir a uri de redirecionamento.
+identity-provider.enabled.tooltip=Habilita/Desabilita este provedor de identidade.
+identity-provider.authenticate-by-default.tooltip=Indica se este provedor deve ser tentado por padrão para a autenticação mesmo ante de exibir a tela de login.
+identity-provider.store-tokens.tooltip=Habilita/desabilita se os tokens deve ser guardados depois de autenticar os usuários.
+identity-provider.stored-tokens-readable.tooltip=Habilita/desabilita se novos usuários podem ler quaisquer tokens salvo. Isto irá adicionar o role broker.read-token.
+update-profile-on-first-login.tooltip=Define condições onde um usuário precisa atualizar o seu perfil durante o primeiro login.
+trust-email.tooltip=Se habilitado então o e-mail provido por este provedor não será verificado mesmo que a verificação esteja habilitada para este realm.
+first-broker-login-flow.tooltip=Alias do fluxo de autenticação que será invocado depois do primeiro login com este provedor de identificação. O termo 'Primeiro Login' significa que ainda não existe uma conta no Keycloak ligada a esta conta autenticada neste provedor.
+post-broker-login-flow.tooltip=Alias do fluxo de autenticação que será invocado depois de cada login com esse provedor de identificação. É útil se você pretende adicionar verificações adicionais de cada usuário autenticado com este provedor (por exemplo OTP). Deixa vazio se você não deseja que nenhum autenticador adicionar seja invocado depois do login com este provedor de identificação. Note também que as implementações de autenticação devem assumir que o usuários já está definido na ClientSessioncom e com o provedor de identidade já definido.
+openid-connect-config.tooltip=OIDC SP e configuração externa IDP.
+authorization-url.tooltip=A URL de autorização.
+token-url.tooltip=A URL do Token.
+identity-provider.logout-url.tooltip='End session endpoint' para utilizar para realizar logour dos usuários do IDP externo.
+backchannel-logout.tooltip=O IDP externo suporta logou via backchannel?
+user-info-url.tooltip=A Url de informações de usuário. Opcional.
+identity-provider.client-id.tooltip=O cliente ou identificador do cliente registrado junto ao provedor de identificação.
+client-secret.tooltip=O cliente ou senha do cliente registrado junto ao provedor de identificação.
+social.client-secret.tooltip=A senha do cliente registrado junto ao provedor de identificação.
+issuer.tooltip=O identificador de emissor para o emissor da resposta. Se não for provido nenhuma validação será realizada.
+identity-provider.default-scopes.tooltip=Os escopos que serão enviados ao solicitar autorização. Pode ser uma lista de escopos separadas por espaço. Valor padrão é 'openid'.
+prompt.tooltip=Especifica se o Servidor de Autorização solicita ao Usuário Final reautenticação e consentimento.
+identity-provider.validate-signatures.tooltip=Habilita/Desabilita a validação de assinatura de IDP externo.
+identity-provider.validating-public-key.tooltip=A chave pública em formato PEM que deve ser usada para verificar assinaturas de IDP externos.
+import-external-idp-config.tooltip=Permite que vocÊ carregue metadata de IDP externos de um arquivo de configuração ou baixando a partir de uma URL.
+identity-provider.import-from-url.tooltip=Importar metadata de um descritor de descoberta remoto do IDP.
+identity-provider.import-from-file.tooltip=Importar metadata fr um descritor de descoberta baixado do IDP.
+identity-provider.saml-config.tooltip=SAML SP e configuração de IDP externo.
+saml.single-signon-service-url.tooltip=A Url que deve ser utilizada para enviar solicitações de autenticação (SAML AuthnRequest).
+saml.single-logout-service-url.tooltip=A Url que deve ser utilizada para enviar solicitações de logout.
+nameid-policy-format.tooltip=Especifica a referência de URI correspondente a um formato de nome identificador. O padrão é urn:oasis:names:tc:SAML:2.0:nameid-format:persistent.
+http-post-binding-response.tooltip=Indica se deve se responder a solicitações utilizando HTTP-POST. Se falso, HTTP-REDIRECT será utilizado.
+http-post-binding-for-authn-request.tooltip=Indica se o AuthnRequest deve ser enviado utilizando HTTP-POST. Se falso, HTTP-REDIRECT será utilizado.
+want-authn-requests-signed.tooltip=Indicate se um provedor de identificação deve experar um AuthnRequest assinado.
+identity-provider.force-authentication.tooltip=Indica se um provedor de identificação deve autenticar o apresentador diretamente ao invés de confiar em um contexto de segurança anterior.
+saml.validate-signature.tooltip=Habilita/Desabilita validação de assinaturas de respostas SAML.
+saml.import-from-url.tooltip=Importar metadata de um descritor de entidade IDP SAML remoto.
+social.client-id.tooltip=O identificador do cliente registrado com o provedor de identificação.
+social.default-scopes.tooltip=Os escopos que serão enviados ao solicitar autorização. Veja a documentação para valores possíveis, separador e valores padrão.
+stackoverflow.key.tooltip=A chave de cliente obtida do registro no Stack Overflow.
+client-templates.tooltip=Modelos de cliente permitem que você defina configurações comuns que serão compartilhadas entre múltiplos clientes.
+group.add-selected.tooltip=Roles do Realm que serão associadas ao grupo.
+group.effective-roles.tooltip=Todos os mapeamentos de roles do Realm. Alguns roles exibidos podem ter sido herdados de um role composto mapeado.
+group.available-roles.tooltip=Roles associáveis deste cliente.
+group.assigned-roles-client.tooltip=Mapeamentos de roles para este cliente.
+group.effective-roles-client.tooltip=Mapeamentos de roles para este cliente. Alguns roles exibidos podem ter sido herdados de um role composto mapeado.
+user.add-selected.tooltip=Roles do Realm que podem ser associados ao usuário.
+user.assigned-roles.tooltip=Roles do Realm mapeados para o usuário.
+user.effective-roles.tooltip=Todos os mapeamentos de roles do Realm. Alguns roles exibidos podem ter sido herdados de um role composto mapeado.
+user.available-roles.tooltip=Roles associáveis deste cliente.
+user.assigned-roles-client.tooltip=Mapeamentos de roles para este cliente.
+user.effective-roles-client.tooltip=Mapeamentos de Role para este cliente. Alguns roles exibidos podem ter sido herdados de um role composto mapeado.
+default.available-roles.tooltip=Roles do nível de Realm que podem ser associados.
+realm-default-roles.tooltip=Roles do nível de Realm associados a novos usuários.
+default.available-roles-client.tooltip=Roles para este cliente que são associáveis por padrão.
+client-default-roles.tooltip=Roles para este cliente que são associados como roles padrão.
+composite.available-roles.tooltip=Roles do nível de Realm associáveis com este role composto.
+composite.associated-roles.tooltip=Roles do nível de Realm associados com este role composto.
+composite.available-roles-client.tooltip=Roles para este cliente que podem ser associados com este role composto.
+composite.associated-roles-client.tooltip=Roles do cliente associados com este role composto.
+partial-import.tooltip=Importação parcial permite que você importe usuários, clientes, e outros recursos de um arquivo json previamente exportado.
+if-resource-exists.tooltip=Especifica o que deve ser feito se você tentar importar um recurso já existente.
+realm-roles.tooltip=Roles do Realm que podem ser selecionados.
+authz-policy-role-realm-roles.tooltip=Especifica quais role(s) de *realm* são permitidos por esta política.
+client-roles.tooltip=Roles do cliente que podem ser selecionados.
+authz-policy-role-client-roles.tooltip=Especifica quais role(s) do *cliente* são permitidos por esta política.
+client-template.tooltip=Modelo de cliente do qual ete cliente herda as configurações.
+client-template.name.tooltip=Nome do modelo de cliente. Deve ser único neste Realm.
+client-template.description.tooltip=Descrição do modelo de cliente.
+client-template.protocol.tooltip=Qual configuração de protocolo SSO será provida por este modelo de cliente.
+console-display-name.tooltip=Nome de exibição do provedor quando linkado no console de administração.
+priority.tooltip=Prioridade do provedor quando da busca de usuários. Valores mais baixos são utilizados primeiro.
+periodic-full-sync.tooltip=Habilitar ou não a sincronização completa periódica dos usuários deste provedor.
+ldap.periodic-full-sync.tooltip=Habilitar ou não a sincronização completa dos usuários do LDAP para o Keycloak.
+full-sync-period.tooltip=Intervalo para a sincronização completa em segundos.
+periodic-changed-users-sync.tooltip=Habilitar ou não a sincronização de usuários novos ou alterados do provedor para o Keycloak.
+ldap.periodic-changed-users-sync.tooltip=Habilitar ou não a sincronização de usuários novos ou alterados do LDAP para o Keycloak.
+changed-users-sync-period.tooltip=Intervalo para sincronização dos usuários alterados ou novos do provedor em segundos.
+ldap.changed-users-sync-period.tooltip=Intervalo para sincronização dos usuários alterados ou novos do LDAP em segundos.
+kerberos-realm.tooltip=Nome do realm kerberos. Por exemplo FOO.ORG
+server-principal.tooltip=Nome completo do principal do servidor para o serviço HTTP incluindo o servidor e nome do domínio. Por exemplo HTTP/host.foo.org@FOO.ORG
+keytab.tooltip=Localização do arquivo KeyTab do Kerberos contendo as credenciais do principal do servidor. Por exemplo /etc/krb5.keytab
+debug.tooltip=Habilita/Desabilita log de nível debug para a saída padrão para Krb5LoginModule.
+allow-password-authentication.tooltip=Habilita/Desabilita a possibilidade de autenticação via usuário/senha contra o banco Kerberos
+edit-mode.tooltip=READ_ONLY significa que atualizações de senhas não são permitidas e o usuário sempre autenticará com a senha do Kerberos. UNSYNCED significa que o usuário pode alterar a senha no banco do Keycloak e essa senha será utilizda ao invés da senha do Kerberos.
+ldap.edit-mode.tooltip=READ_ONLY é um LDAP somente leitura. WRITABLE significa que os dados serão sicronizados de volta para o LDAP on demand. UNSYNCED significa que os dados do usuário serão importados, mas não sicronizados de volta para o LDAP.
+update-profile-first-login.tooltip=Atualizar o perfil no primeiro login
+ldap.sync-registrations.tooltip=Os novos usuários criados devem ser criados no LDAP? A prioridade afeta qual provedor é utilizado para sincronizar o novo usuário.
+ldap.vendor.tooltip=LDAP vendor (provedor)
+username-ldap-attribute.tooltip=Nome do atributo do LDAP que será mapeado como nome do usuário no Keycloak. Para muitos servidores LDAP este valor deve ser 'uid'. Para o Active Directory pode ser 'sAMAccountName' ou 'cn'. O atributo deve ser preenchido para todos os registros de usuários do LDAP que você deseja importar do LDAP para o Keycloak.
+rdn-ldap-attribute.tooltip=Nome do atributo LDAP que é utilizado como RDN (atributo topo) do DN do usuário típico. Geralmente é o mesmo que o atributo do nome do usuário, mas isto não é obrigatório. Por exemplo para o Active Directory é comum utilizar 'cn' como atributo RDN quando o atributo do nome de usuário pode ser 'sAMAccountName',
+uuid-ldap-attribute.tooltip=Nome do atributo LDAP que é utilizado como identificador único do objeto(UUID) para objetos no LDAP. Para muitos servidores LDAP o valor é 'entryUUID', porém alguns são diferentes. Por exemplo para o Active Directory este valor para objetos no LDAP deve ser 'objectGUID'. Se o seu servidor LDAP realmente não suporta a noção de UUID, vocÊ pode usar qualquer outro atributo que seja único na árvore de usuários do LDAP. Por exemplo 'uid' ou 'entryDN'.
+ldap.user-object-classes.tooltip=Todos os valores de objectClass para usuários no LDAP separados por vírgula. Por exemplo: 'inetOrgPerson, organizationalPerson'. Usuários criados no Keycloak serão enviados para o LDAP com todas essas classes de objetos associadas e dados de usuários do LDAP somente serão localizados se ele contiverem todas estas classes de objeto.
+ldap.connection-url.tooltip=Conexão URL para o seu servidor LDAP
+ldap.users-dn.tooltip=DN completo da árvore LDAP onde os usuários estão. Este DN é o pai dos usuários do LDAP. Por exemplo pode ser 'ou=users,dc=example,dc=com' entendendo que o usuário típico irá ter um DN como 'uid=john,ou=users,dc=example,dc=com'.
+ldap.authentication-type.tooltip=Tipo de autenticação no LDAP. No momento apenas os mecanismos 'none' (anonymous LDAP authentication) ou 'simple' (Credencial de bind + senha para bind) estão disponíveis.
+ldap.bind-dn.tooltip=DN do administrador do LDAP, que será utilizado pelo Keycloak para acessar o servidor LDAP.
+ldap.bind-credential.tooltip=Senha do administrador do LDAP
+ldap.custom-user-ldap-filter.tooltip=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 ')'
+ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in keycloak-server.json. 'Always' means that it will always use it. 'Never' means that it won't use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if keycloak-server.json is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used.
+ldap.connection-pooling.tooltip=Does Keycloak should use connection pooling for accessing LDAP server
+ldap.pagination.tooltip=Does the LDAP server support pagination.
+ldap.allow-kerberos-authentication.tooltip=Enable/disable HTTP authentication of users with SPNEGO/Kerberos tokens. The data about authenticated users will be provisioned from this LDAP server
+ldap.use-kerberos-for-password-authentication.tooltip=Use Kerberos login module for authenticate username/password against Kerberos server instead of authenticating against LDAP server with Directory Service API
+ldap.batch-size.tooltip=Count of LDAP users to be imported from LDAP to Keycloak within single transaction.
+identity-provider-user-id.tooltip=Unique ID of the user on the Identity Provider side
+identity-provider-username.tooltip=Username on the Identity Provider side
+browser-flow.tooltip=Select the flow you want to use for browser authentication.
+registration-flow.tooltip=Select the flow you want to use for registration.
+direct-grant-flow.tooltip=Select the flow you want to use for direct grant authentication.
+reset-credentials.tooltip=Select the flow you want to use when the user has forgotten their credentials.
+client-authentication.tooltip=Select the flow you want to use for authentication of clients.
+authenticator.alias.tooltip=Name of the configuration
+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.tooltip=What hashing algorithm should be used to generate the OTP.
+otp.number-of-digits.tooltip=How many digits should the OTP have?
+otp.look-ahead-window.tooltip=How far ahead should the server look just in case the token generator and server are out of time sync or counter sync?
+otp.initial-counter.tooltip=What should the initial counter value be?
+otp-token-period.tooltip=How many seconds should an OTP token be valid? Defaults to 30 seconds.
+admin-events.tooltip=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.
+clear-admin-events.tooltip=Deletes all admin events in the database.
+resource-path.tooltip=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.
+auth.default-action.tooltip=If enabled, any new user will have this required action assigned to it.
+groups.default-groups.tooltip=Set of groups that new users will automatically join.
+flow.alias.tooltip=Specifies display name for the flow.
+top-level-flow-type.tooltip=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
+flow-type.tooltip=What kind of form is it
+default-groups.tooltip=Newly created or registered users will automatically be added to these groups
+available-groups.tooltip=Select a group you want to add as a default.
+membership.available-groups.tooltip=Groups a user can join. Select a group and click the join button.
+events.tooltip=Displays saved events for the realm. Events are related to user accounts, for example a user login. To enable persisted events go to config.
+login.save-events.tooltip=If enabled login events are saved to the database which makes events available to the admin and account management consoles.
+admin.save-events.tooltip=If enabled admin events are saved to the database which makes events available to the admin console.
+events-config.tooltip=Displays configuration options to enable persistence of user and admin events.
+event-listeners.tooltip=Configure what listeners receive events for the realm.
+clear-events.tooltip=Deletes all events in the database.
+events.expiration.tooltip=Sets the expiration for events. Expired events are periodically deleted from the database.
+saved-types.tooltip=Configure what event types are saved.
+include-representation.tooltip=Include JSON representation for create and update requests.
+credentials.temporary.tooltip=If enabled user is required to change password on next login
+credentials.remove-totp.tooltip=Remove one time password generator for user.
+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-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.
+user-enabled.tooltip=A disabled user cannot login.
+user-temporarily-locked.tooltip=The user may have been locked due to failing to login too many times.
+email-verified.tooltip=Has the user's email been verified?
+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.
+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.
+group-membership.tooltip=Groups user is a member of. Select a listed group and click the Leave button to leave the group.
+import-keys-and-cert.tooltip=Upload the client's key pair and cert.
+authz-icon-uri.tooltip=An URI pointing to an icon.
+authz-authorization-services-enabled.tooltip=Enable/Disable fine-grained authorization support for a client
+authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server.
+authz-policy-enforcement-mode.tooltip=The policy enforcement mode dictates how policies are enforced when evaluating authorization requests. 'Enforcing' means requests are denied by default even when there is no policy associated with a given resource. 'Permissive' means requests are allowed even when there is no policy associated with a given resource. 'Disabled' completely disables the evaluation of policies and allow access to any resource.
+authz-remote-resource-management.tooltip=Should resources be managed remotely by the resource server? If false, resources can only be managed from this admin console.
+authz-export-settings.tooltip=Export and download all authorization settings for this resource server.
+authz-resource-name.tooltip=An unique name for this resource. The name can be used to uniquely identify a resource, useful when querying for a specific resource.
+authz-resource-owner.tooltip=The owner of this resource.
+authz-resource-type.tooltip=The type of this resource. It can be used to group different resource instances with the same type.
+authz-resource-uri.tooltip=An URI that can also be used to uniquely identify this resource.
+authz-resource-scopes.tooltip=The scopes associated with this resource.
+authz-scope-name.tooltip=An unique name for this scope. The name can be used to uniquely identify a scope, useful when querying for a specific scope.
+authz-policy-name.tooltip=The name of this policy.
+authz-policy-description.tooltip=A description for this policy.
+authz-policy-logic.tooltip=The logic dictates how the policy decision should be made. If 'Positive', the resulting effect (permit or deny) obtained during the evaluation of this policy will be used to perform a decision. If 'Negative', the resulting effect will be negated, in other words, a permit becomes a deny and vice-versa.
+authz-policy-apply-policy.tooltip=Specifies all the policies that must be applied to the scopes defined by this policy or permission.
+authz-policy-decision-strategy.tooltip=The decision strategy dictates how the policies associated with a given policy are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order to the overall decision be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order to the overall decision be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative.
+authz-policy-user-users.tooltip=Specifies which user(s) are allowed by this policy.
+authz-policy-time-not-before.tooltip=Defines the time before which the policy MUST NOT be granted. Only granted if current date/time is after or equal to this value.
+authz-policy-time-not-on-after.tooltip=Defines the time after which the policy MUST NOT be granted. Only granted if current date/time is before or equal to this value.
+authz-policy-drools-maven-artifact.tooltip=A Maven GAV pointing to an artifact from where the rules would be loaded from. Once you have provided the GAV, you can click *Resolve* to load both *Module* and *Session* fields.
+authz-policy-drools-module.tooltip=The module used by this policy. You must provide a module in order to select a specific session from where rules will be loaded from.
+authz-policy-drools-session.tooltip=The session used by this policy. The session provides all the rules to evaluate when processing the policy.
+authz-policy-drools-update-period.tooltip=Specifies an interval for scanning for artifact updates.
+authz-policy-js-code.tooltip=The JavaScript code providing the conditions for this policy.
+authz-permission-name.tooltip=The name of this permission.
+authz-permission-description.tooltip=A description for this permission.
+authz-permission-resource-apply-to-resource-type.tooltip=Specifies if this permission would be applied to all resources with a given type. In this case, this permission will be evaluated for all instances of a given resource type.
+authz-permission-resource-resource.tooltip=Specifies that this permission must be applied to a specific resource instance.
+authz-permission-resource-type.tooltip=Specifies that this permission must be applied to all resources instances of a given type.
+authz-permission-scope-resource.tooltip=Restrict the scopes to those associated with the selected resource. If not selected all scopes would be available.
+authz-evaluation-identity-information.tooltip=The available options to configure the identity information that will be used when evaluating policies.
+authz-evaluation-client.tooltip=Select the client making this authorization request. If not provided, authorization requests would be done based on the client you are in.
+authz-evaluation-user.tooltip=Select an user whose identity is going to be used to query permissions from the server.
+authz-evaluation-role.tooltip=Select the roles you want to associate with the selected user.
+authz-evaluation-contextual-info.tooltip=The available options to configure any contextual information that will be used when evaluating policies.
+authz-evaluation-contextual-attributes.tooltip=Any attribute provided by a running environment or execution context.
+authz-evaluation-permissions.tooltip=The available options to configure the permissions to which policies will be applied.
+authz-evaluation-result.tooltip=The overall result for this permission request.
+authz-evaluation-scopes.tooltip=The list of allowed scopes.
+authz-evaluation-policies.tooltip=Details about which policies were evaluated and their decisions.
+authz-evaluation-authorization-data.tooltip=Represents a token carrying authorization data as a result of the processing of an authorization request. This representation is basically what Keycloak issues to clients asking for permissions. Check the 'authorization' claim for the permissions that were granted based on the current authorization request.
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties
index c28da56..6a59cc9 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties
@@ -233,8 +233,8 @@ assertion-consumer-post-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u
assertion-consumer-post-binding-url.tooltip=SAML POST \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u0432 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u043E\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043B\u044F (\u0437\u0430\u043F\u0440\u043E\u0441\u044B \u0432\u0445\u043E\u0434\u0430). \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u043D\u0435 \u0438\u043C\u0435\u0435\u0442\u0435 URL \u0434\u043B\u044F \u043E\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043B\u0435\u043D\u0438\u044F \u0442\u0430\u043A\u043E\u0439 \u0441\u0432\u044F\u0437\u043A\u0438.
assertion-consumer-redirect-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0438 \u0441 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u043C \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u043E\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043B\u044F
assertion-consumer-redirect-binding-url.tooltip=SAML \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u044F \u043D\u0430 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u043E\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043B\u044F (\u0437\u0430\u043F\u0440\u043E\u0441\u044B \u0432\u0445\u043E\u0434\u0430). \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0432\u044B \u043D\u0435 \u0438\u043C\u0435\u0435\u0442\u0435 URL \u0434\u043B\u044F \u0442\u0430\u043A\u043E\u0433\u043E \u0441\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u044F.
-logout-service-binding-post-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043B\u044F POST-\u043C\u0435\u0442\u043E\u0434\u0430
-logout-service-binding-post-url.tooltip=SAML POST \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0435\u0434\u0438\u043D\u043E\u0433\u043E \u0432\u044B\u0445\u043E\u0434\u0430. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0432\u044B \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 \u0440\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0435 \u0441\u0432\u044F\u0437\u0438
+logout-service-post-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043B\u044F POST-\u043C\u0435\u0442\u043E\u0434\u0430
+logout-service-post-binding-url.tooltip=SAML POST \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0435\u0434\u0438\u043D\u043E\u0433\u043E \u0432\u044B\u0445\u043E\u0434\u0430. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0432\u044B \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 \u0440\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0435 \u0441\u0432\u044F\u0437\u0438
logout-service-redir-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0438 \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430
logout-service-redir-binding-url.tooltip=SAML \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0443\u0435\u0442 \u043D\u0430 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u0435\u0434\u0438\u043D\u043E\u0439 \u0442\u043E\u0447\u043A\u0438 \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u043E\u0432. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 \u0440\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0435 \u0441\u0432\u044F\u0437\u0438.
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 345cb25..f4044ab 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
@@ -14,4 +14,11 @@ ldapErrorCantWriteOnlyForReadOnlyLdap=Can't set write only when LDAP provider mo
ldapErrorCantWriteOnlyAndReadOnly=Can't set write-only and read-only together
clientRedirectURIsFragmentError=Redirect URIs must not contain an URI fragment
-clientRootURLFragmentError=Root URL must not contain an URL fragment
\ No newline at end of file
+clientRootURLFragmentError=Root URL must not contain an URL fragment
+
+pairwiseMalformedClientRedirectURI=Client contained an invalid redirect URI.
+pairwiseClientRedirectURIsMissingHost=Client redirect URIs must contain a valid host component.
+pairwiseClientRedirectURIsMultipleHosts=Without a configured Sector Identifier URI, client redirect URIs must not contain multiple host components.
+pairwiseMalformedSectorIdentifierURI=Malformed Sector Identifier URI.
+pairwiseFailedToGetRedirectURIs=Failed to get redirect URIs from the Sector Identifier URI.
+pairwiseRedirectURIsMismatch=Client redirect URIs does not match redirect URIs fetched from the Sector Identifier URI.
diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_lt.properties b/themes/src/main/resources/theme/base/admin/messages/messages_lt.properties
new file mode 100644
index 0000000..4908925
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/messages/messages_lt.properties
@@ -0,0 +1,17 @@
+invalidPasswordMinLengthMessage=Per trumpas slapta\u00c5\u00beodis: ma\u00c5\u00beiausias ilgis {0}.
+invalidPasswordMinLowerCaseCharsMessage=Neteisingas slapta\u00c5\u00beodis: privaloma \u00c4\u00c6vesti {0} ma\u00c5\u00be\u00c4\u2026j\u00c4\u2026 raid\u00c4\u2122.
+invalidPasswordMinDigitsMessage=Neteisingas slapta\u00c5\u00beodis: privaloma \u00c4\u00c6vesti {0} skaitmen\u00c4\u00c6.
+invalidPasswordMinUpperCaseCharsMessage=Neteisingas slapta\u00c5\u00beodis: privaloma \u00c4\u00c6vesti {0} did\u00c5\u00bei\u00c4\u2026j\u00c4\u2026 raid\u00c4\u2122.
+invalidPasswordMinSpecialCharsMessage=Neteisingas slapta\u00c5\u00beodis: privaloma \u00c4\u00c6vesti {0} special\u00c5\u00b3 simbol\u00c4\u00c6.
+invalidPasswordNotUsernameMessage=Neteisingas slapta\u00c5\u00beodis: slapta\u00c5\u00beodis negali sutapti su naudotojo vardu.
+invalidPasswordRegexPatternMessage=Neteisingas slapta\u00c5\u00beodis: slapta\u00c5\u00beodis netenkina regex taisykl\u00c4\u2014s(i\u00c5\u00b3).
+invalidPasswordHistoryMessage=Neteisingas slapta\u00c5\u00beodis: slapta\u00c5\u00beodis negali sutapti su prie\u00c5\ufffd tai buvusiais {0} slapta\u00c5\u00beod\u00c5\u00beiais.
+
+ldapErrorInvalidCustomFilter=Sukonfig\u016Bruotas LDAP filtras neprasideda "(" ir nesibaigia ")" simboliais.
+ldapErrorMissingClientId=Privaloma nurodyti kliento ID kai srities roli\u0173 susiejimas n\u0117ra nenaudojamas.
+ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Grupi\u0173 paveld\u0117jimo ir UID naryst\u0117s tipas kartu negali b\u016Bti naudojami.
+ldapErrorCantWriteOnlyForReadOnlyLdap=Negalima nustatyti ra\u0161ymo r\u0117\u017Eimo kuomet LDAP teik\u0117jo r\u0117\u017Eimas ne WRITABLE
+ldapErrorCantWriteOnlyAndReadOnly=Negalima nustatyti tik ra\u0161yti ir tik skaityti kartu
+
+clientRedirectURIsFragmentError=Nurodykite URI fragment\u0105, kurio negali b\u016Bti peradresuojamuose URI adresuose
+clientRootURLFragmentError=Nurodykite URL fragment\u0105, kurio negali b\u016Bti \u0161akniniame URL adrese
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_no.properties b/themes/src/main/resources/theme/base/admin/messages/messages_no.properties
new file mode 100644
index 0000000..43cb61d
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/messages/messages_no.properties
@@ -0,0 +1,14 @@
+invalidPasswordMinLengthMessage=Ugyldig passord: minimum lengde {0}.
+invalidPasswordMinLowerCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} sm\u00E5 bokstaver.
+invalidPasswordMinDigitsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} sifre.
+invalidPasswordMinUpperCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} store bokstaver.
+invalidPasswordMinSpecialCharsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} spesialtegn.
+invalidPasswordNotUsernameMessage=Ugyldig passord: kan ikke v\u00E6re likt brukernavn.
+invalidPasswordRegexPatternMessage=Ugyldig passord: tilfredsstiller ikke kravene for passord-m\u00F8nster.
+invalidPasswordHistoryMessage=Ugyldig passord: kan ikke v\u00E6re likt noen av de {0} foreg\u00E5ende passordene.
+
+ldapErrorInvalidCustomFilter=Tilpasset konfigurasjon av LDAP-filter starter ikke med "(" eller slutter ikke med ")".
+ldapErrorMissingClientId=KlientID m\u00E5 v\u00E6re tilgjengelig i config n\u00E5r sikkerhetsdomenerollemapping ikke brukes.
+ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Ikke mulig \u00E5 bevare gruppearv og samtidig bruke UID medlemskapstype.
+ldapErrorCantWriteOnlyForReadOnlyLdap=Kan ikke sette write-only n\u00E5r LDAP leverand\u00F8r-modus ikke er WRITABLE
+ldapErrorCantWriteOnlyAndReadOnly=Kan ikke sette b\u00E5de write-only og read-only
diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_pt_BR.properties b/themes/src/main/resources/theme/base/admin/messages/messages_pt_BR.properties
index e69de29..b26e465 100644
--- a/themes/src/main/resources/theme/base/admin/messages/messages_pt_BR.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/messages_pt_BR.properties
@@ -0,0 +1,18 @@
+#encoding: utf-8
+invalidPasswordMinLengthMessage=Senha inválida: deve conter ao menos {0} caracteres.
+invalidPasswordMinLowerCaseCharsMessage=Senha inválida: deve conter ao menos {0} caracteres minúsculos.
+invalidPasswordMinDigitsMessage=Senha inválida: deve conter ao menos {0} digitos numéricos.
+invalidPasswordMinUpperCaseCharsMessage=Senha inválida: deve conter ao menos {0} caracteres maiúsculos.
+invalidPasswordMinSpecialCharsMessage=Senha inválida: deve conter ao menos {0} caracteres especiais.
+invalidPasswordNotUsernameMessage=Senha inválida: não deve ser igual ao nome de usuário.
+invalidPasswordRegexPatternMessage=Senha inválida: falha ao passar por padrões.
+invalidPasswordHistoryMessage=Senha inválida: não deve ser igual às últimas {0} senhas.
+
+ldapErrorInvalidCustomFilter=Filtro LDAP não inicia com "(" ou não termina com ")".
+ldapErrorMissingClientId=ID do cliente precisa ser definido na configuração quando mapeamentos de Roles do Realm não é utilizado.
+ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Não é possível preservar herança de grupos e usar tipo de associação de UID ao mesmo tempo.
+ldapErrorCantWriteOnlyForReadOnlyLdap=Não é possível definir modo de somente escrita quando o provedor LDAP não suporta escrita
+ldapErrorCantWriteOnlyAndReadOnly=Não é possível definir somente escrita e somente leitura ao mesmo tempo
+
+clientRedirectURIsFragmentError=URIs de redirecionamento não podem conter fragmentos
+clientRootURLFragmentError=URL raiz não pode conter fragmentos
\ No newline at end of file
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 24efd88..12af561 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
@@ -797,6 +797,12 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
"RS256"
];
+ $scope.requestObjectSignatureAlgorithms = [
+ "any",
+ "none",
+ "RS256"
+ ];
+
$scope.realm = realm;
$scope.samlAuthnStatement = false;
$scope.samlMultiValuedRoles = false;
@@ -806,6 +812,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
$scope.samlEncrypt = false;
$scope.samlForcePostBinding = false;
$scope.samlForceNameIdFormat = false;
+ $scope.disableAuthorizationTab = !client.authorizationServicesEnabled;
function updateProperties() {
if (!$scope.client.attributes) {
@@ -898,7 +905,11 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
}
}
- $scope.userInfoSignedResponseAlg = getSignatureAlgorithm('user.info.response');
+ var attrVal1 = $scope.client.attributes['user.info.response.signature.alg'];
+ $scope.userInfoSignedResponseAlg = attrVal1==null ? 'unsigned' : attrVal1;
+
+ var attrVal2 = $scope.client.attributes['request.object.signature.alg'];
+ $scope.requestObjectSignatureAlg = attrVal2==null ? 'any' : attrVal2;
}
if (!$scope.create) {
@@ -964,23 +975,20 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates,
};
$scope.changeUserInfoSignedResponseAlg = function() {
- changeSignatureAlgorithm('user.info.response', $scope.userInfoSignedResponseAlg);
+ if ($scope.userInfoSignedResponseAlg === 'unsigned') {
+ $scope.client.attributes['user.info.response.signature.alg'] = null;
+ } else {
+ $scope.client.attributes['user.info.response.signature.alg'] = $scope.userInfoSignedResponseAlg;
+ }
};
- function changeSignatureAlgorithm(attrPrefix, attrValue) {
- var attrName = attrPrefix + '.signature.alg';
- if (attrValue === 'unsigned') {
- $scope.client.attributes[attrName] = null;
+ $scope.changeRequestObjectSignatureAlg = function() {
+ if ($scope.requestObjectSignatureAlg === 'any') {
+ $scope.client.attributes['request.object.signature.alg'] = null;
} else {
- $scope.client.attributes[attrName] = attrValue;
+ $scope.client.attributes['request.object.signature.alg'] = $scope.requestObjectSignatureAlg;
}
- }
-
- function getSignatureAlgorithm(attrPrefix) {
- var attrName = attrPrefix + '.signature.alg';
- var attrVal = $scope.client.attributes[attrName];
- return attrVal==null ? 'unsigned' : attrVal;
- }
+ };
$scope.$watch(function() {
return $location.path();
@@ -1695,6 +1703,12 @@ module.controller('ClientProtocolMapperCtrl', function($scope, realm, serverInfo
mapper = angular.copy($scope.mapper);
$location.url("/realms/" + realm.realm + '/clients/' + client.id + "/mappers/" + $scope.model.mapper.id);
Notifications.success("Your changes have been saved.");
+ }, function(error) {
+ if (error.status == 400 && error.data.error_description) {
+ Notifications.error(error.data.error_description);
+ } else {
+ Notifications.error('Unexpected error when updating protocol 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 6d8fdcd..a063143 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
@@ -274,7 +274,7 @@ module.service('ServerInfo', function($resource, $q, $http) {
var delay = $q.defer();
$http.get(authUrl + '/admin/serverinfo').success(function(data) {
- info = data;
+ angular.copy(data, info);
delay.resolve(info);
});
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html
index a121f17..ba53e0b 100644
--- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html
@@ -88,10 +88,10 @@
<select class="form-control" name="policy.config.scannerPeriodUnit"
data-ng-model="policy.config.scannerPeriodUnit"
ng-disabled="!policy.config.sessionName">
- <option>{{:: 'seconds' | translate}}</option>
- <option>{{:: 'minutes' | translate}}</option>
- <option>{{:: 'hours' | translate}}</option>
- <option>{{:: 'days' | translate}}</option>
+ <option value="Seconds">{{:: 'seconds' | translate}}</option>
+ <option value="Minutes">{{:: 'minutes' | translate}}</option>
+ <option value="Hours">{{:: 'hours' | translate}}</option>
+ <option value="Days">{{:: 'days' | translate}}</option>
</select>
</div>
<kc-tooltip>{{:: 'authz-policy-drools-update-period.tooltip' | translate}}</kc-tooltip>
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 3b6c48c..4a6312b 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
@@ -19,10 +19,10 @@
max="31536000" data-ng-model="client.nodeReRegistrationTimeout"
id="nodeReRegistrationTimeout" name="nodeReRegistrationTimeout"/>
<select class="form-control" name="nodeReRegistrationTimeoutUnit" data-ng-model="client.nodeReRegistrationTimeoutUnit" >
- <option data-ng-selected="!client.nodeReRegistrationTimeoutUnit">{{:: 'seconds' | translate}}</option>
- <option>{{:: 'minutes' | translate}}</option>
- <option>{{:: 'hours' | translate}}</option>
- <option>{{:: 'days' | translate}}</option>
+ <option data-ng-selected="!client.nodeReRegistrationTimeoutUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
+ <option value="Minutes">{{:: 'minutes' | translate}}</option>
+ <option value="Hours">{{:: 'hours' | translate}}</option>
+ <option value="Days">{{:: 'days' | translate}}</option>
</select>
</div>
</div>
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 af10a22..9fb3a2d 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
@@ -110,7 +110,7 @@
<input ng-model="client.serviceAccountsEnabled" name="serviceAccountsEnabled" id="serviceAccountsEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
</div>
- <div class="form-group" data-ng-show="protocol == 'openid-connect'">
+ <div class="form-group" data-ng-show="serverInfo.profileInfo.previewEnabled && protocol == 'openid-connect'">
<label class="col-md-2 control-label" for="authorizationServicesEnabled">{{:: 'authz-authorization-services-enabled' | translate}}</label>
<kc-tooltip>{{:: 'authz-authorization-services-enabled.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
@@ -348,6 +348,19 @@
</div>
<kc-tooltip>{{:: 'user-info-signed-response-alg.tooltip' | translate}}</kc-tooltip>
</div>
+ <div class="form-group clearfix block" data-ng-show="protocol == 'openid-connect'">
+ <label class="col-md-2 control-label" for="requestObjectSignatureAlg">{{:: 'request-object-signature-alg' | translate}}</label>
+ <div class="col-sm-6">
+ <div>
+ <select class="form-control" id="requestObjectSignatureAlg"
+ ng-change="changeRequestObjectSignatureAlg()"
+ ng-model="requestObjectSignatureAlg"
+ ng-options="sig for sig in requestObjectSignatureAlgorithms">
+ </select>
+ </div>
+ </div>
+ <kc-tooltip>{{:: 'request-object-signature-alg.tooltip' | translate}}</kc-tooltip>
+ </div>
</fieldset>
<div class="form-group">
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html b/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html
index 49d1fae..d2a75c2 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html
@@ -232,7 +232,7 @@
<kc-tooltip>{{:: 'debug.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group" data-ng-show="instance.config.allowKerberosAuthentication">
- <label class="col-md-2 control-label" for="debug">{{:: 'use-kerberos-for-password-authentication' | translate}} </label>
+ <label class="col-md-2 control-label" for="useKerberosForPasswordAuthentication">{{:: 'use-kerberos-for-password-authentication' | translate}} </label>
<div class="col-md-6">
<input ng-model="instance.config.useKerberosForPasswordAuthentication" id="useKerberosForPasswordAuthentication" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html
index d49561e..bcb3150 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html
@@ -84,7 +84,8 @@
</button>
</div>
<div class="col-md-3">
- <label class="control-label" for="client-composite">{{:: 'effective-roles' | translate}} <span tooltip-placement="right" tooltip-trigger="mouseover mouseout" tooltip="{{:: 'group.effective-roles-client.tooltip' | translate}}" class="fa fa-info-circle"></span></label>
+ <label class="control-label" for="client-composite">{{:: 'effective-roles' | translate}}</label>
+ <kc-tooltip>{{:: 'group.effective-roles-client.tooltip' | translate}}</kc-tooltip>
<select id="client-composite" class="form-control" multiple size=5
disabled="true"
ng-model="dummymodel"
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/menu.html b/themes/src/main/resources/theme/base/admin/resources/partials/menu.html
index 85a9a1c..8523142 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/menu.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/menu.html
@@ -1,13 +1,14 @@
<div class="navbar-header">
- <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse-1">
- <span class="sr-only">{{:: 'toggle-navigation' | translate}}</span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- </button>
- <a class="navbar-brand" href="#/"></a>
+ <button type="button" class="navbar-toggle" ng-init="navCollapsed = true" ng-click="navCollapsed = !navCollapsed">
+ <span class="sr-only">{{:: 'toggle-navigation' | translate}}</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="#"></a>
</div>
-<div class="collapse navbar-collapse navbar-collapse-1">
+
+<div class="collapse navbar-collapse" collapse="navCollapsed">
<ul class="nav navbar-nav navbar-utility">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-admin.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-admin.html
index 04a6311..b4a66a7 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-admin.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-admin.html
@@ -46,10 +46,10 @@
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="resource">{{:: 'resource-path' | translate}}</label>
+ <kc-tooltip>{{:: 'resource-path.tooltip' | translate}}</kc-tooltip>
<div class="col-sm-4">
<input class="form-control" type="text" id="resource" name="resource" data-ng-model="query.resourcePath">
</div>
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'resource-path.tooltip' | translate}}" class="fa fa-info-circle"></span>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="dateFrom">{{:: 'date-(from)' | translate}}</label>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html
index 8f36f6a..a304784 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html
@@ -17,14 +17,12 @@
<fieldset class="border-top">
<div class="form-group">
<label class="col-md-2 control-label" for="eventsListeners" class="control-label">{{:: 'event-listeners' | translate}}</label>
-
+ <kc-tooltip>{{:: 'event-listeners.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<select ui-select2 ng-model="eventsConfig.eventsListeners" data-placeholder="{{:: 'select-an-action.placeholder' | translate}}" multiple>
<option ng-repeat="listener in eventListeners" value="{{listener}}">{{listener}}</option>
</select>
</div>
-
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'event-listeners.tooltip' | translate}}" class="fa fa-info-circle"></span>
</div>
</fieldset>
@@ -33,41 +31,37 @@
<div class="form-group">
<label class="col-md-2 control-label" for="enabled">{{:: 'save-events' | translate}}</label>
+ <kc-tooltip>{{:: 'login.save-events.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<input ng-model="eventsConfig.eventsEnabled" name="enabled" id="enabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'login.save-events.tooltip' | translate}}" class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-show="eventsConfig.eventsEnabled">
<label class="col-md-2 control-label" for="enabledEventTypes" class="control-label">{{:: 'saved-types' | translate}}</label>
-
+ <kc-tooltip>{{:: 'saved-types.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<input ui-select2="eventSelectOptions" id="enabledEventTypes" ng-model="eventsConfig.enabledEventTypes" data-placeholder="{{:: 'select-event-types.placeholder' | translate}}"/>
</div>
-
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'saved-types.tooltip' | translate}}" class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-show="access.manageEvents && eventsConfig.eventsEnabled">
<label class="col-md-2 control-label" for="password">{{:: 'clear-events' | translate}}</label>
+ <kc-tooltip>{{:: 'clear-events.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<button class="btn btn-danger" type="submit" data-ng-click="clearEvents()" >{{:: 'clear-events' | translate}}</button>
</div>
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'clear-events.tooltip' | translate}}" class="fa fa-info-circle"></span>
</div>
- <div class="form-group input-select" data-ng-show="eventsConfig.eventsEnabled">
+ <div class="form-group" data-ng-show="eventsConfig.eventsEnabled">
<label class="col-md-2 control-label" for="expiration">{{:: 'expiration' | translate}}</label>
- <div class="col-md-6">
+ <kc-tooltip>{{:: 'events.expiration.tooltip' | translate}}</kc-tooltip>
+ <div class="col-md-6 time-selector">
<input class="form-control" type="number" data-ng-model="eventsConfig.eventsExpiration" id="expiration" name="expiration" min="0"/>
- </div>
- <div class="col-md-2 select-kc">
- <select name="expirationUnit" data-ng-model="eventsConfig.expirationUnit" >
- <option>{{:: 'minutes' | translate}}</option>
- <option>{{:: 'hours' | translate}}</option>
- <option>{{:: 'days' | translate}}</option>
+ <select class="form-control" name="expirationUnit" data-ng-model="eventsConfig.expirationUnit">
+ <option value="Minutes">{{:: 'minutes' | translate}}</option>
+ <option value="Hours">{{:: 'hours' | translate}}</option>
+ <option value="Days">{{:: 'days' | translate}}</option>
</select>
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'events.expiration.tooltip' | translate}}" class="fa fa-info-circle"></span>
</div>
</div>
</fieldset>
@@ -78,28 +72,26 @@
<div class="form-group">
<label class="col-md-2 control-label" for="adminEventsEnabled">{{:: 'save-events' | translate}}</label>
+ <kc-tooltip>{{:: 'admin.save-events.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<input ng-model="eventsConfig.adminEventsEnabled" name="adminEventsEnabled" id="adminEventsEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
-
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'admin.save-events.tooltip' | translate}}" class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-show="eventsConfig.adminEventsEnabled">
<label class="col-md-2 control-label" for="adminEventsDetailsEnabled">{{:: 'include-representation' | translate}}</label>
+ <kc-tooltip>{{:: 'include-representation.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<input ng-model="eventsConfig.adminEventsDetailsEnabled" name="adminEventsDetailsEnabled" id="adminEventsDetailsEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
</div>
-
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'include-representation.tooltip' | translate}}" class="fa fa-info-circle"></span>
</div>
<div class="form-group" data-ng-show="access.manageEvents && eventsConfig.adminEventsEnabled">
<label class="col-md-2 control-label" for="password">{{:: 'clear-admin-events' | translate}}</label>
+ <kc-tooltip>{{:: 'clear-admin-events.tooltip' | translate}}</kc-tooltip>
<div class="col-md-6">
<button class="btn btn-danger" type="submit" data-ng-click="clearAdminEvents()" >{{:: 'clear-admin-events' | translate}}</button>
</div>
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'clear-admin-events.tooltip' | translate}}" class="fa fa-info-circle"></span>
</div>
</fieldset>
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 1b1ae96..2f5ed40 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
@@ -1,7 +1,32 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<h1>{{:: 'identity-providers' | translate}}</h1>
+ <div class="blank-slate-pf" data-ng-hide="configuredProviders.length > 0">
+ <div class="blank-slate-pf-icon">
+ <span class="fa fa-exchange"></span>
+ </div>
+ <h1>
+ Identity Providers
+ </h1>
+ <p class="">
+ Through Identity Brokering it's easy to allow users to authenticate to Keycloak using external Identity Providers or Social Networks.<br> We have built-in support for OpenID Connect and SAML 2.0 as well as a number of social networks such as Google, GitHub, Facebook and Twitter.
+ </p>
+ <p>To get started select a provider from the dropdown below:</p>
+ <div class="blank-slate-pf-main-action">
+ <div class="row" data-ng-show="access.manageIdentityProviders">
+ <div class="col-sm-4 col-sm-offset-4">
+ <div class="form-group">
+ <select class="selectpicker form-control" ng-model="provider"
+ ng-options="p.name group by p.groupName for p in allProviders track by p.id"
+ data-ng-change="addProvider(provider); provider = null">
+ <option value="" disabled selected>{{:: 'add-provider.placeholder' | translate}}</option>
+ </select>
+ </div>
+ </div>
+</div>
+</div>
- <form name="realmForm" novalidate class="form-horizontal">
+</div>
+ <form name="realmForm" novalidate class="form-horizontal" ng-show="configuredProviders.length > 0">
<fieldset>
<div>
<table class="table table-striped table-bordered">
@@ -18,7 +43,7 @@
</div>
</th>
</tr>
- <tr ng-show="configuredProviders.length > 0">
+ <tr>
<th>{{:: 'name' | translate}}</th>
<th>{{:: 'provider' | translate}}</th>
<th>{{:: 'enabled' | translate}}</th>
@@ -26,7 +51,7 @@
<th colspan="2">{{:: 'actions' | translate}}</th>
</tr>
</thead>
- <tbody ng-show="configuredProviders.length > 0">
+ <tbody>
<tr ng-repeat="identityProvider in configuredProviders">
<td>
<a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{identityProvider.alias}}</a>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
index 1db4c6a..bbbf9b9 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html
@@ -35,13 +35,6 @@
<kc-tooltip>{{:: 'identity-provider.enabled.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
- <label class="col-md-2 control-label" for="authenticateByDefault">{{:: 'authenticate-by-default' | translate}}</label>
- <div class="col-md-6">
- <input ng-model="identityProvider.authenticateByDefault" name="identityProvider.authenticateByDefault" id="authenticateByDefault" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
- </div>
- <kc-tooltip>{{:: 'identity-provider.authenticate-by-default.tooltip' | translate}}</kc-tooltip>
- </div>
- <div class="form-group">
<label class="col-md-2 control-label" for="storeToken">{{:: 'store-tokens' | translate}}</label>
<div class="col-md-6">
<input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
@@ -118,13 +111,13 @@
</div>
<kc-tooltip>{{:: 'identity-provider.logout-url.tooltip' | translate}}</kc-tooltip>
</div>
- <div class="form-group">
- <label class="col-sm-2 control-label" for="backchannelSupported">{{:: 'backchannel-logout' | translate}}</label>
- <div class="col-sm-4">
- <input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
- </div>
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'backchannel-logout.tooltip' | translate}}" class="fa fa-info-circle"></span>
- </div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="backchannelSupported">{{:: 'backchannel-logout' | translate}}</label>
+ <div class="col-sm-4">
+ <input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'backchannel-logout.tooltip' | translate}}</kc-tooltip>
+ </div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="userInfoUrl">{{:: 'user-info-url' | translate}}</label>
<div class="col-md-6">
@@ -167,9 +160,9 @@
<select class="form-control" id="prompt" ng-model="identityProvider.config.prompt">
<option value="">{{:: 'unspecified.option' | translate}}</option>
<option value="none">{{:: 'none.option' | translate}}</option>
- <option>{{:: 'consent.option' | translate}}</option>
- <option>{{:: 'login.option' | translate}}</option>
- <option>{{:: 'select-account.option' | translate}}</option>
+ <option value="consent">{{:: 'consent.option' | translate}}</option>
+ <option value="login">{{:: 'login.option' | translate}}</option>
+ <option value="select_account">{{:: 'select-account.option' | translate}}</option>
</select>
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
index aa94559..eaf4439 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html
@@ -32,13 +32,6 @@
<kc-tooltip>{{:: 'identity-provider.enabled.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
- <label class="col-md-2 control-label" for="authenticateByDefault">{{:: 'authenticate-by-default' | translate}}</label>
- <div class="col-md-6">
- <input ng-model="identityProvider.authenticateByDefault" name="identityProvider.authenticateByDefault" id="authenticateByDefault" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
- </div>
- <kc-tooltip>{{:: 'identity-provider.authenticate-by-default.tooltip' | translate}}</kc-tooltip>
- </div>
- <div class="form-group">
<label class="col-md-2 control-label" for="storeToken">{{:: 'store-tokens' | translate}}</label>
<div class="col-md-6">
<input ng-model="identityProvider.storeToken" id="storeToken" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
@@ -109,13 +102,13 @@
</div>
<kc-tooltip>{{:: 'saml.single-logout-service-url.tooltip' | translate}}</kc-tooltip>
</div>
- <div class="form-group">
- <label class="col-sm-2 control-label" for="backchannelSupported">{{:: 'backchannel-logout' | translate}}</label>
- <div class="col-sm-4">
- <input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
- </div>
- <span tooltip-trigger="mouseover mouseout" tooltip-placement="right" tooltip="{{:: 'backchannel-logout.tooltip' | translate}}" class="fa fa-info-circle"></span>
- </div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="backchannelSupported">{{:: 'backchannel-logout' | translate}}</label>
+ <div class="col-sm-4">
+ <input ng-model="identityProvider.config.backchannelSupported" id="backchannelSupported" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
+ </div>
+ <kc-tooltip>{{:: 'backchannel-logout.tooltip' | translate}}</kc-tooltip>
+ </div>
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="nameIDPolicyFormat">{{:: 'nameid-policy-format' | translate}}</label>
<div class="col-md-6">
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
index 9599d98..6c7ceb7 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-social.html
@@ -71,13 +71,6 @@
<kc-tooltip>{{:: 'trust-email.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
- <label class="col-md-2 control-label" for="authenticateByDefault">{{:: 'authenticate-by-default' | translate}}</label>
- <div class="col-md-6">
- <input ng-model="identityProvider.authenticateByDefault" name="identityProvider.authenticateByDefault" id="authenticateByDefault" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
- </div>
- <kc-tooltip>{{:: 'identity-provider.authenticate-by-default.tooltip' | translate}}</kc-tooltip>
- </div>
- <div class="form-group">
<label class="col-md-2 control-label" for="guiOrder">{{:: 'gui-order' | translate}}</label>
<div class="col-md-6">
<input class="form-control" id="guiOrder" type="text" ng-model="identityProvider.config.guiOrder">
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html
index fd1a0fa..9970be4 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html
@@ -84,7 +84,8 @@
</button>
</div>
<div class="col-md-3">
- <label class="control-label" for="client-composite">{{:: 'effective-roles' | translate}} <span tooltip-placement="right" tooltip-trigger="mouseover mouseout" tooltip="{{:: 'user.effective-roles-client.tooltip' | translate}}" class="fa fa-info-circle"></span></label>
+ <label class="control-label" for="client-composite">{{:: 'effective-roles' | translate}}</label>
+ <kc-tooltip>{{:: 'user.effective-roles-client.tooltip' | translate}}</kc-tooltip>
<select id="client-composite" class="form-control" multiple size=5
disabled="true"
ng-model="dummymodel"
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html b/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html
index 0607250..299a93b 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/server-info.html
@@ -15,6 +15,10 @@
<td>{{serverInfo.systemInfo.version}}</td>
</tr>
<tr>
+ <td width="20%">{{:: 'server-profile' | translate}}</td>
+ <td>{{serverInfo.profileInfo.name}}</td>
+ </tr>
+ <tr>
<td>{{:: 'server-time' | translate}}</td>
<td>{{serverInfo.systemInfo.serverTime}}</td>
</tr>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
index a0d88c6..e2d5db2 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
@@ -19,7 +19,7 @@
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/scope-mappings">{{:: 'scope' | translate}}</a>
<kc-tooltip>{{:: 'scope.tooltip' | translate}}</kc-tooltip>
</li>
- <li ng-class="{active: path[4] == 'authz'}" data-ng-show="client.authorizationServicesEnabled"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'authz-authorization' | translate}}</a></li>
+ <li ng-class="{active: path[4] == 'authz'}" data-ng-show="serverInfo.profileInfo.previewEnabled && !disableAuthorizationTab && client.authorizationServicesEnabled"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/authz/resource-server">{{:: 'authz-authorization' | translate}}</a></li>
<li ng-class="{active: path[4] == 'revocation'}"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/revocation">{{:: 'revocation' | translate}}</a></li>
<!-- <li ng-class="{active: path[4] == 'identity-provider'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/clients/{{client.id}}/identity-provider">Identity Provider</a></li> -->
<li ng-class="{active: path[4] == 'sessions'}" data-ng-show="!client.bearerOnly">
diff --git a/themes/src/main/resources/theme/base/admin/theme.properties b/themes/src/main/resources/theme/base/admin/theme.properties
index 6b8008c..46cee85 100644
--- a/themes/src/main/resources/theme/base/admin/theme.properties
+++ b/themes/src/main/resources/theme/base/admin/theme.properties
@@ -1,2 +1,2 @@
import=common/keycloak
-locales=ca,de,en,es,fr,it,ja,pt-BR,ru
\ No newline at end of file
+locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/email/messages/messages_lt.properties b/themes/src/main/resources/theme/base/email/messages/messages_lt.properties
new file mode 100644
index 0000000..f0eb51c
--- /dev/null
+++ b/themes/src/main/resources/theme/base/email/messages/messages_lt.properties
@@ -0,0 +1,24 @@
+emailVerificationSubject=El. pa\u0161to patvirtinimas
+emailVerificationBody=Paskyra {2} sukurta naudojant \u0161\u012F el. pa\u0161to adres\u0105. Jei tau buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105\n\n{0}\n\n\u0160i nuoroda galioja {1} min.\n\nJei paskyros nek\u016Br\u0117te, tuomet ignuoruokite \u0161\u012F lai\u0161k\u0105.
+emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant \u0161\u012F el. pa\u0161to adres\u0105. Jei tau buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105</p><p><a href=LT"{0}">{0}</a></p><p>\u0160i nuoroda galioja {1} min.</p><p>nJei paskyros nek\u016Br\u0117te, tuomet ignuoruokite \u0161\u012F lai\u0161k\u0105.</p>
+identityProviderLinkSubject=S\u0105saja {0}
+identityProviderLinkBody=Ka\u017Eas pageidauja susieti J\u016Bs\u0173 "{1}" paskyr\u0105 su "{0}" {2} naudotojo paskyr\u0105. Jei tai buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 nor\u0117dami susieti paskyras\n\n{3}\n\n\u0160i nuoroda galioja {4} min.\n\nJei paskyr\u0173 susieti nenorite, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105. Jei paskyras susiesite, tuomet prie {1} gal\u0117siste prisijungti per {0}.
+identityProviderLinkBodyHtml=<p>\u017Eas pageidauja susieti J\u016Bs\u0173 <b>{1}</b> paskyr\u0105 su <b>{0}</b> {2} naudotojo paskyr\u0105. Jei tai buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 nor\u0117dami susieti paskyras</p><p><a href=LT"{3}">{3}</a></p><p>\u0160i nuoroda galioja {4} min.</p><p>Jei paskyr\u0173 susieti nenorite, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105. Jei paskyras susiesite, tuomet prie {1} gal\u0117siste prisijungti per {0}.</p>
+passwordResetSubject=Slapta\u017Eod\u017Eio atk\u016Brimas
+passwordResetBody=Ka\u017Ekas pageidauja pakeisti J\u016Bs\u0173 paskyros {2} slapta\u017Eod\u012F. Jei tai buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 slapta\u017Eod\u017Eio pakeitimui.\n\n{0}\n\n\u0160i nuoroda ir kodas galioja {1} min.\n\nJei nepageidajate keisti slapta\u017Eod\u017Eio, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105 ir niekas nebus pakeista.
+passwordResetBodyHtml=<p>Ka\u017Ekas pageidauja pakeisti J\u016Bs\u0173 paskyros {2} slapta\u017Eod\u012F. Jei tai buvote J\u016Bs, tuomet paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 slapta\u017Eod\u017Eio pakeitimui.</p><p><a href=LT"{0}">{0}</a></p><p>\u0160i nuoroda ir kodas galioja {1} min.</p><p>Jei nepageidajate keisti slapta\u017Eod\u017Eio, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105 ir niekas nebus pakeista.</p>
+executeActionsSubject=Atnaujinkite savo paskyr\u0105
+executeActionsBody=Sistemos administratorius pageidauja, kad J\u016Bs atnaujintum\u0117te savo {2} paskyr\u0105. Paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 paskyros duomen\u0173 atnaujinimui.\n\n{0}\n\n\u0160i nuoroda galioja {1} min.\n\nJei J\u016Bs neasate tikri, kad tai administratoriaus pageidavimas, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105 ir niekas nebus pakeista.
+executeActionsBodyHtml=<p>Sistemos administratorius pageidauja, kad J\u016Bs atnaujintum\u0117te savo {2} paskyr\u0105. Paspauskite \u017Eemiau esan\u010Di\u0105 nuorod\u0105 paskyros duomen\u0173 atnaujinimui.</p><p><a href=LT"{0}">{0}</a></p><p>\u0160i nuoroda galioja {1} min.</p><p>Jei J\u016Bs neasate tikri, kad tai administratoriaus pageidavimas, tuomet ignoruokite \u0161\u012F lai\u0161k\u0105 ir niekas nebus pakeista.</p>
+eventLoginErrorSubject=Bandymas prisijungti prie J\u016Bs\u0173 paskyros
+eventLoginErrorBody=Bandymas prisijungti prie J\u016Bs\u0173 paskyros {0} i\u0161 {1} nes\u012Fkmingas. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi
+eventLoginErrorBodyHtml=<p>Bandymas prisijungti prie J\u016Bs\u0173 paskyros {0} i\u0161 {1} nes\u012Fkmingas. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi</p>
+eventRemoveTotpSubject=TOTP pa\u0161alinimas
+eventRemoveTotpBody=Ka\u017Ekas pageidauja atsieti TOPT J\u016Bs\u0173 {1} paskyroje su {0}. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi
+eventRemoveTotpBodyHtml=<p>Ka\u017Ekas pageidauja atsieti TOPT J\u016Bs\u0173 <b>{1}</b> paskyroje su <b>{0}</b>. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi</p>
+eventUpdatePasswordSubject=Slapta\u017Eod\u017Eio atnaujinimas
+eventUpdatePasswordBody=J\u016Bs\u0173 slapta\u017Eodis J\u016Bs\u0173 {1} paskyroje su {0} buvo pakeisas. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi
+eventUpdatePasswordBodyHtml=<p>J\u016Bs\u0173 {1} paskyroje su {0. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi</p>
+eventUpdateTotpSubject=TOTP atnaujinimas
+eventUpdateTotpBody=TOTP J\u016Bs\u0173 {1} paskyroje su {0} buvo atnaujintas. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi
+eventUpdateTotpBodyHtml=<p>TOTP J\u016Bs\u0173 {1} paskyroje su {0} buvo atnaujintas. Jei tai nebuvote J\u016Bs, tuomet susisiekite su administratoriumi</p>
diff --git a/themes/src/main/resources/theme/base/email/messages/messages_no.properties b/themes/src/main/resources/theme/base/email/messages/messages_no.properties
new file mode 100644
index 0000000..32334e9
--- /dev/null
+++ b/themes/src/main/resources/theme/base/email/messages/messages_no.properties
@@ -0,0 +1,24 @@
+emailVerificationSubject=Bekreft e-postadresse
+emailVerificationBody=Noen har opprettet en {2} konto med denne e-postadressen. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 bekrefte e-postadressen din\n\n{0}\n\nDenne lenken vil utl\u00F8pe om {1} minutter.\n\nHvis du ikke opprettet denne kontoen, vennligst ignorer denne meldingen.
+emailVerificationBodyHtml=<p>Noen har opprettet en {2} konto med denne e-postadressen. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 bekrefte e-postadressen din</p><p><a href="{0}">{0}</a></p><p>Denne lenken vil utl\u00F8pe om {1} minutter.</p><p>Hvis du ikke opprettet denne kontoen, vennligst ignorer denne meldingen.</p>
+identityProviderLinkSubject=Lenke {0}
+identityProviderLinkBody=Noen vil koble din <b>{1}</b> konto med <b>{0}</b> konto til bruker {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 koble kontoene\n\n{3}\n\nDenne lenken vil utl\u00F8pe om {4} minutter\n\nHvis du ikke vil koble kontoene, vennligst ignorer denne meldingen. Hvis du kobler kontoene sammen vil du kunne logge inn til {1} gjennom {0}.
+identityProviderLinkBodyHtml=<p>Noen vil koble din <b>{1}</b> konto med <b>{0}</b> konto til bruker {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 koble kontoene.</p><p><a href="{3}">{3}</a></p><p>Denne lenken vil utl\u00F8pe om {4} minutter.</p><p>Hvis du ikke vil koble kontoene, vennligst ignorer denne meldingen. Hvis du kobler kontoene sammen vil du kunne logge inn til {1} gjennom {0}.</p>
+passwordResetSubject=Tilbakestill passord
+passwordResetBody=Noen har bedt om \u00E5 endre innloggingsdetaljene til din konto {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 tilbakestille dem.\n\n{0}\n\nDenne lenken vil utl\u00F8pe om {1} minutter.\n\nHvis du ikke vil tilbakestille din innloggingsdata, vennligst ignorer denne meldingen og ingenting vil bli endret.
+passwordResetBodyHtml=<p>Noen har bedt om \u00E5 endre innloggingsdetaljene til din konto {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 tilbakestille dem.</p><p><a href="{0}">{0}</a></p><p>Denne lenken vil utl\u00F8pe om {1} minutter.</p><p>Hvis du ikke vil tilbakestille din innloggingsdata, vennligst ignorer denne meldingen og ingenting vil bli endret.</p>
+executeActionsSubject=Oppdater kontoen din
+executeActionsBody=Administrator har anmodet at du oppdaterer din {2} konto. Klikk p\u00E5 lenken nedenfor for \u00E5 starte denne prosessen\n\n{0}\n\nDenne lenken vil utl\u00F8pe om {1} minutter.\n\nHvis du ikke var klar over at administrator har bedt om dette, vennligst ignorer denne meldingen og ingenting vil bli endret.
+executeActionsBodyHtml=<p>Administrator har anmodet at du oppdaterer din {2} konto. Klikk p\u00E5 linken nedenfor for \u00E5 starte denne prosessen.</p><p><a href="{0}">{0}</a></p><p>Denne lenken vil utl\u00F8pe om {1} minutter.</p><p>Hvis du ikke var klar over at administrator har bedt om dette, ignorer denne meldingen og ingenting vil bli endret. </p>
+eventLoginErrorSubject=Innlogging feilet
+eventLoginErrorBody=Et mislykket innloggingsfors\u00F8k ble oppdaget p\u00E5 din konto p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.
+eventLoginErrorBodyHtml=<p>Et mislykket innloggingsfors\u00F8k ble oppdaget p\u00E5 din konto p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.</p>
+eventRemoveTotpSubject=Fjern engangskode
+eventRemoveTotpBody=Engangskode ble fjernet fra kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.
+eventRemoveTotpBodyHtml=<p>Engangskode ble fjernet fra kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.</p>
+eventUpdatePasswordSubject=Oppdater passord
+eventUpdatePasswordBody=Ditt passord ble endret i {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.
+eventUpdatePasswordBodyHtml=<p>Ditt passord ble endret i {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. </p>
+eventUpdateTotpSubject=Oppdater engangskode
+eventUpdateTotpBody=Engangskode ble oppdatert for kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.
+eventUpdateTotpBodyHtml=<p>Engangskode ble oppdatert for kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. </p>
diff --git a/themes/src/main/resources/theme/base/email/theme.properties b/themes/src/main/resources/theme/base/email/theme.properties
index 2f853e8..2cf724d 100644
--- a/themes/src/main/resources/theme/base/email/theme.properties
+++ b/themes/src/main/resources/theme/base/email/theme.properties
@@ -1 +1 @@
-locales=ca,de,en,es,fr,it,ja,pt-BR,ru
\ No newline at end of file
+locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/login/login-reset-password.ftl b/themes/src/main/resources/theme/base/login/login-reset-password.ftl
index 404de17..a561b2a 100755
--- a/themes/src/main/resources/theme/base/login/login-reset-password.ftl
+++ b/themes/src/main/resources/theme/base/login/login-reset-password.ftl
@@ -8,7 +8,7 @@
<form id="kc-reset-password-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
- <label for="username" class="${properties.kcLabelClass!}">${msg("usernameOrEmail")}</label>
+ <label for="username" class="${properties.kcLabelClass!}"><#if !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input type="text" id="username" name="username" class="${properties.kcInputClass!}" autofocus/>
@@ -30,4 +30,4 @@
<#elseif section = "info" >
${msg("emailInstruction")}
</#if>
-</@layout.registrationLayout>
\ No newline at end of file
+</@layout.registrationLayout>
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 4e1bd57..f69733a 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -215,9 +215,11 @@ locale_es=Espa\u00F1ol
locale_fr=Fran\u00e7ais
locale_it=Italian
locale_ja=\u65E5\u672C\u8A9E
+locale_no=Norsk
locale_pt_BR=Portugu\u00EAs (Brasil)
locale_pt-BR=Portugu\u00EAs (Brasil)
locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439
+locale_lt=Lietuvi\u0173
backToApplication=« Back to Application
missingParameterMessage=Missing parameters\: {0}
@@ -226,4 +228,4 @@ clientDisabledMessage=Client disabled.
invalidParameterMessage=Invalid parameter\: {0}
alreadyLoggedIn=You are already logged in.
-p3pPolicy=CP="This is not a P3P policy!"
\ No newline at end of file
+p3pPolicy=CP="This is not a P3P policy!"
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_lt.properties b/themes/src/main/resources/theme/base/login/messages/messages_lt.properties
new file mode 100644
index 0000000..bffd8ed
--- /dev/null
+++ b/themes/src/main/resources/theme/base/login/messages/messages_lt.properties
@@ -0,0 +1,218 @@
+doLogIn=Prisijungti
+doRegister=Registruotis
+doCancel=At\u0161aukti
+doSubmit=Patvirtinti
+doYes=Taip
+doNo=Ne
+doContinue=T\u0119sti
+doAccept=Patvirtinti
+doDecline=At\u0161aukti
+doForgotPassword=Pamir\u0161ote slapta\u017Eod\u012F?
+doClickHere=Spauskite \u010Dia
+doImpersonate=Apsimesti kaip
+kerberosNotConfigured=Kerberos nesukonfig\u016Bruotas
+kerberosNotConfiguredTitle=Kerberos nesukonfig\u016Bruotas
+bypassKerberosDetail=J\u016Bs neprisijung\u0119s per Kerberos arba J\u016Bs\u0173 nar\u0161ykl\u0117 nesukonfig\u016Bruota Kerberos prisijungimui. T\u0119skite ir pasirinkite kit\u0105 prisijungimo b\u016Bd\u0105
+kerberosNotSetUp=Kerberos nesukonfig\u016Bruotas. J\u016Bs negalite prisijungti.
+registerWithTitle=Registruotis su {0}
+registerWithTitleHtml={0}
+loginTitle=Prisijungti su {0}
+loginTitleHtml={0}
+impersonateTitle=Apsimesti kaip naudotojas {0}
+impersonateTitleHtml=Apsimesti kaip <strong>{0}</strong>
+realmChoice=Sritis
+unknownUser=Ne\u017Einomas naudotojas
+loginTotpTitle=Mobilaus autentifikatoriaus nustatymas
+loginProfileTitle=Atnaujinti paskyros informacij\u0105
+loginTimeout=U\u017Etrukote per ilgai. Prisijungimo procesas pradedamas i\u0161 naujo.
+oauthGrantTitle=Suteitikti prieig\u0105
+oauthGrantTitleHtml={0}
+errorTitle=Atsipra\u0161ome ...
+errorTitleHtml=<strong>Atsipra\u0161ome</strong> ...
+emailVerifyTitle=El. pa\u0161to adreso patvirtinimas
+emailForgotTitle=Pamir\u0161ote slapta\u017Eod\u012F?
+updatePasswordTitle=Atnaujinti slapta\u017Eod\u012F
+codeSuccessTitle=S\u0117km\u0117
+codeErrorTitle=Klaidos kodas\: {0}
+
+termsTitle=Naudojimo s\u0105lygos
+termsTitleHtml=Naudojimo s\u0105lygos
+termsText=<p>Naudojimo s\u0105lygos nenurodytos</p>
+
+recaptchaFailed=Recaptcha neteisingas
+recaptchaNotConfigured=Reikalingas Recaptcha nesukonfig\u016Bruotas
+consentDenied=Prieiga draud\u017Eiama.
+
+noAccount=Dar neturite paskyros?
+username=Naudotojo vardas
+usernameOrEmail=Naudotojo vardas arba el. pa\u0161to adresas
+firstName=Vardas
+givenName=Vardas
+fullName=Pavard\u0117
+lastName=Pavard\u0117
+familyName=Pavard\u0117
+email=El. pa\u0161tas
+password=Slapta\u017Eodis
+passwordConfirm=Pakartotas slapta\u017Eodis
+passwordNew=Naujas slapta\u017Eodis
+passwordNewConfirm=Pakartotas naujas slapta\u017Eodis
+rememberMe=Prisiminti mane
+authenticatorCode=Vienkartinis kodas
+address=Adresas
+street=Gatv\u0117
+locality=Miestas arba vietov\u0117
+region=Rajonas
+postal_code=Pa\u0161to kodas
+country=\u0160alis
+emailVerified=El. pa\u0161to adresas patvirtintas
+gssDelegationCredential=GSS prisijungimo duomen\u0173 delegavimas
+
+loginTotpStep1=\u012Ediekite <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> arba Google Authenticator savo \u012Frenginyje. Program\u0117l\u0117s prieinamos <a href="https://play.google.com">Google Play</a> ir Apple App Store.
+loginTotpStep2=Atidarykite program\u0117l\u0119 ir nuskenuokite barkod\u0105 arba \u012Fveskite kod\u0105.
+loginTotpStep3=\u012Eveskite program\u0117l\u0117je sugeneruot\u0105 vien\u0105 kart\u0105 galiojant\u012F kod\u0105 ir paspauskite Saugoti nor\u0117dami prisijungti.
+loginTotpOneTime=Vienkartinis kodas
+
+oauthGrantRequest=Ar J\u016Bs suteikiate \u0161ias prieigos teises?
+inResource=\u012F
+
+emailVerifyInstruction1=El. pa\u0161tas su instrukcijomis ir patvirtinimo nuoroda nusi\u0173sti \u012F J\u016Bs\u0173 el. pa\u0161t\u0105.
+emailVerifyInstruction2=El. pa\u0161tu negavote patvirtinimo kodo?
+emailVerifyInstruction3=pakartotoinai si\u0173sti el. lai\u0161k\u0105.
+
+emailLinkIdpTitle=Susieti {0}
+emailLinkIdp1=El. pa\u0161to lai\u0161kas su instrukcijomis susieti {0} paskyr\u0105 {1} su {2} buvo nusi\u0173stas.
+emailLinkIdp2=Negavote patvirtinimo kodo el. pa\u0161tu?
+emailLinkIdp3=pakartotoinai si\u0173sti el. lai\u0161k\u0105.
+
+backToLogin=« Gr\u012F\u017Eti \u012F prisijungimo lang\u0105
+
+emailInstruction=\u012Eveskite naudotojo vard\u0105 arba slapta\u017Eod\u012F ir slapta\u017Eod\u017Eio pakeitimo instrukcijos bus atsi\u0173stos Jums el. pa\u0161tu
+
+copyCodeInstruction=Nukopijuokite \u0161\u012F kod\u0105 \u012F J\u016Bs\u0173 program\u0105:
+
+personalInfo=Asmenin\u0117 informacija:
+role_admin=Administratorius
+role_realm-admin=Srities administravimas
+role_create-realm=Kurti srit\u012F
+role_create-client=Kurti program\u0105
+role_view-realm=Per\u017Ei\u016Br\u0117ti srit\u012F
+role_view-users=Per\u017Ei\u016Br\u0117ti naudotojus
+role_view-applications=Per\u017Ei\u016Br\u0117ti programas
+role_view-clients=Per\u017Ei\u016Br\u0117ti klientines programas
+role_view-events=Per\u017Ei\u016Br\u0117ti \u012Fvyki\u0173 \u017Eurnal\u0105
+role_view-identity-providers=Per\u017Ei\u016Br\u0117ti tapatyb\u0117s teik\u0117jus
+role_manage-realm=Valdyti sritis
+role_manage-users=Valdyti naudotojus
+role_manage-applications=Valdyti programas
+role_manage-identity-providers=Valdyti tapatyb\u0117s teik\u0117jus
+role_manage-clients=Valdyti programas
+role_manage-events=Valdyti \u012Fvykius
+role_view-profile=Per\u017Ei\u016Br\u0117ti paskyr\u0105
+role_manage-account=Valdyti paskyr\u0105
+role_read-token=Skaityti prieigos rak\u0161\u0105
+role_offline-access=Darbas neprisijungus
+client_account=Paskyra
+client_security-admin-console=Saugumo administravimo konsol\u0117
+client_admin-cli=Administravimo CLI
+client_realm-management=Srities valdymas
+client_broker=Tarpininkas
+
+invalidUserMessage=Neteisingas naudotojo vardas arba slapta\u017Eodis.
+invalidEmailMessage=Neteisingas el. pa\u0161to adresas.
+accountDisabledMessage=Paskyros galiojimas sustabdytas, kreipkit\u0117s \u012F administratori\u0173.
+accountTemporarilyDisabledMessage=Paskyros galiojimas laikinai sustabdytas. Kreipkit\u0117s \u012F administratori\u0173 arba pabandykite v\u0117liau.
+expiredCodeMessage=Prisijungimo laikas baig\u0117si. Bandykite dar kart\u0105.
+
+missingFirstNameMessage=Pra\u0161ome \u012Fvesti vard\u0105.
+missingLastNameMessage=Pra\u0161ome \u012Fvesti pavard\u0119.
+missingEmailMessage=Pra\u0161ome \u012Fvesti el. pa\u0161to adres\u0105.
+missingUsernameMessage=Pra\u0161ome \u012Fvesti naudotojo vard\u0105.
+missingPasswordMessage=Pra\u0161ome \u012Fvesti slapta\u017Eod\u012F.
+missingTotpMessage=Pra\u0161ome \u012Fvesti autentifikacijos kod\u0105.
+notMatchPasswordMessage=Slapta\u017Eod\u017Eiai nesutampa.
+
+invalidPasswordExistingMessage=Neteisingas dabartinis slapta\u017Eodis.
+invalidPasswordConfirmMessage=Pakartotas slapta\u017Eodis nesutampa.
+invalidTotpMessage=Neteisingas autentifikacijos kodas.
+
+usernameExistsMessage=Toks naudotojas jau egzistuoja.
+emailExistsMessage=El. pa\u0161to adresas jau egzistuoja.
+
+federatedIdentityExistsMessage=Naudotojas {0} {1} jau egzistuoja. Pra\u0161ome prsijungti prie naudotoj\u0173 valdymo posistem\u0117s paskyr\u0173 susiejimui.
+
+confirmLinkIdpTitle=Paskyra jau egzistuoja
+federatedIdentityConfirmLinkMessage=Naudotojas {0} {1} jau egzistuoja. Ar t\u0119sti?
+federatedIdentityConfirmReauthenticateMessage=Prisijunkite prie {0} nor\u0117dami susieti paskyr\u0105 su {1}
+confirmLinkIdpReviewProfile=Per\u017Ei\u016Br\u0117ti naudotojo profilio informacij\u0105
+confirmLinkIdpContinue=Susieti su egzistuojan\u010Dia paskyra
+
+configureTotpMessage=Paskyros aktyvavimui Jums reikalingas Mobilus autentifikatorius.
+updateProfileMessage=Paskyros aktyvavimui Jums reikia atnaujinti profilio informacij\u0105.
+updatePasswordMessage=Paskyros aktyvavimui Jums reikia pakeisti slapta\u017Eod\u012F.
+verifyEmailMessage=Paskyros aktyvavimui Jums reikia patvirtinti el. pa\u0161to adres\u0105.
+linkIdpMessage=El. pa\u0161to adreso susiejimui su J\u016Bsu paskyra {0} reikalingas patvirtinimas.
+
+emailSentMessage=Netrukus tur\u0117tum\u0117te gauti el. pa\u0161to adres\u0105 su instrukcijomis.
+emailSendErrorMessage=Klaida siun\u010Diant el. pa\u0161t\u0105, bandykite v\u0117liau.
+
+accountUpdatedMessage=J\u0173s\u0173 paskyros informacija atnaujinta.
+accountPasswordUpdatedMessage=J\u016Bs\u0173 slapta\u017Eodis pakeistas.
+
+noAccessMessage=Prieiga negalima
+
+invalidPasswordMinLengthMessage=Neteisingas slapta\u017Eodis: privalomi bent {0} simboliai.
+invalidPasswordMinDigitsMessage=Neteisingas slapta\u017Eodis: privalomi bent {0} skaitmenys.
+invalidPasswordMinLowerCaseCharsMessage=Neteisingas slapta\u017Eodis: privalomos bent {0} ma\u017Eosios raid\u0117s.
+invalidPasswordMinUpperCaseCharsMessage=Neteisingas slapta\u017Eodis: privalomos bent {0} did\u017Eiosios raid\u0117s.
+invalidPasswordMinSpecialCharsMessage=Neteisingas slapta\u017Eodis: privalomi bent {0} special\u016Bs simboliai.
+invalidPasswordNotUsernameMessage=Neteisingas slapta\u017Eodis: negali sutapti su naudotojo vardu.
+invalidPasswordRegexPatternMessage=Neteisingas slapta\u017Eodis: neatitinka regexp taisykl\u0117s.
+invalidPasswordHistoryMessage=Neteisingas slapta\u017Eodis: negali sutapti su prie\u0161 tai naudotais {0} slapta\u017Eod\u017Eiais.
+
+failedToProcessResponseMessage=Klaida apdorojant atsakym\u0105
+httpsRequiredMessage=Privalomas HTTPS
+realmNotEnabledMessage=Srities galiojimas i\u0161jungtas
+invalidRequestMessage=Neteisinga u\u017Eklausa
+failedLogout=Nepavyko u\u017Ebaigti sesijos
+unknownLoginRequesterMessage=Ne\u017Einomas prisijungimo pra\u0161ytojas
+loginRequesterNotEnabledMessage=Prisijungimo pra\u0161ytojo galiojimas i\u0161jungtas
+bearerOnlyMessage=Programos, sukonfig\u016Bruotos tik kaip perdav\u0117jai, negali inicijuoti prisijungim\u0105 per nar\u0161ykl\u0119.
+standardFlowDisabledMessage=Su pateiktu atsakymo tipu prisijungimas per nar\u0161ykl\u0119 \u0161iam klientui negalimas. \u0160iam klientui ne\u012Fgalinta standartin\u0117 seka.
+implicitFlowDisabledMessage=Su pateiktu atsakymo tipu prisijungimas per nar\u0161ykl\u0119 \u0161iam klientui negalimas. \u0160iam klientui ne\u012Fgalinta i\u0161reik\u0161tin\u0117 seka.
+invalidRedirectUriMessage=Neteisinga nukreipimo nuoroda
+unsupportedNameIdFormatMessage=Nepalaikomas NameIDFormat
+invalidRequesterMessage=Neteisingas pra\u0161ytojas
+registrationNotAllowedMessage=Registracija negalima
+resetCredentialNotAllowedMessage=Prisijungimo duomen\u0173 atk\u016Brimas negalimas
+
+permissionNotApprovedMessage=Teis\u012F nepatvirtinta.
+noRelayStateInResponseMessage=Tapatyb\u0117s teik\u0117jo atsakyme tr\u016Bksta perdavimo b\u016Bsenos.
+insufficientPermissionMessage=Tr\u016Bksta teisi\u0173 tapatybi\u0173 susiejimui.
+couldNotProceedWithAuthenticationRequestMessage=Nepavyksta prad\u0117ti tapatyb\u0117s teik\u0117jo autentifikacijos u\u017Eklausos.
+couldNotObtainTokenMessage=Negaunamas prieigos raktas i\u0161 tapatyb\u0117s teik\u0117jo.
+unexpectedErrorRetrievingTokenMessage=Prieigos rak\u0161o gavimo i\u0161 tapatyb\u0117s teik\u0117jo metu \u012Fvyko netik\u0117ta klaida.
+unexpectedErrorHandlingResponseMessage=Tapatyb\u0117s teik\u0117jo atsakymo apdorojimo metu \u012Fvyko netik\u0117ta klaida.
+identityProviderAuthenticationFailedMessage=Autentifikacijos klaida. Nepavyksta autentifikacija su tapatyb\u0117s teik\u0117ju.
+identityProviderDifferentUserMessage=Autentifikuota kaip {0}, nors buvo tikimasi {1}
+couldNotSendAuthenticationRequestMessage=Tapatyb\u0117s teik\u0117jui nepavyksta nusi\u0173sti autentifikacijos u\u017Eklausos.
+unexpectedErrorHandlingRequestMessage=U\u017Eklausos tapatyb\u0117s teik\u0117jui formavimo metu \u012Fvyko netik\u0117ta klaida.
+invalidAccessCodeMessage=Neteisingas prieigos kodas.
+sessionNotActiveMessage=Sesija neaktyvi.
+invalidCodeMessage=\u012Evyko klaida. Pra\u0161ome bandyti prisijungti dar kart\u0105.
+identityProviderUnexpectedErrorMessage=Autentifikavimo su i\u0161oriniu tapatyb\u0117s teik\u0117ju metu \u012Fvyko netik\u0117ta klaida.
+identityProviderNotFoundMessage=Su nurodytu identifikatoriumi nerastas tapatyb\u0117s teik\u0117jas.
+identityProviderLinkSuccess=J\u016Bs\u0173 naudotojo paskyra buvo s\u0117kmingai susieta su {0} paskyra {1} .
+staleCodeMessage=\u0160is puslapis nebegalioja. Pra\u0161ome gr\u012F\u017Eti \u012F program\u0105 ir bandyti prisijungti i\u0161 naujo.
+realmSupportsNoCredentialsMessage=Sritis nepalaiko prisijungim\u0173 naudojant prisijungimo duomenis.
+identityProviderNotUniqueMessage=Sritis palaiko daugiau nei vien\u0105 tapatyb\u0117s teik\u0117j\u0105. Negalima nustatyti kuris tapatyb\u0117s teik\u0117jas turi b\u016Bti naudojamas autentifikacijai.
+emailVerifiedMessage=J\u016Bs\u0173 el. pa\u0161to adresas patvirtintas.
+staleEmailVerificationLink=Nuoroda, kuri\u0105 paspaud\u0117te nebegalioja? Galb\u016Bt J\u016Bs jau patvirtinote el. pa\u0161to adres\u0105?
+
+backToApplication=« Gr\u012F\u017Eti \u012F program\u0105
+missingParameterMessage=Nenurodytas parametras\: {0}
+clientNotFoundMessage=Nenurodytas klientas.
+clientDisabledMessage=Kliento galiojimas i\u0161jungtas.
+invalidParameterMessage=Neteisingas parametras\: {0}
+alreadyLoggedIn=J\u016Bs jau esate prisijung\u0119.
+
+p3pPolicy=CP="Nurodyta reik\u0161m\u0117 n\u0117ra P3P taisykl\u0117!"
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_no.properties b/themes/src/main/resources/theme/base/login/messages/messages_no.properties
new file mode 100644
index 0000000..a90af46
--- /dev/null
+++ b/themes/src/main/resources/theme/base/login/messages/messages_no.properties
@@ -0,0 +1,229 @@
+doLogIn=Logg inn
+doRegister=Registrer deg
+doCancel=Avbryt
+doSubmit=Send inn
+doYes=Ja
+doNo=Nei
+doContinue=Fortsett
+doAccept=Aksepter
+doDecline=Avsl\u00E5
+doForgotPassword=Glemt passord?
+doClickHere=Klikk her
+doImpersonate=Utgi deg for \u00E5 v\u00E6re en annen bruker
+kerberosNotConfigured=Kerberos er ikke konfigurert
+kerberosNotConfiguredTitle=Kerberos er ikke konfigurert
+bypassKerberosDetail=Enten er du ikke logget inn via Kerberos eller s\u00E5 st\u00F8tter ikke nettleseren innlogging med Kerberos. Vennligst klikk Fortsett for \u00E5 logge inn p\u00E5 andre m\u00E5ter
+kerberosNotSetUp=Kerberos er ikke konfigurert. Du kan ikke logge inn.
+registerWithTitle=Registrer deg med {0}
+registerWithTitleHtml={0}
+loginTitle=Logg inn p\u00E5 {0}
+loginTitleHtml={0}
+impersonateTitle={0} Gi deg ut for \u00E5 v\u00E6re en annen bruker
+impersonateTitleHtml=<strong>{0} Gi deg ut for \u00E5 v\u00E6re en annen bruker</strong>
+realmChoice=Sikkerhetsdomene
+unknownUser=Ukjent bruker
+loginTotpTitle=Konfigurer autentifikator for mobil
+loginProfileTitle=Oppdater konto
+loginTimeout=Du brukte for lang tid p\u00E5 \u00E5 logge inn. Vennligst pr\u00F8v igjen.
+oauthGrantTitle=Gi tilgang
+oauthGrantTitleHtml={0}
+errorTitle=Vi beklager...
+errorTitleHtml=Vi <strong>beklager</strong> ...
+emailVerifyTitle=E-postbekreftelse
+emailForgotTitle=Glemt passord?
+updatePasswordTitle=Oppdater passord
+codeSuccessTitle=Suksesskode
+codeErrorTitle=Feilkode\: {0}
+
+termsTitle=Vilk\u00E5r og betingelser
+termsTitleHtml=Vilk\u00E5r og betingelser
+termsText=<p>Vilk\u00E5r og betingelser kommer</p>
+
+recaptchaFailed=Ugyldig Bildebekreftelse
+recaptchaNotConfigured=Bildebekreftelse er p\u00E5krevet, men er ikke konfigurert
+consentDenied=Samtykke avsl\u00E5tt.
+
+noAccount=Ny bruker?
+username=Brukernavn
+usernameOrEmail=Brukernavn eller e-postadresse
+firstName=Fornavn
+givenName=Fornavn
+fullName=Fullstendig navn
+lastName=Etternavn
+familyName=Etternavn
+email=E-postadresse
+password=Passord
+passwordConfirm=Bekreft passord
+passwordNew=Nytt passord
+passwordNewConfirm=Bekreft nytt Passord
+rememberMe=Husk meg
+authenticatorCode=Engangskode
+address=Adresse
+street=Gate-/veinavn + husnummer
+locality=By
+region=Fylke
+postal_code=Postnummer
+country=Land
+emailVerified=E-postadresse bekreftet
+gssDelegationCredential=GSS legitimasjons-delegering
+
+loginTotpStep1=Installer <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> eller Google Authenticator p\u00E5 din mobiltelefon. Begge applikasjoner er tilgjengelige p\u00E5 <a href="https://play.google.com">Google Play</a> og Apple App Store.
+loginTotpStep2=\u00C5pne applikasjonen og skann strekkoden eller skriv inn koden
+loginTotpStep3=Skriv inn engangskoden fra applikasjonen og klikk send inn for \u00E5 fullf\u00F8re
+loginTotpOneTime=Engangskode
+
+oauthGrantRequest=Vil du gi disse tilgangsrettighetene?
+inResource=i
+
+emailVerifyInstruction1=En e-post med instruksjoner for \u00E5 bekrefte din e-postadresse har blitt sendt til deg.
+emailVerifyInstruction2=Ikke mottatt en bekreftelseskode i e-posten vi sendte til deg?
+emailVerifyInstruction3=for \u00E5 sende e-post p\u00E5 nytt.
+
+emailLinkIdpTitle=Lenke {0}
+emailLinkIdp1=En e-post med instruksjoner for \u00E5 koble {0} konto med din {2} konto har blitt sendt til deg.
+emailLinkIdp2=Ikke mottatt en bekreftelseskode i e-posten vi sendte til deg?
+emailLinkIdp3=for \u00E5 sende e-post p\u00E5 nytt.
+
+backToLogin=« Tilbake til innlogging
+emailInstruction=Skriv inn e-postadressen din og vi vil sende deg instruksjoner for hvordan du oppretter et nytt passord.
+
+copyCodeInstruction=Vennligst kopier denne koden og lim den inn i applikasjonen din:
+
+personalInfo=Personlig informasjon:
+role_admin=Administrator
+role_realm-admin=Administrator for sikkerhetsdomene
+role_create-realm=Opprette sikkerhetsdomene
+role_create-client=Opprette klient
+role_view-realm=Se sikkerhetsdomene
+role_view-users=Se brukere
+role_view-applications=Se applikasjoner
+role_view-clients=Se klienter
+role_view-events=Se hendelser
+role_view-identity-providers=Se identitetsleverand\u00F8rer
+role_manage-realm=Administrere sikkerhetsdomene
+role_manage-users=Administrere brukere
+role_manage-applications=Administrere applikasjoner
+role_manage-identity-providers=Administrere identitetsleverand\u00F8rer
+role_manage-clients=Administrere klienter
+role_manage-events=Administrere hendelser
+role_view-profile=Se profil
+role_manage-account=Administrere konto
+role_read-token=Lese token
+role_offline-access=Frakoblet tilgang
+role_uma_authorization=Skaffe tillatelser
+client_account=Konto
+client_security-admin-console=Sikkerthetsadministrasjonskonsoll
+client_realm-management=Sikkerhetsdomene-administrasjon
+client_broker=Broker
+
+invalidUserMessage=Ugyldig brukernavn eller passord.
+invalidEmailMessage=Ugyldig e-postadresse.
+accountDisabledMessage=Konto er deaktivert, kontakt administrator.
+accountTemporarilyDisabledMessage=Konto er midlertidig deaktivert, kontakt administrator eller pr\u00F8v p\u00E5 nytt senere.
+expiredCodeMessage=Login ble tidsavbrutt. Vennligst logg inn p\u00E5 nytt.
+
+missingFirstNameMessage=Vennligst oppgi fornavn.
+missingLastNameMessage=Vennligst oppgi etternavn.
+missingEmailMessage=Vennligst oppgi e-postadresse.
+missingUsernameMessage=Vennligst oppgi brukernavn.
+missingPasswordMessage=Vennligst oppgi passord.
+missingTotpMessage=Vennligst oppgi autentiseringskode.
+notMatchPasswordMessage=Passordene er ikke like.
+
+invalidPasswordExistingMessage=Ugyldig eksisterende passord.
+invalidPasswordConfirmMessage=Passord er ikke like.
+invalidTotpMessage=Ugyldig engangskode.
+
+usernameExistsMessage=Brukernavnet finnes allerede.
+emailExistsMessage=E-post finnes allerede.
+
+federatedIdentityExistsMessage=Bruker med {0} {1} finnes allerede. Vennligst logg inn p\u00E5 kontoadministratsjon for \u00E5 koble sammen kontoene.
+
+confirmLinkIdpTitle=Kontoen finnes allerede
+federatedIdentityConfirmLinkMessage=Bruker med {0} {1} finnes allerede. Hvordan vil du fortsette?
+federatedIdentityConfirmReauthenticateMessage=Bekreft at du er {0} for \u00E5 koble din konto med {1}
+confirmLinkIdpReviewProfile=Se over og bekreft profil
+confirmLinkIdpContinue=Legg til eksisterende konto
+
+configureTotpMessage=Du m\u00E5 sette opp en engangskode-generator for \u00E5 aktivere konto.
+updateProfileMessage=Du m\u00E5 oppdatere brukerprofilen din for \u00E5 aktivere konto.
+updatePasswordMessage=Du m\u00E5 skifte passord for \u00E5 aktivere kontoen din.
+verifyEmailMessage=Du m\u00E5 bekrefte e-postadressen din for \u00E5 aktivere konto.
+linkIdpMessage=You need to verify your email address to link your account with {0}.
+
+emailSentMessage=Du vil straks motta en e-post med ytterlige instruksjoner.
+emailSendErrorMessage=Mislyktes \u00E5 sende e-post, vennligst pr\u00F8v igjen senere.
+
+accountUpdatedMessage=Din konto har blitt oppdatert.
+accountPasswordUpdatedMessage=Ditt passord har blitt oppdatert.
+
+noAccessMessage=Ingen tilgang
+
+invalidPasswordMinLengthMessage=Ugyldig passord: minimum lengde {0}.
+invalidPasswordMinDigitsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sifre.
+invalidPasswordMinLowerCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sm\u00E5 bokstaver.
+invalidPasswordMinUpperCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} store bokstaver.
+invalidPasswordMinSpecialCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} spesialtegn.
+invalidPasswordNotUsernameMessage=Ugyldig passord: kan ikke v\u00E6re likt brukernavn.
+invalidPasswordRegexPatternMessage=Ugyldig passord: tilfredsstiller ikke kravene for passord-m\u00F8nster.
+invalidPasswordHistoryMessage=Ugyldig passord: kan ikke v\u00E6re likt noen av de {0} foreg\u00E5ende passordene.
+
+failedToProcessResponseMessage=Kunne ikke behandle svar
+httpsRequiredMessage=HTTPS p\u00E5krevd
+realmNotEnabledMessage=Sikkerhetsdomene er ikke aktivert
+invalidRequestMessage=Ugyldig foresp\u00F8rsel
+failedLogout=Utlogging feilet
+unknownLoginRequesterMessage=Ukjent anmoder for innlogging
+loginRequesterNotEnabledMessage=Anmoder for innlogging er ikke aktivert
+bearerOnlyMessage=Bearer-only applikasjoner har ikke lov til \u00E5 initiere innlogging via nettleser
+standardFlowDisabledMessage=Klienten har ikke lov til \u00E5 initiere innlogging via nettleser med gitt response_type. Standard flow er deaktivert for denne klienten.
+implicitFlowDisabledMessage=Klienten har ikke lov til \u00E5 initiere innlogging via nettleser med gitt response_type. Implicit flow er deaktivert for denne klienten.
+invalidRedirectUriMessage=Ugyldig redirect uri
+unsupportedNameIdFormatMessage=NameIDFormat er ikke st\u00F8ttet
+invalidRequesterMessage=Ugyldig sender av foresp\u00F8rsel
+registrationNotAllowedMessage=Registrering er ikke lov
+resetCredentialNotAllowedMessage=Tilbakestilling av innloggingsdata er ikke lov
+
+permissionNotApprovedMessage=Tillatelse ikke godkjent.
+noRelayStateInResponseMessage=Ingen relay state i svar fra identitetsleverand\u00F8r.
+insufficientPermissionMessage=Utilstrekkelige rettigheter for \u00E5 koble identiteter.
+couldNotProceedWithAuthenticationRequestMessage=Kunne ikke g\u00E5 videre med autentiseringsforesp\u00F8rsel til identitetsleverand\u00F8r.
+couldNotObtainTokenMessage=Klarte ikke \u00E5 innhente token fra identitetsleverand\u00F8r.
+unexpectedErrorRetrievingTokenMessage=Uventet feil ved henting av token fra identitetsleverand\u00F8r.
+unexpectedErrorHandlingResponseMessage=Uventet feil ved h\u00E5ndtering av svar fra identitetsleverand\u00F8r.
+identityProviderAuthenticationFailedMessage=Autentisering feilet. Kunne ikke autentisere med identitetsleverand\u00F8r.
+identityProviderDifferentUserMessage= Autentisert som {0}, men forventet \u00E5 bli identifisert som {1}
+couldNotSendAuthenticationRequestMessage=Kunne ikke sende autentiseringsforesp\u00F8rsel til identitetsleverand\u00F8r.
+unexpectedErrorHandlingRequestMessage=Uventet feil ved h\u00E5ndtering av autentiseringsforesp\u00F8rsel til identitetsleverand\u00F8r.
+invalidAccessCodeMessage=Ugyldig tilgangskode.
+sessionNotActiveMessage=Sesjonen er ikke aktiv.
+invalidCodeMessage=En feil oppstod, vennligst logg inn p\u00E5 nytt i din applikasjon.
+identityProviderUnexpectedErrorMessage=Uventet feil ved autentisering med identitetsleverand\u00F8r
+identityProviderNotFoundMessage=Kunne ikke finne en identitetsleverand\u00F8r med identifikatoren.
+identityProviderLinkSuccess=Din konto ble suksessfullt koblet med {0} konto {1}.
+staleCodeMessage=Denne siden er ikke lenger gyldig. Vennligst g\u00E5 tilbake til applikasjonen din og logg inn p\u00E5 nytt.
+realmSupportsNoCredentialsMessage=Sikkerhetsdomene st\u00F8tter ingen legitimasjonstyper.
+identityProviderNotUniqueMessage=Sikkerhetsdomene st\u00F8tter flere identitetsleverand\u00F8rer. Kunne ikke avgj\u00F8re hvilken identitetsleverand\u00F8r som burde brukes for autentisering.
+emailVerifiedMessage=Din e-postadresse har blitt verifisert.
+staleEmailVerificationLink=Lenken du klikket er utg\u00E5tt og er ikke lenger gyldig. Har du kanskje allerede bekreftet e-postadressen din?
+
+locale_ca=Catal\u00E0
+locale_de=Deutsch
+locale_en=English
+locale_es=Espa\u00F1ol
+locale_fr=Fran\u00e7ais
+locale_it=Italian
+locale_ja=\u65E5\u672C\u8A9E
+locale_no=Norsk
+locale_pt_BR=Portugu\u00EAs (Brasil)
+locale_pt-BR=Portugu\u00EAs (Brasil)
+locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439
+
+backToApplication=« Tilbake til applikasjonen
+missingParameterMessage=Manglende parameter\: {0}
+clientNotFoundMessage=Klient ikke funnet.
+clientDisabledMessage=Klient deaktivert.
+invalidParameterMessage=Ugyldig parameter\: {0}
+alreadyLoggedIn=Du er allerede innlogget.
+
+p3pPolicy=CP="Dette er ikke en P3P policy!"
diff --git a/themes/src/main/resources/theme/base/login/theme.properties b/themes/src/main/resources/theme/base/login/theme.properties
index 2f853e8..2cf724d 100644
--- a/themes/src/main/resources/theme/base/login/theme.properties
+++ b/themes/src/main/resources/theme/base/login/theme.properties
@@ -1 +1 @@
-locales=ca,de,en,es,fr,it,ja,pt-BR,ru
\ No newline at end of file
+locales=ca,de,en,es,fr,it,ja,lt,no,pt-BR,ru
\ No newline at end of file
diff --git a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties
index 2355ab0..e29d1bd 100644
--- a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties
+++ b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties
@@ -30,6 +30,9 @@ keycloak.server.subsys.default.config=\
<spi name="user">\
<default-provider>jpa</default-provider>\
</spi>\
+ <spi name="userFederatedStorage">\
+ <default-provider>jpa</default-provider>\
+ </spi>\
<spi name="userCache">\
<provider name="default" enabled="true"/>\
</spi>\
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
index 326fa7f..c823e4f 100755
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml
@@ -29,7 +29,7 @@
<password>sa</password>
</security>
</datasource>
- <datasource jndi-name="java:jboss/datasources/KeycloakDS" jta="false" pool-name="KeycloakDS" enabled="true" use-java-context="true">
+ <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
<?KEYCLOAK_DS_CONNECTION_URL?>
<driver>h2</driver>
<security>