From 80af2bba81520d2d5ae04ac3c21e6be18c3651b1 Mon Sep 17 00:00:00 2001 From: Aaron Lefkowitz Date: Fri, 20 Nov 2015 15:36:21 -0800 Subject: [PATCH 001/103] Stop removing licenses from final build. --- .clean-cf-release-build | 2 -- 1 file changed, 2 deletions(-) diff --git a/.clean-cf-release-build b/.clean-cf-release-build index efbee6b405a..409a27662f8 100755 --- a/.clean-cf-release-build +++ b/.clean-cf-release-build @@ -20,8 +20,6 @@ rm -f ${BUILD_DIR}/uaa/build.gradle rm -f ${BUILD_DIR}/uaa/gradle.properties rm -f ${BUILD_DIR}/uaa/gradlew rm -f ${BUILD_DIR}/uaa/gradlew.bat -rm -f ${BUILD_DIR}/uaa/LICENSE -rm -f ${BUILD_DIR}/uaa/NOTICE rm -f ${BUILD_DIR}/uaa/README.md rm -f ${BUILD_DIR}/uaa/settings.gradle rm -f ${BUILD_DIR}/uaa/shared_versions.gradle From 5e0de68cdb4f25988e16330fa10b61106bada768 Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Mon, 16 Nov 2015 17:04:15 -0800 Subject: [PATCH 002/103] Move IdentityZone model into the payload JAR [#107504490] https://www.pivotaltracker.com/story/show/107504490 --- .../identity/uaa/authentication/Origin.java | 9 - .../authentication/SessionResetFilter.java | 3 +- .../login/LoginInfoEndpoint.java | 18 +- .../login/RemoteAuthenticationEndpoint.java | 8 +- .../manager/AuthzAuthenticationManager.java | 4 +- .../manager/LoginAuthenticationManager.java | 6 +- .../manager/PeriodLockoutPolicy.java | 4 +- .../uaa/config/IdentityProviderBootstrap.java | 23 +- .../uaa/db/BootstrapIdentityZones.java | 6 +- .../login/PasscodeAuthenticationFilter.java | 8 +- .../login/saml/ProviderChangedListener.java | 5 +- .../login/saml/ZoneAwareMetadataManager.java | 4 +- .../identity/uaa/oauth/AccessController.java | 3 +- .../oauth/ClientAdminEndpointsValidator.java | 5 +- .../ZoneEndpointsClientDetailsValidator.java | 4 +- .../identity/uaa/user/UaaUserEditor.java | 4 +- .../identity/uaa/util/DomainFilter.java | 2 +- .../DisableInternalUserManagementFilter.java | 4 +- .../DisableUserManagementSecurityFilter.java | 4 +- .../identity/uaa/zone/IdentityProvider.java | 13 +- .../uaa/zone/IdentityZoneEndpoints.java | 49 +++- .../JdbcIdentityProviderProvisioning.java | 13 +- .../SessionResetFilterTests.java | 12 +- .../login/LoginInfoEndpointTests.java | 6 +- .../RemoteAuthenticationEndpointTests.java | 4 +- .../AuthzAuthenticationManagerTests.java | 34 +-- ...ckIdpEnabledAuthenticationManagerTest.java | 8 +- .../LdapLoginAuthenticationManagerTests.java | 10 +- .../LoginAuthenticationManagerTests.java | 16 +- .../manager/PeriodLockoutPolicyTests.java | 4 +- .../config/IdentityProviderBootstrapTest.java | 86 +++---- .../uaa/oauth/CheckTokenEndpointTests.java | 14 +- .../UaaAuthorizationRequestManagerTests.java | 4 +- ...eEndpointsClientDetailsValidatorTests.java | 8 +- .../oauth/expression/IsUserSelfCheckTest.java | 6 +- .../oauth/token/UaaTokenServicesTests.java | 28 +-- .../uaa/oauth/token/UaaTokenStoreTests.java | 4 +- .../uaa/openid/UserInfoEndpointTests.java | 4 +- .../DefaultSecurityContextAccessorTests.java | 8 +- .../identity/uaa/test/TestAccountSetup.java | 4 +- .../identity/uaa/test/UaaTestAccounts.java | 4 +- .../user/InMemoryUaaUserDatabaseTests.java | 10 +- .../uaa/user/JdbcUaaUserDatabaseTests.java | 18 +- .../uaa/user/MockUaaUserDatabase.java | 6 +- .../identity/uaa/user/UaaUserTestFactory.java | 7 +- .../identity/uaa/util/DomainFilterTest.java | 12 +- .../uaa/zone/IdentityProviderTests.java | 237 ------------------ ...JdbcIdentityProviderProvisioningTests.java | 5 +- ...DynamicZoneAwareAuthenticationManager.java | 6 +- .../invitations/InvitationsController.java | 12 +- .../uaa/invitations/InvitationsEndpoint.java | 2 +- .../uaa/login/AbstractControllerInfo.java | 6 +- .../uaa/login/AccountsController.java | 4 +- .../login/AutologinAuthenticationManager.java | 7 +- .../uaa/login/ChangeEmailController.java | 4 +- .../login/EmailAccountCreationService.java | 6 +- .../uaa/login/EmailChangeEmailService.java | 4 +- .../uaa/login/EmailInvitationsService.java | 4 +- .../uaa/login/LoginUaaApprovalsService.java | 4 +- .../identity/uaa/login/ProfileController.java | 4 +- .../uaa/login/ResetPasswordController.java | 4 +- .../uaa/login/RestUaaApprovalsService.java | 4 +- .../saml/LoginSamlAuthenticationProvider.java | 10 +- .../uaa/login/util/LocalUaaRestTemplate.java | 4 +- .../uaa/zone/IdentityProviderEndpoints.java | 6 +- .../InvitationsControllerTest.java | 18 +- .../AutologinAuthenticationManagerTest.java | 15 +- .../uaa/login/ChangeEmailControllerTest.java | 10 +- .../EmailAccountCreationServiceTests.java | 4 +- .../login/EmailInvitationsServiceTests.java | 8 +- .../uaa/login/ProfileControllerTests.java | 8 +- .../uaa/login/util/SecurityUtils.java | 7 +- payload/build.gradle | 6 +- .../uaa/config/IdentityZoneConfiguration.java | 0 .../identity/uaa/config/KeyPair.java | 7 + .../identity/uaa/config/KeyPairsMap.java | 0 .../identity/uaa/config/SamlConfig.java | 0 .../identity/uaa/config/TokenPolicy.java | 0 .../identity/uaa/constants/OriginKeys.java | 32 +++ .../identity/uaa/zone/IdentityZone.java | 11 +- .../LdapGroupMappingAuthorizationManager.java | 6 +- .../uaa/login/UaaResetPasswordService.java | 7 +- .../identity/uaa/scim/ScimGroupMember.java | 4 +- .../uaa/scim/ScimUserJsonDeserializer.java | 4 +- .../bootstrap/ScimExternalGroupBootstrap.java | 4 +- .../uaa/scim/bootstrap/ScimUserBootstrap.java | 10 +- .../scim/endpoints/ChangeEmailEndpoints.java | 4 +- .../scim/endpoints/PasswordResetEndpoint.java | 5 +- .../scim/endpoints/ScimGroupEndpoints.java | 12 +- .../endpoints/UserIdConversionEndpoints.java | 4 +- .../scim/jdbc/JdbcScimUserProvisioning.java | 8 +- .../validate/UaaPasswordPolicyValidator.java | 4 +- .../ScimExternalGroupBootstrapTests.java | 53 ++-- .../bootstrap/ScimUserBootstrapTests.java | 8 +- .../endpoints/ChangeEmailEndpointsTest.java | 6 +- .../endpoints/PasswordResetEndpointTest.java | 20 +- .../endpoints/ScimGroupEndpointsTests.java | 4 +- ...imGroupExternalMembershipManagerTests.java | 12 +- .../JdbcScimGroupMembershipManagerTests.java | 23 +- .../jdbc/JdbcScimUserProvisioningTests.java | 11 +- .../UaaPasswordPolicyValidatorTests.java | 6 +- shared_versions.gradle | 1 + uaa/src/main/resources/messages.properties | 3 + ...micZoneAwareAuthenticationManagerTest.java | 40 +-- .../uaa/db/TestZonifyGroupSchema_V2_4_1.java | 13 +- ...IdentityZoneEndpointsIntegrationTests.java | 6 +- .../uaa/integration/LdapIntegationTests.java | 10 +- .../LoginServerSecurityIntegrationTests.java | 14 +- .../RemoteAuthenticationEndpointTests.java | 16 +- .../integration/feature/InvitationsIT.java | 12 +- .../uaa/integration/feature/SamlLoginIT.java | 20 +- .../util/IntegrationTestUtils.java | 10 +- .../InvitationsEndpointMockMvcTests.java | 10 +- .../login/AccountsControllerMockMvcTests.java | 14 +- .../identity/uaa/login/BootstrapTests.java | 4 +- .../login/InvitationsServiceMockMvcTests.java | 28 +-- .../identity/uaa/login/LoginMockMvcTests.java | 16 +- .../uaa/login/PasscodeMockMvcTests.java | 4 +- .../ResetPasswordControllerMockMvcTests.java | 6 +- .../LoginSamlAuthenticationProviderTests.java | 30 +-- .../saml/SamlIDPRefreshMockMvcTests.java | 8 +- .../uaa/mock/ldap/LdapMockMvcTests.java | 56 ++--- .../uaa/mock/token/TokenMvcMockTests.java | 110 ++++---- .../identity/uaa/mock/util/MockMvcUtils.java | 14 +- ...IdentityProviderEndpointsMockMvcTests.java | 15 +- .../IdentityZoneEndpointsMockMvcTests.java | 63 +++-- .../PasswordResetEndpointMockMvcTests.java | 4 +- .../ScimGroupEndpointsMockMvcTests.java | 4 +- .../endpoints/ScimUserLookupMockMvcTests.java | 4 +- 129 files changed, 788 insertions(+), 937 deletions(-) delete mode 100644 common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderTests.java rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java (100%) rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java (92%) rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java (100%) rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java (100%) rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java (100%) create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java (93%) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/Origin.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/Origin.java index 45392999ebc..12c3af87766 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/Origin.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/Origin.java @@ -21,15 +21,6 @@ public class Origin { - public static final String ORIGIN = "origin"; - public static final String UAA = "uaa"; - public static final String LOGIN_SERVER = "login-server"; - public static final String LDAP = "ldap"; - public static final String KEYSTONE = "keystone"; - public static final String SAML = "saml"; - public static final String NotANumber = "NaN"; - public static final String UNKNOWN = "unknown"; - public static String getUserId(Authentication authentication) { String id; if (authentication.getPrincipal() instanceof UaaPrincipal) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilter.java index 86b58d9aca7..5933ec2de3d 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilter.java @@ -17,6 +17,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.springframework.security.core.context.SecurityContext; @@ -57,7 +58,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse if (context!=null && context.getAuthentication()!=null && context.getAuthentication() instanceof UaaAuthentication) { UaaAuthentication authentication = (UaaAuthentication)context.getAuthentication(); if (authentication.isAuthenticated() && - Origin.UAA.equals(authentication.getPrincipal().getOrigin()) && + OriginKeys.UAA.equals(authentication.getPrincipal().getOrigin()) && null != request.getSession(false)) { boolean redirect = false; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java index ff7a26591f5..c1684da1346 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java @@ -13,13 +13,13 @@ package org.cloudfoundry.identity.uaa.authentication.login; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.AutologinRequest; import org.cloudfoundry.identity.uaa.login.AutologinResponse; import org.cloudfoundry.identity.uaa.login.PasscodeInformation; @@ -81,7 +81,7 @@ @Controller public class LoginInfoEndpoint { - public static final String NotANumber = Origin.NotANumber; + public static final String NotANumber = OriginKeys.NotANumber; public static final String CREATE_ACCOUNT_LINK = "createAccountLink"; public static final String FORGOT_PASSWORD_LINK = "forgotPasswordLink"; public static final String LINK_CREATE_ACCOUNT_SHOW = "linkCreateAccountShow"; @@ -226,9 +226,9 @@ private String login(Model model, Principal principal, List excludedProm boolean fieldUsernameShow = true; if (allowedIdps==null || - allowedIdps.contains(Origin.LDAP) || - allowedIdps.contains(Origin.UAA) || - allowedIdps.contains(Origin.KEYSTONE)) { + allowedIdps.contains(OriginKeys.LDAP) || + allowedIdps.contains(OriginKeys.UAA) || + allowedIdps.contains(OriginKeys.KEYSTONE)) { fieldUsernameShow = true; } else if (idps!=null && idps.size()==1) { String url = SamlRedirectUtils.getIdpRedirectUrl(idps.get(0), entityID); @@ -237,7 +237,7 @@ private String login(Model model, Principal principal, List excludedProm fieldUsernameShow = false; } boolean linkCreateAccountShow = fieldUsernameShow; - if (fieldUsernameShow && (allowedIdps!=null && !allowedIdps.contains(Origin.UAA))) { + if (fieldUsernameShow && (allowedIdps!=null && !allowedIdps.contains(OriginKeys.UAA))) { linkCreateAccountShow = false; } String zonifiedEntityID = getZonifiedEntityId(); @@ -378,7 +378,7 @@ public AutologinResponse generateAutologinCode(@RequestBody AutologinRequest req UaaPrincipal p = (UaaPrincipal)userAuthentication.getPrincipal(); if (p!=null) { codeData.put("user_id", p.getId()); - codeData.put(Origin.ORIGIN, p.getOrigin()); + codeData.put(OriginKeys.ORIGIN, p.getOrigin()); } } ExpiringCode expiringCode = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000)); @@ -442,11 +442,11 @@ protected ExpiringCode doGenerateCode(Object o) throws IOException { protected Map getLinksInfo() { Map model = new HashMap<>(); - model.put(Origin.UAA, getUaaBaseUrl()); + model.put(OriginKeys.UAA, getUaaBaseUrl()); if (getBaseUrl().contains("localhost:")) { model.put("login", getUaaBaseUrl()); } else { - model.put("login", getUaaBaseUrl().replaceAll(Origin.UAA, "login")); + model.put("login", getUaaBaseUrl().replaceAll(OriginKeys.UAA, "login")); } if (selfServiceLinksEnabled && !disableInternalUserManagement) { model.put(CREATE_ACCOUNT_LINK, "/create_account"); diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java index d99d1c32864..4ab0cc7a9fa 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java @@ -22,9 +22,9 @@ import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.AccountNotVerifiedException; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -100,7 +100,7 @@ public HttpEntity> authenticate(HttpServletRequest request, @ResponseBody public HttpEntity> authenticate(HttpServletRequest request, @RequestParam(value = "username", required = true) String username, - @RequestParam(value = Origin.ORIGIN, required = true) String origin, + @RequestParam(value = OriginKeys.ORIGIN, required = true) String origin, @RequestParam(value = "email", required = false) String email) { Map responseBody = new HashMap<>(); HttpStatus status = HttpStatus.UNAUTHORIZED; @@ -112,7 +112,7 @@ public HttpEntity> authenticate(HttpServletRequest request, Map userInfo = new HashMap<>(); userInfo.put("username", username); - userInfo.put(Origin.ORIGIN, origin); + userInfo.put(OriginKeys.ORIGIN, origin); if (StringUtils.hasText(email)) { userInfo.put("email", email); } @@ -138,7 +138,7 @@ private void processAdditionalInformation(Map responseBody, Auth if (hasClientOauth2Authentication()) { UaaPrincipal principal = getPrincipal(a); if (principal!=null) { - responseBody.put(Origin.ORIGIN, principal.getOrigin()); + responseBody.put(OriginKeys.ORIGIN, principal.getOrigin()); responseBody.put("user_id", principal.getId()); } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java index e462844c400..5d17b44916d 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java @@ -16,7 +16,6 @@ import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.AccountNotVerifiedException; import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.PasswordExpiredException; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; @@ -25,6 +24,7 @@ import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationFailureEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserNotFoundEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.ObjectUtils; @@ -163,7 +163,7 @@ public Authentication authenticate(Authentication req) throws AuthenticationExce protected int getPasswordExpiresInMonths() { int result = 0; - IdentityProvider provider = providerProvisioning.retrieveByOrigin(Origin.UAA, IdentityZoneHolder.get().getId()); + IdentityProvider provider = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); if (provider!=null) { UaaIdentityProviderDefinition idpDefinition = ObjectUtils.castInstance(provider.getConfig(),UaaIdentityProviderDefinition.class); if (idpDefinition!=null) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java index 2e89fbc1ec9..276583d38e0 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java @@ -15,11 +15,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; @@ -41,7 +41,7 @@ import java.util.Map; public class LoginAuthenticationManager implements AuthenticationManager, ApplicationEventPublisherAware { - public static final String NotANumber = Origin.NotANumber; + public static final String NotANumber = OriginKeys.NotANumber; private final Log logger = LogFactory.getLog(getClass()); @@ -127,7 +127,7 @@ protected UaaUser getUser(AuthzAuthenticationRequest req, Map in String name = req.getName(); String email = info.get("email"); String userId = info.get("user_id")!=null?info.get("user_id"):NotANumber; - String origin = info.get(Origin.ORIGIN)!=null?info.get(Origin.ORIGIN):Origin.LOGIN_SERVER; + String origin = info.get(OriginKeys.ORIGIN)!=null?info.get(OriginKeys.ORIGIN): OriginKeys.LOGIN_SERVER; if (name == null && email != null) { name = email; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java index e5044d51cbe..4fde9460925 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java @@ -17,8 +17,8 @@ import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.UaaAuditService; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.config.LockoutPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.cloudfoundry.identity.uaa.zone.IdentityProvider; @@ -107,7 +107,7 @@ public void setLockoutPolicy(LockoutPolicy lockoutPolicy) { } private LockoutPolicy getLockoutPolicyFromDb() { - IdentityProvider idp = providerProvisioning.retrieveByOrigin(Origin.UAA, IdentityZoneHolder.get().getId()); + IdentityProvider idp = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); UaaIdentityProviderDefinition idpDefinition = ObjectUtils.castInstance(idp.getConfig(),UaaIdentityProviderDefinition.class); if (idpDefinition != null && idpDefinition.getLockoutPolicy() !=null ) { return idpDefinition.getLockoutPolicy(); diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java b/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java index 8d6b390a27b..2dd6f600e90 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java @@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.config; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.KeystoneIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.authentication.Origin; @@ -71,7 +72,7 @@ protected void addSamlProviders() { } for (SamlIdentityProviderDefinition def : configurator.getIdentityProviderDefinitions()) { IdentityProvider provider = new IdentityProvider(); - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); provider.setOriginKey(def.getIdpEntityAlias()); provider.setName("UAA SAML Identity Provider["+provider.getOriginKey()+"]"); provider.setActive(true); @@ -89,12 +90,12 @@ public void setLdapConfig(HashMap ldapConfig) { } protected void addLdapProvider() { - boolean ldapProfile = Arrays.asList(environment.getActiveProfiles()).contains(Origin.LDAP); + boolean ldapProfile = Arrays.asList(environment.getActiveProfiles()).contains(OriginKeys.LDAP); if (ldapConfig != null || ldapProfile) { IdentityProvider provider = new IdentityProvider(); provider.setActive(ldapProfile); - provider.setOriginKey(Origin.LDAP); - provider.setType(Origin.LDAP); + provider.setOriginKey(OriginKeys.LDAP); + provider.setType(OriginKeys.LDAP); provider.setName("UAA LDAP Provider"); Map ldap = new HashMap<>(); ldap.put(LDAP, ldapConfig); @@ -144,12 +145,12 @@ protected AbstractIdentityProviderDefinition getKeystoneDefinition(Map originMap = new HashMap(); Set origins = new LinkedHashSet(); - origins.addAll(Arrays.asList(new String[] {Origin.UAA,Origin.LOGIN_SERVER,Origin.LDAP,Origin.KEYSTONE})); + origins.addAll(Arrays.asList(new String[] {OriginKeys.UAA, OriginKeys.LOGIN_SERVER, OriginKeys.LDAP, OriginKeys.KEYSTONE})); origins.addAll(jdbcTemplate.queryForList("SELECT DISTINCT origin from users", String.class)); for (String origin : origins) { String identityProviderId = UUID.randomUUID().toString(); @@ -33,7 +33,7 @@ public void migrate(JdbcTemplate jdbcTemplate) throws Exception { jdbcTemplate.update("update oauth_client_details set identity_zone_id = ?",uaa.getId()); List clientIds = jdbcTemplate.queryForList("SELECT client_id from oauth_client_details", String.class); for (String clientId : clientIds) { - jdbcTemplate.update("insert into client_idp values (?,?) ",clientId,originMap.get(Origin.UAA)); + jdbcTemplate.update("insert into client_idp values (?,?) ",clientId,originMap.get(OriginKeys.UAA)); } jdbcTemplate.update("update users set identity_provider_id = (select id from identity_provider where identity_provider.origin_key = users.origin), identity_zone_id = (select identity_zone_id from identity_provider where identity_provider.origin_key = users.origin);"); jdbcTemplate.update("update group_membership set identity_provider_id = (select id from identity_provider where identity_provider.origin_key = group_membership.origin);"); diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeAuthenticationFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeAuthenticationFilter.java index 40f4dc96b12..d7f5b3ea3af 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeAuthenticationFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeAuthenticationFilter.java @@ -32,12 +32,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.BackwardsCompatibleTokenEndpointAuthenticationFilter; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.client.SocialClientUserDetails; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; -import org.cloudfoundry.identity.uaa.user.UaaAuthority; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -224,7 +222,7 @@ public Authentication authenticate(Authentication authentication) throws Authent PasscodeHttpServletRequest pcRequest = (PasscodeHttpServletRequest)expiringCodeAuthentication.getRequest(); //pcRequest.addParameter("user_id", new String[] {pi.getUserId()}); pcRequest.addParameter("username", new String[] {pi.getUsername()}); - pcRequest.addParameter(Origin.ORIGIN, new String[] {pi.getOrigin()}); + pcRequest.addParameter(OriginKeys.ORIGIN, new String[] {pi.getOrigin()}); return result; } @@ -282,4 +280,4 @@ public void destroy() { public void setParameterNames(List parameterNames) { this.parameterNames = parameterNames; } -} \ No newline at end of file +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java index 31542b568ad..71d0273bd7c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java @@ -16,7 +16,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.cloudfoundry.identity.uaa.zone.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; @@ -46,7 +47,7 @@ public void onApplicationEvent(IdentityProviderModifiedEvent event) { return; } IdentityProvider eventProvider = (IdentityProvider)event.getSource(); - if (Origin.SAML.equals(eventProvider.getType())) { + if (OriginKeys.SAML.equals(eventProvider.getType())) { IdentityProvider provider = (IdentityProvider)eventProvider; IdentityZone zone = zoneProvisioning.retrieve(provider.getIdentityZoneId()); ZoneAwareMetadataManager.ExtensionMetadataManager manager = metadataManager.getManager(zone); diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java index d6886ffdc03..6fdf342977a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java @@ -16,7 +16,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; @@ -129,7 +129,7 @@ protected void refreshAllProviders(boolean ignoreTimestamp) throws MetadataProvi ExtensionMetadataManager manager = getManager(zone); boolean hasChanges = false; for (IdentityProvider provider : providerDao.retrieveAll(false,zone.getId())) { - if (Origin.SAML.equals(provider.getType()) && (ignoreTimestamp || lastRefresh < provider.getLastModified().getTime())) { + if (OriginKeys.SAML.equals(provider.getType()) && (ignoreTimestamp || lastRefresh < provider.getLastModified().getTime())) { try { SamlIdentityProviderDefinition definition = (SamlIdentityProviderDefinition)provider.getConfig(); try { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java index 2cb1de1d3fb..13164607fa1 100755 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; import org.springframework.security.authentication.InsufficientAuthenticationException; @@ -234,7 +235,7 @@ private List> getScopes(ClientDetails client, ArrayList map = new HashMap(); String value = SCOPE_PREFIX + scope; String resource = scope.substring(0, scope.lastIndexOf(".")); - if (Origin.UAA.equals(resource)) { + if (OriginKeys.UAA.equals(resource)) { // special case: don't need to prompt for internal uaa // scopes continue; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsValidator.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsValidator.java index ae098a3aff6..206c8161dae 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsValidator.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsValidator.java @@ -20,11 +20,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; -import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.provider.ClientDetails; @@ -49,7 +48,7 @@ public class ClientAdminEndpointsValidator implements InitializingBean, ClientDe private SecurityContextAccessor securityContextAccessor = new DefaultSecurityContextAccessor(); - private Set reservedClientIds = StringUtils.commaDelimitedListToSet(Origin.UAA); + private Set reservedClientIds = StringUtils.commaDelimitedListToSet(OriginKeys.UAA); /** diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java index 99290698adf..b87f5616ebc 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java @@ -3,8 +3,8 @@ import java.util.Collections; import org.apache.commons.lang.StringUtils; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails; @@ -37,7 +37,7 @@ public ClientDetails validate(ClientDetails clientDetails, Mode mode) throws Inv throw new InvalidClientDetailsException("client_secret cannot be blank"); } } - if (!Collections.singletonList(Origin.UAA).equals(clientDetails.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS))) { + if (!Collections.singletonList(OriginKeys.UAA).equals(clientDetails.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS))) { throw new InvalidClientDetailsException("only the internal IdP ('uaa') is allowed"); } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserEditor.java b/common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserEditor.java index c12f8ae5e05..3c9f4956298 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserEditor.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserEditor.java @@ -16,7 +16,7 @@ import java.util.Arrays; import java.util.List; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.core.authority.AuthorityUtils; @@ -35,7 +35,7 @@ public void setAsText(String text) throws IllegalArgumentException { } String username = values[0], password = values[1]; - String email = username, firstName = null, lastName = null, origin = Origin.UAA; + String email = username, firstName = null, lastName = null, origin = OriginKeys.UAA; String authorities = null; if (values.length > 2) { switch (values.length) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java index 364ced9ea17..d236ea755c0 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java @@ -26,7 +26,7 @@ import java.util.stream.Collectors; import static java.util.Collections.EMPTY_LIST; -import static org.cloudfoundry.identity.uaa.authentication.Origin.UAA; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; public class DomainFilter { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java index 7db713c8a6a..948fbed6765 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.zone; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -40,7 +40,7 @@ public DisableInternalUserManagementFilter(IdentityProviderProvisioning identity protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (matches(request)) { - IdentityProvider idp = identityProviderProvisioning.retrieveByOrigin(Origin.UAA, IdentityZoneHolder.get().getId()); + IdentityProvider idp = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); boolean isDisableInternalUserManagement = false; UaaIdentityProviderDefinition config = ObjectUtils.castInstance(idp.getConfig(), UaaIdentityProviderDefinition.class); if (config != null) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableUserManagementSecurityFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableUserManagementSecurityFilter.java index 0c55e42f7a4..b5de9ba487f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableUserManagementSecurityFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableUserManagementSecurityFilter.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.zone; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.ExceptionReport; import org.cloudfoundry.identity.uaa.error.ExceptionReportHttpMessageConverter; import org.cloudfoundry.identity.uaa.util.ObjectUtils; @@ -66,7 +66,7 @@ public DisableUserManagementSecurityFilter(IdentityProviderProvisioning identity protected void doFilterInternal(HttpServletRequest request, final HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (matches(request)) { - IdentityProvider idp = identityProviderProvisioning.retrieveByOrigin(Origin.UAA, IdentityZoneHolder.get().getId()); + IdentityProvider idp = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); boolean isDisableInternalUserManagement = false; UaaIdentityProviderDefinition config = ObjectUtils.castInstance(idp.getConfig(), UaaIdentityProviderDefinition.class); if (config != null) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java index cbaa36e45a1..073f5d8e695 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java @@ -14,6 +14,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.type.TypeReference; + import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; @@ -27,6 +29,7 @@ import org.cloudfoundry.identity.uaa.KeystoneIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.config.LockoutPolicy; import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -37,11 +40,11 @@ import java.io.IOException; import java.util.Date; -import static org.cloudfoundry.identity.uaa.authentication.Origin.KEYSTONE; -import static org.cloudfoundry.identity.uaa.authentication.Origin.LDAP; -import static org.cloudfoundry.identity.uaa.authentication.Origin.SAML; -import static org.cloudfoundry.identity.uaa.authentication.Origin.UAA; -import static org.cloudfoundry.identity.uaa.authentication.Origin.UNKNOWN; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.KEYSTONE; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.SAML; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UNKNOWN; @JsonSerialize(using = IdentityProvider.IdentityProviderSerializer.class) @JsonDeserialize(using = IdentityProvider.IdentityProviderDeserializer.class) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java index f9936985c9a..b36bc9e25f5 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java @@ -12,10 +12,13 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.zone; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.oauth.InvalidClientDetailsException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; @@ -24,6 +27,9 @@ import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.util.StringUtils; +import org.springframework.validation.BindingResult; +import org.springframework.validation.Errors; +import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; @@ -32,9 +38,11 @@ import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.UUID; import static org.springframework.http.HttpStatus.CONFLICT; @@ -50,12 +58,14 @@ @RequestMapping("/identity-zones") public class IdentityZoneEndpoints { + @Autowired + private MessageSource messageSource; + private static final Logger logger = LoggerFactory.getLogger(IdentityZoneEndpoints.class); private final IdentityZoneProvisioning zoneDao; private final IdentityProviderProvisioning idpDao; private final IdentityZoneEndpointClientRegistrationService clientRegistrationService; - public IdentityZoneEndpoints(IdentityZoneProvisioning zoneDao, IdentityProviderProvisioning idpDao, IdentityZoneEndpointClientRegistrationService clientRegistrationService) { super(); @@ -93,7 +103,12 @@ protected List filterForCurrentZone(List zones) { } @RequestMapping(method = POST) - public ResponseEntity createIdentityZone(@RequestBody @Valid IdentityZone body) { + public ResponseEntity createIdentityZone(@RequestBody @Valid IdentityZone body, BindingResult result) { + + if (result.hasErrors()) { + throw new UnprocessableEntityException(getErrorMessages(result)); + } + if (!IdentityZoneHolder.isUaa()) { throw new AccessDeniedException("Zones can only be created by being authenticated in the default zone."); } @@ -107,9 +122,9 @@ public ResponseEntity createIdentityZone(@RequestBody @Valid Ident IdentityZone created = zoneDao.create(body); IdentityZoneHolder.set(created); IdentityProvider defaultIdp = new IdentityProvider(); - defaultIdp.setName(Origin.UAA); - defaultIdp.setType(Origin.UAA); - defaultIdp.setOriginKey(Origin.UAA); + defaultIdp.setName(OriginKeys.UAA); + defaultIdp.setType(OriginKeys.UAA); + defaultIdp.setOriginKey(OriginKeys.UAA); defaultIdp.setIdentityZoneId(created.getId()); UaaIdentityProviderDefinition idpDefinition = new UaaIdentityProviderDefinition(); idpDefinition.setPasswordPolicy(null); @@ -122,6 +137,14 @@ public ResponseEntity createIdentityZone(@RequestBody @Valid Ident } } + private String getErrorMessages(Errors errors) { + List messages = new ArrayList<>(); + for(ObjectError error : errors.getAllErrors()) { + messages.add(messageSource.getMessage(error, Locale.getDefault())); + } + return String.join("\r\n", messages); + } + @RequestMapping(value = "{id}", method = PUT) public ResponseEntity updateIdentityZone( @RequestBody @Valid IdentityZone body, @PathVariable String id) { @@ -158,7 +181,7 @@ public ResponseEntity createClient( } IdentityZone previous = IdentityZoneHolder.get(); try { - logger.debug("Zone creating client zone["+identityZoneId+"] client["+clientDetails.getClientId()+"]"); + logger.debug("Zone creating client zone[" + identityZoneId + "] client[" + clientDetails.getClientId() + "]"); IdentityZone identityZone = zoneDao.retrieve(identityZoneId); IdentityZoneHolder.set(identityZone); ClientDetails createdClient = clientRegistrationService.createClient(clientDetails); @@ -229,14 +252,24 @@ public ResponseEntity handleValidationException(MethodArgumentNotValidExce } @ExceptionHandler(AccessDeniedException.class) - public ResponseEntity handleAccessDeniedException(MethodArgumentNotValidException e) { + public ResponseEntity handleAccessDeniedException(AccessDeniedException e) { return new ResponseEntity<>(HttpStatus.FORBIDDEN); } + @ExceptionHandler(UnprocessableEntityException.class) + public ResponseEntity handleUnprocessableEntityException(UnprocessableEntityException e) { + return new ResponseEntity<>(e, HttpStatus.UNPROCESSABLE_ENTITY); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception e) { logger.error(e.getClass() + ": " + e.getMessage(), e); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } + private class UnprocessableEntityException extends UaaException { + public UnprocessableEntityException(String message) { + super("invalid_identity_zone", message, 422); + } + } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java index 150523925f3..a544dbd651f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.zone; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.KeystoneIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.authentication.Origin; @@ -142,8 +143,8 @@ protected void validate(IdentityProvider provider) { throw new DataIntegrityViolationException("Identity zone ID must be set."); } //ensure that SAML IDPs have reduntant fields synchronized - if (Origin.SAML.equals(provider.getType()) && provider.getConfig()!=null) { - SamlIdentityProviderDefinition saml = ObjectUtils.castInstance(provider.getConfig(),SamlIdentityProviderDefinition.class); + if (OriginKeys.SAML.equals(provider.getType()) && provider.getConfig()!=null) { + SamlIdentityProviderDefinition saml = ObjectUtils.castInstance(provider.getConfig(), SamlIdentityProviderDefinition.class); saml.setIdpEntityAlias(provider.getOriginKey()); saml.setZoneId(provider.getIdentityZoneId()); provider.setConfig(saml); @@ -166,16 +167,16 @@ public IdentityProvider mapRow(ResultSet rs, int rowNum) throws SQLException { if (StringUtils.hasText(config)) { AbstractIdentityProviderDefinition definition; switch (identityProvider.getType()) { - case Origin.SAML : + case OriginKeys.SAML : definition = JsonUtils.readValue(config, SamlIdentityProviderDefinition.class); break; - case Origin.UAA : + case OriginKeys.UAA : definition = JsonUtils.readValue(config, UaaIdentityProviderDefinition.class); break; - case Origin.LDAP : + case OriginKeys.LDAP : definition = JsonUtils.readValue(config, LdapIdentityProviderDefinition.class); break; - case Origin.KEYSTONE : + case OriginKeys.KEYSTONE : definition = JsonUtils.readValue(config, KeystoneIdentityProviderDefinition.class); break; default: diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java index 94e98068de3..eddec9ae3af 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java @@ -15,6 +15,7 @@ package org.cloudfoundry.identity.uaa.authentication; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.InMemoryUaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; @@ -35,17 +36,14 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.lang.reflect.Field; -import java.util.Calendar; import java.util.Collections; import java.util.Date; -import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -95,7 +93,7 @@ private void addUsersToInMemoryDb() { "family name", yesterday, yesterday, - Origin.UAA, + OriginKeys.UAA, null, true, IdentityZone.getUaa().getId(), @@ -113,7 +111,7 @@ private void addUsersToInMemoryDb() { "family name", yesterday, yesterday, - Origin.UAA, + OriginKeys.UAA, null, true, IdentityZone.getUaa().getId(), @@ -190,7 +188,7 @@ public void test_User_Not_Modified() throws Exception { @Test public void test_User_Not_Originated_In_Uaa() throws Exception { SecurityContextHolder.getContext().setAuthentication(authentication); - setFieldValue("origin", Origin.LDAP, authentication.getPrincipal()); + setFieldValue("origin", OriginKeys.LDAP, authentication.getPrincipal()); filter.doFilterInternal(request, response, chain); verify(chain, times(1)).doFilter(request, response); verifyZeroInteractions(request); @@ -203,4 +201,4 @@ protected void setFieldValue(String fieldname, Object value, Object object) { ReflectionUtils.setField(f, object, value); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java index 601c4931a68..6d7a712c172 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java @@ -1,12 +1,12 @@ package org.cloudfoundry.identity.uaa.authentication.login; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.InMemoryExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; @@ -78,7 +78,7 @@ public void testLoginReturnsSystemZone() throws Exception { LoginInfoEndpoint endpoint = getEndpoint(); assertFalse(model.containsAttribute("zone_name")); endpoint.loginForHtml(model, null, new MockHttpServletRequest()); - assertEquals(Origin.UAA, model.asMap().get("zone_name")); + assertEquals(OriginKeys.UAA, model.asMap().get("zone_name")); } @Test @@ -352,7 +352,7 @@ public void testFilterIDPsForAuthcodeClientInDefaultZone() throws Exception { // mock session and saved request MockHttpServletRequest request = getMockHttpServletRequest(); - List allowedProviders = Arrays.asList("my-client-awesome-idp1", "my-client-awesome-idp2", Origin.LDAP); + List allowedProviders = Arrays.asList("my-client-awesome-idp1", "my-client-awesome-idp2", OriginKeys.LDAP); // mock Client service BaseClientDetails clientDetails = new BaseClientDetails(); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpointTests.java index fc5f012ded4..6aa429c65c4 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpointTests.java @@ -18,8 +18,8 @@ import static org.mockito.Mockito.when; import org.cloudfoundry.identity.uaa.authentication.AccountNotVerifiedException; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpStatus; @@ -44,7 +44,7 @@ public class RemoteAuthenticationEndpointTests { @Before public void setUp() throws Exception { - UaaPrincipal principal = new UaaPrincipal("user-id-001", "joe", "joe@example.com", Origin.UAA, null, null); + UaaPrincipal principal = new UaaPrincipal("user-id-001", "joe", "joe@example.com", OriginKeys.UAA, null, null); success = new UsernamePasswordAuthenticationToken(principal, null); loginAuthMgr = mock(AuthenticationManager.class); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java index a5de0a41374..6e42cdd80e5 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java @@ -15,7 +15,6 @@ import org.cloudfoundry.identity.uaa.authentication.AccountNotVerifiedException; import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.PasswordExpiredException; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; @@ -24,6 +23,7 @@ import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserNotFoundEvent; import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; @@ -85,7 +85,7 @@ public void setUp() throws Exception { "A", "User", new Date(), new Date(), - Origin.UAA, + OriginKeys.UAA, null, true, IdentityZoneHolder.get().getId(), @@ -96,12 +96,12 @@ public void setUp() throws Exception { publisher = mock(ApplicationEventPublisher.class); mgr = new AuthzAuthenticationManager(db, encoder, providerProvisioning); mgr.setApplicationEventPublisher(publisher); - mgr.setOrigin(Origin.UAA); + mgr.setOrigin(OriginKeys.UAA); } @Test public void successfulAuthentication() throws Exception { - when(db.retrieveUserByName("auser", Origin.UAA)).thenReturn(user); + when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); Authentication result = mgr.authenticate(createAuthRequest("auser", "password")); assertNotNull(result); assertEquals("auser", result.getName()); @@ -130,31 +130,31 @@ public void unsuccessfulPasswordExpired() throws Exception { user.getFamilyName(), oneYearAgo, oneYearAgo, - Origin.UAA, + OriginKeys.UAA, null, true, IdentityZoneHolder.get().getId(), user.getSalt(), oneYearAgo); - when(db.retrieveUserByName("auser", Origin.UAA)).thenReturn(user); + when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); mgr.authenticate(createAuthRequest("auser", "password")); } @Test(expected = BadCredentialsException.class) public void unsuccessfulLoginServerUserAuthentication() throws Exception { - when(db.retrieveUserByName(loginServerUserName,Origin.UAA)).thenReturn(null); + when(db.retrieveUserByName(loginServerUserName, OriginKeys.UAA)).thenReturn(null); mgr.authenticate(createAuthRequest(loginServerUserName, "")); } @Test(expected = BadCredentialsException.class) public void unsuccessfulLoginServerUserWithPasswordAuthentication() throws Exception { - when(db.retrieveUserByName(loginServerUserName,Origin.UAA)).thenReturn(null); + when(db.retrieveUserByName(loginServerUserName, OriginKeys.UAA)).thenReturn(null); mgr.authenticate(createAuthRequest(loginServerUserName, "dadas")); } @Test public void successfulAuthenticationReturnsTokenAndPublishesEvent() throws Exception { - when(db.retrieveUserByName("auser",Origin.UAA)).thenReturn(user); + when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); Authentication result = mgr.authenticate(createAuthRequest("auser", "password")); assertNotNull(result); @@ -166,7 +166,7 @@ public void successfulAuthenticationReturnsTokenAndPublishesEvent() throws Excep @Test public void invalidPasswordPublishesAuthenticationFailureEvent() { - when(db.retrieveUserByName("auser",Origin.UAA)).thenReturn(user); + when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); try { mgr.authenticate(createAuthRequest("auser", "wrongpassword")); fail(); @@ -178,7 +178,7 @@ public void invalidPasswordPublishesAuthenticationFailureEvent() { @Test(expected = AuthenticationPolicyRejectionException.class) public void authenticationIsDeniedIfRejectedByLoginPolicy() throws Exception { - when(db.retrieveUserByName("auser", Origin.UAA)).thenReturn(user); + when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); AccountLoginPolicy lp = mock(AccountLoginPolicy.class); when(lp.isAllowed(any(UaaUser.class), any(Authentication.class))).thenReturn(false); mgr.setAccountLoginPolicy(lp); @@ -187,7 +187,7 @@ public void authenticationIsDeniedIfRejectedByLoginPolicy() throws Exception { @Test public void missingUserPublishesNotFoundEvent() { - when(db.retrieveUserByName(eq("aguess"),eq(Origin.UAA))).thenThrow(new UsernameNotFoundException("mocked")); + when(db.retrieveUserByName(eq("aguess"),eq(OriginKeys.UAA))).thenThrow(new UsernameNotFoundException("mocked")); try { mgr.authenticate(createAuthRequest("aguess", "password")); fail(); @@ -217,7 +217,7 @@ public void originAuthenticationFail() throws Exception { @Test public void unverifiedAuthenticationSucceedsWhenAllowed() throws Exception { user.setVerified(false); - when(db.retrieveUserByName("auser", Origin.UAA)).thenReturn(user); + when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); Authentication result = mgr.authenticate(createAuthRequest("auser", "password")); assertEquals("auser", result.getName()); assertEquals("auser", ((UaaPrincipal) result.getPrincipal()).getName()); @@ -227,7 +227,7 @@ public void unverifiedAuthenticationSucceedsWhenAllowed() throws Exception { public void unverifiedAuthenticationFailsWhenNotAllowed() throws Exception { mgr.setAllowUnverifiedUsers(false); user.setVerified(false); - when(db.retrieveUserByName("auser", Origin.UAA)).thenReturn(user); + when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); try { mgr.authenticate(createAuthRequest("auser", "password")); fail("Expected AccountNotVerifiedException"); @@ -254,7 +254,7 @@ public void unverified_authentication_never_allowed_in_non_default_zone() throws user.getFamilyName(), justASecondAgo, justASecondAgo, - Origin.UAA, + OriginKeys.UAA, null, true, IdentityZoneHolder.get().getId(), @@ -262,7 +262,7 @@ public void unverified_authentication_never_allowed_in_non_default_zone() throws justASecondAgo); calZoneUser.setVerified(false); - when(db.retrieveUserByName("auser", Origin.UAA)).thenReturn(calZoneUser); + when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(calZoneUser); try { mgr.authenticate(createAuthRequest("auser", "password")); fail("Expected AccountNotVerifiedException"); @@ -276,7 +276,7 @@ public void unverified_authentication_never_allowed_in_non_default_zone() throws public void userIsLockedOutAfterNumberOfFailedTriesIsExceeded() throws Exception { AccountLoginPolicy lockoutPolicy = mock(PeriodLockoutPolicy.class); mgr.setAccountLoginPolicy(lockoutPolicy); - when(db.retrieveUserByName("auser", Origin.UAA)).thenReturn(user); + when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); Authentication authentication = createAuthRequest("auser", "password"); when(lockoutPolicy.isAllowed(any(UaaUser.class), eq(authentication))).thenReturn(false); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java index 27f69e6824a..eafea73e9b0 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java @@ -15,7 +15,7 @@ package org.cloudfoundry.identity.uaa.authentication.manager; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.user.MockUaaUserDatabase; import org.cloudfoundry.identity.uaa.zone.IdentityProvider; @@ -48,8 +48,8 @@ public void setupAuthManager() throws Exception { PasswordEncoder encoder = mock(PasswordEncoder.class); when(encoder.matches(anyString(),anyString())).thenReturn(true); AuthzAuthenticationManager authzAuthenticationManager = new AuthzAuthenticationManager(userDatabase, encoder, identityProviderProvisioning); - authzAuthenticationManager.setOrigin(Origin.UAA); - manager = new CheckIdpEnabledAuthenticationManager(authzAuthenticationManager, Origin.UAA, identityProviderProvisioning); + authzAuthenticationManager.setOrigin(OriginKeys.UAA); + manager = new CheckIdpEnabledAuthenticationManager(authzAuthenticationManager, OriginKeys.UAA, identityProviderProvisioning); token = new UsernamePasswordAuthenticationToken("marissa", "koala"); } @@ -63,7 +63,7 @@ public void testAuthenticate() throws Exception { @Test(expected = ProviderNotFoundException.class) public void testAuthenticateIdpDisabled() throws Exception { - IdentityProvider provider = identityProviderProvisioning.retrieveByOrigin(Origin.UAA, IdentityZoneHolder.get().getId()); + IdentityProvider provider = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); provider.setActive(false); identityProviderProvisioning.update(provider); manager.authenticate(token); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java index c736c23a5a4..447313dde39 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java @@ -14,8 +14,8 @@ */ package org.cloudfoundry.identity.uaa.authentication.manager; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.ldap.extension.ExtendedLdapUserImpl; import org.cloudfoundry.identity.uaa.user.UaaAuthority; @@ -227,9 +227,9 @@ public void test_custom_user_attributes() throws Exception { when(auth.getPrincipal()).thenReturn(authDetails); UaaUserDatabase db = mock(UaaUserDatabase.class); - when(db.retrieveUserByName(anyString(), eq(Origin.LDAP))).thenReturn(user); + when(db.retrieveUserByName(anyString(), eq(OriginKeys.LDAP))).thenReturn(user); when(db.retrieveUserById(anyString())).thenReturn(user); - am.setOrigin(Origin.LDAP); + am.setOrigin(OriginKeys.LDAP); am.setUserDatabase(db); UaaAuthentication authentication = (UaaAuthentication)am.authenticate(auth); @@ -277,7 +277,7 @@ protected UaaUser getUaaUser() { .withPhoneNumber("8675309") .withCreated(new Date()) .withModified(new Date()) - .withOrigin(Origin.ORIGIN) + .withOrigin(OriginKeys.ORIGIN) .withExternalId(DN) .withVerified(false) .withZoneId(IdentityZoneHolder.get().getId()) @@ -303,4 +303,4 @@ public String[] getValues() { return values; } } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java index 0d3ff75f311..eda4f71a642 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java @@ -19,10 +19,10 @@ import java.util.Arrays; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.test.TestApplicationEventPublisher; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; @@ -89,7 +89,7 @@ public void testNotProcessingNotAuthenticated() { @Test public void testHappyDayNoAutoAdd() { UaaUser user = UaaUserTestFactory.getUser("FOO", "foo", "fo@test.org", "Foo", "Bar"); - Mockito.when(userDatabase.retrieveUserByName("foo", Origin.LOGIN_SERVER)).thenReturn(user); + Mockito.when(userDatabase.retrieveUserByName("foo", OriginKeys.LOGIN_SERVER)).thenReturn(user); Authentication authentication = manager.authenticate(UaaAuthenticationTestFactory .getAuthenticationRequest("foo")); assertEquals(user.getUsername(), ((UaaPrincipal) authentication.getPrincipal()).getName()); @@ -99,7 +99,7 @@ public void testHappyDayNoAutoAdd() { @Test public void testHappyDayWithAuthorities() { UaaUser user = UaaUserTestFactory.getAdminUser("FOO", "foo", "fo@test.org", "Foo", "Bar"); - Mockito.when(userDatabase.retrieveUserByName("foo", Origin.LOGIN_SERVER)).thenReturn(user); + Mockito.when(userDatabase.retrieveUserByName("foo", OriginKeys.LOGIN_SERVER)).thenReturn(user); Authentication authentication = manager.authenticate(UaaAuthenticationTestFactory .getAuthenticationRequest("foo")); assertEquals(user.getUsername(), ((UaaPrincipal) authentication.getPrincipal()).getName()); @@ -108,14 +108,14 @@ public void testHappyDayWithAuthorities() { @Test(expected = BadCredentialsException.class) public void testUserNotFoundNoAutoAdd() { - Mockito.when(userDatabase.retrieveUserByName("foo", Origin.LOGIN_SERVER)).thenThrow(new UsernameNotFoundException("planned")); + Mockito.when(userDatabase.retrieveUserByName("foo", OriginKeys.LOGIN_SERVER)).thenThrow(new UsernameNotFoundException("planned")); manager.authenticate(UaaAuthenticationTestFactory.getAuthenticationRequest("foo")); } @Test public void testHappyDayAutoAddButWithExistingUser() { UaaUser user = UaaUserTestFactory.getUser("FOO", "foo", "fo@test.org", "Foo", "Bar"); - Mockito.when(userDatabase.retrieveUserByName("foo", Origin.LOGIN_SERVER)).thenReturn(user); + Mockito.when(userDatabase.retrieveUserByName("foo", OriginKeys.LOGIN_SERVER)).thenReturn(user); Authentication authentication = manager.authenticate(UaaAuthenticationTestFactory .getAuthenticationRequest("foo", true)); assertEquals(user.getUsername(), ((UaaPrincipal) authentication.getPrincipal()).getName()); @@ -125,7 +125,7 @@ public void testHappyDayAutoAddButWithExistingUser() { @Test public void testHappyDayAutoAddButWithNewUser() { UaaUser user = UaaUserTestFactory.getUser("FOO", "foo", "fo@test.org", "Foo", "Bar"); - Mockito.when(userDatabase.retrieveUserByName("foo", Origin.LOGIN_SERVER)).thenThrow(new UsernameNotFoundException("planned")) + Mockito.when(userDatabase.retrieveUserByName("foo", OriginKeys.LOGIN_SERVER)).thenThrow(new UsernameNotFoundException("planned")) .thenReturn(user); Authentication authentication = manager.authenticate(UaaAuthenticationTestFactory .getAuthenticationRequest("foo", true)); @@ -136,7 +136,7 @@ public void testHappyDayAutoAddButWithNewUser() { @Test(expected = BadCredentialsException.class) public void testFailedAutoAddButWithNewUser() { UaaUser user = UaaUserTestFactory.getUser("FOO", "foo", "fo@test.org", "Foo", "Bar"); - Mockito.when(userDatabase.retrieveUserByName("foo", Origin.LOGIN_SERVER)).thenThrow(new UsernameNotFoundException("planned")); + Mockito.when(userDatabase.retrieveUserByName("foo", OriginKeys.LOGIN_SERVER)).thenThrow(new UsernameNotFoundException("planned")); Authentication authentication = manager.authenticate(UaaAuthenticationTestFactory .getAuthenticationRequest("foo", true)); assertEquals(user.getUsername(), ((UaaPrincipal) authentication.getPrincipal()).getName()); @@ -164,7 +164,7 @@ public void testAuthenticateWithStrangeNameAndMissingEmail() { @Test public void testSuccessfulAuthenticationPublishesEvent() throws Exception { UaaUser user = UaaUserTestFactory.getUser("FOO", "foo", "fo@test.org", "Foo", "Bar"); - Mockito.when(userDatabase.retrieveUserByName("foo", Origin.LOGIN_SERVER)).thenReturn(user); + Mockito.when(userDatabase.retrieveUserByName("foo", OriginKeys.LOGIN_SERVER)).thenReturn(user); AuthzAuthenticationRequest authenticationRequest = UaaAuthenticationTestFactory.getAuthenticationRequest("foo"); manager.authenticate(authenticationRequest); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java index 4e7ebebb0e1..88cc30722f0 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java @@ -14,8 +14,8 @@ import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.UaaAuditService; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.config.LockoutPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.zone.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; @@ -125,7 +125,7 @@ public void testUseLockoutPolicyFromDbIfPresent() throws Exception { lockoutPolicy.setCountFailuresWithin(3600); IdentityProvider provider = new IdentityProvider<>(); provider.setConfig(new UaaIdentityProviderDefinition(null, lockoutPolicy)); - when(providerProvisioning.retrieveByOrigin(Origin.UAA, IdentityZoneHolder.get().getId())).thenReturn(provider); + when(providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId())).thenReturn(provider); assertFalse(policy.isAllowed(joe, mock(Authentication.class))); } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java index 6e7cc865797..0531ea06a69 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java @@ -14,8 +14,9 @@ package org.cloudfoundry.identity.uaa.config; +import com.fasterxml.jackson.core.type.TypeReference; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.KeystoneIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; @@ -41,6 +42,7 @@ import static org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition.EMAIL_DOMAIN_ATTR; import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.EXTERNAL_GROUPS_WHITELIST; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.KEYSTONE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -60,16 +62,16 @@ public void clearIdentityHolder() { @Test public void testLdapProfileBootstrap() throws Exception { MockEnvironment environment = new MockEnvironment(); - environment.setActiveProfiles(Origin.LDAP); + environment.setActiveProfiles(OriginKeys.LDAP); IdentityProviderProvisioning provisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); IdentityProviderBootstrap bootstrap = new IdentityProviderBootstrap(provisioning, environment); bootstrap.afterPropertiesSet(); - IdentityProvider ldapProvider = provisioning.retrieveByOrigin(Origin.LDAP, IdentityZoneHolder.get().getId()); + IdentityProvider ldapProvider = provisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZoneHolder.get().getId()); assertNotNull(ldapProvider); assertNotNull(ldapProvider.getCreated()); assertNotNull(ldapProvider.getLastModified()); - assertEquals(Origin.LDAP, ldapProvider.getType()); + assertEquals(OriginKeys.LDAP, ldapProvider.getType()); LdapIdentityProviderDefinition definition = ldapProvider.getConfig(); assertNotNull(definition); assertFalse(definition.isConfigured()); @@ -92,11 +94,11 @@ public void testLdapBootstrap() throws Exception { bootstrap.setLdapConfig(ldapConfig); bootstrap.afterPropertiesSet(); - IdentityProvider ldapProvider = provisioning.retrieveByOrigin(Origin.LDAP, IdentityZoneHolder.get().getId()); + IdentityProvider ldapProvider = provisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZoneHolder.get().getId()); assertNotNull(ldapProvider); assertNotNull(ldapProvider.getCreated()); assertNotNull(ldapProvider.getLastModified()); - assertEquals(Origin.LDAP, ldapProvider.getType()); + assertEquals(OriginKeys.LDAP, ldapProvider.getType()); assertEquals("test.domain", ldapProvider.getConfig().getEmailDomain().get(0)); assertEquals(Arrays.asList("value"), ldapProvider.getConfig().getExternalGroupsWhitelist()); assertEquals("first_name", ldapProvider.getConfig().getAttributeMappings().get("given_name")); @@ -106,53 +108,53 @@ public void testLdapBootstrap() throws Exception { public void testRemovedLdapBootstrapIsInactive() throws Exception { IdentityProviderProvisioning provisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); MockEnvironment env = new MockEnvironment(); - env.setActiveProfiles(Origin.LDAP); + env.setActiveProfiles(OriginKeys.LDAP); IdentityProviderBootstrap bootstrap = new IdentityProviderBootstrap(provisioning, env); HashMap ldapConfig = new HashMap<>(); ldapConfig.put("base.url","ldap://localhost:389/"); bootstrap.setLdapConfig(ldapConfig); bootstrap.afterPropertiesSet(); - IdentityProvider ldapProvider = provisioning.retrieveByOrigin(Origin.LDAP, IdentityZoneHolder.get().getId()); + IdentityProvider ldapProvider = provisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZoneHolder.get().getId()); assertNotNull(ldapProvider); assertNotNull(ldapProvider.getCreated()); assertNotNull(ldapProvider.getLastModified()); - assertEquals(Origin.LDAP, ldapProvider.getType()); + assertEquals(OriginKeys.LDAP, ldapProvider.getType()); assertTrue(ldapProvider.isActive()); bootstrap.setLdapConfig(null); bootstrap.afterPropertiesSet(); - ldapProvider = provisioning.retrieveByOrigin(Origin.LDAP, IdentityZoneHolder.get().getId()); + ldapProvider = provisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZoneHolder.get().getId()); assertNotNull(ldapProvider); assertNotNull(ldapProvider.getCreated()); assertNotNull(ldapProvider.getLastModified()); - assertEquals(Origin.LDAP, ldapProvider.getType()); + assertEquals(OriginKeys.LDAP, ldapProvider.getType()); assertFalse(ldapProvider.isActive()); bootstrap.setLdapConfig(ldapConfig); bootstrap.afterPropertiesSet(); - ldapProvider = provisioning.retrieveByOrigin(Origin.LDAP, IdentityZoneHolder.get().getId()); + ldapProvider = provisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZoneHolder.get().getId()); assertNotNull(ldapProvider); assertNotNull(ldapProvider.getCreated()); assertNotNull(ldapProvider.getLastModified()); - assertEquals(Origin.LDAP, ldapProvider.getType()); + assertEquals(OriginKeys.LDAP, ldapProvider.getType()); assertTrue(ldapProvider.isActive()); } @Test public void testKeystoneProfileBootstrap() throws Exception { MockEnvironment environment = new MockEnvironment(); - environment.setActiveProfiles(Origin.KEYSTONE); + environment.setActiveProfiles(KEYSTONE); IdentityProviderProvisioning provisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); IdentityProviderBootstrap bootstrap = new IdentityProviderBootstrap(provisioning, environment); bootstrap.afterPropertiesSet(); - IdentityProvider keystoneProvider = provisioning.retrieveByOrigin(Origin.KEYSTONE, IdentityZoneHolder.get().getId()); + IdentityProvider keystoneProvider = provisioning.retrieveByOrigin(KEYSTONE, IdentityZoneHolder.get().getId()); assertNotNull(keystoneProvider); assertEquals(new KeystoneIdentityProviderDefinition(), keystoneProvider.getConfig()); assertNotNull(keystoneProvider.getCreated()); assertNotNull(keystoneProvider.getLastModified()); - assertEquals(Origin.KEYSTONE, keystoneProvider.getType()); + assertEquals(KEYSTONE, keystoneProvider.getType()); assertNotNull(keystoneProvider.getConfig()); assertNull(keystoneProvider.getConfig().getAdditionalConfiguration()); } @@ -166,18 +168,18 @@ public void testKeystoneBootstrap() throws Exception { bootstrap.setKeystoneConfig(keystoneConfig); bootstrap.afterPropertiesSet(); - IdentityProvider keystoneProvider = provisioning.retrieveByOrigin(Origin.KEYSTONE, IdentityZoneHolder.get().getId()); + IdentityProvider keystoneProvider = provisioning.retrieveByOrigin(KEYSTONE, IdentityZoneHolder.get().getId()); assertNotNull(keystoneProvider); assertEquals(new KeystoneIdentityProviderDefinition(keystoneConfig), keystoneProvider.getConfig()); assertNotNull(keystoneProvider.getCreated()); assertNotNull(keystoneProvider.getLastModified()); - assertEquals(Origin.KEYSTONE, keystoneProvider.getType()); + assertEquals(KEYSTONE, keystoneProvider.getType()); } @Test public void testRemovedKeystoneBootstrapIsInactive() throws Exception { MockEnvironment env = new MockEnvironment(); - env.setActiveProfiles(Origin.KEYSTONE); + env.setActiveProfiles(KEYSTONE); IdentityProviderProvisioning provisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); IdentityProviderBootstrap bootstrap = new IdentityProviderBootstrap(provisioning, env); HashMap keystoneConfig = new HashMap<>(); @@ -185,31 +187,31 @@ public void testRemovedKeystoneBootstrapIsInactive() throws Exception { bootstrap.setKeystoneConfig(keystoneConfig); bootstrap.afterPropertiesSet(); - IdentityProvider keystoneProvider = provisioning.retrieveByOrigin(Origin.KEYSTONE, IdentityZoneHolder.get().getId()); + IdentityProvider keystoneProvider = provisioning.retrieveByOrigin(KEYSTONE, IdentityZoneHolder.get().getId()); assertNotNull(keystoneProvider); assertEquals(new KeystoneIdentityProviderDefinition(keystoneConfig), keystoneProvider.getConfig()); assertNotNull(keystoneProvider.getCreated()); assertNotNull(keystoneProvider.getLastModified()); - assertEquals(Origin.KEYSTONE, keystoneProvider.getType()); + assertEquals(KEYSTONE, keystoneProvider.getType()); assertTrue(keystoneProvider.isActive()); bootstrap.setKeystoneConfig(null); bootstrap.afterPropertiesSet(); - keystoneProvider = provisioning.retrieveByOrigin(Origin.KEYSTONE, IdentityZoneHolder.get().getId()); + keystoneProvider = provisioning.retrieveByOrigin(KEYSTONE, IdentityZoneHolder.get().getId()); assertNotNull(keystoneProvider); assertNotNull(keystoneProvider.getCreated()); assertNotNull(keystoneProvider.getLastModified()); - assertEquals(Origin.KEYSTONE, keystoneProvider.getType()); + assertEquals(KEYSTONE, keystoneProvider.getType()); assertFalse(keystoneProvider.isActive()); bootstrap.setKeystoneConfig(keystoneConfig); bootstrap.afterPropertiesSet(); - keystoneProvider = provisioning.retrieveByOrigin(Origin.KEYSTONE, IdentityZoneHolder.get().getId()); + keystoneProvider = provisioning.retrieveByOrigin(KEYSTONE, IdentityZoneHolder.get().getId()); assertNotNull(keystoneProvider); assertEquals(new KeystoneIdentityProviderDefinition(keystoneConfig), keystoneProvider.getConfig()); assertNotNull(keystoneProvider.getCreated()); assertNotNull(keystoneProvider.getLastModified()); - assertEquals(Origin.KEYSTONE, keystoneProvider.getType()); + assertEquals(KEYSTONE, keystoneProvider.getType()); assertTrue(keystoneProvider.isActive()); } @@ -248,7 +250,7 @@ public void testSamlBootstrap() throws Exception { assertEquals(definition, samlProvider.getConfig()); assertNotNull(samlProvider.getCreated()); assertNotNull(samlProvider.getLastModified()); - assertEquals(Origin.SAML, samlProvider.getType()); + assertEquals(OriginKeys.SAML, samlProvider.getType()); } @Test @@ -281,7 +283,7 @@ public void testRemovedSamlBootstrapIsInactive() throws Exception { assertEquals(definition, samlProvider.getConfig()); assertNotNull(samlProvider.getCreated()); assertNotNull(samlProvider.getLastModified()); - assertEquals(Origin.SAML, samlProvider.getType()); + assertEquals(OriginKeys.SAML, samlProvider.getType()); assertTrue(samlProvider.isActive()); IdentityProvider samlProvider2 = provisioning.retrieveByOrigin(definition2.getIdpEntityAlias(), IdentityZoneHolder.get().getId()); @@ -290,7 +292,7 @@ public void testRemovedSamlBootstrapIsInactive() throws Exception { assertEquals(definition2, samlProvider2.getConfig()); assertNotNull(samlProvider2.getCreated()); assertNotNull(samlProvider2.getLastModified()); - assertEquals(Origin.SAML, samlProvider2.getType()); + assertEquals(OriginKeys.SAML, samlProvider2.getType()); assertTrue(samlProvider2.isActive()); configurator = mock(SamlIdentityProviderConfigurator.class); @@ -303,7 +305,7 @@ public void testRemovedSamlBootstrapIsInactive() throws Exception { assertEquals(definition, samlProvider.getConfig()); assertNotNull(samlProvider.getCreated()); assertNotNull(samlProvider.getLastModified()); - assertEquals(Origin.SAML, samlProvider.getType()); + assertEquals(OriginKeys.SAML, samlProvider.getType()); assertTrue(samlProvider.isActive()); samlProvider2 = provisioning.retrieveByOrigin(definition2.getIdpEntityAlias(), IdentityZoneHolder.get().getId()); @@ -311,7 +313,7 @@ public void testRemovedSamlBootstrapIsInactive() throws Exception { assertEquals(definition2, samlProvider2.getConfig()); assertNotNull(samlProvider2.getCreated()); assertNotNull(samlProvider2.getLastModified()); - assertEquals(Origin.SAML, samlProvider2.getType()); + assertEquals(OriginKeys.SAML, samlProvider2.getType()); assertFalse(samlProvider2.isActive()); configurator = mock(SamlIdentityProviderConfigurator.class); @@ -324,7 +326,7 @@ public void testRemovedSamlBootstrapIsInactive() throws Exception { assertEquals(definition, samlProvider.getConfig()); assertNotNull(samlProvider.getCreated()); assertNotNull(samlProvider.getLastModified()); - assertEquals(Origin.SAML, samlProvider.getType()); + assertEquals(OriginKeys.SAML, samlProvider.getType()); assertFalse(samlProvider.isActive()); samlProvider2 = provisioning.retrieveByOrigin(definition2.getIdpEntityAlias(), IdentityZoneHolder.get().getId()); @@ -332,7 +334,7 @@ public void testRemovedSamlBootstrapIsInactive() throws Exception { assertEquals(definition2, samlProvider2.getConfig()); assertNotNull(samlProvider2.getCreated()); assertNotNull(samlProvider2.getLastModified()); - assertEquals(Origin.SAML, samlProvider2.getType()); + assertEquals(OriginKeys.SAML, samlProvider2.getType()); assertTrue(samlProvider2.isActive()); configurator = mock(SamlIdentityProviderConfigurator.class); @@ -345,7 +347,7 @@ public void testRemovedSamlBootstrapIsInactive() throws Exception { assertEquals(definition, samlProvider.getConfig()); assertNotNull(samlProvider.getCreated()); assertNotNull(samlProvider.getLastModified()); - assertEquals(Origin.SAML, samlProvider.getType()); + assertEquals(OriginKeys.SAML, samlProvider.getType()); assertFalse(samlProvider.isActive()); samlProvider2 = provisioning.retrieveByOrigin(definition2.getIdpEntityAlias(), IdentityZoneHolder.get().getId()); @@ -353,7 +355,7 @@ public void testRemovedSamlBootstrapIsInactive() throws Exception { assertEquals(definition2, samlProvider2.getConfig()); assertNotNull(samlProvider2.getCreated()); assertNotNull(samlProvider2.getLastModified()); - assertEquals(Origin.SAML, samlProvider2.getType()); + assertEquals(OriginKeys.SAML, samlProvider2.getType()); assertFalse(samlProvider2.isActive()); configurator = mock(SamlIdentityProviderConfigurator.class); @@ -366,7 +368,7 @@ public void testRemovedSamlBootstrapIsInactive() throws Exception { assertEquals(definition, samlProvider.getConfig()); assertNotNull(samlProvider.getCreated()); assertNotNull(samlProvider.getLastModified()); - assertEquals(Origin.SAML, samlProvider.getType()); + assertEquals(OriginKeys.SAML, samlProvider.getType()); assertTrue(samlProvider.isActive()); samlProvider2 = provisioning.retrieveByOrigin(definition2.getIdpEntityAlias(), IdentityZoneHolder.get().getId()); @@ -374,7 +376,7 @@ public void testRemovedSamlBootstrapIsInactive() throws Exception { assertEquals(definition2, samlProvider2.getConfig()); assertNotNull(samlProvider2.getCreated()); assertNotNull(samlProvider2.getLastModified()); - assertEquals(Origin.SAML, samlProvider2.getType()); + assertEquals(OriginKeys.SAML, samlProvider2.getType()); assertTrue(samlProvider2.isActive()); } @@ -403,7 +405,7 @@ private void setDisableInternalUserManagement(String expectedValue) throws Excep bootstrap.setDisableInternalUserManagement(Boolean.valueOf(expectedValue)); bootstrap.afterPropertiesSet(); - IdentityProvider internalIDP = provisioning.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId()); + IdentityProvider internalIDP = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); if (expectedValue == null) { expectedValue = "false"; @@ -418,7 +420,7 @@ public void setPasswordPolicyToInternalIDP() throws Exception { bootstrap.setDefaultPasswordPolicy(new PasswordPolicy(123, 4567, 1, 0, 1, 0, 6)); bootstrap.afterPropertiesSet(); - IdentityProvider internalIDP = provisioning.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId()); + IdentityProvider internalIDP = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); PasswordPolicy passwordPolicy = internalIDP.getConfig().getPasswordPolicy(); assertEquals(123, passwordPolicy.getMinLength()); assertEquals(4567, passwordPolicy.getMaxLength()); @@ -440,7 +442,7 @@ public void setLockoutPolicyToInternalIDP() throws Exception { bootstrap.setDefaultLockoutPolicy(lockoutPolicy); bootstrap.afterPropertiesSet(); - IdentityProvider internalIDP = provisioning.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId()); + IdentityProvider internalIDP = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); lockoutPolicy = internalIDP.getConfig().getLockoutPolicy(); assertEquals(123, lockoutPolicy.getLockoutPeriodSeconds()); @@ -456,13 +458,13 @@ public void deactivate_and_activate_InternalIDP() throws Exception { IdentityProviderBootstrap bootstrap = new IdentityProviderBootstrap(provisioning, environment); bootstrap.afterPropertiesSet(); - IdentityProvider internalIdp = provisioning.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId()); + IdentityProvider internalIdp = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); assertFalse(internalIdp.isActive()); environment.setProperty("disableInternalAuth", "false"); bootstrap.afterPropertiesSet(); - internalIdp = provisioning.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId()); + internalIdp = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); assertTrue(internalIdp.isActive()); } @@ -473,7 +475,7 @@ public void defaultActiveFlagOnInternalIDP() throws Exception { IdentityProviderBootstrap bootstrap = new IdentityProviderBootstrap(provisioning, environment); bootstrap.afterPropertiesSet(); - IdentityProvider internalIdp = provisioning.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId()); + IdentityProvider internalIdp = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); assertTrue(internalIdp.isActive()); } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java index ba02f42c291..0b3072ec45e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java @@ -12,10 +12,10 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.config.TokenPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; @@ -28,10 +28,8 @@ import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -210,7 +208,7 @@ public void setUp() { "FamilyName", new Date(System.currentTimeMillis() - 2000), new Date(System.currentTimeMillis() - 2000), - Origin.UAA, + OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), @@ -304,7 +302,7 @@ public void testRejectUserSaltChange() throws Exception { "FamilyName", new Date(System.currentTimeMillis() - 2000), new Date(System.currentTimeMillis() - 2000), - Origin.UAA, + OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), @@ -326,7 +324,7 @@ public void testRejectUserUsernameChange() throws Exception { "FamilyName", new Date(System.currentTimeMillis() - 2000), new Date(System.currentTimeMillis() - 2000), - Origin.UAA, + OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), @@ -348,7 +346,7 @@ public void testRejectUserEmailChange() throws Exception { "FamilyName", new Date(System.currentTimeMillis() - 2000), new Date(System.currentTimeMillis() - 2000), - Origin.UAA, + OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), @@ -372,7 +370,7 @@ public void testRejectUserPasswordChange() throws Exception { "FamilyName", new Date(System.currentTimeMillis() - 2000), new Date(System.currentTimeMillis() - 2000), - Origin.UAA, + OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java index 7512670382d..d1aa471ecf4 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java @@ -13,8 +13,8 @@ package org.cloudfoundry.identity.uaa.oauth; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.StubSecurityContextAccessor; import org.cloudfoundry.identity.uaa.user.UaaUser; @@ -76,7 +76,7 @@ public void initUaaAuthorizationRequestManagerTests() { factory = new UaaAuthorizationRequestManager(clientDetailsService, uaaUserDatabase, providerProvisioning); factory.setSecurityContextAccessor(new StubSecurityContextAccessor()); when(clientDetailsService.loadClientByClientId("foo")).thenReturn(client); - user = new UaaUser("testid", "testuser","","test@test.org",AuthorityUtils.commaSeparatedStringToAuthorityList("foo.bar,spam.baz,space.1.developer,space.2.developer,space.1.admin"),"givenname", "familyname", null, null, Origin.UAA, null, true, IdentityZone.getUaa().getId(), "testid", new Date()); + user = new UaaUser("testid", "testuser","","test@test.org",AuthorityUtils.commaSeparatedStringToAuthorityList("foo.bar,spam.baz,space.1.developer,space.2.developer,space.1.admin"),"givenname", "familyname", null, null, OriginKeys.UAA, null, true, IdentityZone.getUaa().getId(), "testid", new Date()); when(uaaUserDatabase.retrieveUserById(anyString())).thenReturn(user); } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java index 40d0ef18d42..ed181d86944 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java @@ -4,8 +4,8 @@ import java.util.Collections; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.ClientDetailsValidator.Mode; import org.junit.Before; import org.junit.Test; @@ -25,14 +25,14 @@ public void setUp() throws Exception { public void testCreateLimitedClient() { BaseClientDetails clientDetails = new BaseClientDetails("valid-client", null, "openid", "authorization_code,password", "uaa.resource"); clientDetails.setClientSecret("secret"); - clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(Origin.UAA)); + clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA)); ClientDetails validatedClientDetails = zoneEndpointsClientDetailsValidator.validate(clientDetails, Mode.CREATE); assertEquals(clientDetails.getClientId(), validatedClientDetails.getClientId()); assertEquals(clientDetails.getScope(), validatedClientDetails.getScope()); assertEquals(clientDetails.getAuthorizedGrantTypes(), validatedClientDetails.getAuthorizedGrantTypes()); assertEquals(clientDetails.getAuthorities(), validatedClientDetails.getAuthorities()); assertEquals(Collections.singleton("none"), validatedClientDetails.getResourceIds()); - assertEquals(Collections.singletonList(Origin.UAA), validatedClientDetails.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS)); + assertEquals(Collections.singletonList(OriginKeys.UAA), validatedClientDetails.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS)); } @Test(expected = InvalidClientDetailsException.class) @@ -51,7 +51,7 @@ public void testCreateClientNoSecretIsInvalid() { @Test public void testCreateClientNoSecretForImplicitIsValid() { BaseClientDetails clientDetails = new BaseClientDetails("client", null, "openid", "implicit", "uaa.resource"); - clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(Origin.UAA)); + clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA)); ClientDetails validatedClientDetails = zoneEndpointsClientDetailsValidator.validate(clientDetails, Mode.CREATE); assertEquals(clientDetails.getAuthorizedGrantTypes(), validatedClientDetails.getAuthorizedGrantTypes()); } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheckTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheckTest.java index 0acbdcefed0..4c0a8ac906a 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheckTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheckTest.java @@ -14,10 +14,10 @@ package org.cloudfoundry.identity.uaa.oauth.expression; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.After; @@ -51,7 +51,7 @@ public void getBean() { id = new RandomValueStringGenerator(25).generate(); request = new MockHttpServletRequest(); request.setRemoteAddr("127.0.0.1"); - principal = new UaaPrincipal(id, "username","username@email.org", Origin.UAA, null, IdentityZoneHolder.get().getId()); + principal = new UaaPrincipal(id, "username","username@email.org", OriginKeys.UAA, null, IdentityZoneHolder.get().getId()); authentication = new UaaAuthentication(principal, Collections.emptyList(), new UaaAuthenticationDetails(request)); bean = new IsUserSelfCheck(); } @@ -111,4 +111,4 @@ public void testSelfCheck_Token_ClientAuth_Fails() { assertFalse(bean.isSelf(request, 1)); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java index 32c49f1ea68..4980bf3bcba 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java @@ -15,12 +15,12 @@ import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.event.TokenIssuedEvent; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.config.TokenPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; @@ -157,7 +157,7 @@ public class UaaTokenServicesTests { .withPhoneNumber("1234567890") .withCreated(new Date(System.currentTimeMillis() - 15000)) .withModified(new Date(System.currentTimeMillis() - 15000)) - .withOrigin(Origin.UAA) + .withOrigin(OriginKeys.UAA) .withExternalId(externalId) .withVerified(false) .withZoneId(IdentityZoneHolder.get().getId()) @@ -255,9 +255,9 @@ public void testInvalidGrantType() { @Test(expected = InvalidTokenException.class) public void testInvalidRefreshToken() { Map map = new HashMap<>(); - map.put("grant_type","refresh_token"); + map.put("grant_type", "refresh_token"); AuthorizationRequest authorizationRequest = new AuthorizationRequest(map,null,null,null,null,null,false,null,null,null); - tokenServices.refreshAccessToken("dasdasdasdasdas", requestFactory.createTokenRequest(authorizationRequest,"refresh_token")); + tokenServices.refreshAccessToken("dasdasdasdasdas", requestFactory.createTokenRequest(authorizationRequest, "refresh_token")); } @Test @@ -891,8 +891,8 @@ public void testCreateAccessTokenAuthcodeGrantExpandedScopes() { Calendar expiresAt = Calendar.getInstance(); expiresAt.add(Calendar.MILLISECOND, 3000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); + approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED, new Date())); + approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED, new Date())); // First Request AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); authorizationRequest.setResourceIds(new HashSet<>(resourceIds)); @@ -918,7 +918,7 @@ public void testCreateAccessTokenAuthcodeGrantExpandedScopes() { expandedScopeAuthorizationRequest.setRequestParameters(refreshAzParameters); OAuth2Authentication expandedScopeAuthentication = new OAuth2Authentication(expandedScopeAuthorizationRequest.createOAuth2Request(),userAuthentication); - tokenServices.refreshAccessToken(accessToken.getRefreshToken().getValue(),requestFactory.createTokenRequest(expandedScopeAuthorizationRequest,"refresh_token")); + tokenServices.refreshAccessToken(accessToken.getRefreshToken().getValue(), requestFactory.createTokenRequest(expandedScopeAuthorizationRequest, "refresh_token")); } @Test @@ -961,7 +961,7 @@ public void testUserUpdatedAfterRefreshTokenIssued() { OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken accessToken = tokenServices.createAccessToken(authentication); - UaaUser user = userDatabase.retrieveUserByName(username, Origin.UAA); + UaaUser user = userDatabase.retrieveUserByName(username, OriginKeys.UAA); UaaUser newUser = new UaaUser(user.getUsername(), "blah", user.getEmail(), null, null); userDatabase.updateUser(userId, newUser); @@ -1066,7 +1066,7 @@ public void testRefreshTokenAfterApprovalsDenied() { Calendar expiresAt = Calendar.getInstance(); expiresAt.add(Calendar.MILLISECOND, -3000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.DENIED,new Date())); + approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.DENIED, new Date())); approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); @@ -1132,7 +1132,7 @@ public void testRefreshTokenAfterApprovalsMissing2() { refreshAzParameters.put(GRANT_TYPE, REFRESH_TOKEN); refreshAuthorizationRequest.setRequestParameters(refreshAzParameters); - tokenServices.refreshAccessToken(accessToken.getRefreshToken().getValue(), requestFactory.createTokenRequest(refreshAuthorizationRequest,"refresh_token")); + tokenServices.refreshAccessToken(accessToken.getRefreshToken().getValue(), requestFactory.createTokenRequest(refreshAuthorizationRequest, "refresh_token")); } @Test @@ -1171,8 +1171,8 @@ public void testReadAccessTokenForDeletedUserId() { Calendar updatedAt = Calendar.getInstance(); updatedAt.add(Calendar.MILLISECOND, -1000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,updatedAt.getTime())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,updatedAt.getTime())); + approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED, updatedAt.getTime())); + approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED, updatedAt.getTime())); OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken accessToken = tokenServices.createAccessToken(authentication); @@ -1306,7 +1306,7 @@ private void assertCommonUserAccessTokenProperties(OAuth2AccessToken accessToken clientId(is(CLIENT_ID)), subject(is(userId)), audience(is(resourceIds)), - origin(is(Origin.UAA)), + origin(is(OriginKeys.UAA)), revocationSignature(is(not(nullValue()))), cid(is(CLIENT_ID)), userId(is(userId)), @@ -1324,7 +1324,7 @@ private void assertCommonUserRefreshTokenProperties(OAuth2RefreshToken refreshTo OAuth2RefreshTokenMatchers.clientId(is(CLIENT_ID)), OAuth2RefreshTokenMatchers.subject(is(not(nullValue()))), OAuth2RefreshTokenMatchers.audience(is(resourceIds)), - OAuth2RefreshTokenMatchers.origin(is(Origin.UAA)), + OAuth2RefreshTokenMatchers.origin(is(OriginKeys.UAA)), OAuth2RefreshTokenMatchers.revocationSignature(is(not(nullValue()))), OAuth2RefreshTokenMatchers.jwtId(not(isEmptyString())), OAuth2RefreshTokenMatchers.issuedAt(is(greaterThan(0))), diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenStoreTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenStoreTests.java index abcc3a4f983..ba1c4ab4d22 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenStoreTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenStoreTests.java @@ -14,10 +14,10 @@ package org.cloudfoundry.identity.uaa.oauth.token; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; @@ -72,7 +72,7 @@ public class UaaTokenStoreTests extends JdbcTestBase { private OAuth2Authentication uaaAuthentication; public static final String LONG_CLIENT_ID = "a-client-id-that-is-longer-than-thirty-six-characters-but-less-than-two-hundred-fifty-five-characters-wow-two-hundred-fifty-five-characters-is-actually-a-very-long-client-id-and-we-hope-that-size-limit-should-be-sufficient-for-any-reasonable-application"; - private UaaPrincipal principal = new UaaPrincipal("userid","username","username@test.org", Origin.UAA, null, IdentityZone.getUaa().getId()); + private UaaPrincipal principal = new UaaPrincipal("userid","username","username@test.org", OriginKeys.UAA, null, IdentityZone.getUaa().getId()); @Before public void createTokenStore() throws Exception { diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java index f0f7579c0e9..b396b6a047f 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java @@ -17,9 +17,9 @@ import java.util.Collections; import java.util.Map; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.InMemoryUaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserTestFactory; @@ -44,7 +44,7 @@ public UserInfoEndpointTests() { @Test public void testSunnyDay() { - UaaUser user = userDatabase.retrieveUserByName("olds", Origin.UAA); + UaaUser user = userDatabase.retrieveUserByName("olds", OriginKeys.UAA); UaaAuthentication authentication = UaaAuthenticationTestFactory.getAuthentication(user.getId(), "olds", "olds@vmware.com"); Map map = endpoint.loginInfo(new OAuth2Authentication(null, authentication)); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessorTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessorTests.java index 2c15af578da..0379471b969 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessorTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessorTests.java @@ -15,11 +15,11 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -36,8 +36,6 @@ import org.springframework.security.oauth2.provider.OAuth2Authentication; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; @@ -93,7 +91,7 @@ public void zoneAdminUserIsAdmin() throws Exception { authorities.add(new SimpleGrantedAuthority("zones." + IdentityZoneHolder.get().getId() + ".admin")); client.setAuthorities(authorities); - UaaPrincipal principal = new UaaPrincipal("id","username","email", Origin.UAA,null,IdentityZoneHolder.get().getId()); + UaaPrincipal principal = new UaaPrincipal("id","username","email", OriginKeys.UAA,null,IdentityZoneHolder.get().getId()); UaaAuthentication userAuthentication = new UaaAuthentication(principal, authorities, new UaaAuthenticationDetails(new MockHttpServletRequest())); AuthorizationRequest authorizationRequest = new AuthorizationRequest("admin", UaaStringUtils.getStringsFromAuthorities(authorities)); @@ -112,7 +110,7 @@ public void zoneAdminUserIsNotAdmin_BecauseOriginIsNotUaa() throws Exception { authorities.add(new SimpleGrantedAuthority("zones." + IdentityZoneHolder.get().getId() + ".admin")); client.setAuthorities(authorities); - UaaPrincipal principal = new UaaPrincipal("id","username","email", Origin.UAA,null, MultitenancyFixture.identityZone("test","test").getId()); + UaaPrincipal principal = new UaaPrincipal("id","username","email", OriginKeys.UAA,null, MultitenancyFixture.identityZone("test","test").getId()); UaaAuthentication userAuthentication = new UaaAuthentication(principal, authorities, new UaaAuthenticationDetails(new MockHttpServletRequest())); AuthorizationRequest authorizationRequest = new AuthorizationRequest("admin", UaaStringUtils.getStringsFromAuthorities(authorities)); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestAccountSetup.java b/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestAccountSetup.java index 8e5979dcdde..aa2c438a6f1 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestAccountSetup.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestAccountSetup.java @@ -27,7 +27,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.rules.TestWatchman; @@ -215,7 +215,7 @@ private UaaUser getUserFromMap(Map map) { @SuppressWarnings("unchecked") Collection> groups = (Collection>) map.get("groups"); return new UaaUser(id, userName, "", email, extractAuthorities(groups), givenName, familyName, new Date(), - new Date(), Origin.UAA, "externalId", false, IdentityZoneHolder.get().getId(), null,null); + new Date(), OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), null,null); } private List extractAuthorities(Collection> groups) { diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/UaaTestAccounts.java b/common/src/test/java/org/cloudfoundry/identity/uaa/test/UaaTestAccounts.java index c10521a90ed..518f2a18569 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/test/UaaTestAccounts.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/test/UaaTestAccounts.java @@ -20,7 +20,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -90,7 +90,7 @@ public String getEmail() { public UaaUser getUserWithRandomID() { String id = UUID.randomUUID().toString(); UaaUser user = new UaaUser(id, getUserName(), "", getEmail(), - UaaAuthority.USER_AUTHORITIES, "Test", "User", new Date(), new Date(), Origin.UAA, "externalId", true, + UaaAuthority.USER_AUTHORITIES, "Test", "User", new Date(), new Date(), OriginKeys.UAA, "externalId", true, IdentityZoneHolder.get().getId(), id, new Date()); ReflectionTestUtils.setField(user, "password", getPassword()); return user; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java index 4c0c2908124..af312b58083 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.user; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.Before; import org.junit.Test; @@ -14,7 +14,7 @@ public class InMemoryUaaUserDatabaseTests { - UaaUser user = new UaaUser("test-id","username","password","email",UaaAuthority.USER_AUTHORITIES,"givenname","familyname", new Date(), new Date(), Origin.UAA,"externalID", false, IdentityZoneHolder.get().getId(), "test-id", new Date()); + UaaUser user = new UaaUser("test-id","username","password","email",UaaAuthority.USER_AUTHORITIES,"givenname","familyname", new Date(), new Date(), OriginKeys.UAA,"externalID", false, IdentityZoneHolder.get().getId(), "test-id", new Date()); InMemoryUaaUserDatabase db; @Before public void setUp() { @@ -31,12 +31,12 @@ public void testRetrieveUserByName() throws Exception { @Test(expected = UsernameNotFoundException.class) public void testRetrieveUserByNameInvalidOrigin() throws Exception { - db.retrieveUserByName(user.getUsername(), Origin.LDAP); + db.retrieveUserByName(user.getUsername(), OriginKeys.LDAP); } @Test(expected = UsernameNotFoundException.class) public void testRetrieveUserByNameInvalidUsername() throws Exception { - db.retrieveUserByName(user.getUsername() + "1", Origin.UAA); + db.retrieveUserByName(user.getUsername() + "1", OriginKeys.UAA); } @Test @@ -71,4 +71,4 @@ public void testUpdateUser() throws Exception { db.updateUser(user.getId(), newUser); assertSame(newUser, db.retrieveUserById(user.getId())); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java index cd5c67b812c..8cabe35dfd1 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.user; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.test.TestUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; @@ -56,7 +56,7 @@ public class JdbcUaaUserDatabaseTests extends JdbcTestBase { private void addUser(String id, String name, String password) { TestUtils.assertNoSuchUser(template, "id", id); Timestamp t = new Timestamp(System.currentTimeMillis()); - template.update(addUserSql, id, name, password, name.toLowerCase() + "@test.org", name, name, "", Origin.UAA, IdentityZoneHolder.get().getId(),t,t,t); + template.update(addUserSql, id, name, password, name.toLowerCase() + "@test.org", name, name, "", OriginKeys.UAA, IdentityZoneHolder.get().getId(),t,t,t); } private void addAuthority(String authority, String userId) { @@ -96,7 +96,7 @@ public void clearDb() throws Exception { @Test public void getValidUserSucceeds() { - UaaUser joe = db.retrieveUserByName("joe",Origin.UAA); + UaaUser joe = db.retrieveUserByName("joe", OriginKeys.UAA); assertNotNull(joe); assertEquals(JOE_ID, joe.getId()); assertEquals("Joe", joe.getUsername()); @@ -111,18 +111,18 @@ public void getValidUserSucceeds() { @Test public void getSaltValueWorks() { - UaaUser joe = db.retrieveUserByName("joe",Origin.UAA); + UaaUser joe = db.retrieveUserByName("joe", OriginKeys.UAA); assertNotNull(joe); assertNull(joe.getSalt()); template.update(addSaltSql, "salt", JOE_ID); - joe = db.retrieveUserByName("joe",Origin.UAA); + joe = db.retrieveUserByName("joe", OriginKeys.UAA); assertNotNull(joe); assertEquals("salt", joe.getSalt()); } @Test public void getValidUserCaseInsensitive() { - UaaUser joe = db.retrieveUserByName("JOE", Origin.UAA); + UaaUser joe = db.retrieveUserByName("JOE", OriginKeys.UAA); assertNotNull(joe); assertEquals(JOE_ID, joe.getId()); assertEquals("Joe", joe.getUsername()); @@ -134,13 +134,13 @@ public void getValidUserCaseInsensitive() { @Test(expected = UsernameNotFoundException.class) public void getNonExistentUserRaisedNotFoundException() { - db.retrieveUserByName("jo", Origin.UAA); + db.retrieveUserByName("jo", OriginKeys.UAA); } @Test public void getUserWithExtraAuthorities() { addAuthority("dash.admin", JOE_ID); - UaaUser joe = db.retrieveUserByName("joe", Origin.UAA); + UaaUser joe = db.retrieveUserByName("joe", OriginKeys.UAA); assertTrue("authorities does not contain uaa.user", joe.getAuthorities().contains(new SimpleGrantedAuthority("uaa.user"))); assertTrue("authorities does not contain dash.admin", @@ -162,7 +162,7 @@ public void getValidUserInOtherZoneFromOtherZone() { @Test(expected = UsernameNotFoundException.class) public void getValidUserInOtherZoneFromDefaultZoneFails() { - UaaUser alice = db.retrieveUserByName("alice",Origin.UAA); + UaaUser alice = db.retrieveUserByName("alice", OriginKeys.UAA); assertNotNull(alice); assertEquals(ALICE_ID, alice.getId()); assertEquals("alice", alice.getUsername()); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java b/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java index 607119cdfca..e146f828c8c 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java @@ -14,7 +14,7 @@ import java.util.Date; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -26,13 +26,13 @@ public class MockUaaUserDatabase implements UaaUserDatabase { public MockUaaUserDatabase(String id, String name, String email, String givenName, String familyName) { user = new UaaUser(id, name, "", email, UaaAuthority.USER_AUTHORITIES, givenName, familyName, - new Date(), new Date(), Origin.UAA, "externalId", false, IdentityZoneHolder.get().getId(), id, new Date()); + new Date(), new Date(), OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), id, new Date()); } public MockUaaUserDatabase(String id, String name, String email, String givenName, String familyName, Date createdAt, Date updatedAt) { user = new UaaUser(id, name, "", email, UaaAuthority.USER_AUTHORITIES, givenName, familyName, - createdAt, updatedAt, Origin.UAA, "externalId", false, IdentityZoneHolder.get().getId(), id, new Date()); + createdAt, updatedAt, OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), id, new Date()); } @Override diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserTestFactory.java b/common/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserTestFactory.java index 465a678be24..358e499a357 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserTestFactory.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserTestFactory.java @@ -12,8 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.user; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import java.util.Date; @@ -26,12 +25,12 @@ public class UaaUserTestFactory { public static UaaUser getUser(String id, String name, String email, String givenName, String familyName) { return new UaaUser(id, name, "", email, UaaAuthority.USER_AUTHORITIES, givenName, familyName, new Date(), - new Date(), Origin.UAA, "externalId", false, IdentityZoneHolder.get().getId(), id, new Date()); + new Date(), OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), id, new Date()); } public static UaaUser getAdminUser(String id, String name, String email, String givenName, String familyName) { return new UaaUser(id, name, "", email, UaaAuthority.ADMIN_AUTHORITIES, givenName, familyName, new Date(), - new Date(), Origin.UAA, "externalId", false, IdentityZoneHolder.get().getId(), id, new Date()); + new Date(), OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), id, new Date()); } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java index 262a175f75d..93d067f9111 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.util; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityProvider; @@ -29,7 +29,7 @@ import java.util.List; import static java.util.Collections.EMPTY_LIST; -import static org.cloudfoundry.identity.uaa.authentication.Origin.LOGIN_SERVER; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LOGIN_SERVER; import static org.cloudfoundry.identity.uaa.client.ClientConstants.ALLOWED_PROVIDERS; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; @@ -97,10 +97,10 @@ public void setUp() throws Exception { } private void configureTestData() { - uaaProvider = new IdentityProvider().setActive(true).setType(Origin.UAA).setOriginKey(Origin.UAA).setConfig(uaaDef); - ldapProvider = new IdentityProvider().setActive(true).setType(Origin.LDAP).setOriginKey(Origin.LDAP).setConfig(ldapDef); - samlProvider1 = new IdentityProvider().setActive(true).setType(Origin.SAML).setOriginKey("saml1").setConfig(samlDef1); - samlProvider2 = new IdentityProvider().setActive(true).setType(Origin.SAML).setOriginKey("saml2").setConfig(samlDef2); + uaaProvider = new IdentityProvider().setActive(true).setType(OriginKeys.UAA).setOriginKey(OriginKeys.UAA).setConfig(uaaDef); + ldapProvider = new IdentityProvider().setActive(true).setType(OriginKeys.LDAP).setOriginKey(OriginKeys.LDAP).setConfig(ldapDef); + samlProvider1 = new IdentityProvider().setActive(true).setType(OriginKeys.SAML).setOriginKey("saml1").setConfig(samlDef1); + samlProvider2 = new IdentityProvider().setActive(true).setType(OriginKeys.SAML).setOriginKey("saml2").setConfig(samlDef2); loginServerProvider = new IdentityProvider().setActive(true).setType(LOGIN_SERVER).setOriginKey(LOGIN_SERVER); activeProviders = Arrays.asList(uaaProvider, ldapProvider, samlProvider1, samlProvider2, loginServerProvider); } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderTests.java deleted file mode 100644 index 651057711c5..00000000000 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderTests.java +++ /dev/null @@ -1,237 +0,0 @@ -/** - ******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. - *

- * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - *

- * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - ***************************************************************************** - */ -package org.cloudfoundry.identity.uaa.zone; - -import com.fasterxml.jackson.core.type.TypeReference; -import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.KeystoneIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.junit.Test; - -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -public class IdentityProviderTests { - - @Test - public void test_backwards_compatible_json_where_config_is_a_string() { - List providers = - JsonUtils.readValue( - BACKWARDS_COMPATIBLE_LIST_OF_IDPS, - new TypeReference>() {} - ); - assertEquals(7, providers.size()); - } - - @Test - public void configIsAlwaysValidWhenOriginIsOtherThanUaa() { - IdentityProvider identityProvider = new IdentityProvider().setOriginKey(Origin.LDAP).setConfig(new LdapIdentityProviderDefinition()); - assertTrue(identityProvider.configIsValid()); - } - - @Test - public void uaaConfigMustContainAllPasswordPolicyFields() { - assertValidity(true, JsonUtils.readValue("",UaaIdentityProviderDefinition.class)); - assertValidity(true, JsonUtils.readValue("{\"passwordPolicy\": null}",UaaIdentityProviderDefinition.class)); - assertValidity(false,JsonUtils.readValue( "{\"passwordPolicy\": {}}",UaaIdentityProviderDefinition.class)); - assertValidity(false,JsonUtils.readValue( "{\"passwordPolicy\":{\"minLength\":6}}",UaaIdentityProviderDefinition.class)); - assertValidity(false,JsonUtils.readValue( "{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128}}",UaaIdentityProviderDefinition.class)); - assertValidity(false,JsonUtils.readValue( "{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128,\"requireUpperCaseCharacter\":1}}",UaaIdentityProviderDefinition.class)); - assertValidity(false,JsonUtils.readValue( "{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128,\"requireUpperCaseCharacter\":1,\"requireLowerCaseCharacter\":1}}",UaaIdentityProviderDefinition.class)); - assertValidity(false,JsonUtils.readValue( "{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128,\"requireUpperCaseCharacter\":1,\"requireLowerCaseCharacter\":1,\"requireDigit\":1}}",UaaIdentityProviderDefinition.class)); - assertValidity(false,JsonUtils.readValue( "{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128,\"requireUpperCaseCharacter\":1,\"requireLowerCaseCharacter\":1,\"requireDigit\":1,\"requireSpecialCharacter\":0}}",UaaIdentityProviderDefinition.class)); - assertValidity(true, JsonUtils.readValue("{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128,\"requireUpperCaseCharacter\":1,\"requireLowerCaseCharacter\":1,\"requireDigit\":1,\"requireSpecialCharacter\":0,\"expirePasswordInMonths\":0}}",UaaIdentityProviderDefinition.class)); - } - - @Test - public void uaaConfigDoesNotAllowNegativeNumbersForPasswordPolicy() { - assertValidity(false, JsonUtils.readValue("{\"passwordPolicy\":{\"minLength\":-6,\"maxLength\":128,\"requireUpperCaseCharacter\":1,\"requireLowerCaseCharacter\":1,\"requireDigit\":1,\"requireSpecialCharacter\":0,\"expirePasswordInMonths\":0}}", UaaIdentityProviderDefinition.class)); - assertValidity(false, JsonUtils.readValue("{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":-128,\"requireUpperCaseCharacter\":1,\"requireLowerCaseCharacter\":1,\"requireDigit\":1,\"requireSpecialCharacter\":0,\"expirePasswordInMonths\":0}}", UaaIdentityProviderDefinition.class)); - assertValidity(false, JsonUtils.readValue("{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128,\"requireUpperCaseCharacter\":-1,\"requireLowerCaseCharacter\":1,\"requireDigit\":1,\"requireSpecialCharacter\":0,\"expirePasswordInMonths\":0}}", UaaIdentityProviderDefinition.class)); - assertValidity(false, JsonUtils.readValue("{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128,\"requireUpperCaseCharacter\":1,\"requireLowerCaseCharacter\":-1,\"requireDigit\":1,\"requireSpecialCharacter\":0,\"expirePasswordInMonths\":0}}", UaaIdentityProviderDefinition.class)); - assertValidity(false, JsonUtils.readValue("{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128,\"requireUpperCaseCharacter\":1,\"requireLowerCaseCharacter\":1,\"requireDigit\":-1,\"requireSpecialCharacter\":0,\"expirePasswordInMonths\":0}}", UaaIdentityProviderDefinition.class)); - assertValidity(false, JsonUtils.readValue("{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128,\"requireUpperCaseCharacter\":1,\"requireLowerCaseCharacter\":1,\"requireDigit\":1,\"requireSpecialCharacter\":-1,\"expirePasswordInMonths\":0}}", UaaIdentityProviderDefinition.class)); - assertValidity(false, JsonUtils.readValue("{\"passwordPolicy\":{\"minLength\":6,\"maxLength\":128,\"requireUpperCaseCharacter\":1,\"requireLowerCaseCharacter\":1,\"requireDigit\":1,\"requireSpecialCharacter\":0,\"expirePasswordInMonths\":-1}}", UaaIdentityProviderDefinition.class)); - } - - @Test - public void uaaConfigMustContainAllLockoutPolicyFieldsIfSpecified() throws Exception { - assertValidity(true, JsonUtils.readValue("", UaaIdentityProviderDefinition.class)); - assertValidity(true, JsonUtils.readValue("{\"lockoutPolicy\": null}", UaaIdentityProviderDefinition.class)); - assertValidity(false,JsonUtils.readValue( "{\"lockoutPolicy\": {}}", UaaIdentityProviderDefinition.class)); - assertValidity(false,JsonUtils.readValue( "{\"lockoutPolicy\":{\"lockoutPeriodSeconds\":900}}", UaaIdentityProviderDefinition.class)); - assertValidity(false,JsonUtils.readValue( "{\"lockoutPolicy\":{\"lockoutPeriodSeconds\":900,\"lockoutAfterFailures\":128}}", UaaIdentityProviderDefinition.class)); - assertValidity(true, JsonUtils.readValue("{\"lockoutPolicy\":{\"lockoutPeriodSeconds\":900,\"lockoutAfterFailures\":128,\"countFailuresWithin\":1800}}", UaaIdentityProviderDefinition.class)); - } - - @Test - public void uaaConfigDoesNotAllNegativeNumbersForLockoutPolicy() throws Exception { - assertValidity(false, JsonUtils.readValue("{\"lockoutPolicy\":{\"lockoutPeriodSeconds\":-6,\"lockoutAfterFailures\":128,\"countFailuresWithin\":1}}", UaaIdentityProviderDefinition.class)); - assertValidity(false, JsonUtils.readValue("{\"lockoutPolicy\":{\"lockoutPeriodSeconds\":6,\"lockoutAfterFailures\":-128,\"countFailuresWithin\":1}}", UaaIdentityProviderDefinition.class)); - assertValidity(false, JsonUtils.readValue("{\"lockoutPolicy\":{\"lockoutPeriodSeconds\":6,\"lockoutAfterFailures\":128,\"countFailuresWithin\":-1}}", UaaIdentityProviderDefinition.class)); - } - - @Test - public void test_serialize_uaa() { - UaaIdentityProviderDefinition definition = new UaaIdentityProviderDefinition(); - IdentityProvider identityProvider = new IdentityProvider().setOriginKey(Origin.UAA).setConfig(definition); - test_serialization(identityProvider); - } - - @Test - public void test_serialize_saml() { - SamlIdentityProviderDefinition definition = new SamlIdentityProviderDefinition(); - definition.setMetaDataLocation("http://test.org"); - definition.setIdpEntityAlias(Origin.SAML); - definition.setZoneId(IdentityZone.getUaa().getId()); - IdentityProvider identityProvider = - new IdentityProvider() - .setOriginKey(definition.getIdpEntityAlias()) - .setConfig(definition) - .setIdentityZoneId(definition.getZoneId()); - test_serialization(identityProvider); - } - - protected IdentityProvider test_serialization(IdentityProvider identityProvider) { - String json = JsonUtils.writeValueAsString(identityProvider); - IdentityProvider identityProvider2 = JsonUtils.readValue(json, IdentityProvider.class); - assertNotNull(identityProvider2); - assertEquals(identityProvider.getConfig(), identityProvider2.getConfig()); - return identityProvider2; - } - - @Test - public void test_serialize_ldap() { - LdapIdentityProviderDefinition definition = new LdapIdentityProviderDefinition(); - IdentityProvider identityProvider = new IdentityProvider().setOriginKey(Origin.LDAP).setConfig(definition); - test_serialization(identityProvider); - } - - @Test - public void test_serialize_keystone() { - KeystoneIdentityProviderDefinition definition = new KeystoneIdentityProviderDefinition(); - IdentityProvider identityProvider = new IdentityProvider().setOriginKey(Origin.LDAP).setConfig(definition); - test_serialization(identityProvider); - } - - @Test - public void test_serialize_other_origin() { - AbstractIdentityProviderDefinition definition = new AbstractIdentityProviderDefinition(); - IdentityProvider identityProvider = new IdentityProvider().setOriginKey("other").setConfig(definition); - IdentityProvider other = test_serialization(identityProvider); - assertEquals("unknown", other.getType()); - assertEquals("other", other.getOriginKey()); - assertTrue(other.getConfig() instanceof AbstractIdentityProviderDefinition); - } - - private void assertValidity(boolean expected, AbstractIdentityProviderDefinition config) { - IdentityProvider identityProvider = new IdentityProvider().setOriginKey(Origin.UAA).setConfig(config); - assertEquals(expected, identityProvider.configIsValid()); - } - - public static final String BACKWARDS_COMPATIBLE_LIST_OF_IDPS = - "[\n" + - " {\n" + - " \"id\": \"2bfcef9b-33df-4c76-843f-e0e6b484a60a\",\n" + - " \"originKey\": \"keystone\",\n" + - " \"name\": \"keystone\",\n" + - " \"type\": \"keystone\",\n" + - " \"config\": null,\n" + - " \"version\": 1208,\n" + - " \"created\": 946684800000,\n" + - " \"active\": false,\n" + - " \"identityZoneId\": \"uaa\",\n" + - " \"last_modified\": 1447811837000\n" + - " },\n" + - " {\n" + - " \"id\": \"72209e6f-6434-491f-a170-398755bdc06d\",\n" + - " \"originKey\": \"ldap\",\n" + - " \"name\": \"UAA LDAP Provider\",\n" + - " \"type\": \"ldap\",\n" + - " \"config\": \"{\\\"emailDomain\\\":null,\\\"externalGroupsWhitelist\\\":[],\\\"attributeMappings\\\":{},\\\"ldapProfileFile\\\":\\\"ldap/ldap-search-and-bind.xml\\\",\\\"baseUrl\\\":\\\"ldap://52.20.5.106:389/\\\",\\\"referral\\\":null,\\\"skipSSLVerification\\\":false,\\\"userDNPattern\\\":null,\\\"userDNPatternDelimiter\\\":null,\\\"bindUserDn\\\":\\\"cn=admin,dc=test,dc=com\\\",\\\"bindPassword\\\":\\\"password\\\",\\\"userSearchBase\\\":\\\"dc=test,dc=com\\\",\\\"userSearchFilter\\\":\\\"cn={0}\\\",\\\"passwordAttributeName\\\":null,\\\"passwordEncoder\\\":null,\\\"localPasswordCompare\\\":null,\\\"mailAttributeName\\\":\\\"mail\\\",\\\"mailSubstitute\\\":\\\"\\\",\\\"mailSubstituteOverridesLdap\\\":false,\\\"ldapGroupFile\\\":\\\"ldap/ldap-groups-map-to-scopes.xml\\\",\\\"groupSearchBase\\\":\\\"ou=scopes,dc=test,dc=com\\\",\\\"groupSearchFilter\\\":\\\"member={0}\\\",\\\"groupsIgnorePartialResults\\\":null,\\\"autoAddGroups\\\":true,\\\"groupSearchSubTree\\\":true,\\\"maxGroupSearchDepth\\\":1,\\\"groupRoleAttribute\\\":\\\"spring.security.ldap.dn\\\"}\",\n" + - " \"version\": 932,\n" + - " \"created\": 946684800000,\n" + - " \"active\": true,\n" + - " \"identityZoneId\": \"uaa\",\n" + - " \"last_modified\": 1447811837000\n" + - " },\n" + - " {\n" + - " \"id\": \"69efc352-cb8d-4e85-9a43-86ddff9b4c91\",\n" + - " \"originKey\": \"login-server\",\n" + - " \"name\": \"login-server\",\n" + - " \"type\": \"login-server\",\n" + - " \"config\": null,\n" + - " \"version\": 0,\n" + - " \"created\": 946684800000,\n" + - " \"active\": true,\n" + - " \"identityZoneId\": \"uaa\",\n" + - " \"last_modified\": 1438372376000\n" + - " },\n" + - " {\n" + - " \"id\": \"58773443-0857-4f13-9dd9-0dc15fdeef06\",\n" + - " \"originKey\": \"okta-preview\",\n" + - " \"name\": \"UAA SAML Identity Provider[okta-preview]\",\n" + - " \"type\": \"saml\",\n" + - " \"config\": \"{\\\"emailDomain\\\":null,\\\"externalGroupsWhitelist\\\":[],\\\"attributeMappings\\\":{},\\\"metaDataLocation\\\":\\\"MIICmTCCAgKgAwIBAgIGAUPATqmEMA0GCSqGSIb3DQEBBQUAMIGPMQswCQYDVQQGEwJVUzETMBEG\\\\nA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\\\\nMBIGA1UECwwLU1NPUHJvdmlkZXIxEDAOBgNVBAMMB1Bpdm90YWwxHDAaBgkqhkiG9w0BCQEWDWlu\\\\nZm9Ab2t0YS5jb20wHhcNMTQwMTIzMTgxMjM3WhcNNDQwMTIzMTgxMzM3WjCBjzELMAkGA1UEBhMC\\\\nVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNVBAoM\\\\nBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRAwDgYDVQQDDAdQaXZvdGFsMRwwGgYJKoZIhvcN\\\\nAQkBFg1pbmZvQG9rdGEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeil67/TLOiTZU\\\\nWWgW2XEGgFZ94bVO90v5J1XmcHMwL8v5Z/8qjdZLpGdwI7Ph0CyXMMNklpaR/Ljb8fsls3amdT5O\\\\nBw92Zo8ulcpjw2wuezTwL0eC0wY/GQDAZiXL59npE6U+fH1lbJIq92hx0HJSru/0O1q3+A/+jjZL\\\\n3tL/SwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAI5BoWZoH6Mz9vhypZPOJCEKa/K+biZQsA4Zqsuk\\\\nvvphhSERhqk/Nv76Vkl8uvJwwHbQrR9KJx4L3PRkGCG24rix71jEuXVGZUsDNM3CUKnARx4MEab6\\\\nGFHNkZ6DmoT/PFagngecHu+EwmuDtaG0rEkFrARwe+d8Ru0BN558abFburn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\\\\n\\\",\\\"idpEntityAlias\\\":\\\"okta-preview\\\",\\\"zoneId\\\":\\\"uaa\\\",\\\"nameID\\\":\\\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\\\",\\\"assertionConsumerIndex\\\":0,\\\"metadataTrustCheck\\\":false,\\\"showSamlLink\\\":true,\\\"socketFactoryClassName\\\":\\\"org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory\\\",\\\"linkText\\\":null,\\\"iconUrl\\\":null,\\\"addShadowUserOnLogin\\\":true}\",\n" + - " \"version\": 48,\n" + - " \"created\": 1447100573000,\n" + - " \"active\": true,\n" + - " \"identityZoneId\": \"uaa\",\n" + - " \"last_modified\": 1447811837000\n" + - " },\n" + - " {\n" + - " \"id\": \"a937f8da-f47b-4b94-ae51-5bb23a590a69\",\n" + - " \"originKey\": \"simplesamlphp-url\",\n" + - " \"name\": \"UAA SAML Identity Provider[simplesamlphp-url]\",\n" + - " \"type\": \"saml\",\n" + - " \"config\": \"{\\\"emailDomain\\\":null,\\\"externalGroupsWhitelist\\\":[],\\\"attributeMappings\\\":{\\\"user.attribute.terribleBosses\\\":\\\"manager\\\",\\\"user.attribute.employeeCostCenter\\\":\\\"costCenter\\\"},\\\"metaDataLocation\\\":\\\"http://simplesamlphp.identity.cf-app.com/saml2/idp/metadata.php\\\",\\\"idpEntityAlias\\\":\\\"simplesamlphp-url\\\",\\\"zoneId\\\":\\\"uaa\\\",\\\"nameID\\\":\\\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\\\",\\\"assertionConsumerIndex\\\":0,\\\"metadataTrustCheck\\\":false,\\\"showSamlLink\\\":true,\\\"socketFactoryClassName\\\":\\\"org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory\\\",\\\"linkText\\\":\\\"Log in with Simple SAML PHP URL\\\",\\\"iconUrl\\\":null,\\\"addShadowUserOnLogin\\\":true}\",\n" + - " \"version\": 46,\n" + - " \"created\": 1447168745000,\n" + - " \"active\": true,\n" + - " \"identityZoneId\": \"uaa\",\n" + - " \"last_modified\": 1447811837000\n" + - " },\n" + - " {\n" + - " \"id\": \"eb82ad76-376e-4215-bb0f-de4677155ade\",\n" + - " \"originKey\": \"siteminder\",\n" + - " \"name\": \"UAA SAML Identity Provider[siteminder]\",\n" + - " \"type\": \"saml\",\n" + - " \"config\": \"{\\\"emailDomain\\\":null,\\\"externalGroupsWhitelist\\\":[],\\\"attributeMappings\\\":{},\\\"metaDataLocation\\\":\\\" CN=siteminder,OU=security,O=ca,L=islandia,ST=new york,C=US 1389887106 MIICRzCCAbCgAwIBAgIEUtf+gjANBgkqhkiG9w0BAQQFADBoMQswCQYDVQQGEwJVUzERMA8GA1UECBMIbmV3IHlvcmsxETAPBgNVBAcTCGlzbGFuZGlhMQswCQYDVQQKEwJjYTERMA8GA1UECxMIc2VjdXJpdHkxEzARBgNVBAMTCnNpdGVtaW5kZXIwHhcNMTQwMTE2MTU0NTA2WhcNMjQwMTE0MTU0NTA2WjBoMQswCQYDVQQGEwJVUzERMA8GA1UECBMIbmV3IHlvcmsxETAPBgNVBAcTCGlzbGFuZGlhMQswCQYDVQQKEwJjYTERMA8GA1UECxMIc2VjdXJpdHkxEzARBgNVBAMTCnNpdGVtaW5kZXIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOap0m7c+LSOAoGLUD3TAdS7BcJFns6HPSGAYK9NBY6MxITKElqVWHaVoaqxHCQxdQsF9oZvhPAmiNsbIRniKA+cypUov8U0pNIRPPBfl7p9ojGPZf5OtotnUnEN2ZcYuZwxRnKPfpfEs5fshSvcZIa34FCSCw8L0sRDoWFIucBjAgMBAAEwDQYJKoZIhvcNAQEEBQADgYEAFbsuhxBm3lUkycfZZuNYft1j41k+FyLLTyXyPJKmc2s2RPOYtLQyolNB214ZCIZzVSExyfo959ZBvdWz+UinpFNPd8cEc0nuXOmfW/XBEgT0YS1vIDUzfeVRyZLj2u4BdBGwmK5oYRbgHxViFVnn3C6UN5rcg5mZl0FBXJ31Zuk= CN=siteminder,OU=security,O=ca,L=islandia,ST=new york,C=US urn:oasis:names:tc:SAML:2.0:nameid-format:persistent \\\",\\\"idpEntityAlias\\\":\\\"siteminder\\\",\\\"zoneId\\\":\\\"uaa\\\",\\\"nameID\\\":\\\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\\\",\\\"assertionConsumerIndex\\\":0,\\\"metadataTrustCheck\\\":false,\\\"showSamlLink\\\":true,\\\"socketFactoryClassName\\\":\\\"org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory\\\",\\\"linkText\\\":\\\"SiteMinder\\\",\\\"iconUrl\\\":null,\\\"addShadowUserOnLogin\\\":true}\",\n" + - " \"version\": 2,\n" + - " \"created\": 1447811113000,\n" + - " \"active\": true,\n" + - " \"identityZoneId\": \"uaa\",\n" + - " \"last_modified\": 1447811837000\n" + - " },\n" + - " {\n" + - " \"id\": \"c0042c9e-1962-4f5c-a0ee-6282611eaec5\",\n" + - " \"originKey\": \"uaa\",\n" + - " \"name\": \"uaa\",\n" + - " \"type\": \"uaa\",\n" + - " \"config\": \"{\\\"emailDomain\\\":null,\\\"passwordPolicy\\\":{\\\"minLength\\\":0,\\\"maxLength\\\":255,\\\"requireUpperCaseCharacter\\\":0,\\\"requireLowerCaseCharacter\\\":0,\\\"requireDigit\\\":0,\\\"requireSpecialCharacter\\\":0,\\\"expirePasswordInMonths\\\":0},\\\"lockoutPolicy\\\":{\\\"lockoutPeriodSeconds\\\":300,\\\"lockoutAfterFailures\\\":5,\\\"countFailuresWithin\\\":3600},\\\"disableInternalUserManagement\\\":false}\",\n" + - " \"version\": 575,\n" + - " \"created\": 946684800000,\n" + - " \"active\": true,\n" + - " \"identityZoneId\": \"uaa\",\n" + - " \"last_modified\": 1447811837000\n" + - " }\n" + - "]"; -} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java index e5ddc1455b4..bc1e6dfc162 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java @@ -3,6 +3,7 @@ import org.apache.commons.lang.RandomStringUtils; import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -65,7 +66,7 @@ public void testCreateAndUpdateIdentityProviderInDefaultZone() throws Exception assertEquals(idp.getName(), createdIdp.getName()); assertEquals(rawCreatedIdp.get("origin_key"), createdIdp.getOriginKey()); - assertEquals(Origin.UNKNOWN, createdIdp.getType()); //we don't allow other types anymore + assertEquals(OriginKeys.UNKNOWN, createdIdp.getType()); //we don't allow other types anymore assertEquals(idp.getConfig(), createdIdp.getConfig()); assertEquals(idp.getLastModified().getTime()/1000, createdIdp.getLastModified().getTime()/1000); assertEquals(Integer.valueOf(rawCreatedIdp.get("version").toString())+1, createdIdp.getVersion()); @@ -129,7 +130,7 @@ public void testUpdateIdentityProviderInDefaultZone() throws Exception { String idpId = RandomStringUtils.randomAlphabetic(6); IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, zoneId); idp.setId(idpId); - idp.setType(Origin.LDAP); + idp.setType(OriginKeys.LDAP); idp = db.create(idp); LdapIdentityProviderDefinition definition = new LdapIdentityProviderDefinition(); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java b/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java index 6aa3b61481a..fcad8a40f92 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java @@ -14,8 +14,8 @@ import org.cloudfoundry.identity.uaa.authentication.AccountNotVerifiedException; import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.manager.ChainedAuthenticationManager.AuthenticationManagerConfiguration; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; @@ -64,8 +64,8 @@ public Authentication authenticate(Authentication authentication) throws Authent } protected ChainedAuthenticationManager getChainedAuthenticationManager(IdentityZone zone) { - IdentityProvider ldapProvider = getProvider(Origin.LDAP, zone); - IdentityProvider uaaProvider = getProvider(Origin.UAA, zone); + IdentityProvider ldapProvider = getProvider(OriginKeys.LDAP, zone); + IdentityProvider uaaProvider = getProvider(OriginKeys.UAA, zone); List delegates = new LinkedList<>(); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java index e4521296fec..fa33e4bcdd1 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java @@ -3,13 +3,13 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicZoneAwareAuthenticationManager; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.invitations.InvitationsService.AcceptedInvitation; import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserDetails; import org.cloudfoundry.identity.uaa.login.PasswordConfirmationValidation; @@ -55,7 +55,7 @@ import java.util.HashMap; import java.util.Map; -import static org.cloudfoundry.identity.uaa.authentication.Origin.ORIGIN; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.ORIGIN; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; @@ -135,8 +135,8 @@ public String acceptInvitePage(@RequestParam String code, Model model, HttpServl String redirect = "redirect:" + accepted.getRedirectUri(); logger.debug(String.format("Redirecting accepted invitation for email:%s, id:%s to URL:%s", codeData.get("email"), codeData.get("user_id"), redirect)); return redirect; - } else if (Origin.SAML.equals(provider.getType())) { - SamlIdentityProviderDefinition definition = ObjectUtils.castInstance(provider.getConfig(),SamlIdentityProviderDefinition.class); + } else if (OriginKeys.SAML.equals(provider.getType())) { + SamlIdentityProviderDefinition definition = ObjectUtils.castInstance(provider.getConfig(), SamlIdentityProviderDefinition.class); RequestContextHolder.getRequestAttributes().setAttribute("IS_INVITE_ACCEPTANCE", true, RequestAttributes.SCOPE_SESSION); RequestContextHolder.getRequestAttributes().setAttribute("user_id", user.getId(), RequestAttributes.SCOPE_SESSION); @@ -256,7 +256,7 @@ public String acceptLdapInvitation(@RequestParam("enterprise_username") String u AuthenticationManager authenticationManager = null; IdentityProvider ldapProvider = null; try { - ldapProvider = providerProvisioning.retrieveByOrigin(Origin.LDAP, IdentityZoneHolder.get().getId()); + ldapProvider = providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZoneHolder.get().getId()); zoneAwareAuthenticationManager.getLdapAuthenticationManager(IdentityZoneHolder.get(), ldapProvider).getLdapAuthenticationManager(); authenticationManager = zoneAwareAuthenticationManager.getLdapAuthenticationManager(IdentityZoneHolder.get(), ldapProvider).getLdapManagerActual(); } catch (EmptyResultDataAccessException e) { @@ -273,7 +273,7 @@ public String acceptLdapInvitation(@RequestParam("enterprise_username") String u ScimUser user = userProvisioning.retrieve(data.get("user_id")); if (!user.getPrimaryEmail().equalsIgnoreCase(((ExtendedLdapUserDetails) authentication.getPrincipal()).getEmailAddress())) { model.addAttribute("email", data.get("email")); - model.addAttribute(Origin.LDAP, Origin.LDAP); + model.addAttribute(OriginKeys.LDAP, OriginKeys.LDAP); model.addAttribute("code", expiringCodeStore.generateCode(expiringCode.getData(), new Timestamp(System.currentTimeMillis() + (10 * 60 * 1000))).getCode()); return handleUnprocessableEntity(model, response, "error_message", "invite.email_mismatch", "invitations/accept_invite"); } diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java index 69c57463001..3ef88ab266b 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java @@ -33,7 +33,7 @@ import java.util.List; import java.util.Map; -import static org.cloudfoundry.identity.uaa.authentication.Origin.ORIGIN; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.ORIGIN; import static org.springframework.security.oauth2.common.util.OAuth2Utils.CLIENT_ID; import static org.springframework.security.oauth2.common.util.OAuth2Utils.REDIRECT_URI; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AbstractControllerInfo.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/AbstractControllerInfo.java index 35f33ca51a1..07b1467c3a8 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AbstractControllerInfo.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/AbstractControllerInfo.java @@ -15,7 +15,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.http.HttpHeaders; @@ -107,8 +107,8 @@ protected String getUaaHost() { protected Map getLinksInfo() { Map model = new HashMap(); - model.put(Origin.UAA, getUaaBaseUrl()); - model.put("login", getUaaBaseUrl().replaceAll(Origin.UAA, "login")); + model.put(OriginKeys.UAA, getUaaBaseUrl()); + model.put("login", getUaaBaseUrl().replaceAll(OriginKeys.UAA, "login")); model.putAll(getLinks()); return model; } diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java index e58044a2902..1d56fb713a5 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java @@ -12,8 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.user.UaaAuthority; @@ -102,7 +102,7 @@ public String verifyUser(Model model, return "accounts/new_activation_email"; } - UaaPrincipal uaaPrincipal = new UaaPrincipal(accountCreation.getUserId(), accountCreation.getUsername(), accountCreation.getEmail(), Origin.UAA, null, IdentityZoneHolder.get().getId()); + UaaPrincipal uaaPrincipal = new UaaPrincipal(accountCreation.getUserId(), accountCreation.getUsername(), accountCreation.getEmail(), OriginKeys.UAA, null, IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManager.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManager.java index 8840660e254..b8463d7ddf8 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManager.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManager.java @@ -17,26 +17,23 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.client.SocialClientUserDetails; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.InvalidCodeException; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.util.OAuth2Utils; -import java.io.IOException; import java.util.Map; /** @@ -89,7 +86,7 @@ public Authentication authenticate(Authentication authentication) throws Authent String username; String clientId; username = codeData.get("username"); - origin = codeData.get(Origin.ORIGIN); + origin = codeData.get(OriginKeys.ORIGIN); userId = codeData.get("user_id"); clientId = codeData.get(OAuth2Utils.CLIENT_ID); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangeEmailController.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangeEmailController.java index 87475cf9d61..21077a67b63 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangeEmailController.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangeEmailController.java @@ -1,9 +1,9 @@ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; @@ -68,7 +68,7 @@ public String changeEmail(Model model, @Valid @ModelAttribute("newEmail") ValidE return "change_email"; } String origin = ((UaaPrincipal)securityContext.getAuthentication().getPrincipal()).getOrigin(); - if (!origin.equals(Origin.UAA)) { + if (!origin.equals(OriginKeys.UAA)) { redirectAttributes.addAttribute("error_message_code", "email_change.non-uaa-origin"); return "redirect:profile"; } diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java index 49de83774bd..8ad21bbeac9 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java @@ -3,9 +3,9 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; @@ -71,10 +71,10 @@ public void beginActivation(String email, String password, String clientId, Stri String subject = getSubjectText(); try { - ScimUser scimUser = createUser(email, password, Origin.UAA); + ScimUser scimUser = createUser(email, password, OriginKeys.UAA); generateAndSendCode(email, clientId, subject, scimUser.getId(), redirectUri); } catch (ScimResourceAlreadyExistsException e) { - List users = scimUserProvisioning.query("userName eq \""+email+"\" and origin eq \""+Origin.UAA+"\""); + List users = scimUserProvisioning.query("userName eq \""+email+"\" and origin eq \""+ OriginKeys.UAA+"\""); try { if (users.size()>0) { if (users.get(0).isVerified()) { diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java index abd6217c947..ba40928b873 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java @@ -21,9 +21,9 @@ import java.util.regex.Pattern; import com.fasterxml.jackson.core.type.TypeReference; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; @@ -63,7 +63,7 @@ public EmailChangeEmailService(TemplateEngine templateEngine, MessageService mes @Override public void beginEmailChange(String userId, String email, String newEmail, String clientId, String redirectUri) { ScimUser user = scimUserProvisioning.retrieve(userId); - List results = scimUserProvisioning.query("userName eq \"" + newEmail + "\" and origin eq \"" + Origin.UAA + "\""); + List results = scimUserProvisioning.query("userName eq \"" + newEmail + "\" and origin eq \"" + OriginKeys.UAA + "\""); if (user.getUserName().equals(user.getPrimaryEmail())) { if (!results.isEmpty()) { diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsService.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsService.java index 429c51836c7..5989bb143f9 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsService.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsService.java @@ -3,9 +3,9 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.invitations.InvitationsService; import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -100,7 +100,7 @@ public AcceptedInvitation acceptInvitation(String code, String password) { user = scimUserProvisioning.verifyUser(userId, user.getVersion()); - if (Origin.UAA.equals(user.getOrigin())) { + if (OriginKeys.UAA.equals(user.getOrigin())) { PasswordChangeRequest request = new PasswordChangeRequest(); request.setPassword(password); scimUserProvisioning.changePassword(userId, null, password); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/LoginUaaApprovalsService.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/LoginUaaApprovalsService.java index 6622ce41538..5ac3a376ac7 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/LoginUaaApprovalsService.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/LoginUaaApprovalsService.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalsControllerService; import org.springframework.beans.factory.annotation.Autowired; @@ -53,7 +53,7 @@ public Map> getCurrentApprovalsByClientId() { clientApprovals.add(approval); } else { String resource = scope.substring(0, scope.lastIndexOf(".")); - if (Origin.UAA.equals(resource)) { + if (OriginKeys.UAA.equals(resource)) { // special case: don't need to prompt for internal uaa // scopes continue; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java index 79a3d647429..54337ffbaa0 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java @@ -12,9 +12,9 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; @@ -109,7 +109,7 @@ else if (null != delete) { private boolean isUaaManagedUser(Authentication authentication) { if (authentication.getPrincipal() instanceof UaaPrincipal) { UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); - return Origin.UAA.equals(principal.getOrigin()); + return OriginKeys.UAA.equals(principal.getOrigin()); } return false; } diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordController.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordController.java index c63f41e877c..ab10fbd5b43 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordController.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordController.java @@ -14,10 +14,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.login.ResetPasswordService.ResetPasswordResponse; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -193,7 +193,7 @@ public String resetPassword(Model model, try { ResetPasswordResponse resetPasswordResponse = resetPasswordService.resetPassword(code, password); ScimUser user = resetPasswordResponse.getUser(); - UaaPrincipal uaaPrincipal = new UaaPrincipal(user.getId(), user.getUserName(), user.getPrimaryEmail(), Origin.UAA, null, IdentityZoneHolder.get().getId()); + UaaPrincipal uaaPrincipal = new UaaPrincipal(user.getId(), user.getUserName(), user.getPrimaryEmail(), OriginKeys.UAA, null, IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsService.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsService.java index ef37632e853..ef18e2b1721 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsService.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsService.java @@ -14,7 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; @@ -65,7 +65,7 @@ public Map> getCurrentApprovalsByClientId() { clientApprovals.add(approval); } else { String resource = scope.substring(0, scope.lastIndexOf(".")); - if (Origin.UAA.equals(resource)) { + if (OriginKeys.UAA.equals(resource)) { // special case: don't need to prompt for internal uaa // scopes continue; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java index 0e74d67ee4e..b2394a2d444 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java @@ -17,13 +17,13 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.SamlUserAuthority; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; @@ -130,7 +130,7 @@ public Authentication authenticate(Authentication authentication) throws Authent throw new ProviderNotFoundException("Not identity provider found in zone."); } ExpiringUsernameAuthenticationToken result = getExpiringUsernameAuthenticationToken(authentication); - UaaPrincipal samlPrincipal = new UaaPrincipal(Origin.NotANumber, result.getName(), result.getName(), alias, result.getName(), zone.getId()); + UaaPrincipal samlPrincipal = new UaaPrincipal(OriginKeys.NotANumber, result.getName(), result.getName(), alias, result.getName(), zone.getId()); Collection samlAuthorities = retrieveSamlAuthorities(samlConfig, (SAMLCredential) result.getCredentials()); Collection authorities = mapAuthorities(idp.getOriginKey(), samlAuthorities); @@ -303,13 +303,13 @@ protected UaaUser getUser(UaaPrincipal principal, MultiValueMap u String givenName = userAttributes.getFirst(GIVEN_NAME_ATTRIBUTE_NAME); String familyName = userAttributes.getFirst(FAMILY_NAME_ATTRIBUTE_NAME); String phoneNumber = userAttributes.getFirst(PHONE_NUMBER_ATTRIBUTE_NAME); - String userId = Origin.NotANumber; - String origin = principal.getOrigin()!=null?principal.getOrigin():Origin.LOGIN_SERVER; + String userId = OriginKeys.NotANumber; + String origin = principal.getOrigin()!=null?principal.getOrigin(): OriginKeys.LOGIN_SERVER; String zoneId = principal.getZoneId(); if (name == null && email != null) { name = email; } - if (name == null && Origin.NotANumber.equals(userId)) { + if (name == null && OriginKeys.NotANumber.equals(userId)) { throw new BadCredentialsException("Cannot determine username from credentials supplied"); } else if (name==null) { //we have user_id, name is irrelevant diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/util/LocalUaaRestTemplate.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/util/LocalUaaRestTemplate.java index 9444c519eba..361351b2e28 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/util/LocalUaaRestTemplate.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/util/LocalUaaRestTemplate.java @@ -16,7 +16,7 @@ import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.springframework.beans.factory.InitializingBean; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; @@ -66,7 +66,7 @@ protected OAuth2AccessToken acquireAccessToken(OAuth2ClientContext oauth2Context scopes.add(authority.getAuthority()); } Set resourceIds = new HashSet<>(); - resourceIds.add(Origin.UAA); + resourceIds.add(OriginKeys.UAA); Set responseTypes = new HashSet<>(); responseTypes.add("token"); Map requestParameters = new HashMap<>(); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java b/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java index 9b29615bbdb..7687a72764b 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java @@ -14,9 +14,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicLdapAuthenticationManager; import org.cloudfoundry.identity.uaa.authentication.manager.LdapLoginAuthenticationManager; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; @@ -80,7 +80,7 @@ public IdentityProviderEndpoints( public ResponseEntity createIdentityProvider(@RequestBody IdentityProvider body) throws MetadataProviderException{ String zoneId = IdentityZoneHolder.get().getId(); body.setIdentityZoneId(zoneId); - if (Origin.SAML.equals(body.getType())) { + if (OriginKeys.SAML.equals(body.getType())) { SamlIdentityProviderDefinition definition = ObjectUtils.castInstance(body.getConfig(), SamlIdentityProviderDefinition.class); definition.setZoneId(zoneId); definition.setIdpEntityAlias(body.getOriginKey()); @@ -100,7 +100,7 @@ public ResponseEntity updateIdentityProvider(@PathVariable Str if (!body.configIsValid()) { return new ResponseEntity<>(UNPROCESSABLE_ENTITY); } - if (Origin.SAML.equals(body.getType())) { + if (OriginKeys.SAML.equals(body.getType())) { body.setOriginKey(existing.getOriginKey()); //we do not allow origin to change for a SAML provider, since that can cause clashes SamlIdentityProviderDefinition definition = ObjectUtils.castInstance(body.getConfig(), SamlIdentityProviderDefinition.class); definition.setZoneId(zoneId); diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java b/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java index 2669f434234..5a94f7a4cf3 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java @@ -1,11 +1,11 @@ package org.cloudfoundry.identity.uaa.invitations; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicLdapAuthenticationManager; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicZoneAwareAuthenticationManager; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserDetails; import org.cloudfoundry.identity.uaa.login.BuildInfo; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; @@ -136,7 +136,7 @@ public void testAcceptInvitationsPage() throws Exception { when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); when(expiringCodeStore.generateCode(anyString(), anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); IdentityProvider provider = new IdentityProvider(); - provider.setType(Origin.UAA); + provider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); @@ -161,7 +161,7 @@ public void acceptInvitePage_for_unverifiedSamlUser() throws Exception { IdentityProvider provider = new IdentityProvider(); SamlIdentityProviderDefinition definition = new SamlIdentityProviderDefinition("http://test.saml.com", "test-saml", "test", 0, false, true, "testsaml", "test.com", IdentityZone.getUaa().getId()); provider.setConfig(definition); - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); when(providerProvisioning.retrieveByOrigin(eq("test-saml"), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); @@ -181,7 +181,7 @@ public void acceptInvitePage_for_unverifiedLdapUser() throws Exception { when(expiringCodeStore.generateCode(anyString(), anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); IdentityProvider provider = new IdentityProvider(); - provider.setType(Origin.LDAP); + provider.setType(OriginKeys.LDAP); when(providerProvisioning.retrieveByOrigin(eq("ldap"), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") @@ -302,7 +302,7 @@ public void acceptInvitePage_for_verifiedUser() throws Exception { when(expiringCodeStore.generateCode(anyString(), anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); when(invitationsService.acceptInvitation(anyString(), anyString())).thenReturn(new InvitationsService.AcceptedInvitation("blah.test.com", new ScimUser())); IdentityProvider provider = new IdentityProvider(); - provider.setType(Origin.UAA); + provider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); @@ -352,7 +352,7 @@ public void testAcceptInvite() throws Exception { } public MockHttpServletRequestBuilder startAcceptInviteFlow(String password) { - UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", Origin.UAA, null, IdentityZoneHolder.get().getId()); + UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null, IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); @@ -364,7 +364,7 @@ public MockHttpServletRequestBuilder startAcceptInviteFlow(String password) { @Test public void acceptInviteWithValidClientRedirect() throws Exception { - UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", Origin.UAA, null,IdentityZoneHolder.get().getId()); + UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null,IdentityZoneHolder.get().getId()); ScimUser user = new ScimUser(uaaPrincipal.getId(), uaaPrincipal.getName(),"fname", "lname"); user.setPrimaryEmail(user.getUserName()); @@ -387,7 +387,7 @@ public void acceptInviteWithValidClientRedirect() throws Exception { @Test public void acceptInviteWithInvalidClientRedirect() throws Exception { - UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", Origin.UAA, null,IdentityZoneHolder.get().getId()); + UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null,IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); @@ -410,7 +410,7 @@ public void acceptInviteWithInvalidClientRedirect() throws Exception { @Test public void testAcceptInviteWithoutMatchingPasswords() throws Exception { - UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", Origin.UAA, null,IdentityZoneHolder.get().getId()); + UaaPrincipal uaaPrincipal = new UaaPrincipal("user-id-001", "user@example.com", "user@example.com", OriginKeys.UAA, null,IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(uaaPrincipal, null, UaaAuthority.USER_AUTHORITIES); SecurityContextHolder.getContext().setAuthentication(token); diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java index 07a6f0fe949..dd72f905b63 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java @@ -1,17 +1,16 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.InvalidCodeException; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.Before; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.mockito.Mockito; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.BadCredentialsException; @@ -62,7 +61,7 @@ public void authentication_successful() throws Exception { codeData.put("user_id", "test-user-id"); codeData.put("client_id", "test-client-id"); codeData.put("username", "test-username"); - codeData.put(Origin.ORIGIN, Origin.UAA); + codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); codeData.put("action", ExpiringCodeType.AUTOLOGIN.name()); when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData))); @@ -72,7 +71,7 @@ public void authentication_successful() throws Exception { UaaAuthentication uaaAuthentication = (UaaAuthentication)authenticate; assertThat(uaaAuthentication.getPrincipal().getId(), is("test-user-id")); assertThat(uaaAuthentication.getPrincipal().getName(), is("test-username")); - assertThat(uaaAuthentication.getPrincipal().getOrigin(), is(Origin.UAA)); + assertThat(uaaAuthentication.getPrincipal().getOrigin(), is(OriginKeys.UAA)); assertThat(uaaAuthentication.getDetails(), is(instanceOf(UaaAuthenticationDetails.class))); UaaAuthenticationDetails uaaAuthDetails = (UaaAuthenticationDetails)uaaAuthentication.getDetails(); assertThat(uaaAuthDetails.getClientId(), is("test-client-id")); @@ -84,7 +83,7 @@ public void authentication_fails_withInvalidClient() { codeData.put("user_id", "test-user-id"); codeData.put("client_id", "actual-client-id"); codeData.put("username", "test-username"); - codeData.put(Origin.ORIGIN, Origin.UAA); + codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); codeData.put("action", ExpiringCodeType.AUTOLOGIN.name()); when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData))); @@ -96,7 +95,7 @@ public void authentication_fails_withNoClientId() { Map codeData = new HashMap<>(); codeData.put("user_id", "test-user-id"); codeData.put("username", "test-username"); - codeData.put(Origin.ORIGIN, Origin.UAA); + codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); codeData.put("action", ExpiringCodeType.AUTOLOGIN.name()); when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData))); @@ -115,7 +114,7 @@ public void authentication_fails_withCodeIntendedForDifferentPurpose() { codeData.put("user_id", "test-user-id"); codeData.put("client_id", "test-client-id"); codeData.put("username", "test-username"); - codeData.put(Origin.ORIGIN, Origin.UAA); + codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData))); manager.authenticate(authenticationToken); @@ -131,4 +130,4 @@ public void authentication_fails_withInvalidCode() { } -} \ No newline at end of file +} diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ChangeEmailControllerTest.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/ChangeEmailControllerTest.java index ebd6ca65a6d..1321e46ab53 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ChangeEmailControllerTest.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/ChangeEmailControllerTest.java @@ -1,9 +1,9 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.TestClassNullifier; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; import org.cloudfoundry.identity.uaa.user.UaaAuthority; @@ -180,7 +180,7 @@ public void testInvalidEmail() throws Exception { @Test public void testVerifyEmail() throws Exception { - UaaUser user = new UaaUser("user-id-001", "new@example.com", "password", "new@example.com", Collections.emptyList(), "name", "name", null, null, Origin.UAA, null, true, IdentityZoneHolder.get().getId(),"user-id-001", null); + UaaUser user = new UaaUser("user-id-001", "new@example.com", "password", "new@example.com", Collections.emptyList(), "name", "name", null, null, OriginKeys.UAA, null, true, IdentityZoneHolder.get().getId(),"user-id-001", null); when(uaaUserDatabase.retrieveUserById(anyString())).thenReturn(user); Map response = new HashMap<>(); @@ -205,7 +205,7 @@ public void testVerifyEmail() throws Exception { @Test public void testVerifyEmailWithRedirectUrl() throws Exception { - UaaUser user = new UaaUser("user-id-001", "new@example.com", "password", "new@example.com", Collections.emptyList(), "name", "name", null, null, Origin.UAA, null, true, IdentityZoneHolder.get().getId(),"user-id-001", null); + UaaUser user = new UaaUser("user-id-001", "new@example.com", "password", "new@example.com", Collections.emptyList(), "name", "name", null, null, OriginKeys.UAA, null, true, IdentityZoneHolder.get().getId(),"user-id-001", null); when(uaaUserDatabase.retrieveUserById(anyString())).thenReturn(user); Map response = new HashMap<>(); @@ -257,7 +257,7 @@ public void testVerifyEmailWithInvalidCode() throws Exception { private void setupSecurityContext() { Authentication authentication = new UaaAuthentication( - new UaaPrincipal("user-id-001", "bob", "user@example.com", Origin.UAA, null,IdentityZoneHolder.get().getId()), + new UaaPrincipal("user-id-001", "bob", "user@example.com", OriginKeys.UAA, null,IdentityZoneHolder.get().getId()), Arrays.asList(UaaAuthority.UAA_USER), null ); @@ -304,4 +304,4 @@ ChangeEmailController changeEmailController(ChangeEmailService changeEmailServic return changeEmailController; } } -} \ No newline at end of file +} diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java index 6110493e6d6..4ea5e3a741b 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java @@ -1,8 +1,8 @@ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -292,7 +292,7 @@ private String setUpForSuccess(String userId, String redirectUri) throws Excepti "familyName"); user.setPrimaryEmail("user@example.com"); user.setPassword("password"); - user.setOrigin(Origin.UAA); + user.setOrigin(OriginKeys.UAA); user.setActive(true); user.setVerified(false); diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java index 75f7d6ac4e7..5536730ea48 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java @@ -1,8 +1,8 @@ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; @@ -36,7 +36,7 @@ import java.util.HashMap; import java.util.Map; -import static org.cloudfoundry.identity.uaa.authentication.Origin.UAA; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; import static org.cloudfoundry.identity.uaa.login.EmailInvitationsService.EMAIL; import static org.cloudfoundry.identity.uaa.login.EmailInvitationsService.USER_ID; import static org.junit.Assert.assertEquals; @@ -109,7 +109,7 @@ public void acceptInvitationNoClientId() throws Exception { @Test public void acceptInvitationWithClientNotFound() throws Exception { ScimUser user = new ScimUser("user-id-001", "user@example.com", "first", "last"); - user.setOrigin(Origin.UAA); + user.setOrigin(OriginKeys.UAA); when(scimUserProvisioning.verifyUser(anyString(), anyInt())).thenReturn(user); when(scimUserProvisioning.update(anyString(), anyObject())).thenReturn(user); when(scimUserProvisioning.retrieve(eq("user-id-001"))).thenReturn(user); @@ -183,7 +183,7 @@ public void accept_invitation_with_external_user_that_does_not_have_email_as_the String actualUsername = "actual_username"; ScimUser userBeforeAccept = new ScimUser(userId, email, "first", "last"); userBeforeAccept.setPrimaryEmail(email); - userBeforeAccept.setOrigin(Origin.SAML); + userBeforeAccept.setOrigin(OriginKeys.SAML); when(scimUserProvisioning.verifyUser(eq(userId), anyInt())).thenReturn(userBeforeAccept); when(scimUserProvisioning.retrieve(eq(userId))).thenReturn(userBeforeAccept); diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java index d4a496db574..e7abe92feb7 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java @@ -14,9 +14,9 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.TestClassNullifier; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.After; @@ -133,7 +133,7 @@ public void testGetProfileNoAppName() throws Exception { public void testGetProfile(String name) throws Exception { - UaaPrincipal uaaPrincipal = new UaaPrincipal("fake-user-id", "username", "email@example.com", Origin.UAA, null, IdentityZoneHolder.get().getId()); + UaaPrincipal uaaPrincipal = new UaaPrincipal("fake-user-id", "username", "email@example.com", OriginKeys.UAA, null, IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(uaaPrincipal, null); mockMvc.perform(get("/profile").principal(authentication)) @@ -157,7 +157,7 @@ public void testSpecialMessageWhenNoAppsAreAuthorized() throws Exception { Map> approvalsByClientId = new HashMap>(); Mockito.when(approvalsService.getCurrentApprovalsByClientId()).thenReturn(approvalsByClientId); - UaaPrincipal uaaPrincipal = new UaaPrincipal("fake-user-id", "username", "email@example.com", Origin.UAA, null, IdentityZoneHolder.get().getId()); + UaaPrincipal uaaPrincipal = new UaaPrincipal("fake-user-id", "username", "email@example.com", OriginKeys.UAA, null, IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(uaaPrincipal, null); mockMvc.perform(get("/profile").principal(authentication)) @@ -169,7 +169,7 @@ public void testSpecialMessageWhenNoAppsAreAuthorized() throws Exception { @Test public void testPasswordLinkHiddenWhenUsersOriginIsNotUaa() throws Exception { - UaaPrincipal uaaPrincipal = new UaaPrincipal("fake-user-id", "username", "email@example.com", Origin.LDAP, "dnEntryForLdapUser", IdentityZoneHolder.get().getId()); + UaaPrincipal uaaPrincipal = new UaaPrincipal("fake-user-id", "username", "email@example.com", OriginKeys.LDAP, "dnEntryForLdapUser", IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(uaaPrincipal, null); mockMvc.perform(get("/profile").principal(authentication)) diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/util/SecurityUtils.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/util/SecurityUtils.java index 7eaaf6e20bd..562afd1617d 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/util/SecurityUtils.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/util/SecurityUtils.java @@ -13,13 +13,12 @@ */ package org.cloudfoundry.identity.uaa.login.util; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; @@ -27,10 +26,8 @@ import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; -import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; -import java.util.List; import java.util.Set; import static org.junit.Assert.assertTrue; @@ -46,7 +43,7 @@ public static SecurityContext defaultSecurityContext(Authentication authenticati } public static Authentication fullyAuthenticatedUser(String id, String username, String email, GrantedAuthority... authorities) { - UaaPrincipal p = new UaaPrincipal(id, username, email, Origin.UAA,"", IdentityZoneHolder.get().getId()); + UaaPrincipal p = new UaaPrincipal(id, username, email, OriginKeys.UAA,"", IdentityZoneHolder.get().getId()); LinkedList grantedAuthorities = new LinkedList<>(); Collections.addAll(grantedAuthorities, authorities); UaaAuthentication auth = new UaaAuthentication(p, "", grantedAuthorities, new UaaAuthenticationDetails(new MockHttpServletRequest()),true, System.currentTimeMillis()); diff --git a/payload/build.gradle b/payload/build.gradle index 1db3575d86e..a5f640ffe8d 100644 --- a/payload/build.gradle +++ b/payload/build.gradle @@ -1,6 +1,10 @@ description = 'CloudFoundry Identity Payload Data Objects JAR' dependencies { + compile group: 'javax.validation', name: 'validation-api', version: parent.validationAPIVersion + + compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version:parent.jacksonVersion + compile(group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version:parent.springSecurityOAuthVersion) { exclude(module: 'commons-codec') exclude(module: 'jackson-mapper-asl') @@ -17,5 +21,5 @@ apply from: file('build_properties.gradle') processResources { //maven replaces project.artifactId in the log4j.properties file //https://www.pivotaltracker.com/story/show/74344574 - filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-common') : line } + filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-payload') : line } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java similarity index 92% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java index 9484baede28..3181b9adeab 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java @@ -17,12 +17,15 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import java.util.HashMap; +import java.util.UUID; /** * Created by pivotal on 11/11/15. */ public class KeyPair { + + private UUID id; private String verificationKey = new RandomValueStringGenerator().generate(); private String signingKey = verificationKey; @@ -38,6 +41,10 @@ public KeyPair(String signingKey, String verificationKey) { this.verificationKey = verificationKey; } + public UUID getId() { return id; } + + public void setId(UUID id) { this.id = id; } + public String getSigningKey() { return signingKey; } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java new file mode 100644 index 00000000000..13670641994 --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java @@ -0,0 +1,32 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.constants; + +/** + * Created by pivotal on 11/16/15. + */ +public final class OriginKeys { + + private OriginKeys() {} + + public static final String ORIGIN = "origin"; + public static final String UAA = "uaa"; + public static final String LOGIN_SERVER = "login-server"; + public static final String LDAP = "ldap"; + public static final String KEYSTONE = "keystone"; + public static final String SAML = "saml"; + public static final String NotANumber = "NaN"; + public static final String UNKNOWN = "unknown"; +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java similarity index 93% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java index 23593907640..5d52adb1067 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java @@ -13,18 +13,13 @@ package org.cloudfoundry.identity.uaa.zone; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import javax.validation.constraints.NotNull; - import java.util.Calendar; import java.util.Date; -@JsonSerialize -@JsonDeserialize public class IdentityZone { public static final IdentityZone getUaa() { Calendar calendar = Calendar.getInstance(); @@ -34,8 +29,8 @@ public static final IdentityZone getUaa() { uaa.setCreated(calendar.getTime()); uaa.setLastModified(calendar.getTime()); uaa.setVersion(0); - uaa.setId(Origin.UAA); - uaa.setName(Origin.UAA); + uaa.setId(OriginKeys.UAA); + uaa.setName(OriginKeys.UAA); uaa.setDescription("The system zone for backwards compatibility"); uaa.setSubdomain(""); return uaa; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManager.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManager.java index 0c62ccbee26..ddbf7b38279 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManager.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManager.java @@ -14,14 +14,12 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.xml.resolver.readers.OASISXMLCatalogReader; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authorization.ExternalGroupMappingAuthorizationManager; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.extension.LdapAuthority; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -43,7 +41,7 @@ public Set findScopesFromAuthorities(Set members = extMbrMgr.getExternalGroupMapsByExternalGroup(la.getDn(), Origin.LDAP); + List members = extMbrMgr.getExternalGroupMapsByExternalGroup(la.getDn(), OriginKeys.LDAP); for (ScimGroupExternalMember member : members) { SimpleGrantedAuthority mapped = new SimpleGrantedAuthority(member.getDisplayName()); result.add(mapped); diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java index 01f817da29e..59d242dc68d 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java @@ -12,9 +12,9 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.InvalidCodeException; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.password.event.PasswordChangeEvent; @@ -35,15 +35,12 @@ import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.NoSuchClientException; -import org.springframework.util.StringUtils; import org.springframework.web.client.RestClientException; import java.sql.Timestamp; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -134,7 +131,7 @@ private ResetPasswordResponse changePasswordCodeAuthenticated(String code, Strin @Override public ForgotPasswordInfo forgotPassword(String email, String clientId, String redirectUri) { String jsonEmail = JsonUtils.writeValueAsString(email); - List results = scimUserProvisioning.query("userName eq " + jsonEmail + " and origin eq \"" + Origin.UAA + "\""); + List results = scimUserProvisioning.query("userName eq " + jsonEmail + " and origin eq \"" + OriginKeys.UAA + "\""); if (results.isEmpty()) { results = scimUserProvisioning.query("userName eq " + jsonEmail); if (results.isEmpty()) { diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java index d97655c9a6e..39e811d7ba5 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java @@ -18,7 +18,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; @JsonInclude(JsonInclude.Include.NON_NULL) public class ScimGroupMember { @@ -34,7 +34,7 @@ public enum Role { @JsonProperty("value") private String memberId; - private String origin = Origin.UAA; + private String origin = OriginKeys.UAA; @JsonInclude(JsonInclude.Include.NON_NULL) public enum Type { diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserJsonDeserializer.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserJsonDeserializer.java index 19d43796fc1..7a474a0a718 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserJsonDeserializer.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserJsonDeserializer.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.util.json.JsonDateDeserializer; @@ -76,7 +76,7 @@ public ScimUser deserialize(JsonParser jp, DeserializationContext ctxt) throws I user.setActive(jp.readValueAs(Boolean.class)); } else if ("verified".equalsIgnoreCase(fieldName)) { user.setVerified(jp.readValueAs(Boolean.class)); - } else if (Origin.ORIGIN.equalsIgnoreCase(fieldName)) { + } else if (OriginKeys.ORIGIN.equalsIgnoreCase(fieldName)) { user.setOrigin(jp.readValueAs(String.class)); } else if ("externalId".equalsIgnoreCase(fieldName)) { user.setExternalId(jp.readValueAs(String.class)); diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrap.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrap.java index 449a19d1225..31a50c88e20 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrap.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrap.java @@ -21,7 +21,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; @@ -110,7 +110,7 @@ public void afterPropertiesSet() throws Exception { } - String origin = Origin.LDAP; + String origin = OriginKeys.LDAP; if (null != groups && groups.size() == 1) { String groupId = groups.get(0).getId(); if (StringUtils.hasText(fields[1])) { diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java index f0d640688dd..769a271e428 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java @@ -14,11 +14,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent; import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupMembershipManager; @@ -99,7 +99,7 @@ public void afterPropertiesSet() throws Exception { protected ScimUser getScimUser(UaaUser user) { List users = scimUserProvisioning.query("userName eq \"" + user.getUsername() + "\"" + " and origin eq \"" + - (user.getOrigin() == null ? Origin.UAA : user.getOrigin()) + "\""); + (user.getOrigin() == null ? OriginKeys.UAA : user.getOrigin()) + "\""); if (users.isEmpty() && StringUtils.hasText(user.getId())) { try { @@ -149,7 +149,7 @@ private void updateUser(ScimUser existingUser, UaaUser updatedUser, boolean upda final ScimUser newScimUser = convertToScimUser(updatedUser); newScimUser.setVersion(existingUser.getVersion()); scimUserProvisioning.update(id, newScimUser); - if (Origin.UAA.equals(newScimUser.getOrigin())) { //password is not relevant for non UAA users + if (OriginKeys.UAA.equals(newScimUser.getOrigin())) { //password is not relevant for non UAA users scimUserProvisioning.changePassword(id, null, updatedUser.getPassword()); } if (updateGroups) { @@ -183,7 +183,7 @@ public void onApplicationEvent(AuthEvent event) { ExternalGroupAuthorizationEvent exEvent = (ExternalGroupAuthorizationEvent)event; //delete previous membership relation ships String origin = exEvent.getUser().getOrigin(); - if (!Origin.UAA.equals(origin)) {//only delete non UAA relationships + if (!OriginKeys.UAA.equals(origin)) {//only delete non UAA relationships membershipManager.delete("member_id eq \""+event.getUser().getId()+"\" and origin eq \""+origin+"\""); } for (GrantedAuthority authority : exEvent.getExternalAuthorities()) { @@ -205,7 +205,7 @@ public void onApplicationEvent(AuthEvent event) { } private void addToGroup(String scimUserId, String gName) { - addToGroup(scimUserId,gName,Origin.UAA, true); + addToGroup(scimUserId,gName, OriginKeys.UAA, true); } private void addToGroup(String scimUserId, String gName, String origin, boolean addGroup) { diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java index eddb1cbef9c..1fc4feb8eab 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java @@ -3,9 +3,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.audit.event.UserModifiedEvent; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -53,7 +53,7 @@ public ResponseEntity generateEmailVerificationCode(@RequestBody EmailCh ScimUser user = scimUserProvisioning.retrieve(userId); if (user.getUserName().equals(user.getPrimaryEmail())) { - List results = scimUserProvisioning.query("userName eq \"" + email + "\" and origin eq \"" + Origin.UAA + "\""); + List results = scimUserProvisioning.query("userName eq \"" + email + "\" and origin eq \"" + OriginKeys.UAA + "\""); if (!results.isEmpty()) { return new ResponseEntity<>(CONFLICT); } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java index 69ce09413a4..e9b35f59632 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java @@ -12,14 +12,13 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.endpoints; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.ConvertingExceptionView; import org.cloudfoundry.identity.uaa.error.ExceptionReport; import org.cloudfoundry.identity.uaa.error.InvalidCodeException; -import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.login.ConflictException; import org.cloudfoundry.identity.uaa.login.ForgotPasswordInfo; import org.cloudfoundry.identity.uaa.login.NotFoundException; @@ -134,7 +133,7 @@ private ExpiringCode getCode(String id, String username, String clientId) { codeData.put("user_id", id); codeData.put("username", username); codeData.put(OAuth2Utils.CLIENT_ID, clientId); - codeData.put(Origin.ORIGIN, Origin.UAA); + codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); codeData.put("action", ExpiringCodeType.AUTOLOGIN.name()); return codeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000)); } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java index 38c66531c63..f01c23d5ed0 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java @@ -14,7 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.ConvertingExceptionView; import org.cloudfoundry.identity.uaa.error.ExceptionReport; import org.cloudfoundry.identity.uaa.rest.SearchResults; @@ -222,7 +222,7 @@ public ScimGroupExternalMember mapExternalGroup(@RequestBody ScimGroupExternalMe String displayName = sgm.getDisplayName(); String groupId = sgm.getGroupId()==null?getGroupId(displayName):sgm.getGroupId(); String externalGroup = sgm.getExternalGroup().trim(); - String origin = StringUtils.hasText(sgm.getOrigin()) ? sgm.getOrigin() : Origin.LDAP; + String origin = StringUtils.hasText(sgm.getOrigin()) ? sgm.getOrigin() : OriginKeys.LDAP; return externalMembershipManager.mapExternalGroup(groupId, externalGroup, origin); } catch (IllegalArgumentException e) { throw new ScimException(e.getMessage(), HttpStatus.BAD_REQUEST); @@ -249,7 +249,7 @@ public ScimGroupExternalMember unmapExternalGroup(@PathVariable String groupId, @PathVariable String origin) { try { if (!StringUtils.hasText(origin)) { - origin = Origin.LDAP; + origin = OriginKeys.LDAP; } return externalMembershipManager.unmapExternalGroup(groupId, externalGroup.trim(), origin); } catch (IllegalArgumentException e) { @@ -266,7 +266,7 @@ public ScimGroupExternalMember unmapExternalGroup(@PathVariable String groupId, @ResponseStatus(HttpStatus.OK) @Deprecated public ScimGroupExternalMember deprecatedUnmapExternalGroup(@PathVariable String groupId, @PathVariable String externalGroup) { - return unmapExternalGroup(groupId, externalGroup, Origin.LDAP); + return unmapExternalGroup(groupId, externalGroup, OriginKeys.LDAP); } @RequestMapping(value = { "/Groups/External/displayName/{displayName}/externalGroup/{externalGroup}" }, method = RequestMethod.DELETE) @@ -274,7 +274,7 @@ public ScimGroupExternalMember deprecatedUnmapExternalGroup(@PathVariable String @ResponseStatus(HttpStatus.OK) @Deprecated public ScimGroupExternalMember unmapExternalGroupUsingName(@PathVariable String displayName, @PathVariable String externalGroup) { - return unmapExternalGroupUsingName(displayName, externalGroup, Origin.LDAP); + return unmapExternalGroupUsingName(displayName, externalGroup, OriginKeys.LDAP); } @RequestMapping(value = { "/Groups/External/displayName/{displayName}/externalGroup/{externalGroup}/origin/{origin}" }, method = RequestMethod.DELETE) @@ -285,7 +285,7 @@ public ScimGroupExternalMember unmapExternalGroupUsingName(@PathVariable String @PathVariable String origin) { try { if (!StringUtils.hasText(origin)) { - origin = Origin.LDAP; + origin = OriginKeys.LDAP; } return externalMembershipManager.unmapExternalGroup(getGroupId(displayName), externalGroup.trim(),origin); diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java index 5a9ed8ad8cb..39b0bdf7489 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java @@ -17,7 +17,7 @@ import com.unboundid.scim.sdk.SCIMFilter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.rest.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimCore; import org.cloudfoundry.identity.uaa.scim.exception.ScimException; @@ -158,7 +158,7 @@ private boolean checkFilter(SCIMFilter filter) { if ("id".equalsIgnoreCase(name) || "userName".equalsIgnoreCase(name)) { return true; - } else if (Origin.ORIGIN.equalsIgnoreCase(name)) { + } else if (OriginKeys.ORIGIN.equalsIgnoreCase(name)) { return false; } else { throw new ScimException("Invalid filter attribute.", HttpStatus.BAD_REQUEST); diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java index c53260fef89..1f2aa31a601 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java @@ -14,7 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.rest.ResourceMonitor; import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryable; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; @@ -142,7 +142,7 @@ public ScimUser create(final ScimUser user) { final String id = UUID.randomUUID().toString(); final String identityZoneId = IdentityZoneHolder.get().getId(); - final String origin = StringUtils.hasText(user.getOrigin()) ? user.getOrigin() : Origin.UAA; + final String origin = StringUtils.hasText(user.getOrigin()) ? user.getOrigin() : OriginKeys.UAA; try { jdbcTemplate.update(CREATE_USER_SQL, new PreparedStatementSetter() { @@ -177,7 +177,7 @@ public void setValues(PreparedStatement ps) throws SQLException { }); } catch (DuplicateKeyException e) { - ScimUser existingUser = query("userName eq \"" + user.getUserName() + "\" and origin eq \"" + (StringUtils.hasText(user.getOrigin())? user.getOrigin() : Origin.UAA) + "\"").get(0); + ScimUser existingUser = query("userName eq \"" + user.getUserName() + "\" and origin eq \"" + (StringUtils.hasText(user.getOrigin())? user.getOrigin() : OriginKeys.UAA) + "\"").get(0); Map userDetails = new HashMap<>(); userDetails.put("active", existingUser.isActive()); userDetails.put("verified", existingUser.isVerified()); @@ -221,7 +221,7 @@ private String extractPhoneNumber(final ScimUser user) { public ScimUser update(final String id, final ScimUser user) throws InvalidScimResourceException { validate(user); logger.debug("Updating user " + user.getUserName()); - final String origin = StringUtils.hasText(user.getOrigin()) ? user.getOrigin() : Origin.UAA; + final String origin = StringUtils.hasText(user.getOrigin()) ? user.getOrigin() : OriginKeys.UAA; int updated = jdbcTemplate.update(UPDATE_USER_SQL, new PreparedStatementSetter() { @Override diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidator.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidator.java index 7dbf039213d..748411f79ec 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidator.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidator.java @@ -1,7 +1,7 @@ package org.cloudfoundry.identity.uaa.scim.validate; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.zone.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; @@ -50,7 +50,7 @@ public void validate(String password) throws InvalidPasswordException { throw new IllegalArgumentException("Password cannot be null"); } - IdentityProvider idp = provisioning.retrieveByOrigin(Origin.UAA, IdentityZoneHolder.get().getId()); + IdentityProvider idp = provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); if (idp==null) { //should never happen return; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrapTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrapTests.java index b8424a7e45d..48fa931bcfc 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrapTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrapTests.java @@ -18,7 +18,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; @@ -26,7 +26,6 @@ import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.Before; import org.junit.Test; @@ -61,12 +60,12 @@ public void canAddExternalGroups() throws Exception { bootstrap.afterPropertiesSet(); - assertEquals(2, eDB.getExternalGroupMapsByExternalGroup("cn=Engineering,ou=groups,dc=example,dc=com", Origin.LDAP).size()); - assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=HR,ou=groups,dc=example,dc=com", Origin.LDAP).size()); - assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=mgmt,ou=groups,dc=example,dc=com", Origin.LDAP).size()); + assertEquals(2, eDB.getExternalGroupMapsByExternalGroup("cn=Engineering,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); + assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=HR,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); + assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=mgmt,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); - assertEquals(3, eDB.getExternalGroupMapsByGroupName("acme", Origin.LDAP).size()); - assertEquals(1, eDB.getExternalGroupMapsByGroupName("acme.dev", Origin.LDAP).size()); + assertEquals(3, eDB.getExternalGroupMapsByGroupName("acme", OriginKeys.LDAP).size()); + assertEquals(1, eDB.getExternalGroupMapsByGroupName("acme.dev", OriginKeys.LDAP).size()); } @Test @@ -78,12 +77,12 @@ public void canAddExternalGroupsWithOrigin() throws Exception { bootstrap.afterPropertiesSet(); - assertEquals(2, eDB.getExternalGroupMapsByExternalGroup("cn=Engineering,ou=groups,dc=example,dc=com", Origin.UAA).size()); - assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=HR,ou=groups,dc=example,dc=com", Origin.UAA).size()); - assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=mgmt,ou=groups,dc=example,dc=com", Origin.UAA).size()); + assertEquals(2, eDB.getExternalGroupMapsByExternalGroup("cn=Engineering,ou=groups,dc=example,dc=com", OriginKeys.UAA).size()); + assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=HR,ou=groups,dc=example,dc=com", OriginKeys.UAA).size()); + assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=mgmt,ou=groups,dc=example,dc=com", OriginKeys.UAA).size()); - assertEquals(3, eDB.getExternalGroupMapsByGroupName("acme", Origin.UAA).size()); - assertEquals(1, eDB.getExternalGroupMapsByGroupName("acme.dev", Origin.UAA).size()); + assertEquals(3, eDB.getExternalGroupMapsByGroupName("acme", OriginKeys.UAA).size()); + assertEquals(1, eDB.getExternalGroupMapsByGroupName("acme.dev", OriginKeys.UAA).size()); } @@ -94,12 +93,12 @@ public void canAddExternalGroupsWithSpaces() throws Exception { externalGroupSet.add("acme.dev|cn=Engineering,ou=groups,dc=example,dc=com "); bootstrap.setExternalGroupMap(externalGroupSet); bootstrap.afterPropertiesSet(); - assertEquals(2, eDB.getExternalGroupMapsByExternalGroup("cn=Engineering,ou=groups,dc=example,dc=com", Origin.LDAP).size()); - assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=HR,ou=groups,dc=example,dc=com", Origin.LDAP).size()); - assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=mgmt,ou=groups,dc=example,dc=com", Origin.LDAP).size()); + assertEquals(2, eDB.getExternalGroupMapsByExternalGroup("cn=Engineering,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); + assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=HR,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); + assertEquals(1, eDB.getExternalGroupMapsByExternalGroup("cn=mgmt,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); - assertEquals(3, eDB.getExternalGroupMapsByGroupName("acme", Origin.LDAP).size()); - assertEquals(1, eDB.getExternalGroupMapsByGroupName("acme.dev", Origin.LDAP).size()); + assertEquals(3, eDB.getExternalGroupMapsByGroupName("acme", OriginKeys.LDAP).size()); + assertEquals(1, eDB.getExternalGroupMapsByGroupName("acme.dev", OriginKeys.LDAP).size()); } @Test @@ -109,12 +108,12 @@ public void cannotAddExternalGroupsThatDoNotExist() throws Exception { externalGroupSet.add("acme1.dev|cn=Engineering,ou=groups,dc=example,dc=com"); bootstrap.setExternalGroupMap(externalGroupSet); bootstrap.afterPropertiesSet(); - assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=Engineering,ou=groups,dc=example,dc=com", Origin.LDAP).size()); - assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=HR,ou=groups,dc=example,dc=com", Origin.LDAP).size()); - assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=mgmt,ou=groups,dc=example,dc=com", Origin.LDAP).size()); + assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=Engineering,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); + assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=HR,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); + assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=mgmt,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); - assertNull(eDB.getExternalGroupMapsByGroupName("acme1", Origin.LDAP)); - assertNull(eDB.getExternalGroupMapsByGroupName("acme1.dev", Origin.LDAP)); + assertNull(eDB.getExternalGroupMapsByGroupName("acme1", OriginKeys.LDAP)); + assertNull(eDB.getExternalGroupMapsByGroupName("acme1.dev", OriginKeys.LDAP)); } @Test @@ -124,11 +123,11 @@ public void cannotAddExternalGroupsThatMapToNothing() throws Exception { externalGroupSet.add("acme.dev"); bootstrap.setExternalGroupMap(externalGroupSet); bootstrap.afterPropertiesSet(); - assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=Engineering,ou=groups,dc=example,dc=com", Origin.LDAP).size()); - assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=HR,ou=groups,dc=example,dc=com", Origin.LDAP).size()); - assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=mgmt,ou=groups,dc=example,dc=com", Origin.LDAP).size()); + assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=Engineering,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); + assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=HR,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); + assertEquals(0, eDB.getExternalGroupMapsByExternalGroup("cn=mgmt,ou=groups,dc=example,dc=com", OriginKeys.LDAP).size()); - assertEquals(0, eDB.getExternalGroupMapsByGroupName("acme", Origin.LDAP).size()); - assertEquals(0, eDB.getExternalGroupMapsByGroupName("acme.dev", Origin.LDAP).size()); + assertEquals(0, eDB.getExternalGroupMapsByGroupName("acme", OriginKeys.LDAP).size()); + assertEquals(0, eDB.getExternalGroupMapsByGroupName("acme.dev", OriginKeys.LDAP).size()); } } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java index 8bfeb1333b5..3003fb02626 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java @@ -12,8 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.bootstrap; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.rest.jdbc.DefaultLimitSqlAdapter; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; @@ -226,14 +226,14 @@ public void canRemoveAuthorities() throws Exception { @Test public void canUpdateUsers() throws Exception { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); - joe = joe.modifyOrigin(Origin.UAA); + joe = joe.modifyOrigin(OriginKeys.UAA); ScimUserBootstrap bootstrap = new ScimUserBootstrap(db, gdb, mdb, Arrays.asList(joe)); bootstrap.afterPropertiesSet(); String passwordHash = jdbcTemplate.queryForObject("select password from users where username='joe'",new Object[0], String.class); joe = new UaaUser("joe", "new", "joe@test.org", "Joe", "Bloggs"); - joe = joe.modifyOrigin(Origin.UAA); + joe = joe.modifyOrigin(OriginKeys.UAA); bootstrap = new ScimUserBootstrap(db, gdb, mdb, Arrays.asList(joe)); bootstrap.setOverride(true); bootstrap.afterPropertiesSet(); @@ -314,7 +314,7 @@ protected void validateAuthoritiesCreated(String[] externalAuthorities, String[] if (external.contains(g.getDisplayName())) { assertEquals("Expecting relationship for Group[" + g.getDisplayName() + "] be of different origin.", origin, m.getOrigin()); } else { - assertEquals("Expecting relationship for Group[" + g.getDisplayName() + "] be of different origin.", Origin.UAA, m.getOrigin()); + assertEquals("Expecting relationship for Group[" + g.getDisplayName() + "] be of different origin.", OriginKeys.UAA, m.getOrigin()); } } } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java index d42515c7ea8..a5ab68e3a16 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java @@ -2,9 +2,9 @@ import org.cloudfoundry.identity.uaa.TestClassNullifier; import org.cloudfoundry.identity.uaa.audit.event.UserModifiedEvent; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; @@ -88,7 +88,7 @@ public void testGenerateEmailChangeCodeWithExistingUsernameChange() throws Excep Mockito.when(scimUserProvisioning.retrieve("user-id-001")).thenReturn(userChangingEmail); ScimUser existingUser = new ScimUser("id001", "new@example.com", null, null); - Mockito.when(scimUserProvisioning.query("userName eq \"new@example.com\" and origin eq \"" + Origin.UAA + "\"")) + Mockito.when(scimUserProvisioning.query("userName eq \"new@example.com\" and origin eq \"" + OriginKeys.UAA + "\"")) .thenReturn(Arrays.asList(existingUser)); MockHttpServletRequestBuilder post = post("/email_verifications") @@ -164,4 +164,4 @@ public void testChangeEmailWhenUsernameNotTheSame() throws Exception { Assert.assertEquals("new@example.com", user.getValue().getPrimaryEmail()); Assert.assertEquals("username", user.getValue().getUserName()); } -} \ No newline at end of file +} diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java index c20521ccb16..b1a75a8ce1e 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java @@ -13,9 +13,9 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; import org.cloudfoundry.identity.uaa.TestClassNullifier; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.ExceptionReportHttpMessageConverter; import org.cloudfoundry.identity.uaa.login.ResetPasswordService; import org.cloudfoundry.identity.uaa.login.UaaResetPasswordService; @@ -95,7 +95,7 @@ public void password_reset_with_client_id_and_redirect_uri() throws Exception { ScimUser user = new ScimUser("id001", email, null, null); user.setPasswordLastModified(yesterday); - when(scimUserProvisioning.query("userName eq \"" + email + "\" and origin eq \"" + Origin.UAA + "\"")) + when(scimUserProvisioning.query("userName eq \"" + email + "\" and origin eq \"" + OriginKeys.UAA + "\"")) .thenReturn(Arrays.asList(user)); PasswordChange change = new PasswordChange("id001", email, yesterday, clientId, redirectUri); @@ -121,7 +121,7 @@ public void password_reset_without_client_id_and_without_redirect_uri() throws E ScimUser user = new ScimUser("id001", email, null, null); user.setPasswordLastModified(yesterday); - when(scimUserProvisioning.query("userName eq \"" + email + "\" and origin eq \"" + Origin.UAA + "\"")) + when(scimUserProvisioning.query("userName eq \"" + email + "\" and origin eq \"" + OriginKeys.UAA + "\"")) .thenReturn(Arrays.asList(user)); PasswordChange change = new PasswordChange("id001", email, yesterday, null, null); @@ -145,7 +145,7 @@ public void testCreatingAPasswordResetWhenTheUsernameExists() throws Exception { user.setMeta(new ScimMeta(yesterday, yesterday, 0)); user.addEmail("user@example.com"); user.setPasswordLastModified(yesterday); - when(scimUserProvisioning.query("userName eq \"user@example.com\" and origin eq \"" + Origin.UAA + "\"")) + when(scimUserProvisioning.query("userName eq \"user@example.com\" and origin eq \"" + OriginKeys.UAA + "\"")) .thenReturn(Arrays.asList(user)); MockHttpServletRequestBuilder post = post("/password_resets") @@ -161,7 +161,7 @@ public void testCreatingAPasswordResetWhenTheUsernameExists() throws Exception { @Test public void testCreatingAPasswordResetWhenTheUserDoesNotExist() throws Exception { - when(scimUserProvisioning.query("userName eq \"user@example.com\" and origin eq \"" + Origin.UAA + "\"")) + when(scimUserProvisioning.query("userName eq \"user@example.com\" and origin eq \"" + OriginKeys.UAA + "\"")) .thenReturn(Arrays.asList()); MockHttpServletRequestBuilder post = post("/password_resets") @@ -175,13 +175,13 @@ public void testCreatingAPasswordResetWhenTheUserDoesNotExist() throws Exception @Test public void testCreatingAPasswordResetWhenTheUserHasNonUaaOrigin() throws Exception { - when(scimUserProvisioning.query("userName eq \"user@example.com\" and origin eq \"" + Origin.UAA + "\"")) + when(scimUserProvisioning.query("userName eq \"user@example.com\" and origin eq \"" + OriginKeys.UAA + "\"")) .thenReturn(Arrays.asList()); ScimUser user = new ScimUser("id001", "user@example.com", null, null); user.setMeta(new ScimMeta(new Date(System.currentTimeMillis()-(1000*60*60*24)), new Date(System.currentTimeMillis()-(1000*60*60*24)), 0)); user.addEmail("user@example.com"); - user.setOrigin(Origin.LDAP); + user.setOrigin(OriginKeys.LDAP); when(scimUserProvisioning.query("userName eq \"user@example.com\"")) .thenReturn(Arrays.asList(user)); @@ -201,7 +201,7 @@ public void testCreatingAPasswordResetWithAUsernameContainingSpecialCharacters() user.setMeta(new ScimMeta(yesterday, yesterday, 0)); user.setPasswordLastModified(yesterday); user.addEmail("user\"'@example.com"); - when(scimUserProvisioning.query("userName eq \"user\\\"'@example.com\" and origin eq \"" + Origin.UAA + "\"")) + when(scimUserProvisioning.query("userName eq \"user\\\"'@example.com\" and origin eq \"" + OriginKeys.UAA + "\"")) .thenReturn(Arrays.asList(user)); PasswordChange change = new PasswordChange("id001", "user\"'@example.com", yesterday, null, null); @@ -218,9 +218,9 @@ public void testCreatingAPasswordResetWithAUsernameContainingSpecialCharacters() .andExpect(content().string(containsString("\"code\":\"secret_code\""))) .andExpect(content().string(containsString("\"user_id\":\"id001\""))); - when(scimUserProvisioning.query("userName eq \"user\\\"'@example.com\" and origin eq \"" + Origin.UAA + "\"")) + when(scimUserProvisioning.query("userName eq \"user\\\"'@example.com\" and origin eq \"" + OriginKeys.UAA + "\"")) .thenReturn(Arrays.asList()); - user.setOrigin(Origin.LDAP); + user.setOrigin(OriginKeys.LDAP); when(scimUserProvisioning.query("userName eq \"user\\\"'@example.com\"")) .thenReturn(Arrays.asList(user)); diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java index 704ac7f7adb..90657df8e27 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java @@ -14,7 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.ExceptionReportHttpMessageConverter; import org.cloudfoundry.identity.uaa.rest.SearchResults; import org.cloudfoundry.identity.uaa.rest.jdbc.DefaultLimitSqlAdapter; @@ -233,7 +233,7 @@ public void mapExternalGroup_truncatesLeadingAndTrailingSpaces_InExternalGroupNa @Test public void unmapExternalGroup_truncatesLeadingAndTrailingSpaces_InExternalGroupName() throws Exception { ScimGroupExternalMember member = getScimGroupExternalMember(); - member = endpoints.unmapExternalGroup(member.getGroupId(), " \nexternal_group_id\n", Origin.LDAP); + member = endpoints.unmapExternalGroup(member.getGroupId(), " \nexternal_group_id\n", OriginKeys.LDAP); assertEquals("external_group_id", member.getExternalGroup()); } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManagerTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManagerTests.java index 1b6dad4f3e4..4882c2542ad 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManagerTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManagerTests.java @@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; @@ -43,7 +43,7 @@ public class JdbcScimGroupExternalMembershipManagerTests extends JdbcTestBase { private static final String addGroupSqlFormat = "insert into groups (id, displayName, identity_zone_id) values ('%s','%s','%s')"; - private String origin = Origin.LDAP; + private String origin = OriginKeys.LDAP; private IdentityZone otherZone; @@ -118,8 +118,8 @@ public void using_filter_query_filters_by_zone() { assertEquals(3, edao.query("").size()); assertEquals(3, edao.query("externalGroup sw \"cn\"").size()); assertEquals(3, edao.query("group_id sw \"g\"").size()); - assertEquals(0, edao.query("origin eq \""+Origin.UAA+"\"").size()); - assertEquals(3, edao.query("origin eq \""+Origin.LDAP+"\"").size()); + assertEquals(0, edao.query("origin eq \""+ OriginKeys.UAA+"\"").size()); + assertEquals(3, edao.query("origin eq \""+ OriginKeys.LDAP+"\"").size()); } @Test @@ -132,13 +132,13 @@ public void using_filter_delete_filters_by_zone() { map3GroupsInEachZone(); assertEquals(3, edao.query("").size()); - edao.delete("origin eq \""+Origin.LDAP+"\""); + edao.delete("origin eq \""+ OriginKeys.LDAP+"\""); assertEquals(0, edao.query("").size()); assertEquals(3, jdbcTemplate.queryForInt("select count(*) from external_group_mapping")); map3GroupsInEachZone(); assertEquals(3, edao.query("").size()); - edao.delete("origin eq \""+Origin.UAA+"\""); + edao.delete("origin eq \""+ OriginKeys.UAA+"\""); assertEquals(3, edao.query("").size()); assertEquals(6, jdbcTemplate.queryForInt("select count(*) from external_group_mapping")); } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java index 9873fd1249d..b63d124b8c3 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java @@ -12,13 +12,12 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.jdbc; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.exception.InvalidScimResourceException; import org.cloudfoundry.identity.uaa.scim.exception.MemberNotFoundException; -import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceConstraintFailedException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; import org.cloudfoundry.identity.uaa.scim.test.TestUtils; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; @@ -84,7 +83,7 @@ public void initJdbcScimGroupMembershipManagerTests() { } private void addMember(String gId, String mId, String mType, String authorities) { - addMember(gId,mId,mType,authorities,Origin.UAA); + addMember(gId,mId,mType,authorities, OriginKeys.UAA); } private void addMember(String gId, String mId, String mType, String authorities, String origin) { jdbcTemplate.execute(String.format(addMemberSqlFormat, gId, mId, mType, authorities, origin)); @@ -143,7 +142,7 @@ public void canQuery_Filter_Has_ZoneIn_Effect() throws Exception { String id = new RandomValueStringGenerator().generate(); IdentityZone zone = MultitenancyFixture.identityZone(id,id); IdentityZoneHolder.set(zone); - assertEquals(0,dao.query("origin eq \"" + Origin.UAA + "\"").size()); + assertEquals(0,dao.query("origin eq \"" + OriginKeys.UAA + "\"").size()); } @@ -151,7 +150,7 @@ public void canQuery_Filter_Has_ZoneIn_Effect() throws Exception { public void canDeleteWithFilter1() throws Exception { addMembers(); validateCount(4); - dao.delete("origin eq \"" + Origin.UAA + "\""); + dao.delete("origin eq \"" + OriginKeys.UAA + "\""); validateCount(0); } @@ -159,7 +158,7 @@ public void canDeleteWithFilter1() throws Exception { public void canDeleteWithFilter2() throws Exception { addMembers(); validateCount(4); - dao.delete("origin eq \""+ Origin.ORIGIN +"\""); + dao.delete("origin eq \""+ OriginKeys.ORIGIN +"\""); validateCount(4); } @@ -167,7 +166,7 @@ public void canDeleteWithFilter2() throws Exception { public void canDeleteWithFilter3() throws Exception { addMembers(); validateCount(4); - dao.delete("member_id eq \"m3\" and origin eq \""+ Origin.UAA +"\""); + dao.delete("member_id eq \"m3\" and origin eq \""+ OriginKeys.UAA +"\""); validateCount(2); } @@ -175,7 +174,7 @@ public void canDeleteWithFilter3() throws Exception { public void canDeleteWithFilter4() throws Exception { addMembers(); validateCount(4); - dao.delete("member_id sw \"m\" and origin eq \""+ Origin.UAA +"\""); + dao.delete("member_id sw \"m\" and origin eq \""+ OriginKeys.UAA +"\""); validateCount(1); } @@ -183,7 +182,7 @@ public void canDeleteWithFilter4() throws Exception { public void canDeleteWithFilter5() throws Exception { addMembers(); validateCount(4); - dao.delete("member_id sw \"m\" and origin eq \""+ Origin.LDAP +"\""); + dao.delete("member_id sw \"m\" and origin eq \""+ OriginKeys.LDAP +"\""); validateCount(4); } @@ -194,7 +193,7 @@ public void cannot_Delete_With_Filter_Outside_Zone() throws Exception { validateCount(4); IdentityZone zone = MultitenancyFixture.identityZone(id,id); IdentityZoneHolder.set(zone); - dao.delete("member_id eq \"m3\" and origin eq \"" + Origin.UAA + "\""); + dao.delete("member_id eq \"m3\" and origin eq \"" + OriginKeys.UAA + "\""); IdentityZoneHolder.clear(); validateCount(4); } @@ -250,7 +249,7 @@ public void addMember_In_Different_Zone_Causes_Issues() throws Exception { IdentityZone otherZone = MultitenancyFixture.identityZone(subdomain, subdomain); IdentityZoneHolder.set(otherZone); ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, null); - m1.setOrigin(Origin.UAA); + m1.setOrigin(OriginKeys.UAA); dao.addMember("g2", m1); } @@ -261,7 +260,7 @@ public void canAddMember_Validate_Origin_and_ZoneId() throws Exception { IdentityZoneHolder.set(otherZone); validateCount(0); ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, null); - m1.setOrigin(Origin.UAA); + m1.setOrigin(OriginKeys.UAA); dao.addMember("g2", m1); } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java index dbc878c0e50..1e6240c5e68 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java @@ -12,14 +12,13 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.jdbc; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUser.Group; import org.cloudfoundry.identity.uaa.scim.ScimUser.PhoneNumber; import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimUserBootstrapTests; -import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.scim.exception.InvalidScimResourceException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceAlreadyExistsException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; @@ -103,7 +102,7 @@ public void initJdbcScimUserProvisioningTests() throws Exception { existingUserCount = jdbcTemplate.queryForInt("select count(id) from users"); - defaultIdentityProviderId = jdbcTemplate.queryForObject("select id from identity_provider where origin_key = ? and identity_zone_id = ?", String.class, Origin.UAA, "uaa"); + defaultIdentityProviderId = jdbcTemplate.queryForObject("select id from identity_provider where origin_key = ? and identity_zone_id = ?", String.class, OriginKeys.UAA, "uaa"); addUser(JOE_ID, "joe", pe.encode("joespassword"), "joe@joe.com", "Joe", "User", "+1-222-1234567", defaultIdentityProviderId, "uaa"); addUser(MABEL_ID, "mabel", pe.encode("mabelspassword"), "mabel@mabel.com", "Mabel", "User", "", defaultIdentityProviderId, "uaa"); @@ -160,7 +159,7 @@ public void canCreateUserInDefaultIdentityZone() { assertEquals(user.getUserName(), map.get("userName")); assertEquals(user.getUserType(), map.get(UaaAuthority.UAA_USER.getUserType())); assertNull(created.getGroups()); - assertEquals(Origin.UAA, created.getOrigin()); + assertEquals(OriginKeys.UAA, created.getOrigin()); assertEquals("uaa", map.get("identity_zone_id")); assertNull(user.getPasswordLastModified()); assertNotNull(created.getPasswordLastModified()); @@ -187,7 +186,7 @@ public void canModifyPassword() throws Exception { public void canCreateUserInOtherIdentityZone() { String otherZoneId = "my-zone-id"; createOtherIdentityZone(otherZoneId); - String idpId = createOtherIdentityProvider(Origin.UAA, otherZoneId); + String idpId = createOtherIdentityProvider(OriginKeys.UAA, otherZoneId); ScimUser user = new ScimUser(null, "jo@foo.com", "Jo", "User"); user.addEmail("jo@blah.com"); ScimUser created = db.createUser(user, "j7hyqpassX"); @@ -198,7 +197,7 @@ public void canCreateUserInOtherIdentityZone() { assertEquals(user.getUserName(), map.get("userName")); assertEquals(user.getUserType(), map.get(UaaAuthority.UAA_USER.getUserType())); assertNull(created.getGroups()); - assertEquals(Origin.UAA, created.getOrigin()); + assertEquals(OriginKeys.UAA, created.getOrigin()); assertEquals("my-zone-id", map.get("identity_zone_id")); } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidatorTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidatorTests.java index 4446c822383..aea061ad8c8 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidatorTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidatorTests.java @@ -15,8 +15,8 @@ package org.cloudfoundry.identity.uaa.scim.validate; import org.apache.commons.lang.RandomStringUtils; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.zone.IdentityProvider; @@ -55,7 +55,7 @@ public void setUp() { UaaIdentityProviderDefinition idpDefinition = new UaaIdentityProviderDefinition(new PasswordPolicy(10, 23, 1, 1, 1, 1, 6), null); internalIDP.setConfig(idpDefinition); - Mockito.when(provisioning.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId())) + Mockito.when(provisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId())) .thenReturn(internalIDP); } @@ -117,7 +117,7 @@ public void testValidateSpaceNotSpecialCharacter() throws Exception { private void validatePassword(String password, String ... expectedErrors) { ScimUser user = new ScimUser(); - user.setOrigin(Origin.UAA); + user.setOrigin(OriginKeys.UAA); try { validator.validate(password); if (expectedErrors != null && expectedErrors.length > 0) { diff --git a/shared_versions.gradle b/shared_versions.gradle index 12845f5551b..c917005692d 100644 --- a/shared_versions.gradle +++ b/shared_versions.gradle @@ -11,4 +11,5 @@ ext { apacheLdapApiVersion = '1.0.0-M22' jacksonVersion = '2.5.3' flywayVersion = '3.2.1' + validationAPIVersion = '1.0.0.GA' } diff --git a/uaa/src/main/resources/messages.properties b/uaa/src/main/resources/messages.properties index bae86998b55..fde4a71c4c2 100644 --- a/uaa/src/main/resources/messages.properties +++ b/uaa/src/main/resources/messages.properties @@ -54,6 +54,9 @@ login.account_locked=Your account has been locked because of too many failed att login.invalid_login_request=Invalid login attempt, request does not meet our security standards, please try again. account_activation.invite.email_mismatch=The authenticated email does not match the invited email. Please log in using a different account. +NotNull.identityZone.subdomain=The subdomain must be provided. +NotNull.identityZone.name=The identity zone must be given a name. + # Passay Properties HISTORY_VIOLATION=Password matches one of %1$s previous passwords. ILLEGAL_WORD=Password contains the dictionary word '%1$s'. diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java index 8fe9bded99c..5ac6b206119 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java @@ -2,7 +2,7 @@ import org.cloudfoundry.identity.uaa.authentication.AccountNotVerifiedException; import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; @@ -68,14 +68,14 @@ public void beforeAndAfter() throws Exception { when(success.isAuthenticated()).thenReturn(true); when(uaaActive.isActive()).thenReturn(true); - when(uaaActive.getOriginKey()).thenReturn(Origin.UAA); + when(uaaActive.getOriginKey()).thenReturn(OriginKeys.UAA); when(uaaInactive.isActive()).thenReturn(false); - when(uaaInactive.getOriginKey()).thenReturn(Origin.UAA); + when(uaaInactive.getOriginKey()).thenReturn(OriginKeys.UAA); when(ldapActive.isActive()).thenReturn(true); - when(ldapActive.getOriginKey()).thenReturn(Origin.LDAP); + when(ldapActive.getOriginKey()).thenReturn(OriginKeys.LDAP); when(ldapInactive.isActive()).thenReturn(false); - when(ldapInactive.getOriginKey()).thenReturn(Origin.LDAP); + when(ldapInactive.getOriginKey()).thenReturn(OriginKeys.LDAP); when(ldapActive.getConfig()).thenReturn(ldapIdentityProviderDefinition); when(ldapActive.getConfig()).thenReturn(ldapIdentityProviderDefinition); @@ -93,8 +93,8 @@ public void testAuthenticateInUaaZone() throws Exception { @Test public void testNonUAAZoneUaaNotActive() throws Exception { IdentityZoneHolder.set(ZONE); - when(providerProvisioning.retrieveByOrigin(Origin.UAA, ZONE.getId())).thenReturn(uaaInactive); - when(providerProvisioning.retrieveByOrigin(Origin.LDAP, ZONE.getId())).thenReturn(ldapActive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.UAA, ZONE.getId())).thenReturn(uaaInactive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, ZONE.getId())).thenReturn(ldapActive); DynamicZoneAwareAuthenticationManager manager = getDynamicZoneAwareAuthenticationManager(true); DynamicLdapAuthenticationManager mockManager = manager.getLdapAuthenticationManager(null, null); when(mockManager.authenticate(any(Authentication.class))).thenReturn(success); @@ -107,8 +107,8 @@ public void testNonUAAZoneUaaNotActive() throws Exception { @Test public void testNonUAAZoneUaaActiveAccountNotVerified() throws Exception { IdentityZoneHolder.set(ZONE); - when(providerProvisioning.retrieveByOrigin(Origin.UAA, ZONE.getId())).thenReturn(uaaActive); - when(providerProvisioning.retrieveByOrigin(Origin.LDAP, ZONE.getId())).thenReturn(ldapActive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.UAA, ZONE.getId())).thenReturn(uaaActive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, ZONE.getId())).thenReturn(ldapActive); DynamicZoneAwareAuthenticationManager manager = getDynamicZoneAwareAuthenticationManager(true); when(uaaAuthenticationMgr.authenticate(any(Authentication.class))).thenThrow(new AccountNotVerifiedException("mock")); DynamicLdapAuthenticationManager mockManager = manager.getLdapAuthenticationManager(null, null); @@ -124,8 +124,8 @@ public void testNonUAAZoneUaaActiveAccountNotVerified() throws Exception { @Test public void testNonUAAZoneUaaActiveAccountLocked() throws Exception { IdentityZoneHolder.set(ZONE); - when(providerProvisioning.retrieveByOrigin(Origin.UAA, ZONE.getId())).thenReturn(uaaActive); - when(providerProvisioning.retrieveByOrigin(Origin.LDAP, ZONE.getId())).thenReturn(ldapActive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.UAA, ZONE.getId())).thenReturn(uaaActive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, ZONE.getId())).thenReturn(ldapActive); DynamicZoneAwareAuthenticationManager manager = getDynamicZoneAwareAuthenticationManager(true); when(uaaAuthenticationMgr.authenticate(any(Authentication.class))).thenThrow(new AuthenticationPolicyRejectionException("mock")); DynamicLdapAuthenticationManager mockManager = manager.getLdapAuthenticationManager(null, null); @@ -141,8 +141,8 @@ public void testNonUAAZoneUaaActiveAccountLocked() throws Exception { @Test public void testNonUAAZoneUaaActiveUaaAuthenticationSucccess() throws Exception { IdentityZoneHolder.set(ZONE); - when(providerProvisioning.retrieveByOrigin(Origin.UAA, ZONE.getId())).thenReturn(uaaActive); - when(providerProvisioning.retrieveByOrigin(Origin.LDAP, ZONE.getId())).thenReturn(ldapActive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.UAA, ZONE.getId())).thenReturn(uaaActive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, ZONE.getId())).thenReturn(ldapActive); DynamicZoneAwareAuthenticationManager manager = getDynamicZoneAwareAuthenticationManager(true); when(uaaAuthenticationMgr.authenticate(any(Authentication.class))).thenReturn(success); DynamicLdapAuthenticationManager mockManager = manager.getLdapAuthenticationManager(null, null); @@ -153,8 +153,8 @@ public void testNonUAAZoneUaaActiveUaaAuthenticationSucccess() throws Exception @Test public void testNonUAAZoneUaaActiveUaaAuthenticationFailure() throws Exception { IdentityZoneHolder.set(ZONE); - when(providerProvisioning.retrieveByOrigin(Origin.UAA, ZONE.getId())).thenReturn(uaaActive); - when(providerProvisioning.retrieveByOrigin(Origin.LDAP, ZONE.getId())).thenReturn(ldapActive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.UAA, ZONE.getId())).thenReturn(uaaActive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, ZONE.getId())).thenReturn(ldapActive); DynamicZoneAwareAuthenticationManager manager = getDynamicZoneAwareAuthenticationManager(true); when(uaaAuthenticationMgr.authenticate(any(Authentication.class))).thenThrow(new BadCredentialsException("mock")); DynamicLdapAuthenticationManager mockManager = manager.getLdapAuthenticationManager(null, null); @@ -165,8 +165,8 @@ public void testNonUAAZoneUaaActiveUaaAuthenticationFailure() throws Exception { @Test public void testAuthenticateInNoneUaaZoneWithLdapProvider() throws Exception { IdentityZoneHolder.set(ZONE); - when(providerProvisioning.retrieveByOrigin(Origin.LDAP, ZONE.getId())).thenReturn(ldapActive); - when(providerProvisioning.retrieveByOrigin(Origin.UAA, ZONE.getId())).thenReturn(uaaInactive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, ZONE.getId())).thenReturn(ldapActive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.UAA, ZONE.getId())).thenReturn(uaaInactive); DynamicZoneAwareAuthenticationManager manager = getDynamicZoneAwareAuthenticationManager(true); DynamicLdapAuthenticationManager mockManager = manager.getLdapAuthenticationManager(null, null); when(mockManager.authenticate(any(Authentication.class))).thenReturn(success); @@ -179,8 +179,8 @@ public void testAuthenticateInNoneUaaZoneWithLdapProvider() throws Exception { @Test public void testAuthenticateInNoneUaaZoneWithInactiveProviders() throws Exception { IdentityZoneHolder.set(ZONE); - when(providerProvisioning.retrieveByOrigin(Origin.LDAP, ZONE.getId())).thenReturn(ldapInactive); - when(providerProvisioning.retrieveByOrigin(Origin.UAA, ZONE.getId())).thenReturn(uaaInactive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, ZONE.getId())).thenReturn(ldapInactive); + when(providerProvisioning.retrieveByOrigin(OriginKeys.UAA, ZONE.getId())).thenReturn(uaaInactive); DynamicZoneAwareAuthenticationManager manager = getDynamicZoneAwareAuthenticationManager(true); DynamicLdapAuthenticationManager mockManager = manager.getLdapAuthenticationManager(null, null); when(mockManager.authenticate(any(Authentication.class))).thenReturn(success); @@ -222,4 +222,4 @@ public DynamicLdapAuthenticationManager getLdapAuthenticationManager(IdentityZon } -} \ No newline at end of file +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/db/TestZonifyGroupSchema_V2_4_1.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/db/TestZonifyGroupSchema_V2_4_1.java index 44b402a164f..46abb04b518 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/db/TestZonifyGroupSchema_V2_4_1.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/db/TestZonifyGroupSchema_V2_4_1.java @@ -30,6 +30,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.validation.AbstractBindingResult; import java.util.Arrays; import java.util.HashMap; @@ -52,7 +53,17 @@ public void populateDataUsingEndpoints() { for (int i=0; i groups = new LinkedList<>(); IdentityZoneHolder.set(zone); for (int j=0; j clientCreateResponse = client.exchange( serverRunning.getUrl("/identity-zones/"+id+"/clients"), diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java index 3633e7095ab..02c67764820 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java @@ -15,8 +15,8 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.oauth.Claims; @@ -120,15 +120,15 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() throws Exception { IdentityProvider provider = new IdentityProvider(); provider.setIdentityZoneId(zoneId); - provider.setType(Origin.LDAP); + provider.setType(OriginKeys.LDAP); provider.setActive(true); provider.setConfig(ldapIdentityProviderDefinition); - provider.setOriginKey(Origin.LDAP); + provider.setOriginKey(OriginKeys.LDAP); provider.setName("simplesamlphp for uaa"); provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); assertNotNull(provider.getId()); - assertEquals(Origin.LDAP, provider.getOriginKey()); + assertEquals(OriginKeys.LDAP, provider.getOriginKey()); List idps = Arrays.asList(provider.getOriginKey()); @@ -186,7 +186,7 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() throws Exception { protected boolean doesSupportZoneDNS_and_isLdapEnabled() { String profile = System.getProperty("spring.profiles.active",""); - if (!profile.contains(Origin.LDAP)) { + if (!profile.contains(OriginKeys.LDAP)) { return false; } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java index 9a0e4b6eb1d..17b959c1959 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LoginServerSecurityIntegrationTests.java @@ -14,8 +14,8 @@ import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; @@ -155,7 +155,7 @@ public void testAuthenticateReturnsUserID() throws Exception { ResponseEntity response = serverRunning.postForMap("/authenticate", params, headers); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(JOE, response.getBody().get("username")); - assertEquals(Origin.UAA, response.getBody().get(Origin.ORIGIN)); + assertEquals(OriginKeys.UAA, response.getBody().get(OriginKeys.ORIGIN)); assertTrue(StringUtils.hasText((String)response.getBody().get("user_id"))); } @@ -167,7 +167,7 @@ public void testAuthenticateMarissaReturnsUserID() throws Exception { ResponseEntity response = serverRunning.postForMap("/authenticate", params, headers); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals("marissa", response.getBody().get("username")); - assertEquals(Origin.UAA, response.getBody().get(Origin.ORIGIN)); + assertEquals(OriginKeys.UAA, response.getBody().get(OriginKeys.ORIGIN)); assertTrue(StringUtils.hasText((String)response.getBody().get("user_id"))); } @@ -187,7 +187,7 @@ public void testAuthenticateDoesNotReturnsUserID() throws Exception { ResponseEntity response = serverRunning.postForMap("/authenticate", params, headers); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals("marissa", response.getBody().get("username")); - assertNull(response.getBody().get(Origin.ORIGIN)); + assertNull(response.getBody().get(OriginKeys.ORIGIN)); assertNull(response.getBody().get("user_id")); } @@ -196,7 +196,7 @@ public void testAuthenticateDoesNotReturnsUserID() throws Exception { public void testLoginServerCanAuthenticateUserForCf() throws Exception { ImplicitResourceDetails resource = testAccounts.getDefaultImplicitResource(); params.set("client_id", resource.getClientId()); - params.set(Origin.ORIGIN, joe.getOrigin()); + params.set(OriginKeys.ORIGIN, joe.getOrigin()); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); String redirect = resource.getPreEstablishedRedirectUri(); if (redirect != null) { @@ -214,7 +214,7 @@ public void testLoginServerCanAuthenticateUserForCf() throws Exception { public void testLoginServerCanAuthenticateUserForAuthorizationCode() throws Exception { params.set("client_id", testAccounts.getDefaultAuthorizationCodeResource().getClientId()); params.set("response_type", "code"); - params.set(Origin.ORIGIN, joe.getOrigin()); + params.set(OriginKeys.ORIGIN, joe.getOrigin()); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); @SuppressWarnings("rawtypes") ResponseEntity response = serverRunning.postForMap(serverRunning.getAuthorizationUri(), params, headers); @@ -328,7 +328,7 @@ public void testLoginServerCfPasswordToken() throws Exception { params.set("client_id", resource.getClientId()); params.set("client_secret",""); params.set("source","login"); - params.set(Origin.ORIGIN, joe.getOrigin()); + params.set(OriginKeys.ORIGIN, joe.getOrigin()); params.set(UaaAuthenticationDetails.ADD_NEW, "false"); params.set("grant_type", "password"); String redirect = resource.getPreEstablishedRedirectUri(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RemoteAuthenticationEndpointTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RemoteAuthenticationEndpointTests.java index c1c92e8db46..6b6a84c560e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RemoteAuthenticationEndpointTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RemoteAuthenticationEndpointTests.java @@ -27,7 +27,7 @@ import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.UaaOauth2ErrorHandler; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.Rule; @@ -67,11 +67,11 @@ public void remoteAuthenticationSucceedsWithCorrectCredentials() throws Exceptio @Test public void remoteAuthenticationSucceedsAndCreatesUser() throws Exception { String username = new RandomValueStringGenerator().generate(); - String origin = Origin.LOGIN_SERVER; + String origin = OriginKeys.LOGIN_SERVER; Map info = new HashMap<>(); info.put("source", "login"); info.put("add_new", "true"); - info.put(Origin.ORIGIN, origin); + info.put(OriginKeys.ORIGIN, origin); @SuppressWarnings("rawtypes") ResponseEntity response = authenticate(username, null, info); assertEquals(HttpStatus.OK, response.getStatusCode()); @@ -91,11 +91,11 @@ public void remoteAuthenticationFailsWithIncorrectCredentials() throws Exception public void validateLdapOrKeystoneOrigin() throws Exception { String profiles = System.getProperty("spring.profiles.active"); if (profiles!=null && profiles.contains("ldap")) { - validateOrigin("marissa3","ldap3",Origin.LDAP, null); + validateOrigin("marissa3","ldap3", OriginKeys.LDAP, null); } else if (profiles!=null && profiles.contains("keystone")) { - validateOrigin("marissa2", "keystone", Origin.KEYSTONE, null); + validateOrigin("marissa2", "keystone", OriginKeys.KEYSTONE, null); } else { - validateOrigin(testAccounts.getUserName(), testAccounts.getPassword(), Origin.UAA, null); + validateOrigin(testAccounts.getUserName(), testAccounts.getPassword(), OriginKeys.UAA, null); } } @@ -115,12 +115,12 @@ public void validateOrigin(String username, String password, String origin, Map< for (Map user : list) { assertThat(user, hasKey("id")); assertThat(user, hasKey("userName")); - assertThat(user, hasKey(Origin.ORIGIN)); + assertThat(user, hasKey(OriginKeys.ORIGIN)); assertThat(user, not(hasKey("name"))); assertThat(user, not(hasKey("emails"))); if (user.get("userName").equals(username)) { found = true; - assertEquals(origin, user.get(Origin.ORIGIN)); + assertEquals(origin, user.get(OriginKeys.ORIGIN)); } } assertTrue(found); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java index a58ec403f3f..2c6255eec74 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java @@ -14,8 +14,8 @@ import com.dumbster.smtp.SimpleSmtpServer; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.junit.After; @@ -112,8 +112,8 @@ public void testInviteUserWithClientRedirect() throws Exception { public void performInviteUser(String email, boolean isVerified) throws Exception { webDriver.get(baseUrl + "/logout.do"); - String code = createInvitation(email, email, "http://localhost:8080/app/", Origin.UAA); - String invitedUserId = IntegrationTestUtils.getUserIdByField(scimToken, baseUrl, Origin.UAA, "email", email); + String code = createInvitation(email, email, "http://localhost:8080/app/", OriginKeys.UAA); + String invitedUserId = IntegrationTestUtils.getUserIdByField(scimToken, baseUrl, OriginKeys.UAA, "email", email); if (isVerified) { ScimUser user = IntegrationTestUtils.getUser(scimToken, baseUrl, invitedUserId); user.setVerified(true); @@ -121,7 +121,7 @@ public void performInviteUser(String email, boolean isVerified) throws Exception } String currentUserId = null; try { - currentUserId = IntegrationTestUtils.getUserId(scimToken, baseUrl, Origin.UAA, email); + currentUserId = IntegrationTestUtils.getUserId(scimToken, baseUrl, OriginKeys.UAA, email); } catch (RuntimeException x) { } assertEquals(invitedUserId, currentUserId); @@ -137,7 +137,7 @@ public void performInviteUser(String email, boolean isVerified) throws Exception //redirect to the home page to login Assert.assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), containsString("Welcome!")); } - String acceptedUserId = IntegrationTestUtils.getUserId(scimToken, baseUrl, Origin.UAA, email); + String acceptedUserId = IntegrationTestUtils.getUserId(scimToken, baseUrl, OriginKeys.UAA, email); if (currentUserId == null) { assertEquals(invitedUserId, acceptedUserId); } else { @@ -184,7 +184,7 @@ public void testInsecurePasswordDisplaysErrorMessage() throws Exception { private String createInvitation() { String userEmail = "user" + new SecureRandom().nextInt() + "@example.com"; - return createInvitation(userEmail, userEmail, "http://localhost:8080/app/", Origin.UAA); + return createInvitation(userEmail, userEmail, "http://localhost:8080/app/", OriginKeys.UAA); } private String createInvitation(String username, String userEmail, String redirectUri, String origin) { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index 24ed755d328..74b6d9a8619 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -17,10 +17,10 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.config.LockoutPolicy; import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.test.LoginServerClassRunner; @@ -235,7 +235,7 @@ public void failureResponseFromSamlIDP_showErrorFromSaml() throws Exception { SamlIdentityProviderDefinition samlIdentityProviderDefinition = createSimplePHPSamlIDP("simplesamlphp", "testzone3"); IdentityProvider provider = new IdentityProvider(); provider.setIdentityZoneId(zoneId); - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); provider.setActive(true); provider.setConfig(samlIdentityProviderDefinition); provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); @@ -313,7 +313,7 @@ protected BaseClientDetails createClientAndSpecifyProvider(String clientId, Iden ); String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); - IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), Origin.UAA); + IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), OriginKeys.UAA); String zoneAdminToken = IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, @@ -393,7 +393,7 @@ public void perform_SamlInvitation_Automatic_Redirect_In_Zone2(String username, SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZone2IDP("simplesamlphp"); IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(zoneId); - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); provider.setActive(true); provider.setConfig(samlIdentityProviderDefinition); provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); @@ -407,7 +407,7 @@ public void perform_SamlInvitation_Automatic_Redirect_In_Zone2(String username, new LockoutPolicy(10, 10, 10) ); uaaDefinition.setEmailDomain(emptyList ? Collections.EMPTY_LIST : Arrays.asList("*.*","*.*.*")); - IdentityProvider uaaProvider = IntegrationTestUtils.getProvider(zoneAdminToken, baseUrl, zoneId, Origin.UAA); + IdentityProvider uaaProvider = IntegrationTestUtils.getProvider(zoneAdminToken, baseUrl, zoneId, OriginKeys.UAA); uaaProvider.setConfig(uaaDefinition); uaaProvider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,uaaProvider); @@ -492,7 +492,7 @@ public void testSamlLoginClientIDPAuthorizationAutomaticRedirectInZone1() throws SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZone1IDP("simplesamlphp"); IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(zoneId); - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); provider.setActive(true); provider.setConfig(samlIdentityProviderDefinition); provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); @@ -567,7 +567,7 @@ public void testSamlLogin_Map_Groups_In_Zone1() throws Exception { IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(zoneId); - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); provider.setActive(true); provider.setConfig(samlIdentityProviderDefinition); provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); @@ -673,7 +673,7 @@ public void testSamlLogin_Custom_User_Attributes_In_ID_Token() throws Exception IdentityProvider provider = new IdentityProvider(); provider.setIdentityZoneId(zoneId); - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); provider.setActive(true); provider.setConfig(samlIdentityProviderDefinition); provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); @@ -774,7 +774,7 @@ public void testSimpleSamlPhpLoginInTestZone1Works() throws Exception { SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZone1IDP("simplesamlphp"); IdentityProvider provider = new IdentityProvider<>(); provider.setIdentityZoneId(zoneId); - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); provider.setActive(true); provider.setConfig(samlIdentityProviderDefinition); provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); @@ -789,7 +789,7 @@ public void testSimpleSamlPhpLoginInTestZone1Works() throws Exception { samlIdentityProviderDefinition1.setMetaDataLocation(getValidRandomIDPMetaData()); IdentityProvider provider1 = new IdentityProvider(); provider1.setIdentityZoneId(zoneId); - provider1.setType(Origin.SAML); + provider1.setType(OriginKeys.SAML); provider1.setActive(true); provider1.setConfig(samlIdentityProviderDefinition1); provider1.setOriginKey(samlIdentityProviderDefinition1.getIdpEntityAlias()); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index 0e5a1b0b57c..444d4662e72 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -19,7 +19,7 @@ import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.rest.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimGroup; @@ -591,7 +591,7 @@ public static IdentityProvider createIdentityProvider(String originKey, boolean ); String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl, email, "firstname", "lastname", email, true); - IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), Origin.UAA); + IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), OriginKeys.UAA); String zoneAdminToken = IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, @@ -601,11 +601,11 @@ public static IdentityProvider createIdentityProvider(String originKey, boolean email, "secr3T"); - SamlIdentityProviderDefinition samlIdentityProviderDefinition = createSimplePHPSamlIDP(originKey, Origin.UAA); + SamlIdentityProviderDefinition samlIdentityProviderDefinition = createSimplePHPSamlIDP(originKey, OriginKeys.UAA); samlIdentityProviderDefinition.setAddShadowUserOnLogin(addShadowUserOnLogin); IdentityProvider provider = new IdentityProvider(); - provider.setIdentityZoneId(Origin.UAA); - provider.setType(Origin.SAML); + provider.setIdentityZoneId(OriginKeys.UAA); + provider.setType(OriginKeys.SAML); provider.setActive(true); provider.setConfig(samlIdentityProviderDefinition); provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java index 3d393dba99a..5fe5cef3ed4 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java @@ -1,10 +1,10 @@ package org.cloudfoundry.identity.uaa.invitations; import com.fasterxml.jackson.core.type.TypeReference; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.config.IdentityProviderBootstrap; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -28,8 +28,8 @@ import java.util.Arrays; import java.util.Map; -import static org.cloudfoundry.identity.uaa.authentication.Origin.ORIGIN; -import static org.cloudfoundry.identity.uaa.authentication.Origin.UAA; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.ORIGIN; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; @@ -224,7 +224,7 @@ private void assertResponseAndCodeCorrect(String[] emails, String redirectUrl, S for (int i = 0; i < emails.length; i++) { assertThat(response.getNewInvites().size(), is(emails.length)); assertThat(response.getNewInvites().get(i).getEmail(), is(emails[i])); - assertThat(response.getNewInvites().get(i).getOrigin(), is(Origin.UAA)); + assertThat(response.getNewInvites().get(i).getOrigin(), is(OriginKeys.UAA)); assertThat(response.getNewInvites().get(i).getUserId(), is(notNullValue())); assertThat(response.getNewInvites().get(i).getErrorCode(), is(nullValue())); assertThat(response.getNewInvites().get(i).getErrorMessage(), is(nullValue())); @@ -242,7 +242,7 @@ private void assertResponseAndCodeCorrect(String[] emails, String redirectUrl, S Map data = JsonUtils.readValue(expiringCode.getData(), new TypeReference>() {}); assertThat(data.get(InvitationConstants.USER_ID), is(notNullValue())); assertThat(data.get(InvitationConstants.EMAIL), is(emails[i])); - assertThat(data.get(ORIGIN), is(Origin.UAA)); + assertThat(data.get(ORIGIN), is(OriginKeys.UAA)); assertThat(data.get(CLIENT_ID), is(clientDetails.getClientId())); assertThat(data.get(REDIRECT_URI), is(redirectUrl)); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java index 676bf089b7d..005ba019dc7 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java @@ -3,9 +3,9 @@ import com.dumbster.smtp.SimpleSmtpServer; import com.dumbster.smtp.SmtpMessage; import org.apache.commons.lang3.RandomStringUtils; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.codestore.JdbcExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.test.MockMvcTestClient; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; @@ -247,7 +247,7 @@ public void testCreatingAnAccount() throws Exception { assertThat(authentication.getPrincipal(), instanceOf(UaaPrincipal.class)); UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); assertThat(principal.getEmail(), equalTo(userEmail)); - assertThat(principal.getOrigin(), equalTo(Origin.UAA)); + assertThat(principal.getOrigin(), equalTo(OriginKeys.UAA)); } @Test @@ -275,7 +275,7 @@ public void testCreatingAnAccountWithAnEmptyClientId() throws Exception { assertThat(authentication.getPrincipal(), instanceOf(UaaPrincipal.class)); UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); assertThat(principal.getEmail(), equalTo(userEmail)); - assertThat(principal.getOrigin(), equalTo(Origin.UAA)); + assertThat(principal.getOrigin(), equalTo(OriginKeys.UAA)); } @Test @@ -317,7 +317,7 @@ public void testCreatingAnAccountWithNoClientRedirect() throws Exception { assertThat(authentication.getPrincipal(), instanceOf(UaaPrincipal.class)); UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); assertThat(principal.getEmail(), equalTo(userEmail)); - assertThat(principal.getOrigin(), equalTo(Origin.UAA)); + assertThat(principal.getOrigin(), equalTo(OriginKeys.UAA)); } @Test @@ -369,7 +369,7 @@ public void testCreatingAnAccountInAnotherZoneWithNoClientRedirect() throws Exce assertThat(authentication.getPrincipal(), instanceOf(UaaPrincipal.class)); UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); assertThat(principal.getEmail(), equalTo(userEmail)); - assertThat(principal.getOrigin(), equalTo(Origin.UAA)); + assertThat(principal.getOrigin(), equalTo(OriginKeys.UAA)); } @Test @@ -422,7 +422,7 @@ public void testCreatingAnAccountInAnotherZoneWithClientRedirect() throws Except assertThat(authentication.getPrincipal(), instanceOf(UaaPrincipal.class)); UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); assertThat(principal.getEmail(), equalTo(userEmail)); - assertThat(principal.getOrigin(), equalTo(Origin.UAA)); + assertThat(principal.getOrigin(), equalTo(OriginKeys.UAA)); } @Test @@ -495,6 +495,6 @@ private void createAccount(String expectedRedirectUri, String redirectUri) throw assertThat(authentication.getPrincipal(), instanceOf(UaaPrincipal.class)); UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); assertThat(principal.getEmail(), equalTo(userEmail)); - assertThat(principal.getOrigin(), equalTo(Origin.UAA)); + assertThat(principal.getOrigin(), equalTo(OriginKeys.UAA)); } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index f29e1c5c96e..a7f088728f5 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -15,7 +15,6 @@ import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory; import org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory; import org.apache.tomcat.jdbc.pool.DataSource; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.login.Prompt; import org.cloudfoundry.identity.uaa.authentication.manager.PeriodLockoutPolicy; import org.cloudfoundry.identity.uaa.config.KeyPair; @@ -23,6 +22,7 @@ import org.cloudfoundry.identity.uaa.config.PasswordPolicy; import org.cloudfoundry.identity.uaa.config.TokenPolicy; import org.cloudfoundry.identity.uaa.config.YamlServletProfileInitializer; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.util.FakeJavaMailSender; @@ -421,7 +421,7 @@ public void testBootstrappedIdps_and_ExcludedClaims() throws Exception { assertNotNull(providerProvisioning.retrieveByOrigin(def.getIdpEntityAlias(), IdentityZone.getUaa().getId())); } - assertNotNull(providerProvisioning.retrieveByOrigin(Origin.LDAP, IdentityZone.getUaa().getId())); + assertNotNull(providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZone.getUaa().getId())); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java index 08ecaf87472..27b574e4fa4 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java @@ -15,7 +15,7 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.util.FakeJavaMailSender; @@ -107,7 +107,7 @@ public void clearOutCodeTable() { @Test public void inviteUser_Correct_Origin_Set() throws Exception { String email = new RandomValueStringGenerator().generate().toLowerCase()+"@test.org"; - inviteUser(email, userInviteToken, null, clientId, Origin.UAA); + inviteUser(email, userInviteToken, null, clientId, OriginKeys.UAA); } protected T queryUserForField(String email, String field, Class type) { @@ -118,8 +118,8 @@ protected T queryUserForField(String email, String field, Class type) { @Test public void test_authorize_with_invitation_login() throws Exception { String email = new RandomValueStringGenerator().generate().toLowerCase()+"@test.org"; - URL inviteLink = inviteUser(email, userInviteToken, null, clientId, Origin.UAA); - assertEquals(Origin.UAA, getWebApplicationContext().getBean(JdbcTemplate.class).queryForObject("select origin from users where username=?", new Object[]{email}, String.class)); + URL inviteLink = inviteUser(email, userInviteToken, null, clientId, OriginKeys.UAA); + assertEquals(OriginKeys.UAA, getWebApplicationContext().getBean(JdbcTemplate.class).queryForObject("select origin from users where username=?", new Object[]{email}, String.class)); String code = extractInvitationCode(inviteLink.toString()); MvcResult result = getMockMvc().perform( @@ -161,8 +161,8 @@ public void test_authorize_with_invitation_login() throws Exception { @Test public void accept_invitation_should_not_log_you_in() throws Exception { String email = new RandomValueStringGenerator().generate().toLowerCase()+"@test.org"; - URL inviteLink = inviteUser(email, userInviteToken, null, clientId, Origin.UAA); - assertEquals(Origin.UAA, getWebApplicationContext().getBean(JdbcTemplate.class).queryForObject("select origin from users where username=?", new Object[]{email}, String.class)); + URL inviteLink = inviteUser(email, userInviteToken, null, clientId, OriginKeys.UAA); + assertEquals(OriginKeys.UAA, getWebApplicationContext().getBean(JdbcTemplate.class).queryForObject("select origin from users where username=?", new Object[]{email}, String.class)); String code = extractInvitationCode(inviteLink.toString()); MvcResult result = getMockMvc().perform(get("/invitations/accept") @@ -187,11 +187,11 @@ public void accept_invitation_should_not_log_you_in() throws Exception { @Test public void accept_invitation_for_verified_user_sends_redirect() throws Exception { String email = new RandomValueStringGenerator().generate().toLowerCase() + "@test.org"; - URL inviteLink = inviteUser(email, userInviteToken, null, clientId, Origin.UAA); + URL inviteLink = inviteUser(email, userInviteToken, null, clientId, OriginKeys.UAA); getWebApplicationContext().getBean(JdbcTemplate.class).update("UPDATE users SET verified=true WHERE email=?",email); assertTrue("User should not be verified", queryUserForField(email, "verified", Boolean.class)); - assertEquals(Origin.UAA, queryUserForField(email, Origin.ORIGIN, String.class)); + assertEquals(OriginKeys.UAA, queryUserForField(email, OriginKeys.ORIGIN, String.class)); String code = extractInvitationCode(inviteLink.toString()); getMockMvc().perform( @@ -206,10 +206,10 @@ public void accept_invitation_for_verified_user_sends_redirect() throws Exceptio @Test public void accept_invitation_sets_your_password() throws Exception { String email = new RandomValueStringGenerator().generate().toLowerCase()+"@test.org"; - URL inviteLink = inviteUser(email, userInviteToken, null, clientId, Origin.UAA); + URL inviteLink = inviteUser(email, userInviteToken, null, clientId, OriginKeys.UAA); assertFalse("User should not be verified", queryUserForField(email, "verified", Boolean.class)); - assertEquals(Origin.UAA, queryUserForField(email, Origin.ORIGIN, String.class)); + assertEquals(OriginKeys.UAA, queryUserForField(email, OriginKeys.ORIGIN, String.class)); String code = extractInvitationCode(inviteLink.toString()); MvcResult result = getMockMvc().perform(get("/invitations/accept") @@ -252,13 +252,13 @@ public void invite_ldap_users_verifies_and_redirects() throws Exception { String domain = generator.generate().toLowerCase()+".com"; definition.setEmailDomain(Arrays.asList(domain)); - IdentityProvider provider = createIdentityProvider(zone.getZone(), Origin.LDAP, definition); + IdentityProvider provider = createIdentityProvider(zone.getZone(), OriginKeys.LDAP, definition); String email = new RandomValueStringGenerator().generate().toLowerCase()+"@"+domain; URL inviteLink = inviteUser(email, zone.getAdminToken(), zone.getZone().getIdentityZone().getSubdomain(), zone.getScimInviteClient().getClientId(), provider.getOriginKey()); String code = extractInvitationCode(inviteLink.toString()); assertFalse("User should not be verified", queryUserForField(email, "verified", Boolean.class)); - assertEquals(Origin.LDAP, queryUserForField(email, Origin.ORIGIN, String.class)); + assertEquals(OriginKeys.LDAP, queryUserForField(email, OriginKeys.ORIGIN, String.class)); ResultActions actions = getMockMvc().perform(get("/invitations/accept") .param("code", code) @@ -288,7 +288,7 @@ public void invite_saml_user_will_redirect_upon_accept() throws Exception { String code = extractInvitationCode(inviteLink.toString()); assertFalse("User should not be verified", queryUserForField(email, "verified", Boolean.class)); - assertEquals(originKey, queryUserForField(email, Origin.ORIGIN, String.class)); + assertEquals(originKey, queryUserForField(email, OriginKeys.ORIGIN, String.class)); //should redirect to saml provider getMockMvc().perform( @@ -307,7 +307,7 @@ public void invite_saml_user_will_redirect_upon_accept() throws Exception { ); - assertEquals(provider.getOriginKey(), queryUserForField(email, Origin.ORIGIN, String.class)); + assertEquals(provider.getOriginKey(), queryUserForField(email, OriginKeys.ORIGIN, String.class)); assertFalse("Saml user should not yet be verified after clicking on the accept link", queryUserForField(email, "verified", Boolean.class)); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index ac000deeea4..39d0fab3604 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -12,7 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authentication.WhitelistLogoutHandler; import org.cloudfoundry.identity.uaa.authentication.login.LoginInfoEndpoint; @@ -20,6 +19,7 @@ import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.codestore.JdbcExpiringCodeStore; import org.cloudfoundry.identity.uaa.config.LockoutPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.saml.IdentityProviderConfiguratorTests; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; @@ -620,7 +620,7 @@ public void testSamlLoginLinksShowActiveProviders() throws Exception { String metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition = new SamlIdentityProviderDefinition(metadata, activeAlias, null, 0, false, true, "Active SAML Provider", null, identityZone.getId()); IdentityProvider activeIdentityProvider = new IdentityProvider(); - activeIdentityProvider.setType(Origin.SAML); + activeIdentityProvider.setType(OriginKeys.SAML); activeIdentityProvider.setName("Active SAML Provider"); activeIdentityProvider.setConfig(activeSamlIdentityProviderDefinition); activeIdentityProvider.setActive(true); @@ -630,7 +630,7 @@ public void testSamlLoginLinksShowActiveProviders() throws Exception { metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); SamlIdentityProviderDefinition inactiveSamlIdentityProviderDefinition = new SamlIdentityProviderDefinition(metadata, inactiveAlias, null, 0, false, true, "You should not see me", null, identityZone.getId()); IdentityProvider inactiveIdentityProvider = new IdentityProvider(); - inactiveIdentityProvider.setType(Origin.SAML); + inactiveIdentityProvider.setType(OriginKeys.SAML); inactiveIdentityProvider.setName("Inactive SAML Provider"); inactiveIdentityProvider.setConfig(inactiveSamlIdentityProviderDefinition); inactiveIdentityProvider.setActive(false); @@ -657,7 +657,7 @@ public void testSamlRedirectWhenTheOnlyProvider() throws Exception { String metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition = new SamlIdentityProviderDefinition(metadata, alias, null, 0, false, true, "Active SAML Provider", null, identityZone.getId()); IdentityProvider activeIdentityProvider = new IdentityProvider(); - activeIdentityProvider.setType(Origin.SAML); + activeIdentityProvider.setType(OriginKeys.SAML); activeIdentityProvider.setName("Active SAML Provider"); activeIdentityProvider.setActive(true); activeIdentityProvider.setConfig(activeSamlIdentityProviderDefinition); @@ -721,7 +721,7 @@ public void testNoCreateAccountLinksWhenUAAisNotAllowedProvider() throws Excepti identityZone.getId() ); IdentityProvider activeIdentityProvider3 = new IdentityProvider(); - activeIdentityProvider3.setType(Origin.SAML); + activeIdentityProvider3.setType(OriginKeys.SAML); activeIdentityProvider3.setName("Active 3 SAML Provider"); activeIdentityProvider3.setActive(true); activeIdentityProvider3.setConfig(activeSamlIdentityProviderDefinition3); @@ -730,7 +730,7 @@ public void testNoCreateAccountLinksWhenUAAisNotAllowedProvider() throws Excepti SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition2 = new SamlIdentityProviderDefinition(String.format(IdentityProviderConfiguratorTests.xmlWithoutID,"http://example2.com/saml/metadata"), alias2, null, 0, false, true, "Active2 SAML Provider", null, identityZone.getId()); IdentityProvider activeIdentityProvider2 = new IdentityProvider(); - activeIdentityProvider2.setType(Origin.SAML); + activeIdentityProvider2.setType(OriginKeys.SAML); activeIdentityProvider2.setName("Active 2 SAML Provider"); activeIdentityProvider2.setActive(true); activeIdentityProvider2.setConfig(activeSamlIdentityProviderDefinition2); @@ -786,7 +786,7 @@ public void testDeactivatedProviderIsRemovedFromSamlLoginLinks() throws Exceptio String metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); SamlIdentityProviderDefinition samlIdentityProviderDefinition = new SamlIdentityProviderDefinition(metadata, alias, null, 0, false, true, "SAML Provider", null, identityZone.getId()); IdentityProvider identityProvider = new IdentityProvider(); - identityProvider.setType(Origin.SAML); + identityProvider.setType(OriginKeys.SAML); identityProvider.setName("SAML Provider"); identityProvider.setActive(true); identityProvider.setConfig(samlIdentityProviderDefinition); @@ -1225,7 +1225,7 @@ public void autologin_with_validCode_RedirectsToHome() throws Exception { private void changeLockoutPolicyForIdpInZone(IdentityZone zone) throws Exception { IdentityProviderProvisioning identityProviderProvisioning = getWebApplicationContext().getBean(IdentityProviderProvisioning.class); - IdentityProvider identityProvider = identityProviderProvisioning.retrieveByOrigin(Origin.UAA, zone.getId()); + IdentityProvider identityProvider = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, zone.getId()); LockoutPolicy policy = new LockoutPolicy(); policy.setLockoutAfterFailures(2); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java index ad5f87dc497..cd86821fce2 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java @@ -1,10 +1,10 @@ package org.cloudfoundry.identity.uaa.login; import org.apache.commons.codec.binary.Base64; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.oauth.RemoteUserAuthentication; @@ -83,7 +83,7 @@ public void setUp() throws Exception { } } UaaUserDatabase db = getWebApplicationContext().getBean(UaaUserDatabase.class); - marissa = new UaaPrincipal(db.retrieveUserByName(USERNAME, Origin.UAA)); + marissa = new UaaPrincipal(db.retrieveUserByName(USERNAME, OriginKeys.UAA)); } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java index c269e9a90ab..46e6f95ca74 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java @@ -12,11 +12,11 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.JdbcExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.PredictableGenerator; @@ -94,7 +94,7 @@ public void testResettingAPasswordUsingUsernameToEnsureNoModification() throws E assertThat(principal.getId(), equalTo(users.get(0).getId())); assertThat(principal.getName(), equalTo(users.get(0).getUserName())); assertThat(principal.getEmail(), equalTo(users.get(0).getPrimaryEmail())); - assertThat(principal.getOrigin(), equalTo(Origin.UAA)); + assertThat(principal.getOrigin(), equalTo(OriginKeys.UAA)); } @Test @@ -255,7 +255,7 @@ public void testResettingAPasswordUsingTimestampForUserModification() throws Exc assertThat(principal.getId(), equalTo(users.get(0).getId())); assertThat(principal.getName(), equalTo(users.get(0).getUserName())); assertThat(principal.getEmail(), equalTo(users.get(0).getPrimaryEmail())); - assertThat(principal.getOrigin(), equalTo(Origin.UAA)); + assertThat(principal.getOrigin(), equalTo(OriginKeys.UAA)); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java index 25063d1a59d..4ebec3ada87 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java @@ -15,8 +15,10 @@ package org.cloudfoundry.identity.uaa.login.saml; import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; @@ -152,9 +154,9 @@ public void configureProvider() throws Exception { JdbcScimGroupExternalMembershipManager externalManager = new JdbcScimGroupExternalMembershipManager(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter)); externalManager.setScimGroupProvisioning(groupProvisioning); - externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, Origin.SAML); - externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, Origin.SAML); - externalManager.mapExternalGroup(uaaSamlTest.getId(), SAML_TEST, Origin.SAML); + externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML); + externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML); + externalManager.mapExternalGroup(uaaSamlTest.getId(), SAML_TEST, OriginKeys.SAML); consumer = mock(WebSSOProfileConsumer.class); credential = getUserCredential("marissa-saml", "Marissa", "Bloggs", "marissa.bloggs@test.com", "1234567890"); @@ -184,12 +186,12 @@ public void configureProvider() throws Exception { provider = new IdentityProvider(); provider.setIdentityZoneId(IdentityZone.getUaa().getId()); - provider.setOriginKey(Origin.SAML); + provider.setOriginKey(OriginKeys.SAML); provider.setName("saml-test"); provider.setActive(true); - provider.setType(Origin.SAML); - providerDefinition.setMetaDataLocation(String.format(IDP_META_DATA, Origin.SAML)); - providerDefinition.setIdpEntityAlias(Origin.SAML); + provider.setType(OriginKeys.SAML); + providerDefinition.setMetaDataLocation(String.format(IDP_META_DATA, OriginKeys.SAML)); + providerDefinition.setIdpEntityAlias(OriginKeys.SAML); provider.setConfig(providerDefinition); provider = providerProvisioning.create(provider); } @@ -217,7 +219,7 @@ private SAMLCredential getUserCredential(String username, String firstName, Stri @Test public void testAuthenticateSimple() { - authprovider.authenticate(mockSamlAuthentication(Origin.SAML)); + authprovider.authenticate(mockSamlAuthentication(OriginKeys.SAML)); } @Test @@ -364,7 +366,7 @@ public void update_existingUser_if_attributes_different() throws Exception { when(consumer.processAuthenticationResponse(anyObject())).thenReturn(credential); getAuthentication(); - UaaUser user = userDatabase.retrieveUserByName("marissa-saml", Origin.SAML); + UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); assertEquals("Marissa-changed", user.getGivenName()); assertEquals("marissa.bloggs@change.org", user.getEmail()); } @@ -372,10 +374,10 @@ public void update_existingUser_if_attributes_different() throws Exception { @Test public void dont_update_existingUser_if_attributes_areTheSame() throws Exception { getAuthentication(); - UaaUser user = userDatabase.retrieveUserByName("marissa-saml", Origin.SAML); + UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); getAuthentication(); - UaaUser existingUser = userDatabase.retrieveUserByName("marissa-saml", Origin.SAML); + UaaUser existingUser = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); assertEquals(existingUser.getModified(), user.getModified()); } @@ -392,7 +394,7 @@ public void shadowAccount_createdWith_MappedUserAttributes() throws Exception { providerProvisioning.update(provider); getAuthentication(); - UaaUser user = userDatabase.retrieveUserByName("marissa-saml", Origin.SAML); + UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); assertEquals("Marissa", user.getGivenName()); assertEquals("Bloggs", user.getFamilyName()); assertEquals("marissa.bloggs@test.com", user.getEmail()); @@ -409,7 +411,7 @@ public void shadowUser_GetsCreatedWithDefaultValues_IfAttributeNotMapped() throw providerProvisioning.update(provider); UaaAuthentication authentication = getAuthentication(); - UaaUser user = userDatabase.retrieveUserByName("marissa-saml", Origin.SAML); + UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); assertEquals("marissa.bloggs", user.getGivenName()); assertEquals("test.com", user.getFamilyName()); assertEquals("marissa.bloggs@test.com", user.getEmail()); @@ -442,7 +444,7 @@ public void user_authentication_contains_custom_attributes() throws Exception { } protected UaaAuthentication getAuthentication() { - Authentication authentication = authprovider.authenticate(mockSamlAuthentication(Origin.SAML)); + Authentication authentication = authprovider.authenticate(mockSamlAuthentication(OriginKeys.SAML)); assertNotNull("Authentication should exist", authentication); assertTrue("Authentication should be UaaAuthentication", authentication instanceof UaaAuthentication); return (UaaAuthentication)authentication; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java index b47a8be5936..3175a0dab38 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java @@ -12,9 +12,9 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login.saml; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.config.SamlConfig; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; @@ -83,7 +83,7 @@ public void setUpContext() throws Exception { @After public void cleanSamlProviders() throws Exception { - jdbcTemplate.update("UPDATE identity_provider SET active=? WHERE type=?", false, Origin.SAML); + jdbcTemplate.update("UPDATE identity_provider SET active=? WHERE type=?", false, OriginKeys.SAML); for (SamlIdentityProviderDefinition definition : configurator.getIdentityProviderDefinitions()) { configurator.removeIdentityProviderDefinition(definition); } @@ -104,7 +104,7 @@ public void cleanSamlProviders() throws Exception { //all we have left is the local provider assertEquals(1, zoneAwareMetadataManager.getManager(zone).getAvailableProviders().size()); } - jdbcTemplate.update("delete from identity_provider where type=?", Origin.SAML); + jdbcTemplate.update("delete from identity_provider where type=?", OriginKeys.SAML); SecurityContextHolder.clearContext(); IdentityZoneHolder.clear(); } @@ -463,7 +463,7 @@ public IdentityProvider createSamlProvider(Strin provider.setIdentityZoneId(IdentityZone.getUaa().getId()); provider.setOriginKey(alias); provider.setName("DB Added SAML Provider"); - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); provider = providerProvisioning.create(provider); return provider; } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java index ac5ede9b162..ec63d39e8eb 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java @@ -13,10 +13,10 @@ package org.cloudfoundry.identity.uaa.mock.ldap; import org.cloudfoundry.identity.uaa.TestClassNullifier; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.manager.AuthzAuthenticationManager; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicZoneAwareAuthenticationManager; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserMapper; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.ldap.ProcessLdapProperties; @@ -225,7 +225,7 @@ public void tearDown() throws Exception { } private void deleteLdapUsers() { - jdbcTemplate.update("delete from users where origin='" + Origin.LDAP + "'"); + jdbcTemplate.update("delete from users where origin='" + OriginKeys.LDAP + "'"); } public void acceptInvitation_for_ldap_user_whose_username_is_not_email() throws Exception { @@ -252,14 +252,14 @@ public void acceptInvitation_for_ldap_user_whose_username_is_not_email() throws 10, true); definition.setEmailDomain(Arrays.asList("test.com")); - utils().createIdentityProvider(mockMvc, zone.getZone(), Origin.LDAP, definition); + utils().createIdentityProvider(mockMvc, zone.getZone(), OriginKeys.LDAP, definition); - URL url = utils().inviteUser(mainContext, mockMvc, email, zone.getAdminToken(), zone.getZone().getIdentityZone().getSubdomain(), zone.getScimInviteClient().getClientId(), Origin.LDAP, REDIRECT_URI); + URL url = utils().inviteUser(mainContext, mockMvc, email, zone.getAdminToken(), zone.getZone().getIdentityZone().getSubdomain(), zone.getScimInviteClient().getClientId(), OriginKeys.LDAP, REDIRECT_URI); String code = utils().extractInvitationCode(url.toString()); String userInfoOrigin = mainContext.getBean(JdbcTemplate.class).queryForObject("select origin from users where email=? and identity_zone_id=?", String.class, email, zone.getZone().getIdentityZone().getId()); String userInfoId = mainContext.getBean(JdbcTemplate.class).queryForObject("select id from users where email=? and identity_zone_id=?", String.class, email, zone.getZone().getIdentityZone().getId()); - assertEquals(Origin.LDAP, userInfoOrigin); + assertEquals(OriginKeys.LDAP, userInfoOrigin); ResultActions actions = mockMvc.perform(get("/invitations/accept") .param("code", code) @@ -289,7 +289,7 @@ public void acceptInvitation_for_ldap_user_whose_username_is_not_email() throws String newUserInfoId = mainContext.getBean(JdbcTemplate.class).queryForObject("select id from users where email=? and identity_zone_id=?", String.class, email, zone.getZone().getIdentityZone().getId()); String newUserInfoOrigin = mainContext.getBean(JdbcTemplate.class).queryForObject("select origin from users where email=? and identity_zone_id=?", String.class, email, zone.getZone().getIdentityZone().getId()); String newUserInfoUsername = mainContext.getBean(JdbcTemplate.class).queryForObject("select username from users where email=? and identity_zone_id=?", String.class, email, zone.getZone().getIdentityZone().getId()); - assertEquals(Origin.LDAP, newUserInfoOrigin); + assertEquals(OriginKeys.LDAP, newUserInfoOrigin); assertEquals("marissa2", newUserInfoUsername); //ensure that a new user wasn't created assertEquals(userInfoId, newUserInfoId); @@ -298,7 +298,7 @@ public void acceptInvitation_for_ldap_user_whose_username_is_not_email() throws //email mismatch mainContext.getBean(JdbcTemplate.class).update("delete from expiring_code_store"); email = "different@test.com"; - url = utils().inviteUser(mainContext, mockMvc, email, zone.getAdminToken(), zone.getZone().getIdentityZone().getSubdomain(), zone.getScimInviteClient().getClientId(), Origin.LDAP, REDIRECT_URI); + url = utils().inviteUser(mainContext, mockMvc, email, zone.getAdminToken(), zone.getZone().getIdentityZone().getSubdomain(), zone.getScimInviteClient().getClientId(), OriginKeys.LDAP, REDIRECT_URI); code = utils().extractInvitationCode(url.toString()); actions = mockMvc.perform(get("/invitations/accept") @@ -335,7 +335,7 @@ public void test_whitelisted_external_groups() throws Exception { Assume.assumeThat("ldap-groups-map-to-scopes.xml, ldap-groups-as-scopes.xml", StringContains.containsString(ldapGroup)); setUp(); IdentityProviderProvisioning idpProvisioning = mainContext.getBean(IdentityProviderProvisioning.class); - IdentityProvider idp = idpProvisioning.retrieveByOrigin(Origin.LDAP, IdentityZone.getUaa().getId()); + IdentityProvider idp = idpProvisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZone.getUaa().getId()); LdapIdentityProviderDefinition def = idp.getConfig(); def.addWhiteListedGroup("admins"); def.addWhiteListedGroup("thirdmarissa"); @@ -445,9 +445,9 @@ public void testLdapConfigurationBeforeSave() throws Exception { ); IdentityProvider provider = new IdentityProvider(); - provider.setOriginKey(Origin.LDAP); + provider.setOriginKey(OriginKeys.LDAP); provider.setName("Test ldap provider"); - provider.setType(Origin.LDAP); + provider.setType(OriginKeys.LDAP); provider.setConfig(definition); provider.setActive(true); provider.setIdentityZoneId(zone.getId()); @@ -693,9 +693,9 @@ public void testLoginInNonDefaultZone() throws Exception { ); IdentityProvider provider = new IdentityProvider(); - provider.setOriginKey(Origin.LDAP); + provider.setOriginKey(OriginKeys.LDAP); provider.setName("Test ldap provider"); - provider.setType(Origin.LDAP); + provider.setType(OriginKeys.LDAP); provider.setConfig(definition); provider.setActive(true); provider.setIdentityZoneId(zone.getId()); @@ -710,10 +710,10 @@ public void testLoginInNonDefaultZone() throws Exception { .andExpect(redirectedUrl("/")); IdentityZoneHolder.set(zone); - UaaUser user = userDatabase.retrieveUserByName("marissa2",Origin.LDAP); + UaaUser user = userDatabase.retrieveUserByName("marissa2", OriginKeys.LDAP); IdentityZoneHolder.clear(); assertNotNull(user); - assertEquals(Origin.LDAP, user.getOrigin()); + assertEquals(OriginKeys.LDAP, user.getOrigin()); assertEquals(zone.getId(), user.getZoneId()); provider.setActive(false); @@ -756,10 +756,10 @@ public void testLoginInNonDefaultZone() throws Exception { .andExpect(redirectedUrl("/")); IdentityZoneHolder.set(zone); - user = userDatabase.retrieveUserByName("marissa2",Origin.LDAP); + user = userDatabase.retrieveUserByName("marissa2", OriginKeys.LDAP); IdentityZoneHolder.clear(); assertNotNull(user); - assertEquals(Origin.LDAP, user.getOrigin()); + assertEquals(OriginKeys.LDAP, user.getOrigin()); assertEquals(zone.getId(), user.getZoneId()); assertEquals("marissa2@ldaptest.com", user.getEmail()); } @@ -793,9 +793,9 @@ public void testLogin_partial_result_exception_on_group_search() throws Exceptio ); IdentityProvider provider = new IdentityProvider(); - provider.setOriginKey(Origin.LDAP); + provider.setOriginKey(OriginKeys.LDAP); provider.setName("Test ldap provider"); - provider.setType(Origin.LDAP); + provider.setType(OriginKeys.LDAP); provider.setConfig(definition); provider.setActive(true); provider.setIdentityZoneId(zone.getId()); @@ -810,10 +810,10 @@ public void testLogin_partial_result_exception_on_group_search() throws Exceptio .andExpect(redirectedUrl("/")); IdentityZoneHolder.set(zone); - UaaUser user = userDatabase.retrieveUserByName("marissa8",Origin.LDAP); + UaaUser user = userDatabase.retrieveUserByName("marissa8", OriginKeys.LDAP); IdentityZoneHolder.clear(); assertNotNull(user); - assertEquals(Origin.LDAP, user.getOrigin()); + assertEquals(OriginKeys.LDAP, user.getOrigin()); assertEquals(zone.getId(), user.getZoneId()); } @@ -865,7 +865,7 @@ public void runLdapTestblock() throws Exception { public Object getBean(String name) { IdentityProviderProvisioning provisioning = mainContext.getBean(IdentityProviderProvisioning.class); - IdentityProvider ldapProvider = provisioning.retrieveByOrigin(Origin.LDAP, IdentityZoneHolder.get().getId()); + IdentityProvider ldapProvider = provisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZoneHolder.get().getId()); DynamicZoneAwareAuthenticationManager zm = mainContext.getBean(DynamicZoneAwareAuthenticationManager.class); zm.getLdapAuthenticationManager(IdentityZone.getUaa(), ldapProvider).getLdapAuthenticationManager(); return zm.getLdapAuthenticationManager(IdentityZone.getUaa(), ldapProvider).getContext().getBean(name); @@ -873,7 +873,7 @@ public Object getBean(String name) { public T getBean(Class clazz) { IdentityProviderProvisioning provisioning = mainContext.getBean(IdentityProviderProvisioning.class); - IdentityProvider ldapProvider = provisioning.retrieveByOrigin(Origin.LDAP, IdentityZoneHolder.get().getId()); + IdentityProvider ldapProvider = provisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZoneHolder.get().getId()); DynamicZoneAwareAuthenticationManager zm = mainContext.getBean(DynamicZoneAwareAuthenticationManager.class); zm.getLdapAuthenticationManager(IdentityZone.getUaa(), ldapProvider).getLdapAuthenticationManager(); return zm.getLdapAuthenticationManager(IdentityZone.getUaa(), ldapProvider).getContext().getBean(clazz); @@ -927,7 +927,7 @@ public void testExtendedAttributes() throws Exception { public void testAuthenticateInactiveIdp() throws Exception { IdentityProviderProvisioning provisioning = mainContext.getBean(IdentityProviderProvisioning.class); - IdentityProvider ldapProvider = provisioning.retrieveByOrigin(Origin.LDAP, IdentityZone.getUaa().getId()); + IdentityProvider ldapProvider = provisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZone.getUaa().getId()); try { ldapProvider.setActive(false); ldapProvider = provisioning.update(ldapProvider); @@ -958,7 +958,7 @@ public void validateOriginForNonLdapUser() throws Exception { MvcResult result = performAuthentication(username, password); assertThat(result.getResponse().getContentAsString(), containsString("\"username\":\"" + username + "\"")); assertThat(result.getResponse().getContentAsString(), containsString("\"email\":\"marissa@test.org\"")); - assertEquals(Origin.UAA, getOrigin(username)); + assertEquals(OriginKeys.UAA, getOrigin(username)); } public void validateOriginAndEmailForLdapUser() throws Exception { @@ -1053,19 +1053,19 @@ private String getOrigin(String username) { } private String getEmail(String username) { - return jdbcTemplate.queryForObject("select email from users where username='" + username + "' and origin='" + Origin.LDAP + "'", String.class); + return jdbcTemplate.queryForObject("select email from users where username='" + username + "' and origin='" + OriginKeys.LDAP + "'", String.class); } private String getGivenName(String username) { - return jdbcTemplate.queryForObject("select givenname from users where username='" + username + "' and origin='" + Origin.LDAP + "'", String.class); + return jdbcTemplate.queryForObject("select givenname from users where username='" + username + "' and origin='" + OriginKeys.LDAP + "'", String.class); } private String getFamilyName(String username) { - return jdbcTemplate.queryForObject("select familyname from users where username='" + username + "' and origin='" + Origin.LDAP + "'", String.class); + return jdbcTemplate.queryForObject("select familyname from users where username='" + username + "' and origin='" + OriginKeys.LDAP + "'", String.class); } private String getPhoneNumber(String username) { - return jdbcTemplate.queryForObject("select phonenumber from users where username='" + username + "' and origin='" + Origin.LDAP + "'", String.class); + return jdbcTemplate.queryForObject("select phonenumber from users where username='" + username + "' and origin='" + OriginKeys.LDAP + "'", String.class); } private MvcResult performAuthentication(String username, String password) throws Exception { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java index 83992f60a57..3dd77908bbe 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java @@ -13,13 +13,13 @@ package org.cloudfoundry.identity.uaa.mock.token; import com.fasterxml.jackson.core.type.TypeReference; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authorization.UaaAuthorizationEndpoint; import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.Claims; @@ -158,7 +158,7 @@ private IdentityZone setupIdentityZone(String subdomain) { } private IdentityProvider setupIdentityProvider() { - return setupIdentityProvider(Origin.UAA); + return setupIdentityProvider(OriginKeys.UAA); } private IdentityProvider setupIdentityProvider(String origin) { IdentityProvider defaultIdp = new IdentityProvider(); @@ -248,7 +248,7 @@ public void testClientIdentityProviderWithoutAllowedProvidersForPasswordGrantWor String subdomain = "testzone"+new RandomValueStringGenerator().generate(); IdentityZone testZone = setupIdentityZone(subdomain); IdentityZoneHolder.set(testZone); - IdentityProvider provider = setupIdentityProvider(Origin.UAA); + IdentityProvider provider = setupIdentityProvider(OriginKeys.UAA); String clientId2 = "testclient"+new RandomValueStringGenerator().generate(); setUpClients(clientId2, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, Arrays.asList(provider.getOriginKey())); @@ -258,7 +258,7 @@ public void testClientIdentityProviderWithoutAllowedProvidersForPasswordGrantWor String username = "testuser"+new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; - ScimUser developer = setUpUser(username, userScopes, Origin.UAA, testZone.getId()); + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, testZone.getId()); getMockMvc().perform(post("/oauth/token") .with(new SetServerNameRequestPostProcessor(subdomain + ".localhost")) @@ -290,7 +290,7 @@ public void testClientIdentityProviderClientWithoutAllowedProvidersForAuthCodeAl String subdomain = "testzone"+new RandomValueStringGenerator().generate(); IdentityZone testZone = setupIdentityZone(subdomain); IdentityZoneHolder.set(testZone); - IdentityProvider provider = setupIdentityProvider(Origin.UAA); + IdentityProvider provider = setupIdentityProvider(OriginKeys.UAA); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; @@ -301,13 +301,13 @@ public void testClientIdentityProviderClientWithoutAllowedProvidersForAuthCodeAl setUpClients(clientId2, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, Arrays.asList(provider.getOriginKey())); String clientId3 = "testclient"+new RandomValueStringGenerator().generate(); - setUpClients(clientId3, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, Arrays.asList(Origin.LOGIN_SERVER)); + setUpClients(clientId3, scopes, scopes, "authorization_code,password", true, TEST_REDIRECT_URI, Arrays.asList(OriginKeys.LOGIN_SERVER)); String username = "testuser"+new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; - ScimUser developer = setUpUser(username, userScopes, Origin.UAA, testZone.getId()); + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, testZone.getId()); - UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), Origin.UAA,"", IdentityZoneHolder.get().getId()); + UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), OriginKeys.UAA,"", IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); Assert.assertTrue(auth.isAuthenticated()); SecurityContextHolder.getContext().setAuthentication(auth); @@ -378,7 +378,7 @@ public void testClientIdentityProviderRestrictionForPasswordGrant() throws Excep //create a user in the UAA identity provider String username = "testuser"+new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; - ScimUser developer = setUpUser(username, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); getMockMvc().perform(post("/oauth/token") @@ -407,7 +407,7 @@ public void test_Oauth_Authorize_API_Endpoint() throws Exception { setUpClients(clientId, "", scopes, "authorization_code", true); String username = "testuser"+new RandomValueStringGenerator().generate(); String userScopes = ""; - setUpUser(username, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String cfAccessToken = MockMvcUtils.utils().getUserOAuthAccessToken( getMockMvc(), @@ -465,9 +465,9 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrant_When_IdToken_Disabled() setUpClients(clientId, scopes, scopes, "authorization_code", true); String username = "testuser" + new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; - ScimUser developer = setUpUser(username, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); - UaaPrincipal p = new UaaPrincipal(developer.getId(), developer.getUserName(), developer.getPrimaryEmail(), Origin.UAA, "", IdentityZoneHolder.get().getId()); + UaaPrincipal p = new UaaPrincipal(developer.getId(), developer.getUserName(), developer.getPrimaryEmail(), OriginKeys.UAA, "", IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); Assert.assertTrue(auth.isAuthenticated()); @@ -509,9 +509,9 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrant() throws Exception { setUpClients(clientId, scopes, scopes, "authorization_code", true); String username = "testuser"+new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; - ScimUser developer = setUpUser(username, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); - UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), Origin.UAA,"", IdentityZoneHolder.get().getId()); + UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), OriginKeys.UAA,"", IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); Assert.assertTrue(auth.isAuthenticated()); @@ -549,9 +549,9 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenLenientWhenAppNotApp setUpClients(clientId, scopes, scopes, "authorization_code", false); String username = "testuser"+new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; - ScimUser developer = setUpUser(username, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); - UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), Origin.UAA,"", IdentityZoneHolder.get().getId()); + UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), OriginKeys.UAA,"", IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); Assert.assertTrue(auth.isAuthenticated()); @@ -593,9 +593,9 @@ public void testOpenIdTokenHybridFlowWithNoImplicitGrantWhenStrictWhenAppNotAppr setUpClients(clientId, scopes, scopes, "authorization_code", false); String username = "testuser"+new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; - ScimUser developer = setUpUser(username, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); - UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), Origin.UAA,"", IdentityZoneHolder.get().getId()); + UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), OriginKeys.UAA,"", IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); Assert.assertTrue(auth.isAuthenticated()); @@ -640,10 +640,10 @@ public void testAuthorizationCodeGrantWithEncodedRedirectURL() throws Exception setUpClients(clientId, scopes, scopes, GRANT_TYPES, true, redirectUri); String username = "authuser"+new RandomValueStringGenerator().generate(); String userScopes = "openid"; - ScimUser developer = setUpUser(username, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String basicDigestHeaderValue = "Basic " + new String(org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + SECRET).getBytes())); - UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), Origin.UAA,"", IdentityZoneHolder.get().getId()); + UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), OriginKeys.UAA,"", IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); Assert.assertTrue(auth.isAuthenticated()); @@ -751,10 +751,10 @@ protected void testImplicitGrantRedirectUri(String redirectUri, String delim) th setUpClients(clientId, scopes, scopes, GRANT_TYPES, true, redirectUri); String username = "authuser"+new RandomValueStringGenerator().generate(); String userScopes = "openid"; - ScimUser developer = setUpUser(username, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String basicDigestHeaderValue = "Basic " + new String(org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + SECRET).getBytes())); - UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), Origin.UAA,"", IdentityZoneHolder.get().getId()); + UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), OriginKeys.UAA,"", IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); Assert.assertTrue(auth.isAuthenticated()); @@ -788,7 +788,7 @@ public void testOpenIdToken() throws Exception { setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String username = "testuser"+new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three,openid"; - ScimUser developer = setUpUser(username, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String authCodeClientId = "testclient"+new RandomValueStringGenerator().generate(); setUpClients(authCodeClientId, scopes, scopes, "authorization_code", true); @@ -839,8 +839,8 @@ public void testOpenIdToken() throws Exception { validateOpenIdConnectToken(((List)token.get("id_token")).get(0), developer.getId(), implicitClientId); //authorization_code grant - requesting id_token - UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), Origin.UAA,"", IdentityZoneHolder.get().getId()); - UaaAuthentication auth = new UaaAuthentication(p, UaaAuthority.USER_AUTHORITIES, new UaaAuthenticationDetails(false, "clientId",Origin.ORIGIN,"sessionId")); + UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), OriginKeys.UAA,"", IdentityZoneHolder.get().getId()); + UaaAuthentication auth = new UaaAuthentication(p, UaaAuthority.USER_AUTHORITIES, new UaaAuthenticationDetails(false, "clientId", OriginKeys.ORIGIN,"sessionId")); Assert.assertTrue(auth.isAuthenticated()); SecurityContextHolder.getContext().setAuthentication(auth); @@ -1165,7 +1165,7 @@ public void test_Token_Expiry_Time() throws Exception { setUpClients(clientId, scopes, scopes, GRANT_TYPES, true,null,null,60*60*24*3650); String userId = "testuser" + new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; - ScimUser developer = setUpUser(userId, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(userId, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); Set allUserScopes = new HashSet<>(); allUserScopes.addAll(defaultAuthorities); allUserScopes.addAll(StringUtils.commaDelimitedListToSet(userScopes)); @@ -1197,7 +1197,7 @@ public void testWildcardPasswordGrant() throws Exception { setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String userId = "testuser"+new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; - ScimUser developer = setUpUser(userId, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(userId, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); Set allUserScopes = new HashSet<>(); allUserScopes.addAll(defaultAuthorities); allUserScopes.addAll(StringUtils.commaDelimitedListToSet(userScopes)); @@ -1312,10 +1312,10 @@ public void testLoginAddNewUserForOauthTokenPasswordGrant() throws Exception { .param("family_name", last) .param("given_name", first) .param("email", email) - .param(Origin.ORIGIN, Origin.UAA)) + .param(OriginKeys.ORIGIN, OriginKeys.UAA)) .andExpect(status().isOk()); UaaUserDatabase db = getWebApplicationContext().getBean(UaaUserDatabase.class); - UaaUser user = db.retrieveUserByName(username, Origin.UAA); + UaaUser user = db.retrieveUserByName(username, OriginKeys.UAA); assertNotNull(user); assertEquals(username, user.getUsername()); assertEquals(email, user.getEmail()); @@ -1330,7 +1330,7 @@ public void testLoginAuthenticationFilter() throws Exception { setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); String userId = "testuser" + new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; - ScimUser developer = setUpUser(userId, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(userId, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String loginToken = testClient.getClientCredentialsOAuthAccessToken("login", "loginsecret", ""); //the login server is matched by providing @@ -1351,7 +1351,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("client_secret", SECRET) .param("username", developer.getUserName()) .param("user_id", developer.getId()) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isOk()); //success - user_id only, contains everything we need @@ -1376,7 +1376,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("client_id", clientId) .param("client_secret", SECRET) .param("username", developer.getUserName()) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isOk()); //failure - missing client ID @@ -1401,7 +1401,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("client_secret", SECRET) .param("username", developer.getUserName()) .param("user_id", developer.getId()) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isUnauthorized()); //failure - invalid client secret @@ -1425,7 +1425,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("grant_type", "password") .param("username", developer.getUserName()) .param("user_id", developer.getId()) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isUnauthorized()); //failure - invalid user ID - user_id takes priority over username/origin so it must fail @@ -1439,7 +1439,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("client_secret", SECRET) .param("username", developer.getUserName()) .param("user_id", developer.getId() + "1dsda") - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isUnauthorized()); //failure - no user ID and an invalid origin must fail @@ -1452,7 +1452,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("client_id", clientId) .param("client_secret", SECRET) .param("username", developer.getUserName()) - .param(Origin.ORIGIN, developer.getOrigin() + "dasda")) + .param(OriginKeys.ORIGIN, developer.getOrigin() + "dasda")) .andExpect(status().isUnauthorized()); //failure - no user ID, invalid username must fail @@ -1465,7 +1465,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("client_id", clientId) .param("client_secret", SECRET) .param("username", developer.getUserName() + "asdasdas") - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isUnauthorized()); @@ -1479,7 +1479,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("client_id", clientId) .param("client_secret", SECRET) .param("username", developer.getUserName() + "AddNew" + (new RandomValueStringGenerator().generate())) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isOk()); //failure - pretend to be login server - add new user is false @@ -1492,7 +1492,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("client_id", clientId) .param("client_secret", SECRET) .param("username", developer.getUserName() + "AddNew" + (new RandomValueStringGenerator().generate())) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isUnauthorized()); //failure - source=login missing, so missing user password should trigger a failure @@ -1505,7 +1505,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("client_secret", SECRET) .param("username", developer.getUserName()) .param("user_id", developer.getId()) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isUnauthorized()); //failure - add_new is missing, so missing user password should trigger a failure @@ -1518,7 +1518,7 @@ public void testLoginAuthenticationFilter() throws Exception { .param("client_secret", SECRET) .param("username", developer.getUserName()) .param("user_id", developer.getId()) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isUnauthorized()); } @@ -1536,7 +1536,7 @@ public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { String userId = "testuser" + new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; - ScimUser developer = setUpUser(userId, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(userId, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String loginToken = testClient.getClientCredentialsOAuthAccessToken(oauthClientId, SECRET, ""); //failure - success only if token has oauth.login @@ -1550,7 +1550,7 @@ public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { .param("client_secret", SECRET) .param("username", developer.getUserName()) .param("user_id", developer.getId()) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); //failure - success only if token has oauth.login @@ -1575,7 +1575,7 @@ public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { .param("client_id", clientId) .param("client_secret", SECRET) .param("username", developer.getUserName()) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); //failure - missing client ID @@ -1600,7 +1600,7 @@ public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { .param("client_secret", SECRET) .param("username", developer.getUserName()) .param("user_id", developer.getId()) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); //failure - invalid client secret @@ -1624,7 +1624,7 @@ public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { .param("grant_type", "password") .param("username", developer.getUserName()) .param("user_id", developer.getId()) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); //failure - invalid user ID - user_id takes priority over username/origin so it must fail @@ -1638,7 +1638,7 @@ public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { .param("client_secret", SECRET) .param("username", developer.getUserName()) .param("user_id", developer.getId() + "1dsda") - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); //failure - no user ID and an invalid origin must fail @@ -1651,7 +1651,7 @@ public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { .param("client_id", clientId) .param("client_secret", SECRET) .param("username", developer.getUserName()) - .param(Origin.ORIGIN, developer.getOrigin() + "dasda")) + .param(OriginKeys.ORIGIN, developer.getOrigin() + "dasda")) .andExpect(status().isForbidden()); //failure - no user ID, invalid username must fail @@ -1664,7 +1664,7 @@ public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { .param("client_id", clientId) .param("client_secret", SECRET) .param("username", developer.getUserName() + "asdasdas") - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); @@ -1678,7 +1678,7 @@ public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { .param("client_id", clientId) .param("client_secret", SECRET) .param("username", developer.getUserName() + "AddNew" + (new RandomValueStringGenerator().generate())) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); //failure - pretend to be login server - add new user is false @@ -1691,7 +1691,7 @@ public void testOtherOauthResourceLoginAuthenticationFilter() throws Exception { .param("client_id", clientId) .param("client_secret", SECRET) .param("username", developer.getUserName() + "AddNew" + (new RandomValueStringGenerator().generate())) - .param(Origin.ORIGIN, developer.getOrigin())) + .param(OriginKeys.ORIGIN, developer.getOrigin())) .andExpect(status().isForbidden()); } @@ -1709,7 +1709,7 @@ public void testOtherClientAuthenticationMethods() throws Exception { String userId = "testuser" + new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; - ScimUser developer = setUpUser(userId, userScopes, Origin.UAA, IdentityZoneHolder.get().getId()); + ScimUser developer = setUpUser(userId, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); String loginToken = testClient.getClientCredentialsOAuthAccessToken(oauthClientId, SECRET, ""); //success - regular password grant but client is authenticated using POST parameters @@ -1870,7 +1870,7 @@ public void testGetPasswordGrantInvalidPassword() throws Exception { IdentityZoneHolder.clear(); String clientId = "testclient" + new RandomValueStringGenerator().generate(); String scopes = "cloud_controller.read"; - setUpClients(clientId, scopes, scopes, "password,client_credentials", true, TEST_REDIRECT_URI, Arrays.asList(Origin.UAA)); + setUpClients(clientId, scopes, scopes, "password,client_credentials", true, TEST_REDIRECT_URI, Arrays.asList(OriginKeys.UAA)); setUpUser(username); IdentityZoneHolder.clear(); getMockMvc().perform(post("/oauth/token") @@ -2021,7 +2021,7 @@ public void testGetTokenScopesNotInAuthentication() throws Exception { ScimGroupMember member = new ScimGroupMember(user.getId()); groupMembershipManager.addMember(group.getId(),member); - UaaPrincipal p = new UaaPrincipal(user.getId(),user.getUserName(),user.getPrimaryEmail(), Origin.UAA,"", IdentityZoneHolder.get().getId()); + UaaPrincipal p = new UaaPrincipal(user.getId(),user.getUserName(),user.getPrimaryEmail(), OriginKeys.UAA,"", IdentityZoneHolder.get().getId()); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); Assert.assertTrue(auth.isAuthenticated()); @@ -2077,7 +2077,7 @@ public void testRevocablePasswordGrantTokenForDefaultZone() throws Exception { String tokenKey = "access_token"; String clientId = "testclient" + new RandomValueStringGenerator().generate(); String scopes = "cloud_controller.read"; - setUpClients(clientId, scopes, scopes, "password,client_credentials", true, TEST_REDIRECT_URI, Arrays.asList(Origin.UAA)); + setUpClients(clientId, scopes, scopes, "password,client_credentials", true, TEST_REDIRECT_URI, Arrays.asList(OriginKeys.UAA)); setUpUser(username); Map tokenResponse = diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index ef224d443f0..760b4aa7b66 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -18,10 +18,10 @@ import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.RandomStringUtils; import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.invitations.InvitationsRequest; import org.cloudfoundry.identity.uaa.invitations.InvitationsResponse; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; @@ -252,11 +252,11 @@ public IdentityProvider createIdentityProvider(MockMvc mockMvc, IdentityZoneCrea provider.setName(nameAndOriginKey); provider.setOriginKey(nameAndOriginKey); if (definition instanceof SamlIdentityProviderDefinition) { - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); } else if (definition instanceof LdapIdentityProviderDefinition) { - provider.setType(Origin.LDAP); + provider.setType(OriginKeys.LDAP); } else if (definition instanceof UaaIdentityProviderDefinition) { - provider.setType(Origin.UAA); + provider.setType(OriginKeys.UAA); } provider = utils().createIdpUsingWebRequest(mockMvc, zone.getIdentityZone().getId(), @@ -301,7 +301,7 @@ public ZoneScimInviteData createZoneForInvites(MockMvc mockMvc, ApplicationConte public static void setDisableInternalUserManagement(boolean disableInternalUserManagement, ApplicationContext applicationContext) { IdentityProviderProvisioning identityProviderProvisioning = applicationContext.getBean(IdentityProviderProvisioning.class); - IdentityProvider idp = identityProviderProvisioning.retrieveByOrigin(Origin.UAA, "uaa"); + IdentityProvider idp = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, "uaa"); UaaIdentityProviderDefinition config = idp.getConfig(); if (config == null) { config = new UaaIdentityProviderDefinition(); @@ -363,7 +363,7 @@ public IdentityZoneCreationResult createOtherIdentityZoneAndReturnResult(String // use the identity client to grant the zones..admin scope to a user UaaUserDatabase db = webApplicationContext.getBean(UaaUserDatabase.class); - UaaPrincipal marissa = new UaaPrincipal(db.retrieveUserByName("marissa", Origin.UAA)); + UaaPrincipal marissa = new UaaPrincipal(db.retrieveUserByName("marissa", OriginKeys.UAA)); ScimGroup group = new ScimGroup(); String zoneAdminScope = "zones." + identityZone.getId() + ".admin"; group.setDisplayName(zoneAdminScope); @@ -643,7 +643,7 @@ public String getUserOAuthAccessTokenAuthCode(MockMvc mockMvc, String clientId, String basicDigestHeaderValue = "Basic " + new String(org.apache.commons.codec.binary.Base64.encodeBase64((clientId + ":" + clientSecret) .getBytes())); - UaaPrincipal p = new UaaPrincipal(userId, username, "test@test.org", Origin.UAA, "", IdentityZoneHolder.get() + UaaPrincipal p = new UaaPrincipal(userId, username, "test@test.org", OriginKeys.UAA, "", IdentityZoneHolder.get() .getId()); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java index f6ee3f6d859..5e3b26ec11a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.lang.RandomStringUtils; import org.cloudfoundry.identity.uaa.audit.AuditEventType; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.config.PasswordPolicy; import org.cloudfoundry.identity.uaa.login.saml.IdentityProviderConfiguratorTests; @@ -106,7 +107,7 @@ public void testCreateSamlProvider() throws Exception { provider.setActive(true); provider.setName(origin); provider.setIdentityZoneId(IdentityZone.getUaa().getId()); - provider.setType(Origin.SAML); + provider.setType(OriginKeys.SAML); provider.setOriginKey(origin); SamlIdentityProviderDefinition samlDefinition = new SamlIdentityProviderDefinition(metadata, null, null, 0, false, true, "Test SAML Provider", null, null); samlDefinition.setEmailDomain(Arrays.asList("test.com", "test2.com")); @@ -232,19 +233,19 @@ public void testUpdateIdentityProviderWithInsufficientScopes() throws Exception @Test public void testUpdateUaaIdentityProviderDoesUpdateOfPasswordPolicy() throws Exception { - IdentityProvider identityProvider = identityProviderProvisioning.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId()); + IdentityProvider identityProvider = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); long expireMonths = System.nanoTime() % 100L; PasswordPolicy newConfig = new PasswordPolicy(6,20,1,1,1,0,(int)expireMonths); - identityProvider.setConfig(new UaaIdentityProviderDefinition(newConfig,null)); + identityProvider.setConfig(new UaaIdentityProviderDefinition(newConfig, null)); String accessToken = setUpAccessToken(); updateIdentityProvider(null, identityProvider, accessToken, status().isOk()); - IdentityProvider modifiedIdentityProvider = identityProviderProvisioning.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId()); + IdentityProvider modifiedIdentityProvider = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); assertEquals(newConfig, ((UaaIdentityProviderDefinition)modifiedIdentityProvider.getConfig()).getPasswordPolicy()); } @Test public void testMalformedPasswordPolicyReturnsUnprocessableEntity() throws Exception { - IdentityProvider identityProvider = identityProviderProvisioning.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId()); + IdentityProvider identityProvider = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); PasswordPolicy policy = new PasswordPolicy().setMinLength(6); identityProvider.setConfig(new UaaIdentityProviderDefinition(policy,null)); String accessToken = setUpAccessToken(); @@ -283,7 +284,7 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Other_Zone() throws IdentityProvider identityProvider = MultitenancyFixture.identityProvider(origin1, zone.getId()); - identityProvider.setType(Origin.SAML); + identityProvider.setType(OriginKeys.SAML); SamlIdentityProviderDefinition providerDefinition = new SamlIdentityProviderDefinition( String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://www.okta.com/"+identityProvider.getOriginKey()), @@ -334,7 +335,7 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Default_Zone() throw IdentityProvider identityProvider = MultitenancyFixture.identityProvider(origin1, IdentityZone.getUaa().getId()); - identityProvider.setType(Origin.SAML); + identityProvider.setType(OriginKeys.SAML); SamlIdentityProviderDefinition providerDefinition = new SamlIdentityProviderDefinition( String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://www.okta.com/"+identityProvider.getOriginKey()), diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index b11cf63cc71..3a49bef9217 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -6,12 +6,12 @@ import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; import org.cloudfoundry.identity.uaa.audit.event.GroupModifiedEvent; import org.cloudfoundry.identity.uaa.audit.event.UserModifiedEvent; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.config.SamlConfig; import org.cloudfoundry.identity.uaa.config.TokenPolicy; import org.cloudfoundry.identity.uaa.config.KeyPair; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.IdentityZoneCreationResult; @@ -49,6 +49,7 @@ import java.util.Map; import java.util.UUID; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; @@ -239,6 +240,42 @@ public void testCreateZone() throws Exception { checkAuditEventListener(1, AuditEventType.IdentityZoneCreatedEvent, zoneModifiedEventListener, IdentityZone.getUaa().getId(), "http://localhost:8080/uaa/oauth/token", "identity"); } + @Test + public void createZoneWithNoNameFailsWithUnprocessableEntity() throws Exception { + String id = generator.generate(); + IdentityZone zone = this.getIdentityZone(id); + zone.setName(null); + + getMockMvc().perform( + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.error").value("invalid_identity_zone")) + .andExpect(jsonPath("$.error_description").value("The identity zone must be given a name.")); + + assertEquals(0, zoneModifiedEventListener.getEventCount()); + } + + @Test + public void createZoneWithNoSubdomainFailsWithUnprocessableEntity() throws Exception { + String id = generator.generate(); + IdentityZone zone = this.getIdentityZone(id); + zone.setSubdomain(null); + + getMockMvc().perform( + post("/identity-zones") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(zone))) + .andExpect(status().isUnprocessableEntity()) + .andExpect(jsonPath("$.error").value("invalid_identity_zone")) + .andExpect(jsonPath("$.error_description").value("The subdomain must be provided.")); + + assertEquals(0, zoneModifiedEventListener.getEventCount()); + } + @Test public void testCreateZoneInsufficientScope() throws Exception { String id = new RandomValueStringGenerator().generate(); @@ -402,8 +439,8 @@ public void testCreateZoneAndIdentityProvider() throws Exception { checkZoneAuditEventInUaa(1, AuditEventType.IdentityZoneCreatedEvent); IdentityProviderProvisioning idpp = (IdentityProviderProvisioning) getWebApplicationContext().getBean("identityProviderProvisioning"); - IdentityProvider idp1 = idpp.retrieveByOrigin(Origin.UAA, identityZone.getId()); - IdentityProvider idp2 = idpp.retrieveByOrigin(Origin.UAA, IdentityZone.getUaa().getId()); + IdentityProvider idp1 = idpp.retrieveByOrigin(OriginKeys.UAA, identityZone.getId()); + IdentityProvider idp2 = idpp.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); assertNotEquals(idp1, idp2); IdentityZoneProvisioning identityZoneProvisioning = (IdentityZoneProvisioning) getWebApplicationContext().getBean("identityZoneProvisioning"); @@ -421,7 +458,7 @@ public void testCreateAndDeleteLimitedClientInNewZoneUsingZoneEndpoint() throws BaseClientDetails client = new BaseClientDetails("limited-client", null, "openid", "authorization_code", "uaa.resource"); client.setClientSecret("secret"); - client.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(Origin.UAA)); + client.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA)); client.addAdditionalInformation("foo", "bar"); for (String url : Arrays.asList("","/")) { getMockMvc().perform( @@ -443,7 +480,7 @@ public void testCreateAndDeleteLimitedClientInNewZoneUsingZoneEndpoint() throws BaseClientDetails created = JsonUtils.readValue(result.getResponse().getContentAsString(), BaseClientDetails.class); assertNull(created.getClientSecret()); assertEquals("zones.write", created.getAdditionalInformation().get(ClientConstants.CREATED_WITH)); - assertEquals(Collections.singletonList(Origin.UAA), created.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS)); + assertEquals(Collections.singletonList(OriginKeys.UAA), created.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS)); assertEquals("bar", created.getAdditionalInformation().get("foo")); checkAuditEventListener(1, AuditEventType.ClientCreateSuccess, clientCreateEventListener, id, "http://localhost:8080/uaa/oauth/token", "identity"); @@ -468,7 +505,7 @@ public void testCreateAndDeleteLimitedClientInUAAZoneReturns403() throws Excepti BaseClientDetails client = new BaseClientDetails("limited-client", null, "openid", "authorization_code", "uaa.resource"); client.setClientSecret("secret"); - client.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(Origin.UAA)); + client.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA)); getMockMvc().perform( post("/identity-zones/uaa/clients") .header("Authorization", "Bearer " + identityClientToken) @@ -503,20 +540,6 @@ public void testCreateAdminClientInNewZoneUsingZoneEndpointReturns400() throws E .andExpect(status().isBadRequest()); } - @Test - public void testCreateInvalidZone() throws Exception { - IdentityZone identityZone = new IdentityZone(); - getMockMvc().perform( - post("/identity-zones") - .header("Authorization", "Bearer " + identityClientToken) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsString(identityZone))) - .andExpect(status().isBadRequest()); - - assertEquals(0, zoneModifiedEventListener.getEventCount()); - } - - @Test public void testCreatesZonesWithDuplicateSubdomains() throws Exception { String subdomain = UUID.randomUUID().toString(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointMockMvcTests.java index 2bb8d1606a5..458afaa2370 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointMockMvcTests.java @@ -13,11 +13,11 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; import com.fasterxml.jackson.core.type.TypeReference; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; import org.cloudfoundry.identity.uaa.codestore.JdbcExpiringCodeStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -90,7 +90,7 @@ public void changePassword_isSuccessful() throws Exception { assertThat(data.get("user_id"), is(user.getId())); assertThat(data.get("username"), is(user.getUserName())); assertThat(data.get(OAuth2Utils.CLIENT_ID), is("login")); - assertThat(data.get(Origin.ORIGIN), is(Origin.UAA)); + assertThat(data.get(OriginKeys.ORIGIN), is(OriginKeys.UAA)); assertThat(data.get("action"), is(ExpiringCodeType.AUTOLOGIN.name())); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index c6f180986d3..2f413d190d7 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.endpoints; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.rest.SearchResults; @@ -850,7 +850,7 @@ protected void validateMembers(List expected, ScimGroupExternalMember[] String externalId = data[1]; ScimGroupExternalMember mbr = new ScimGroupExternalMember("N/A", externalId); mbr.setDisplayName(displayName); - mbr.setOrigin(Origin.LDAP); + mbr.setOrigin(OriginKeys.LDAP); members.add(mbr); } validateDbMembers(members, actual); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java index f4ee547983e..213073c3e11 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.endpoints; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -277,7 +277,7 @@ private void validateLookupResults(String[] usernames, String body) throws java. List> resources = (List>) map.get("resources"); assertEquals(usernames.length, resources.size()); for (Map user : resources) { - assertTrue("Response should contain 'origin' object", user.get(Origin.ORIGIN)!=null); + assertTrue("Response should contain 'origin' object", user.get(OriginKeys.ORIGIN)!=null); assertTrue("Response should contain 'id' object", user.get("id")!=null); assertTrue("Response should contain 'userName' object", user.get("userName")!=null); String userName = (String)user.get("userName"); From ba50cb8d63dcad790ef89f9194bd5c620628fb16 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 17 Nov 2015 13:31:03 -0800 Subject: [PATCH 003/103] Move SCIM and Approval into the payload project - Move serializers into impl package [#107504490] https://www.pivotaltracker.com/story/show/107504490 Signed-off-by: Paul Warren Signed-off-by: Madhura Bhave Signed-off-by: Paul Warren --- .../uaa/audit/event/ApprovalModifiedEvent.java | 2 -- .../config/IdentityZoneConfigurationBootstrap.java | 2 ++ .../uaa/login/saml/ZoneAwareMetadataGenerator.java | 2 +- .../uaa/oauth/UserManagedAuthzApprovalHandler.java | 2 -- .../identity/uaa/oauth/token/UaaTokenServices.java | 4 ++-- .../uaa/zone/JdbcIdentityZoneProvisioning.java | 1 - .../uaa/audit/event/ApprovalModifiedEventTest.java | 2 +- .../IdentityZoneConfigurationBootstrapTests.java | 2 ++ .../uaa/config/IdentityZoneConfigurationTests.java | 3 ++- .../login/saml/ZoneAwareMetadataGeneratorTests.java | 4 ++-- .../identity/uaa/oauth/CheckTokenEndpointTests.java | 4 ++-- .../uaa/oauth/token/UaaTokenServicesTests.java | 4 ++-- .../uaa/zone/JdbcIdentityZoneProvisioningTests.java | 2 -- docs/UAA-APIs.rst | 12 ++++++------ .../identity/uaa/constants/OriginKeys.java | 3 --- .../identity/uaa/impl}/JsonDateDeserializer.java | 5 ++--- .../identity/uaa/impl}/JsonDateSerializer.java | 4 ++-- .../identity/uaa/oauth/approval/Approval.java | 5 +++-- .../approval/impl}/ApprovalsJsonDeserializer.java | 4 ++-- .../org/cloudfoundry/identity/uaa/scim/ScimCore.java | 0 .../cloudfoundry/identity/uaa/scim/ScimGroup.java | 2 ++ .../identity/uaa/scim/ScimGroupExternalMember.java | 0 .../identity/uaa/scim/ScimGroupMember.java | 0 .../org/cloudfoundry/identity/uaa/scim/ScimMeta.java | 6 +++--- .../org/cloudfoundry/identity/uaa/scim/ScimUser.java | 3 ++- .../uaa/scim/impl}/ScimGroupJsonDeserializer.java | 5 ++++- .../uaa/scim/impl}/ScimGroupJsonSerializer.java | 4 +++- .../uaa/scim/impl}/ScimUserJsonDeserializer.java | 6 ++++-- .../cloudfoundry/identity/uaa/zone/IdentityZone.java | 1 - .../{config => zone}/IdentityZoneConfiguration.java | 2 +- .../identity/uaa/{config => zone}/KeyPair.java | 2 +- .../identity/uaa/{config => zone}/KeyPairsMap.java | 2 +- .../identity/uaa/{config => zone}/SamlConfig.java | 2 +- .../identity/uaa/{config => zone}/TokenPolicy.java | 2 +- scim/build.gradle | 4 +++- .../uaa/scim/ScimGroupMembershipManager.java | 2 +- .../uaa/scim/ScimExceptionStatusCodeMatcher.java | 2 +- .../main/webapp/WEB-INF/spring/oauth-endpoints.xml | 6 +++--- .../identity/uaa/login/BootstrapTests.java | 4 ++-- .../uaa/login/saml/SamlIDPRefreshMockMvcTests.java | 4 ++-- .../uaa/mock/audit/AuditCheckMockMvcTests.java | 3 --- .../clients/ClientAdminEndpointsMockMvcTests.java | 6 ------ .../zones/IdentityZoneEndpointsMockMvcTests.java | 8 ++++---- 43 files changed, 70 insertions(+), 73 deletions(-) rename {common/src/main/java/org/cloudfoundry/identity/uaa/util/json => payload/src/main/java/org/cloudfoundry/identity/uaa/impl}/JsonDateDeserializer.java (94%) rename {common/src/main/java/org/cloudfoundry/identity/uaa/util/json => payload/src/main/java/org/cloudfoundry/identity/uaa/impl}/JsonDateSerializer.java (96%) rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java (96%) rename {common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval => payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl}/ApprovalsJsonDeserializer.java (95%) rename {scim => payload}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java (100%) rename {scim => payload}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java (93%) rename {scim => payload}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java (100%) rename {scim => payload}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java (100%) rename {scim => payload}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java (93%) rename {scim => payload}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java (99%) rename {scim/src/main/java/org/cloudfoundry/identity/uaa/scim => payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl}/ScimGroupJsonDeserializer.java (94%) rename {scim/src/main/java/org/cloudfoundry/identity/uaa/scim => payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl}/ScimGroupJsonSerializer.java (94%) rename {scim/src/main/java/org/cloudfoundry/identity/uaa/scim => payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl}/ScimUserJsonDeserializer.java (96%) rename payload/src/main/java/org/cloudfoundry/identity/uaa/{config => zone}/IdentityZoneConfiguration.java (96%) rename payload/src/main/java/org/cloudfoundry/identity/uaa/{config => zone}/KeyPair.java (97%) rename payload/src/main/java/org/cloudfoundry/identity/uaa/{config => zone}/KeyPairsMap.java (96%) rename payload/src/main/java/org/cloudfoundry/identity/uaa/{config => zone}/SamlConfig.java (97%) rename payload/src/main/java/org/cloudfoundry/identity/uaa/{config => zone}/TokenPolicy.java (97%) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEvent.java b/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEvent.java index e1032c3bfd7..09ffefa99bc 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEvent.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEvent.java @@ -20,8 +20,6 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.security.core.Authentication; -import java.io.IOException; - public class ApprovalModifiedEvent extends AbstractUaaEvent { private final Log logger = LogFactory.getLog(getClass()); diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrap.java b/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrap.java index acdb42fbacc..2beaf0588c7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrap.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrap.java @@ -13,7 +13,9 @@ package org.cloudfoundry.identity.uaa.config; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.springframework.beans.factory.InitializingBean; public class IdentityZoneConfigurationBootstrap implements InitializingBean { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGenerator.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGenerator.java index d48a367499a..87cae94fdeb 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGenerator.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGenerator.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.login.saml; -import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java index e81cd6959d1..d9aedf6cc59 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -32,7 +31,6 @@ import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; -import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.cloudfoundry.identity.uaa.util.UaaTokenUtils; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.util.OAuth2Utils; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java index 3d0a6648a6e..a0b80e1e3b1 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java @@ -20,8 +20,8 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.client.ClientConstants; -import org.cloudfoundry.identity.uaa.config.TokenPolicy; -import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java index be54117919a..7483cd9a339 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java @@ -14,7 +14,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.EmptyResultDataAccessException; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java index d064c332ae7..74527019714 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java @@ -24,4 +24,4 @@ public void testAuditEvent() throws Exception { Assert.assertEquals("{\"scope\":\"cloud_controller.read\",\"status\":\"APPROVED\"}", auditEvent.getData()); Assert.assertEquals(AuditEventType.ApprovalModifiedEvent, auditEvent.getType()); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index b9e8101fe3b..15e5d5d5539 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -2,8 +2,10 @@ import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.junit.Assert; import org.junit.Test; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java index 608b37c416c..e6482e8e2b3 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java @@ -15,6 +15,7 @@ package org.cloudfoundry.identity.uaa.config; import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.junit.Before; import org.junit.Test; @@ -64,4 +65,4 @@ public void testDeserialize_Without_SamlConfig() { assertTrue(definition.getSamlConfig().isWantAssertionSigned()); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGeneratorTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGeneratorTests.java index 1c498e13ba7..c3def7e641c 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGeneratorTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGeneratorTests.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.login.saml; -import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.After; @@ -69,4 +69,4 @@ public void test_request_and_want_assertion_signed_in_another_zone() { assertTrue(generator.isRequestSigned()); assertTrue(generator.isWantAssertionSigned()); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java index 0b3072ec45e..2b614dadb0d 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java @@ -14,9 +14,9 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; import org.cloudfoundry.identity.uaa.client.ClientConstants; -import org.cloudfoundry.identity.uaa.config.TokenPolicy; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.oauth.approval.InMemoryApprovalStore; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java index 4980bf3bcba..27e0c6a0142 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java @@ -18,8 +18,8 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.client.ClientConstants; -import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.config.TokenPolicy; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java index 237f6986ddd..1275717b92b 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java @@ -1,7 +1,5 @@ package org.cloudfoundry.identity.uaa.zone; -import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.config.TokenPolicy; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.junit.Before; import org.junit.Test; diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 331752e7992..1a9bc383587 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -772,23 +772,23 @@ Fields *Available Fields* :: created epoch timestamp Auto UAA sets the creation date last_modified epoch timestamp Auto UAA sets the modification date - Identity Zone Configuration (provided in JSON format as part of the ``config`` field on the Identity Zone - See class org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration) + Identity Zone Configuration (provided in JSON format as part of the ``config`` field on the Identity Zone - See class org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration) ===================== ==================== ======== ======================================================================================================================================================================== tokenPolicy TokenPolicy Optional Various fields pertaining to the JWT access and refresh tokens. See `Token Policy` section below for details. samlConfig SamlConfig Optional Various fields pertaining to SAML identity provider configuration. See ``SamlConfig`` section below for details. - Token Policy ``TokenPolicy`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.config.TokenPolicy) + Token Policy ``TokenPolicy`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.TokenPolicy) ===================== ==================== ======== ======================================================================================================================================================================== accessTokenValidity int Optional How long the access token is valid for in seconds. refreshTokenValidity int Optional How long the refresh token is valid for seconds. keys Map Optional Signing key and Verification key for generating a token, along with an associated identifier for each key pair. See below for more detail regarding `KeyPair`. - - Signing and Verification Keys ``KeyPair`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.config.KeyPair) + + Signing and Verification Keys ``KeyPair`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.KeyPair) ===================== ==================== ======== ======================================================================================================================================================================== signingKey String Optional JWT signing key. Can be either a simple MAC key or an RSA key in OpenSSH format. verificationKey String Optional Required for RSA signing. - - SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.config.SamlConfig) + + SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig) ===================== ==================== ======== ======================================================================================================================================================================== requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``false``. wantAssertionSigned Boolean Optional Exposed SAML metadata property. If ``true``, all assertions received by the SAML provider must be signed. Defaults to ``false``. diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java index 13670641994..fb89ab7b66d 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java @@ -14,9 +14,6 @@ package org.cloudfoundry.identity.uaa.constants; -/** - * Created by pivotal on 11/16/15. - */ public final class OriginKeys { private OriginKeys() {} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/json/JsonDateDeserializer.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/json/JsonDateDeserializer.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java index b8de1a66547..acd043098dd 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/util/json/JsonDateDeserializer.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java @@ -10,12 +10,11 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.util.json; +package org.cloudfoundry.identity.uaa.impl; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -48,4 +47,4 @@ public static Date getDate(String text, JsonLocation loc) throws IOException { } } -} \ No newline at end of file +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/json/JsonDateSerializer.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/json/JsonDateSerializer.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java index 7186a0afc40..dcb3836de1a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/util/json/JsonDateSerializer.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java @@ -10,7 +10,7 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.util.json; +package org.cloudfoundry.identity.uaa.impl; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; @@ -39,4 +39,4 @@ public void serialize(Date date, JsonGenerator generator, SerializerProvider pro generator.writeString(formatted); } -} \ No newline at end of file +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java index a100db7076c..3818c936b60 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java @@ -19,8 +19,9 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.cloudfoundry.identity.uaa.util.json.JsonDateDeserializer; -import org.cloudfoundry.identity.uaa.util.json.JsonDateSerializer; +import org.cloudfoundry.identity.uaa.impl.JsonDateDeserializer; +import org.cloudfoundry.identity.uaa.impl.JsonDateSerializer; +import org.cloudfoundry.identity.uaa.oauth.approval.impl.ApprovalsJsonDeserializer; @JsonInclude(JsonInclude.Include.NON_NULL) @JsonDeserialize(using = ApprovalsJsonDeserializer.class) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsJsonDeserializer.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsJsonDeserializer.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java index ae2d0489e02..4f24dbb4ddd 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsJsonDeserializer.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java @@ -1,10 +1,10 @@ -package org.cloudfoundry.identity.uaa.oauth.approval; +package org.cloudfoundry.identity.uaa.oauth.approval.impl; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; import java.io.IOException; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java similarity index 93% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java index 53ad93218a2..71a20f5d687 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java @@ -14,6 +14,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.cloudfoundry.identity.uaa.scim.impl.ScimGroupJsonDeserializer; +import org.cloudfoundry.identity.uaa.scim.impl.ScimGroupJsonSerializer; import java.util.List; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java similarity index 93% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java index a37d27b90c1..fd96631b260 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java @@ -17,8 +17,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.cloudfoundry.identity.uaa.util.json.JsonDateDeserializer; -import org.cloudfoundry.identity.uaa.util.json.JsonDateSerializer; +import org.cloudfoundry.identity.uaa.impl.JsonDateDeserializer; +import org.cloudfoundry.identity.uaa.impl.JsonDateSerializer; @JsonInclude(JsonInclude.Include.NON_NULL) public class ScimMeta { @@ -64,4 +64,4 @@ public void setVersion(int version) { public int getVersion() { return version; } -} \ No newline at end of file +} diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java similarity index 99% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java index 23537b2e13f..f0191218f8b 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java @@ -25,7 +25,8 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; -import org.cloudfoundry.identity.uaa.util.json.JsonDateSerializer; +import org.cloudfoundry.identity.uaa.impl.JsonDateSerializer; +import org.cloudfoundry.identity.uaa.scim.impl.ScimUserJsonDeserializer; import org.springframework.util.Assert; import org.springframework.util.StringUtils; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupJsonDeserializer.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java similarity index 94% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupJsonDeserializer.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java index 912998e3dfb..ffd6c02d377 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupJsonDeserializer.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java @@ -10,13 +10,16 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.scim; +package org.cloudfoundry.identity.uaa.scim.impl; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; +import org.cloudfoundry.identity.uaa.scim.ScimGroup; +import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; +import org.cloudfoundry.identity.uaa.scim.ScimMeta; import java.io.IOException; import java.util.ArrayList; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupJsonSerializer.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java similarity index 94% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupJsonSerializer.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java index ddcc1819999..8382a363ad8 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupJsonSerializer.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java @@ -10,12 +10,14 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.scim; +package org.cloudfoundry.identity.uaa.scim.impl; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; +import org.cloudfoundry.identity.uaa.scim.ScimGroup; +import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import java.io.IOException; import java.util.ArrayList; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserJsonDeserializer.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java similarity index 96% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserJsonDeserializer.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java index 7a474a0a718..88ec2553f5e 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserJsonDeserializer.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java @@ -10,7 +10,7 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.scim; +package org.cloudfoundry.identity.uaa.scim.impl; import java.io.IOException; import java.util.Arrays; @@ -25,7 +25,9 @@ import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; -import org.cloudfoundry.identity.uaa.util.json.JsonDateDeserializer; +import org.cloudfoundry.identity.uaa.impl.JsonDateDeserializer; +import org.cloudfoundry.identity.uaa.scim.ScimMeta; +import org.cloudfoundry.identity.uaa.scim.ScimUser; public class ScimUserJsonDeserializer extends JsonDeserializer { @Override diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java index 5d52adb1067..ef2a430291c 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java @@ -13,7 +13,6 @@ package org.cloudfoundry.identity.uaa.zone; import com.fasterxml.jackson.annotation.JsonProperty; -import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import javax.validation.constraints.NotNull; diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java similarity index 96% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java index 387b494f0c2..3dca8b403d8 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.zone; public class IdentityZoneConfiguration { private TokenPolicy tokenPolicy = new TokenPolicy(); diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java similarity index 97% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java index 3181b9adeab..73a9072bfff 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java @@ -12,7 +12,7 @@ * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.zone; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java similarity index 96% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java index d178953b068..355ae0009ce 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java @@ -12,7 +12,7 @@ * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.zone; import java.util.HashMap; import java.util.Map; diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java similarity index 97% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java index a3dd7da44c2..347ae1db043 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java @@ -12,7 +12,7 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.zone; public class SamlConfig { private boolean requestSigned = false; diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java similarity index 97% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java index 18fff7785a6..68b0fd23474 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.zone; import java.util.Map; diff --git a/scim/build.gradle b/scim/build.gradle index a575cb275a1..d5c2d52a740 100644 --- a/scim/build.gradle +++ b/scim/build.gradle @@ -1,9 +1,11 @@ Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } +Project identityPayload = parent.subprojects.find { it.name.equals('cloudfoundry-identity-payload') } description = 'CloudFoundry Identity SCIM' dependencies { compile identityCommon + compile identityPayload compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version:'2.1.2.RELEASE' provided group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' testCompile identityCommon.configurations.testCompile.dependencies @@ -25,4 +27,4 @@ project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> classpath = files(test.classpath.collect(rewriteInstrumentedLibs)) } } -} \ No newline at end of file +} diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java index a5f51dd5afe..ec66f3c2b63 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java @@ -73,7 +73,7 @@ List getMembers(String groupId, ScimGroupMember.Role permission * @param memberId * @return * @throws ScimResourceNotFoundException - * @throws org.cloudfoundry.identity.uaa.scim.exception.MemberNotFoundException + * @throws MemberNotFoundException */ ScimGroupMember getMemberById(String groupId, String memberId) throws ScimResourceNotFoundException, MemberNotFoundException; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimExceptionStatusCodeMatcher.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimExceptionStatusCodeMatcher.java index 83dea4300ce..838c5ba5bb4 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimExceptionStatusCodeMatcher.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimExceptionStatusCodeMatcher.java @@ -38,4 +38,4 @@ public void describeTo(Description description) { public boolean matchesSafely(ScimException e) { return e.getStatus() == status; } -} \ No newline at end of file +} diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml index b941eeef800..92644eff12b 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml @@ -297,19 +297,19 @@ - + - + - + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index a7f088728f5..a0491b2d643 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -17,10 +17,10 @@ import org.apache.tomcat.jdbc.pool.DataSource; import org.cloudfoundry.identity.uaa.authentication.login.Prompt; import org.cloudfoundry.identity.uaa.authentication.manager.PeriodLockoutPolicy; -import org.cloudfoundry.identity.uaa.config.KeyPair; +import org.cloudfoundry.identity.uaa.zone.KeyPair; import org.cloudfoundry.identity.uaa.config.LockoutPolicy; import org.cloudfoundry.identity.uaa.config.PasswordPolicy; -import org.cloudfoundry.identity.uaa.config.TokenPolicy; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.config.YamlServletProfileInitializer; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java index 3175a0dab38..d6c84199dd1 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java @@ -12,8 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login.saml; -import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.config.SamlConfig; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java index 081bb023e57..c1917943ae4 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java @@ -41,7 +41,6 @@ import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordChange; import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordReset; import org.cloudfoundry.identity.uaa.scim.event.ScimEventPublisher; import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; @@ -54,7 +53,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.http.MediaType; @@ -79,7 +77,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java index aaf920f7ce7..0a0e0a71027 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.InvalidClientDetailsException; @@ -27,11 +26,7 @@ import org.cloudfoundry.identity.uaa.scim.endpoints.ScimUserEndpoints; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; -import org.cloudfoundry.identity.uaa.user.UaaAuthority; -import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.util.UaaStringUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -53,7 +48,6 @@ import java.util.Date; import java.util.LinkedHashSet; import java.util.Map; -import java.util.UUID; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 3a49bef9217..e5acb023826 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -7,10 +7,10 @@ import org.cloudfoundry.identity.uaa.audit.event.GroupModifiedEvent; import org.cloudfoundry.identity.uaa.audit.event.UserModifiedEvent; import org.cloudfoundry.identity.uaa.client.ClientConstants; -import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.config.SamlConfig; -import org.cloudfoundry.identity.uaa.config.TokenPolicy; -import org.cloudfoundry.identity.uaa.config.KeyPair; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; +import org.cloudfoundry.identity.uaa.zone.KeyPair; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; From 0bca088b694a6e28497ad811e4f6c40a2d337136 Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Tue, 17 Nov 2015 17:53:33 -0800 Subject: [PATCH 004/103] Move profile related classes to payload project [#107504490] https://www.pivotaltracker.com/story/show/107504490 Signed-off-by: Madhura Bhave --- .../identity/uaa/zone/IdentityProvider.java | 7 -- login/build.gradle | 2 + .../identity/uaa/profile/EmailChange.java | 52 ++++++++++++ .../uaa/profile/EmailChangeResponse.java | 64 ++++++++++++++ .../identity/uaa/profile}/PasswordReset.java | 2 +- .../scim/endpoints/ChangeEmailEndpoints.java | 84 +------------------ .../scim/endpoints/PasswordResetEndpoint.java | 1 + .../scim/ScimExceptionStatusCodeMatcher.java | 41 --------- .../uaa/scim/ScimGroupMemberTests.java | 1 - .../mock/audit/AuditCheckMockMvcTests.java | 2 +- ...erManagementSecurityFilterMockMvcTest.java | 4 +- 11 files changed, 125 insertions(+), 135 deletions(-) create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java rename {scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints => payload/src/main/java/org/cloudfoundry/identity/uaa/profile}/PasswordReset.java (92%) delete mode 100644 scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimExceptionStatusCodeMatcher.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java index 073f5d8e695..809448af996 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java @@ -14,7 +14,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; @@ -29,7 +28,6 @@ import org.cloudfoundry.identity.uaa.KeystoneIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.config.LockoutPolicy; import org.cloudfoundry.identity.uaa.config.PasswordPolicy; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -294,11 +292,6 @@ public String toString() { sb.append(", name='").append(name).append('\''); sb.append(", type='").append(type).append('\''); sb.append(", active=").append(active); -// sb.append(", config='").append(config).append('\''); -// sb.append(", version=").append(version); -// sb.append(", created=").append(created); -// sb.append(", lastModified=").append(lastModified); -// sb.append(", identityZoneId='").append(identityZoneId).append('\''); sb.append('}'); return sb.toString(); } diff --git a/login/build.gradle b/login/build.gradle index 47aa40c7135..85ba143616c 100644 --- a/login/build.gradle +++ b/login/build.gradle @@ -1,5 +1,6 @@ Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } Project identityScim = parent.subprojects.find { it.name.equals('cloudfoundry-identity-scim') } +Project identityPayload = parent.subprojects.find { it.name.equals('cloudfoundry-identity-payload') } description = 'CloudFoundry Identity Login' @@ -10,6 +11,7 @@ dependencies { compile(identityScim) { exclude(module: 'jna') } + compile(identityPayload) compile group: 'org.springframework', name: 'spring-context-support', version:springVersion provided group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java new file mode 100644 index 00000000000..6f3c88c6f45 --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java @@ -0,0 +1,52 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.profile; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class EmailChange { + @JsonProperty("userId") + private String userId; + + @JsonProperty("email") + private String email; + + @JsonProperty("client_id") + private String clientId; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } +} diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java new file mode 100644 index 00000000000..b0308e0cb0b --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java @@ -0,0 +1,64 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.profile; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class EmailChangeResponse { + @JsonProperty("username") + private String username; + + + @JsonProperty("userId") + private String userId; + + @JsonProperty("redirect_url") + private String redirectUrl; + + @JsonProperty("email") + private String email; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public void setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + } +} diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordReset.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordReset.java similarity index 92% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordReset.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordReset.java index 43f8122834b..e10b88b6066 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordReset.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordReset.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.scim.endpoints; +package org.cloudfoundry.identity.uaa.profile; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java index 1fc4feb8eab..fdfbefab5a5 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java @@ -1,12 +1,13 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.audit.event.UserModifiedEvent; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.profile.EmailChange; import org.cloudfoundry.identity.uaa.error.UaaException; +import org.cloudfoundry.identity.uaa.profile.EmailChangeResponse; import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; @@ -110,85 +111,4 @@ public void setApplicationEventPublisher(ApplicationEventPublisher applicationEv this.publisher = applicationEventPublisher; } - public static class EmailChange { - @JsonProperty("userId") - private String userId; - - @JsonProperty("email") - private String email; - - @JsonProperty("client_id") - private String clientId; - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getClientId() { - return clientId; - } - - public void setClientId(String clientId) { - this.clientId = clientId; - } - } - - public static class EmailChangeResponse { - @JsonProperty("username") - private String username; - - - @JsonProperty("userId") - private String userId; - - @JsonProperty("redirect_url") - private String redirectUrl; - - @JsonProperty("email") - private String email; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getRedirectUrl() { - return redirectUrl; - } - - public void setRedirectUrl(String redirectUrl) { - this.redirectUrl = redirectUrl; - } - } } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java index e9b35f59632..c7a3423d1c7 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java @@ -24,6 +24,7 @@ import org.cloudfoundry.identity.uaa.login.NotFoundException; import org.cloudfoundry.identity.uaa.login.ResetPasswordService; import org.cloudfoundry.identity.uaa.login.ResetPasswordService.ResetPasswordResponse; +import org.cloudfoundry.identity.uaa.profile.PasswordReset; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.scim.exception.ScimException; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimExceptionStatusCodeMatcher.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimExceptionStatusCodeMatcher.java deleted file mode 100644 index 838c5ba5bb4..00000000000 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimExceptionStatusCodeMatcher.java +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.scim; - -import org.cloudfoundry.identity.uaa.scim.exception.ScimException; -import org.hamcrest.Description; -import org.junit.internal.matchers.TypeSafeMatcher; -import org.springframework.http.HttpStatus; - -/** - * @author Dave Syer - * - */ -public class ScimExceptionStatusCodeMatcher extends TypeSafeMatcher { - - private final HttpStatus status; - - public ScimExceptionStatusCodeMatcher(HttpStatus status) { - this.status = status; - } - - @Override - public void describeTo(Description description) { - description.appendText("exception has status code ").appendValue(status); - } - - @Override - public boolean matchesSafely(ScimException e) { - return e.getStatus() == status; - } -} diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMemberTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMemberTests.java index 637b07dd821..c6abf9842ef 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMemberTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMemberTests.java @@ -17,7 +17,6 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.Test; public class ScimGroupMemberTests { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java index c1917943ae4..62d09e1c43b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java @@ -41,7 +41,7 @@ import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordReset; +import org.cloudfoundry.identity.uaa.profile.PasswordReset; import org.cloudfoundry.identity.uaa.scim.event.ScimEventPublisher; import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; import org.cloudfoundry.identity.uaa.test.TestClient; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java index 3cda8e8ac98..11bdd02f913 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java @@ -2,11 +2,11 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.profile.EmailChange; import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.scim.endpoints.ChangeEmailEndpoints; import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordChange; import org.cloudfoundry.identity.uaa.scim.test.JsonObjectMatcherUtils; import org.cloudfoundry.identity.uaa.test.TestClient; @@ -295,7 +295,7 @@ public void changeEmailControllerVerifyEmailNotAllowed() throws Exception { ResultActions result = createUser(); ScimUser createdUser = JsonUtils.readValue(result.andReturn().getResponse().getContentAsString(), ScimUser.class); - ChangeEmailEndpoints.EmailChange change = new ChangeEmailEndpoints.EmailChange(); + EmailChange change = new EmailChange(); change.setClientId("login"); change.setEmail(createdUser.getUserName()); change.setUserId(createdUser.getId()); From ae104ed6aa49cac88bb6bf4d1fdbac9603723911 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Wed, 18 Nov 2015 11:31:04 -0800 Subject: [PATCH 005/103] Move IdentityProvider into payload project [#107504490] https://www.pivotaltracker.com/story/show/107504490 Signed-off-by: Paul Warren --- .../login/LoginInfoEndpoint.java | 2 +- .../manager/AuthzAuthenticationManager.java | 4 +- .../CheckIdpEnabledAuthenticationManager.java | 2 +- .../LdapLoginAuthenticationManager.java | 4 +- .../manager/PeriodLockoutPolicy.java | 8 +- .../uaa/config/IdentityProviderBootstrap.java | 24 +-- .../saml/LoginSamlAuthenticationToken.java | 2 +- .../uaa/login/saml/LoginSamlEntryPoint.java | 1 + .../login/saml/ProviderChangedListener.java | 4 +- .../SamlIdentityProviderConfigurator.java | 7 +- .../uaa/login/saml/SamlRedirectUtils.java | 1 + .../login/saml/ZoneAwareMetadataManager.java | 3 +- .../oauth/UaaAuthorizationRequestManager.java | 2 +- .../identity/uaa/util/DomainFilter.java | 2 +- .../identity/uaa/util/LdapUtils.java | 160 ++++++++++++++++++ .../DisableInternalUserManagementFilter.java | 2 + .../DisableUserManagementSecurityFilter.java | 2 + .../zone/IdentityProviderProvisioning.java | 2 + .../IdentityProviderValidationRequest.java | 1 + .../uaa/zone/IdentityZoneEndpoints.java | 2 + .../JdbcIdentityProviderProvisioning.java | 11 +- .../event/IdentityProviderEventPublisher.java | 4 +- .../event/IdentityProviderModifiedEvent.java | 2 +- .../login/LoginInfoEndpointTests.java | 2 +- .../AuthzAuthenticationManagerTests.java | 6 +- ...ckIdpEnabledAuthenticationManagerTest.java | 2 +- .../LdapLoginAuthenticationManagerTests.java | 6 +- .../manager/PeriodLockoutPolicyTests.java | 6 +- .../config/IdentityProviderBootstrapTest.java | 19 ++- .../uaa/config/PasswordPolicyTest.java | 1 + .../LdapIdentityProviderDefinitionTest.java | 14 +- .../IdentityProviderConfiguratorTests.java | 3 +- .../SamlIdentityProviderDefinitionTests.java | 9 +- .../uaa/login/saml/SamlRedirectUtilsTest.java | 3 +- .../identity/uaa/util/DomainFilterTest.java | 8 +- ...JdbcIdentityProviderProvisioningTests.java | 6 +- .../uaa/zone/MultitenancyFixture.java | 2 + docs/UAA-APIs.rst | 6 +- .../DynamicLdapAuthenticationManager.java | 6 +- ...DynamicZoneAwareAuthenticationManager.java | 4 +- .../invitations/InvitationsController.java | 4 +- .../uaa/invitations/InvitationsEndpoint.java | 2 +- .../saml/LoginSamlAuthenticationProvider.java | 13 +- .../uaa/zone/IdentityProviderEndpoints.java | 5 +- .../InvitationsControllerTest.java | 4 +- .../AbstractIdentityProviderDefinition.java | 11 +- .../ExternalIdentityProviderDefinition.java | 2 +- .../uaa/provider}/IdentityProvider.java | 8 +- .../KeystoneIdentityProviderDefinition.java | 2 +- .../LdapIdentityProviderDefinition.java | 131 +------------- .../identity/uaa/provider}/LockoutPolicy.java | 2 +- .../uaa/provider}/PasswordPolicy.java | 2 +- .../SamlIdentityProviderDefinition.java | 25 +-- .../UaaIdentityProviderDefinition.java | 8 +- .../identity/uaa/util/JsonUtils.java | 0 .../identity/uaa/util/ObjectUtils.java | 0 .../endpoints/UserIdConversionEndpoints.java | 2 +- .../validate/UaaPasswordPolicyValidator.java | 6 +- .../jdbc/JdbcScimUserProvisioningTests.java | 2 +- .../UaaPasswordPolicyValidatorTests.java | 6 +- .../webapp/WEB-INF/spring/oauth-endpoints.xml | 4 +- .../webapp/WEB-INF/spring/scim-endpoints.xml | 4 +- .../DynamicLdapAuthenticationManagerTest.java | 4 +- ...micZoneAwareAuthenticationManagerTest.java | 4 +- ...IdentityZoneEndpointsIntegrationTests.java | 4 +- .../uaa/integration/LdapIntegationTests.java | 6 +- .../uaa/integration/feature/SamlLoginIT.java | 14 +- .../util/IntegrationTestUtils.java | 4 +- .../InvitationsEndpointMockMvcTests.java | 5 +- .../identity/uaa/login/BootstrapTests.java | 6 +- .../login/InvitationsServiceMockMvcTests.java | 8 +- .../identity/uaa/login/LoginMockMvcTests.java | 8 +- .../LoginSamlAuthenticationProviderTests.java | 9 +- .../saml/SamlIDPRefreshMockMvcTests.java | 3 +- .../uaa/mock/config/LockoutPolicyTests.java | 2 +- .../uaa/mock/ldap/LdapMockMvcTests.java | 8 +- .../uaa/mock/token/TokenMvcMockTests.java | 6 +- .../identity/uaa/mock/util/MockMvcUtils.java | 10 +- ...IdentityProviderEndpointsMockMvcTests.java | 9 +- .../IdentityZoneEndpointsMockMvcTests.java | 2 +- .../endpoints/ScimUserLookupMockMvcTests.java | 2 +- 81 files changed, 373 insertions(+), 339 deletions(-) create mode 100644 common/src/main/java/org/cloudfoundry/identity/uaa/util/LdapUtils.java rename {common/src/main/java/org/cloudfoundry/identity/uaa => payload/src/main/java/org/cloudfoundry/identity/uaa/provider}/AbstractIdentityProviderDefinition.java (84%) rename {common/src/main/java/org/cloudfoundry/identity/uaa => payload/src/main/java/org/cloudfoundry/identity/uaa/provider}/ExternalIdentityProviderDefinition.java (98%) rename {common/src/main/java/org/cloudfoundry/identity/uaa/zone => payload/src/main/java/org/cloudfoundry/identity/uaa/provider}/IdentityProvider.java (97%) rename {common/src/main/java/org/cloudfoundry/identity/uaa => payload/src/main/java/org/cloudfoundry/identity/uaa/provider}/KeystoneIdentityProviderDefinition.java (96%) rename {common/src/main/java/org/cloudfoundry/identity/uaa/ldap => payload/src/main/java/org/cloudfoundry/identity/uaa/provider}/LdapIdentityProviderDefinition.java (71%) rename {common/src/main/java/org/cloudfoundry/identity/uaa/config => payload/src/main/java/org/cloudfoundry/identity/uaa/provider}/LockoutPolicy.java (97%) rename {common/src/main/java/org/cloudfoundry/identity/uaa/config => payload/src/main/java/org/cloudfoundry/identity/uaa/provider}/PasswordPolicy.java (99%) rename {common/src/main/java/org/cloudfoundry/identity/uaa/login/saml => payload/src/main/java/org/cloudfoundry/identity/uaa/provider}/SamlIdentityProviderDefinition.java (92%) rename {common/src/main/java/org/cloudfoundry/identity/uaa/zone => payload/src/main/java/org/cloudfoundry/identity/uaa/provider}/UaaIdentityProviderDefinition.java (89%) rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java (100%) rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java (100%) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java index c1684da1346..66a380ad511 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java @@ -25,7 +25,7 @@ import org.cloudfoundry.identity.uaa.login.PasscodeInformation; import org.cloudfoundry.identity.uaa.login.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlRedirectUtils; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java index 5d17b44916d..e79a0ffeb19 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java @@ -28,10 +28,10 @@ import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.ObjectUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManager.java index c6090669ad9..2193aea16a7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManager.java @@ -15,7 +15,7 @@ package org.cloudfoundry.identity.uaa.authentication.manager; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.dao.EmptyResultDataAccessException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java index c5374c4bb99..17765671b49 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java @@ -17,11 +17,11 @@ import org.apache.commons.lang.StringUtils; import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserDetails; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.ldap.extension.LdapAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.util.ObjectUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.core.Authentication; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java index 4fde9460925..03739362c59 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java @@ -17,14 +17,14 @@ import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.UaaAuditService; -import org.cloudfoundry.identity.uaa.config.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.util.ObjectUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -108,7 +108,7 @@ public void setLockoutPolicy(LockoutPolicy lockoutPolicy) { private LockoutPolicy getLockoutPolicyFromDb() { IdentityProvider idp = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); - UaaIdentityProviderDefinition idpDefinition = ObjectUtils.castInstance(idp.getConfig(),UaaIdentityProviderDefinition.class); + UaaIdentityProviderDefinition idpDefinition = ObjectUtils.castInstance(idp.getConfig(), UaaIdentityProviderDefinition.class); if (idpDefinition != null && idpDefinition.getLockoutPolicy() !=null ) { return idpDefinition.getLockoutPolicy(); } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java b/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java index 2dd6f600e90..aaf2ccba27c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java @@ -14,18 +14,20 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.KeystoneIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.KeystoneIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.util.LdapUtils; import org.cloudfoundry.identity.uaa.util.UaaMapUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.json.JSONException; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.env.AbstractEnvironment; @@ -39,9 +41,9 @@ import java.util.List; import java.util.Map; -import static org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition.LDAP; -import static org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition.LDAP_PROPERTY_NAMES; -import static org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition.LDAP_PROPERTY_TYPES; +import static org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition.LDAP; +import static org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition.LDAP_PROPERTY_NAMES; +import static org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition.LDAP_PROPERTY_TYPES; public class IdentityProviderBootstrap implements InitializingBean { private IdentityProviderProvisioning provisioning; @@ -114,7 +116,7 @@ protected LdapIdentityProviderDefinition getLdapConfigAsDefinition(Map ldapConfig) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationToken.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationToken.java index 781afd920e8..2ce87dfae9b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationToken.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationToken.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.Set; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; public class LoginSamlAuthenticationToken extends ExpiringUsernameAuthenticationToken { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlEntryPoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlEntryPoint.java index d4b1a9f93c7..ac1854b498b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlEntryPoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlEntryPoint.java @@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.login.saml; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.springframework.security.core.AuthenticationException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java index 71d0273bd7c..1f93e499070 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java @@ -17,9 +17,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.ObjectUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.event.IdentityProviderModifiedEvent; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderConfigurator.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderConfigurator.java index 9ac05d480a7..d6a69160d33 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderConfigurator.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderConfigurator.java @@ -19,6 +19,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.client.utils.URIBuilder; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.xml.parse.BasicParserPool; @@ -42,9 +43,9 @@ import java.util.Timer; import java.util.TimerTask; -import static org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition.EMAIL_DOMAIN_ATTR; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.EXTERNAL_GROUPS_WHITELIST; +import static org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition.EMAIL_DOMAIN_ATTR; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EXTERNAL_GROUPS_WHITELIST; public class SamlIdentityProviderConfigurator implements InitializingBean { private static Log logger = LogFactory.getLog(SamlIdentityProviderConfigurator.class); diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtils.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtils.java index 1c1ea7d7ae8..a6d1ee6d6c9 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtils.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtils.java @@ -14,6 +14,7 @@ package org.cloudfoundry.identity.uaa.login.saml; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; import org.springframework.web.util.UriComponentsBuilder; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java index 6fdf342977a..3a5ad404274 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java @@ -17,8 +17,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManager.java index 11ff57aaf45..ef26661b742 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManager.java @@ -18,7 +18,7 @@ import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.security.core.GrantedAuthority; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java index d236ea755c0..85d156da45e 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java @@ -15,7 +15,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.client.ClientConstants; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.util.StringUtils; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/LdapUtils.java b/common/src/main/java/org/cloudfoundry/identity/uaa/util/LdapUtils.java new file mode 100644 index 00000000000..bffac363796 --- /dev/null +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/util/LdapUtils.java @@ -0,0 +1,160 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.util; + +import org.cloudfoundry.identity.uaa.config.NestedMapPropertySource; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class LdapUtils { + + private LdapUtils() {} + + public static ConfigurableEnvironment getLdapConfigurationEnvironment(LdapIdentityProviderDefinition definition) { + Assert.notNull(definition); + + Map properties = new HashMap<>(); + + setIfNotNull(LdapIdentityProviderDefinition.LDAP_ATTRIBUTE_MAPPINGS, definition.getAttributeMappings(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_LOCAL_PASSWORD_COMPARE, definition.isLocalPasswordCompare(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_MAIL_ATTRIBUTE_NAME, definition.getMailAttributeName(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_MAIL_SUBSTITUTE, definition.getMailSubstitute(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_MAIL_SUBSTITUTE_OVERRIDES_LDAP, definition.isMailSubstituteOverridesLdap(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_PASSWORD, definition.getBindPassword(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_PASSWORD_ATTRIBUTE_NAME, definition.getPasswordAttributeName(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_PASSWORD_ENCODER, definition.getPasswordEncoder(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_REFERRAL, definition.getReferral(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_SEARCH_BASE, definition.getUserSearchBase(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_SEARCH_FILTER, definition.getUserSearchFilter(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_URL, definition.getBaseUrl(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_USER_DN, definition.getBindUserDn(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_USER_DN_PATTERN, definition.getUserDNPattern(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_BASE_USER_DN_PATTERN_DELIMITER, definition.getUserDNPatternDelimiter(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_EMAIL_DOMAIN, definition.getEmailDomain(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_EXTERNAL_GROUPS_WHITELIST, definition.getExternalGroupsWhitelist(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_GROUPS_AUTO_ADD, definition.isAutoAddGroups(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_GROUPS_FILE, definition.getLdapGroupFile(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_GROUPS_GROUP_ROLE_ATTRIBUTE, definition.getGroupRoleAttribute(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_GROUPS_GROUP_SEARCH_FILTER, definition.getGroupSearchFilter(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_GROUPS_IGNORE_PARTIAL_RESULT_EXCEPTION, definition.isGroupsIgnorePartialResults(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_GROUPS_MAX_SEARCH_DEPTH, definition.getMaxGroupSearchDepth(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_GROUPS_SEARCH_BASE, definition.getGroupSearchBase(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_GROUPS_SEARCH_SUBTREE, definition.isGroupSearchSubTree(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_PROFILE_FILE, definition.getLdapProfileFile(), properties); + setIfNotNull(LdapIdentityProviderDefinition.LDAP_SSL_SKIPVERIFICATION, definition.isSkipSSLVerification(), properties); + + MapPropertySource source = new NestedMapPropertySource("ldap", properties); + return new LdapIdentityProviderDefinition.LdapConfigEnvironment(source); + } + + private static void setIfNotNull(String property, Object value, Map map) { + if (value!=null) { + map.put(property, value); + } + } + + /** + * Load a LDAP definition from the Yaml config (IdentityProviderBootstrap) + */ + public static LdapIdentityProviderDefinition fromConfig(Map ldapConfig) { + Assert.notNull(ldapConfig); + + LdapIdentityProviderDefinition definition = new LdapIdentityProviderDefinition(); + if (ldapConfig==null || ldapConfig.isEmpty()) { + return definition; + } + + if (ldapConfig.get(LdapIdentityProviderDefinition.LDAP_EMAIL_DOMAIN)!=null) { + definition.setEmailDomain((List) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_EMAIL_DOMAIN)); + } + + if (ldapConfig.get(LdapIdentityProviderDefinition.LDAP_EXTERNAL_GROUPS_WHITELIST)!=null) { + definition.setExternalGroupsWhitelist((List) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_EXTERNAL_GROUPS_WHITELIST)); + } + + if (ldapConfig.get(LdapIdentityProviderDefinition.LDAP_ATTRIBUTE_MAPPINGS)!=null) { + definition.setAttributeMappings((Map) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_ATTRIBUTE_MAPPINGS)); + } + + definition.setLdapProfileFile((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_PROFILE_FILE)); + + final String profileFile = definition.getLdapProfileFile(); + if (StringUtils.hasText(profileFile)) { + switch (profileFile) { + case LdapIdentityProviderDefinition.LDAP_PROFILE_FILE_SIMPLE_BIND: { + definition.setUserDNPattern((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_USER_DN_PATTERN)); + if (ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_USER_DN_PATTERN_DELIMITER) != null) { + definition.setUserDNPatternDelimiter((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_USER_DN_PATTERN_DELIMITER)); + } + break; + } + case LdapIdentityProviderDefinition.LDAP_PROFILE_FILE_SEARCH_AND_COMPARE: + case LdapIdentityProviderDefinition.LDAP_PROFILE_FILE_SEARCH_AND_BIND: { + definition.setBindUserDn((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_USER_DN)); + definition.setBindPassword((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_PASSWORD)); + definition.setUserSearchBase((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_SEARCH_BASE)); + definition.setUserSearchFilter((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_SEARCH_FILTER)); + break; + } + default: + break; + } + } + + definition.setBaseUrl((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_URL)); + definition.setSkipSSLVerification((Boolean) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_SSL_SKIPVERIFICATION)); + definition.setReferral((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_REFERRAL)); + definition.setMailSubstituteOverridesLdap((Boolean)ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_MAIL_SUBSTITUTE_OVERRIDES_LDAP)); + if (StringUtils.hasText((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_MAIL_ATTRIBUTE_NAME))) { + definition.setMailAttributeName((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_MAIL_ATTRIBUTE_NAME)); + } + definition.setMailSubstitute((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_MAIL_SUBSTITUTE)); + definition.setPasswordAttributeName((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_PASSWORD_ATTRIBUTE_NAME)); + definition.setPasswordEncoder((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_PASSWORD_ENCODER)); + definition.setLocalPasswordCompare((Boolean)ldapConfig.get(LdapIdentityProviderDefinition.LDAP_BASE_LOCAL_PASSWORD_COMPARE)); + if (StringUtils.hasText((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_GROUPS_FILE))) { + definition.setLdapGroupFile((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_GROUPS_FILE)); + } + if (StringUtils.hasText(definition.getLdapGroupFile()) && !LdapIdentityProviderDefinition.LDAP_GROUP_FILE_GROUPS_NULL_XML.equals(definition.getLdapGroupFile())) { + definition.setGroupSearchBase((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_GROUPS_SEARCH_BASE)); + definition.setGroupSearchFilter((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_GROUPS_GROUP_SEARCH_FILTER)); + definition.setGroupsIgnorePartialResults((Boolean)ldapConfig.get(LdapIdentityProviderDefinition.LDAP_GROUPS_IGNORE_PARTIAL_RESULT_EXCEPTION)); + if (ldapConfig.get(LdapIdentityProviderDefinition.LDAP_GROUPS_MAX_SEARCH_DEPTH) != null) { + definition.setMaxGroupSearchDepth((Integer) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_GROUPS_MAX_SEARCH_DEPTH)); + } + definition.setGroupSearchSubTree((Boolean) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_GROUPS_SEARCH_SUBTREE)); + definition.setAutoAddGroups((Boolean) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_GROUPS_AUTO_ADD)); + definition.setGroupRoleAttribute((String) ldapConfig.get(LdapIdentityProviderDefinition.LDAP_GROUPS_GROUP_ROLE_ATTRIBUTE)); + } + + //if flat attributes are set in the properties + final String LDAP_ATTR_MAP_PREFIX = LdapIdentityProviderDefinition.LDAP_ATTRIBUTE_MAPPINGS+"."; + for (Map.Entry entry : ldapConfig.entrySet()) { + if (!LdapIdentityProviderDefinition.LDAP_PROPERTY_NAMES.contains(entry.getKey()) && + entry.getKey().startsWith(LDAP_ATTR_MAP_PREFIX) && + entry.getValue() instanceof String) { + definition.addAttributeMapping(entry.getKey().substring(LDAP_ATTR_MAP_PREFIX.length()), entry.getValue()); + } + } + return definition; + } +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java index 948fbed6765..72d828cc158 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java @@ -13,6 +13,8 @@ package org.cloudfoundry.identity.uaa.zone; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.springframework.web.filter.OncePerRequestFilter; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableUserManagementSecurityFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableUserManagementSecurityFilter.java index b5de9ba487f..59beb8594dd 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableUserManagementSecurityFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableUserManagementSecurityFilter.java @@ -15,6 +15,8 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.ExceptionReport; import org.cloudfoundry.identity.uaa.error.ExceptionReportHttpMessageConverter; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.springframework.http.MediaType; import org.springframework.http.server.ServletServerHttpResponse; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderProvisioning.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderProvisioning.java index d7bc5f78714..657a5402cb7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderProvisioning.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderProvisioning.java @@ -12,6 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.zone; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; + import java.util.List; public interface IdentityProviderProvisioning { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderValidationRequest.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderValidationRequest.java index d8957b9173d..f80b176f744 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderValidationRequest.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderValidationRequest.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java index b36bc9e25f5..0b42b001c9f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java @@ -15,6 +15,8 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.oauth.InvalidClientDetailsException; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java index a544dbd651f..958d6c96ade 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java @@ -13,11 +13,12 @@ package org.cloudfoundry.identity.uaa.zone; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.KeystoneIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.KeystoneIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.springframework.dao.DataIntegrityViolationException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderEventPublisher.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderEventPublisher.java index 46edf599622..daea9c9f77c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderEventPublisher.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderEventPublisher.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.zone.event; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -37,4 +37,4 @@ public void publish(ApplicationEvent event) { publisher.publishEvent(event); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderModifiedEvent.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderModifiedEvent.java index 1386e800cdb..ef0d51e078a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderModifiedEvent.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderModifiedEvent.java @@ -17,7 +17,7 @@ import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.springframework.security.core.Authentication; public class IdentityProviderModifiedEvent extends AbstractUaaEvent { diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java index 6d7a712c172..8887a3f203d 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java @@ -9,7 +9,7 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java index 6e42cdd80e5..82fce08b81f 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java @@ -22,16 +22,16 @@ import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationFailureEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.authentication.event.UserNotFoundEvent; -import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationEventPublisher; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java index eafea73e9b0..2aa0a4b1922 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java @@ -18,7 +18,7 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.user.MockUaaUserDatabase; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityProviderProvisioning; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java index 447313dde39..c1466dfbe98 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java @@ -16,13 +16,13 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.ldap.extension.ExtendedLdapUserImpl; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.Before; @@ -39,7 +39,7 @@ import java.util.HashMap; import java.util.Map; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java index 88cc30722f0..d3991257d55 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java @@ -14,14 +14,14 @@ import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.UaaAuditService; -import org.cloudfoundry.identity.uaa.config.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.Before; import org.junit.Test; import org.springframework.security.core.Authentication; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java index 0531ea06a69..4091dbe5886 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java @@ -14,19 +14,20 @@ package org.cloudfoundry.identity.uaa.config; -import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.KeystoneIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.KeystoneIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -39,9 +40,9 @@ import java.util.List; import java.util.Map; -import static org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition.EMAIL_DOMAIN_ATTR; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.EXTERNAL_GROUPS_WHITELIST; +import static org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition.EMAIL_DOMAIN_ATTR; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EXTERNAL_GROUPS_WHITELIST; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.KEYSTONE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/PasswordPolicyTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/config/PasswordPolicyTest.java index 14646a63e0e..1688963947a 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/PasswordPolicyTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/config/PasswordPolicyTest.java @@ -1,5 +1,6 @@ package org.cloudfoundry.identity.uaa.config; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.junit.Test; import static org.junit.Assert.*; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java index 1ab9cc09f62..108e8eb52da 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java @@ -14,7 +14,9 @@ import org.cloudfoundry.identity.uaa.config.YamlMapFactoryBean; import org.cloudfoundry.identity.uaa.config.YamlProcessor; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.util.LdapUtils; import org.cloudfoundry.identity.uaa.util.UaaMapUtils; import org.junit.Before; import org.junit.Test; @@ -69,7 +71,7 @@ public void testSearchAndBindConfiguration() throws Exception { assertEquals("ldap/ldap-search-and-bind.xml", deserialized.getLdapProfileFile()); assertEquals("ldap/ldap-groups-map-to-scopes.xml", deserialized.getLdapGroupFile()); - ConfigurableEnvironment environment = deserialized.getLdapConfigurationEnvironment(); + ConfigurableEnvironment environment = LdapUtils.getLdapConfigurationEnvironment(deserialized); //mail attribute assertNotNull(environment.getProperty("ldap.base.mailAttributeName")); assertEquals("mail", environment.getProperty("ldap.base.mailAttributeName")); @@ -140,7 +142,7 @@ public void test_Simple_Bind_Config() throws Exception { " url: 'ldap://localhost:10389/'\n" + " mailAttributeName: mail\n" + " userDnPattern: 'cn={0},ou=Users,dc=test,dc=com;cn={0},ou=OtherUsers,dc=example,dc=com'"; - LdapIdentityProviderDefinition def = LdapIdentityProviderDefinition.fromConfig(getLdapConfig(config)); + LdapIdentityProviderDefinition def = LdapUtils.fromConfig(getLdapConfig(config)); assertEquals("ldap://localhost:10389/",def.getBaseUrl()); assertEquals("ldap/ldap-simple-bind.xml",def.getLdapProfileFile()); @@ -176,7 +178,7 @@ public void test_Search_and_Bind_Config() throws Exception { " password: 'password'\n" + " searchBase: ''\n" + " searchFilter: 'cn={0}'"; - LdapIdentityProviderDefinition def = LdapIdentityProviderDefinition.fromConfig(getLdapConfig(config)); + LdapIdentityProviderDefinition def = LdapUtils.fromConfig(getLdapConfig(config)); assertEquals("ldap://localhost:10389/",def.getBaseUrl()); assertEquals("ldap/ldap-search-and-bind.xml",def.getLdapProfileFile()); @@ -219,7 +221,7 @@ public void test_Search_and_Bind_With_Groups_Config() throws Exception { " groupSearchFilter: member={0}\n" + " maxSearchDepth: 30\n" + " autoAdd: true"; - LdapIdentityProviderDefinition def = LdapIdentityProviderDefinition.fromConfig(getLdapConfig(config)); + LdapIdentityProviderDefinition def = LdapUtils.fromConfig(getLdapConfig(config)); assertEquals("ldap://localhost:10389/",def.getBaseUrl()); assertEquals("ldap/ldap-search-and-bind.xml",def.getLdapProfileFile()); @@ -265,7 +267,7 @@ public void test_Search_and_Compare_Config() throws Exception { " ssl:\n"+ " skipverification: true"; - LdapIdentityProviderDefinition def = LdapIdentityProviderDefinition.fromConfig(getLdapConfig(config)); + LdapIdentityProviderDefinition def = LdapUtils.fromConfig(getLdapConfig(config)); assertEquals("ldap://localhost:10389/",def.getBaseUrl()); assertEquals("ldap/ldap-search-and-compare.xml",def.getLdapProfileFile()); @@ -320,7 +322,7 @@ public void test_Search_and_Compare_With_Groups_1_Config_And_Custom_Attributes() " user.attribute.employeeCostCenter: costCenter\n" + " user.attribute.terribleBosses: manager\n"; - LdapIdentityProviderDefinition def = LdapIdentityProviderDefinition.fromConfig(getLdapConfig(config)); + LdapIdentityProviderDefinition def = LdapUtils.fromConfig(getLdapConfig(config)); assertEquals("ldap://localhost:10389/",def.getBaseUrl()); assertEquals("ldap/ldap-search-and-compare.xml",def.getLdapProfileFile()); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java index d7ab45d79ce..b880bcd3024 100755 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java @@ -13,10 +13,11 @@ package org.cloudfoundry.identity.uaa.login.saml; import org.apache.commons.httpclient.params.HttpClientParams; -import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.config.YamlMapFactoryBean; import org.cloudfoundry.identity.uaa.config.YamlProcessor; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java index 9d9253c680b..1bf5d8baab1 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java @@ -1,15 +1,16 @@ package org.cloudfoundry.identity.uaa.login.saml; import org.apache.commons.httpclient.contrib.ssl.StrictSSLProtocolSocketFactory; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.junit.Before; import org.junit.Test; import java.io.File; import java.util.Arrays; -import static org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition.MetadataLocation.DATA; -import static org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN; -import static org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition.MetadataLocation.URL; +import static org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition.MetadataLocation.DATA; +import static org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition.MetadataLocation.UNKNOWN; +import static org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition.MetadataLocation.URL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -163,4 +164,4 @@ public void testGetSocketFactoryClassName() throws Exception { def.setSocketFactoryClassName(StrictSSLProtocolSocketFactory.class.getName()); assertEquals(StrictSSLProtocolSocketFactory.class.getName(), def.getSocketFactoryClassName()); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtilsTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtilsTest.java index 2f5736ea619..e31dc9a0cee 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtilsTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtilsTest.java @@ -14,6 +14,7 @@ package org.cloudfoundry.identity.uaa.login.saml; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.junit.Assert; import org.junit.Test; @@ -37,4 +38,4 @@ public void testGetIdpRedirectUrl() throws Exception { String url = SamlRedirectUtils.getIdpRedirectUrl(definition, "login.identity.cf-app.com"); Assert.assertEquals("saml/discovery?returnIDParam=idp&entityID=login.identity.cf-app.com&idp=simplesamlphp-url&isPassive=true", url); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java index 93d067f9111..40b82251bc7 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java @@ -15,11 +15,11 @@ package org.cloudfoundry.identity.uaa.util; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java index bc1e6dfc162..005875efbc3 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java @@ -1,10 +1,10 @@ package org.cloudfoundry.identity.uaa.zone; import org.apache.commons.lang.RandomStringUtils; -import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.After; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java index ae6bf6cb959..d90ae5e03b3 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java @@ -1,6 +1,8 @@ package org.cloudfoundry.identity.uaa.zone; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; + public class MultitenancyFixture { public static IdentityZone identityZone(String id, String subdomain) { diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 1a9bc383587..7191f5f9262 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -1161,7 +1161,7 @@ Fields *Available Fields* :: created epoch timestamp Auto UAA sets the creation date last_modified epoch timestamp Auto UAA sets the modification date - UAA Provider Configuration (provided in JSON format as part of the ``config`` field on the Identity Provider - See class org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition + UAA Provider Configuration (provided in JSON format as part of the ``config`` field on the Identity Provider - See class org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition ============================= =============== ======== ================================================================================================================================================================================================= minLength int Required Minimum number of characters for a user provided password, 0+ maxLength int Required Maximum number of characters for a user provided password, 1+ @@ -1176,7 +1176,7 @@ Fields *Available Fields* :: disableInternalUserManagement boolean Optional When set to true, user management is disabled for this provider, defaults to false emailDomain List Optional List of email domains associated with the UAA provider. If null and no domains are explicitly matched with any other providers, the UAA acts as a catch-all, wherein the email will be associated with the UAA provider. Wildcards supported. - SAML Provider Configuration (provided in JSON format as part of the ``config`` field on the Identity Provider - See class org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition + SAML Provider Configuration (provided in JSON format as part of the ``config`` field on the Identity Provider - See class org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition ====================== ====================== ======== ================================================================================================================================================================================================================================================================================================================================================================================================================================================= idpEntityAlias String Required Must match ``originKey`` in the provider definition zoneId String Required Must match ``identityZoneId`` in the provider definition @@ -1191,7 +1191,7 @@ Fields *Available Fields* :: attributeMappings Map Optional List of UAA attributes mapped to attributes in the SAML assertion. Currently we support mapping given_name, family_name, email, phone_number and external_groups. Also supports custom user attributes to be populated in the id_token when the `user_attributes` scope is requested. The attributes are pulled out of the user records and have the format `user.attribute.: ` externalGroupsWhitelist List Optional List of external groups that will be included in the ID Token if the `roles` scope is requested. - LDAP Provider Configuration (provided in JSON format as part of the ``config`` field on the Identity Provider - See class org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition + LDAP Provider Configuration (provided in JSON format as part of the ``config`` field on the Identity Provider - See class org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition ====================== ====================== ======== ================================================================================================================================================================================================= ldapProfileFile String Required Value must be "ldap/ldap-search-and-bind.xml" (until other configuration options are supported) ldapGroupFile String Required Value must be "ldap/ldap-groups-map-to-scopes.xml" (until other configuration options are supported) diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManager.java b/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManager.java index ee0bf9f1a75..f8cca206c27 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManager.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManager.java @@ -1,9 +1,10 @@ package org.cloudfoundry.identity.uaa.authentication.manager; import org.cloudfoundry.identity.uaa.config.EnvironmentPropertiesFactoryBean; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; +import org.cloudfoundry.identity.uaa.util.LdapUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -48,7 +49,7 @@ public synchronized AuthenticationManager getLdapAuthenticationManager() throws return manager; } if (context==null) { - ConfigurableEnvironment environment = definition.getLdapConfigurationEnvironment(); + ConfigurableEnvironment environment = LdapUtils.getLdapConfigurationEnvironment(definition); //create parent BeanFactory to inject singletons from the parent DefaultListableBeanFactory parentBeanFactory = new DefaultListableBeanFactory(); parentBeanFactory.registerSingleton("externalGroupMembershipManager", scimGroupExternalMembershipManager); @@ -108,5 +109,4 @@ public void destroy() { applicationContext.destroy(); } } - } diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java b/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java index fcad8a40f92..bf06c0637d9 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java @@ -16,11 +16,11 @@ import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException; import org.cloudfoundry.identity.uaa.authentication.manager.ChainedAuthenticationManager.AuthenticationManagerConfiguration; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.util.ObjectUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java index fa33e4bcdd1..c3b3a7982a8 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java @@ -13,7 +13,7 @@ import org.cloudfoundry.identity.uaa.invitations.InvitationsService.AcceptedInvitation; import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserDetails; import org.cloudfoundry.identity.uaa.login.PasswordConfirmationValidation; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlRedirectUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; @@ -24,7 +24,7 @@ import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.ObjectUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.dao.EmptyResultDataAccessException; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java index 3ef88ab266b..d45f283366f 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java @@ -9,7 +9,7 @@ import org.cloudfoundry.identity.uaa.util.DomainFilter; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.UaaUrlUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.http.HttpStatus; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java index b2394a2d444..32513a02959 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java @@ -25,12 +25,13 @@ import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.SamlUserAuthority; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -70,11 +71,11 @@ import java.util.Set; import java.util.stream.Collectors; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.FAMILY_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GIVEN_NAME_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.PHONE_NUMBER_ATTRIBUTE_NAME; public class LoginSamlAuthenticationProvider extends SAMLAuthenticationProvider implements ApplicationEventPublisherAware { private final static Log logger = LogFactory.getLog(LoginSamlAuthenticationProvider.class); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java b/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java index 7687a72764b..892806d4dd0 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java @@ -17,9 +17,10 @@ import org.cloudfoundry.identity.uaa.authentication.manager.DynamicLdapAuthenticationManager; import org.cloudfoundry.identity.uaa.authentication.manager.LdapLoginAuthenticationManager; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.util.JsonUtils; diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java b/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java index 5a94f7a4cf3..06d68090810 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java @@ -8,7 +8,7 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserDetails; import org.cloudfoundry.identity.uaa.login.BuildInfo; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; @@ -18,7 +18,7 @@ import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/AbstractIdentityProviderDefinition.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java similarity index 84% rename from common/src/main/java/org/cloudfoundry/identity/uaa/AbstractIdentityProviderDefinition.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java index 24148dd1076..8f6698cc1f1 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/AbstractIdentityProviderDefinition.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java @@ -12,9 +12,8 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa; +package org.cloudfoundry.identity.uaa.provider; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,14 +41,6 @@ public AbstractIdentityProviderDefinition setAdditionalConfiguration(Map(); - } - additionalConfiguration.put(key, value); - return this; - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ExternalIdentityProviderDefinition.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ExternalIdentityProviderDefinition.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java index 498f61090f2..f66df6cdfd9 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ExternalIdentityProviderDefinition.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa; +package org.cloudfoundry.identity.uaa.provider; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java index 809448af996..77b26a0096f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java @@ -10,7 +10,7 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.zone; +package org.cloudfoundry.identity.uaa.provider; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; @@ -24,12 +24,6 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.KeystoneIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.config.LockoutPolicy; -import org.cloudfoundry.identity.uaa.config.PasswordPolicy; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.springframework.util.StringUtils; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/KeystoneIdentityProviderDefinition.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/KeystoneIdentityProviderDefinition.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java index 912cdf20ec0..94b4b053fb3 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/KeystoneIdentityProviderDefinition.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java @@ -12,7 +12,7 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa; +package org.cloudfoundry.identity.uaa.provider; import java.util.Map; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinition.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java similarity index 71% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinition.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java index b711afd0cb0..d4f9004c969 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinition.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java @@ -10,13 +10,10 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.ldap; +package org.cloudfoundry.identity.uaa.provider; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.config.NestedMapPropertySource; import org.springframework.core.env.AbstractEnvironment; -import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.util.StringUtils; @@ -192,132 +189,6 @@ public static LdapIdentityProviderDefinition searchAndBindMapGroupToScopes( return definition; } - /** - * Load a LDAP definition from the Yaml config (IdentityProviderBootstrap) - */ - public static LdapIdentityProviderDefinition fromConfig(Map ldapConfig) { - LdapIdentityProviderDefinition definition = new LdapIdentityProviderDefinition(); - if (ldapConfig==null || ldapConfig.isEmpty()) { - return definition; - } - - if (ldapConfig.get(LDAP_EMAIL_DOMAIN)!=null) { - definition.setEmailDomain((List) ldapConfig.get(LDAP_EMAIL_DOMAIN)); - } - - if (ldapConfig.get(LDAP_EXTERNAL_GROUPS_WHITELIST)!=null) { - definition.setExternalGroupsWhitelist((List) ldapConfig.get(LDAP_EXTERNAL_GROUPS_WHITELIST)); - } - - if (ldapConfig.get(LDAP_ATTRIBUTE_MAPPINGS)!=null) { - definition.setAttributeMappings((Map) ldapConfig.get(LDAP_ATTRIBUTE_MAPPINGS)); - } - - definition.setLdapProfileFile((String) ldapConfig.get(LDAP_PROFILE_FILE)); - - final String profileFile = definition.getLdapProfileFile(); - if (StringUtils.hasText(profileFile)) { - switch (profileFile) { - case LDAP_PROFILE_FILE_SIMPLE_BIND: { - definition.setUserDNPattern((String) ldapConfig.get(LDAP_BASE_USER_DN_PATTERN)); - if (ldapConfig.get(LDAP_BASE_USER_DN_PATTERN_DELIMITER) != null) { - definition.setUserDNPatternDelimiter((String) ldapConfig.get(LDAP_BASE_USER_DN_PATTERN_DELIMITER)); - } - break; - } - case LDAP_PROFILE_FILE_SEARCH_AND_COMPARE: - case LDAP_PROFILE_FILE_SEARCH_AND_BIND: { - definition.setBindUserDn((String) ldapConfig.get(LDAP_BASE_USER_DN)); - definition.setBindPassword((String) ldapConfig.get(LDAP_BASE_PASSWORD)); - definition.setUserSearchBase((String) ldapConfig.get(LDAP_BASE_SEARCH_BASE)); - definition.setUserSearchFilter((String) ldapConfig.get(LDAP_BASE_SEARCH_FILTER)); - break; - } - default: - break; - } - } - - definition.setBaseUrl((String) ldapConfig.get(LDAP_BASE_URL)); - definition.setSkipSSLVerification((Boolean) ldapConfig.get(LDAP_SSL_SKIPVERIFICATION)); - definition.setReferral((String) ldapConfig.get(LDAP_BASE_REFERRAL)); - definition.setMailSubstituteOverridesLdap((Boolean)ldapConfig.get(LDAP_BASE_MAIL_SUBSTITUTE_OVERRIDES_LDAP)); - if (StringUtils.hasText((String) ldapConfig.get(LDAP_BASE_MAIL_ATTRIBUTE_NAME))) { - definition.setMailAttributeName((String) ldapConfig.get(LDAP_BASE_MAIL_ATTRIBUTE_NAME)); - } - definition.setMailSubstitute((String) ldapConfig.get(LDAP_BASE_MAIL_SUBSTITUTE)); - definition.setPasswordAttributeName((String) ldapConfig.get(LDAP_BASE_PASSWORD_ATTRIBUTE_NAME)); - definition.setPasswordEncoder((String) ldapConfig.get(LDAP_BASE_PASSWORD_ENCODER)); - definition.setLocalPasswordCompare((Boolean)ldapConfig.get(LDAP_BASE_LOCAL_PASSWORD_COMPARE)); - if (StringUtils.hasText((String) ldapConfig.get(LDAP_GROUPS_FILE))) { - definition.setLdapGroupFile((String) ldapConfig.get(LDAP_GROUPS_FILE)); - } - if (StringUtils.hasText(definition.getLdapGroupFile()) && !LDAP_GROUP_FILE_GROUPS_NULL_XML.equals(definition.getLdapGroupFile())) { - definition.setGroupSearchBase((String) ldapConfig.get(LDAP_GROUPS_SEARCH_BASE)); - definition.setGroupSearchFilter((String) ldapConfig.get(LDAP_GROUPS_GROUP_SEARCH_FILTER)); - definition.setGroupsIgnorePartialResults((Boolean)ldapConfig.get(LDAP_GROUPS_IGNORE_PARTIAL_RESULT_EXCEPTION)); - if (ldapConfig.get(LDAP_GROUPS_MAX_SEARCH_DEPTH) != null) { - definition.setMaxGroupSearchDepth((Integer) ldapConfig.get(LDAP_GROUPS_MAX_SEARCH_DEPTH)); - } - definition.setGroupSearchSubTree((Boolean) ldapConfig.get(LDAP_GROUPS_SEARCH_SUBTREE)); - definition.setAutoAddGroups((Boolean) ldapConfig.get(LDAP_GROUPS_AUTO_ADD)); - definition.setGroupRoleAttribute((String) ldapConfig.get(LDAP_GROUPS_GROUP_ROLE_ATTRIBUTE)); - } - - //if flat attributes are set in the properties - final String LDAP_ATTR_MAP_PREFIX = LDAP_ATTRIBUTE_MAPPINGS+"."; - for (Map.Entry entry : ldapConfig.entrySet()) { - if (!LDAP_PROPERTY_NAMES.contains(entry.getKey()) && - entry.getKey().startsWith(LDAP_ATTR_MAP_PREFIX) && - entry.getValue() instanceof String) { - definition.addAttributeMapping(entry.getKey().substring(LDAP_ATTR_MAP_PREFIX.length()), entry.getValue()); - } - } - return definition; - } - - @JsonIgnore - public ConfigurableEnvironment getLdapConfigurationEnvironment() { - Map properties = new HashMap<>(); - - setIfNotNull(LDAP_ATTRIBUTE_MAPPINGS, getAttributeMappings(), properties); - setIfNotNull(LDAP_BASE_LOCAL_PASSWORD_COMPARE, isLocalPasswordCompare(), properties); - setIfNotNull(LDAP_BASE_MAIL_ATTRIBUTE_NAME, getMailAttributeName(), properties); - setIfNotNull(LDAP_BASE_MAIL_SUBSTITUTE, getMailSubstitute(), properties); - setIfNotNull(LDAP_BASE_MAIL_SUBSTITUTE_OVERRIDES_LDAP, isMailSubstituteOverridesLdap(), properties); - setIfNotNull(LDAP_BASE_PASSWORD, getBindPassword(), properties); - setIfNotNull(LDAP_BASE_PASSWORD_ATTRIBUTE_NAME, getPasswordAttributeName(), properties); - setIfNotNull(LDAP_BASE_PASSWORD_ENCODER, getPasswordEncoder(), properties); - setIfNotNull(LDAP_BASE_REFERRAL, getReferral(), properties); - setIfNotNull(LDAP_BASE_SEARCH_BASE, getUserSearchBase(), properties); - setIfNotNull(LDAP_BASE_SEARCH_FILTER, getUserSearchFilter(), properties); - setIfNotNull(LDAP_BASE_URL, getBaseUrl(), properties); - setIfNotNull(LDAP_BASE_USER_DN, getBindUserDn(), properties); - setIfNotNull(LDAP_BASE_USER_DN_PATTERN, getUserDNPattern(), properties); - setIfNotNull(LDAP_BASE_USER_DN_PATTERN_DELIMITER, getUserDNPatternDelimiter(), properties); - setIfNotNull(LDAP_EMAIL_DOMAIN, getEmailDomain(), properties); - setIfNotNull(LDAP_EXTERNAL_GROUPS_WHITELIST, getExternalGroupsWhitelist(), properties); - setIfNotNull(LDAP_GROUPS_AUTO_ADD, isAutoAddGroups(), properties); - setIfNotNull(LDAP_GROUPS_FILE, getLdapGroupFile(), properties); - setIfNotNull(LDAP_GROUPS_GROUP_ROLE_ATTRIBUTE, getGroupRoleAttribute(), properties); - setIfNotNull(LDAP_GROUPS_GROUP_SEARCH_FILTER, getGroupSearchFilter(), properties); - setIfNotNull(LDAP_GROUPS_IGNORE_PARTIAL_RESULT_EXCEPTION, isGroupsIgnorePartialResults(), properties); - setIfNotNull(LDAP_GROUPS_MAX_SEARCH_DEPTH, getMaxGroupSearchDepth(), properties); - setIfNotNull(LDAP_GROUPS_SEARCH_BASE, getGroupSearchBase(), properties); - setIfNotNull(LDAP_GROUPS_SEARCH_SUBTREE, isGroupSearchSubTree(), properties); - setIfNotNull(LDAP_PROFILE_FILE, getLdapProfileFile(), properties); - setIfNotNull(LDAP_SSL_SKIPVERIFICATION, isSkipSSLVerification(), properties); - - MapPropertySource source = new NestedMapPropertySource("ldap", properties); - return new LdapConfigEnvironment(source); - } - - protected void setIfNotNull(String property, Object value, Map map) { - if (value!=null) { - map.put(property, value); - } - } - public String getReferral() { return referral; } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/LockoutPolicy.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/LockoutPolicy.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java index ba900f69f6e..f109aa74ddf 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/LockoutPolicy.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.provider; public class LockoutPolicy { private int lockoutPeriodSeconds; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/PasswordPolicy.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java similarity index 99% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/PasswordPolicy.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java index d3dd500943a..58ffba6d6ac 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/PasswordPolicy.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.provider; /** * **************************************************************************** diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinition.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java similarity index 92% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinition.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java index 4508117e5db..9bea27ccf4a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinition.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java @@ -10,11 +10,9 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition; -import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -126,11 +124,8 @@ public MetadataLocation getType() { if (trimmedLocation.startsWith(" - + - + - + @@ -39,7 +39,7 @@ - + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManagerTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManagerTest.java index b0608a4e58d..b194afd2715 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManagerTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManagerTest.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.authentication.manager; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.junit.Test; @@ -49,4 +49,4 @@ public void testGetLdapAuthenticationManager() throws Exception { assertEquals(1, providerManager.getProviders().size()); assertTrue(providerManager.getProviders().get(0) instanceof LdapAuthenticationProvider); } -} \ No newline at end of file +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java index 5ac6b206119..95ab0c47bc6 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java @@ -3,10 +3,10 @@ import org.cloudfoundry.identity.uaa.authentication.AccountNotVerifiedException; import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java index 504209089c2..99a4bd3303b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java @@ -8,10 +8,10 @@ import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.ObjectUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java index 02c67764820..c611bb37918 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java @@ -18,12 +18,12 @@ import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.oauth.Claims; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.junit.Rule; import org.junit.Test; import org.springframework.security.jwt.Jwt; @@ -38,7 +38,7 @@ import java.util.List; import java.util.Map; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index 74b6d9a8619..5dd17d6af6b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -18,11 +18,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.client.ClientConstants; -import org.cloudfoundry.identity.uaa.config.LockoutPolicy; -import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.test.LoginServerClassRunner; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.Claims; @@ -32,9 +32,9 @@ import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.flywaydb.core.internal.util.StringUtils; import org.hamcrest.Matchers; import org.junit.Assert; @@ -73,8 +73,8 @@ import java.util.Map; import java.util.UUID; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.createSimplePHPSamlIDP; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index 444d4662e72..a135a9ee7bc 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -20,7 +20,7 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.rest.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; @@ -29,7 +29,7 @@ import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.junit.Assert; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java index 5fe5cef3ed4..c19dcd0806f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java @@ -3,17 +3,16 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; -import org.cloudfoundry.identity.uaa.config.IdentityProviderBootstrap; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.flywaydb.core.internal.util.StringUtils; import org.junit.After; import org.junit.Before; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index a0491b2d643..5c932dfc974 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -18,13 +18,13 @@ import org.cloudfoundry.identity.uaa.authentication.login.Prompt; import org.cloudfoundry.identity.uaa.authentication.manager.PeriodLockoutPolicy; import org.cloudfoundry.identity.uaa.zone.KeyPair; -import org.cloudfoundry.identity.uaa.config.LockoutPolicy; -import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.config.YamlServletProfileInitializer; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.util.FakeJavaMailSender; import org.cloudfoundry.identity.uaa.oauth.Claims; import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java index 27b574e4fa4..6164c46d05f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java @@ -14,16 +14,16 @@ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.util.FakeJavaMailSender; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.IdentityZoneCreationResult; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.ZoneScimInviteData; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.junit.After; import org.junit.Before; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index 39d0fab3604..5317ba9c1d8 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -18,10 +18,10 @@ import org.cloudfoundry.identity.uaa.authentication.login.Prompt; import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.cloudfoundry.identity.uaa.codestore.JdbcExpiringCodeStore; -import org.cloudfoundry.identity.uaa.config.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.saml.IdentityProviderConfiguratorTests; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.IdentityZoneCreationResult; @@ -33,11 +33,11 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.web.CorsFilter; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java index 4ebec3ada87..0941cbc333c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java @@ -14,11 +14,10 @@ package org.cloudfoundry.identity.uaa.login.saml; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; @@ -33,7 +32,7 @@ import org.cloudfoundry.identity.uaa.user.JdbcUaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityProviderProvisioning; @@ -69,8 +68,8 @@ import java.util.List; import java.util.Map; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java index d6c84199dd1..6e2994c0889 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login.saml; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.SamlConfig; import org.cloudfoundry.identity.uaa.constants.OriginKeys; @@ -19,7 +20,7 @@ import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/config/LockoutPolicyTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/config/LockoutPolicyTests.java index ab1d5486357..5c26d036e97 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/config/LockoutPolicyTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/config/LockoutPolicyTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.mock.config; -import org.cloudfoundry.identity.uaa.config.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java index ec63d39e8eb..3a35fe8889f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java @@ -18,7 +18,7 @@ import org.cloudfoundry.identity.uaa.authentication.manager.DynamicZoneAwareAuthenticationManager; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserMapper; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.ldap.ProcessLdapProperties; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.ZoneScimInviteData; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; @@ -33,7 +33,7 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityProviderValidationRequest; import org.cloudfoundry.identity.uaa.zone.IdentityProviderValidationRequest.UsernamePasswordAuthentication; @@ -82,8 +82,8 @@ import java.util.List; import java.util.Set; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; -import static org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition.LDAP_ATTRIBUTE_MAPPINGS; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; +import static org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition.LDAP_ATTRIBUTE_MAPPINGS; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java index 3dd77908bbe..815afdc3c24 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java @@ -18,7 +18,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authorization.UaaAuthorizationEndpoint; import org.cloudfoundry.identity.uaa.client.ClientConstants; -import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; @@ -40,12 +40,12 @@ import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.Assert; import org.junit.Before; import org.junit.Test; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index 760b4aa7b66..5266b5838b1 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -17,15 +17,15 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.RandomStringUtils; -import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.invitations.InvitationsRequest; import org.cloudfoundry.identity.uaa.invitations.InvitationsResponse; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; import org.cloudfoundry.identity.uaa.rest.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimGroup; @@ -41,13 +41,13 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.Assert; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEvent; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java index 5e3b26ec11a..2e41e313670 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java @@ -16,22 +16,21 @@ import org.apache.commons.lang.RandomStringUtils; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.login.saml.IdentityProviderConfiguratorTests; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.event.IdentityProviderModifiedEvent; import org.junit.After; import org.junit.Before; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index e5acb023826..bc0867fd256 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -24,7 +24,7 @@ import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java index 213073c3e11..2c26a84c4c2 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java @@ -19,7 +19,7 @@ import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.junit.After; import org.junit.Before; From 1da6c235e738b7a5220c388a12903821cbd81295 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Wed, 18 Nov 2015 14:10:15 -0800 Subject: [PATCH 006/103] Move client resources into payload [#107504490] https://www.pivotaltracker.com/story/show/107504490 Signed-off-by: Paul Warren --- .../cloudfoundry/identity/uaa/UaaConfiguration.java | 2 +- .../uaa/authentication/login/LoginInfoEndpoint.java | 2 +- .../identity/uaa/oauth/AccessController.java | 2 +- .../identity/uaa/oauth/ClientAdminBootstrap.java | 2 +- .../identity/uaa/oauth/ClientAdminEndpoints.java | 9 +++++---- .../uaa/oauth/UaaAuthorizationRequestManager.java | 2 +- .../identity/uaa/oauth/UaaUserApprovalHandler.java | 2 +- .../uaa/oauth/UserManagedAuthzApprovalHandler.java | 2 +- .../uaa/oauth/ZoneEndpointsClientDetailsValidator.java | 2 +- .../uaa/oauth/approval/ApprovalsAdminEndpoints.java | 8 ++++---- .../uaa/oauth/approval/ApprovalsControllerService.java | 4 ++-- .../identity/uaa/oauth/token/UaaTokenServices.java | 2 +- .../identity/uaa/rest/SearchResultsFactory.java | 2 +- .../cloudfoundry/identity/uaa/util/DomainFilter.java | 2 +- .../authentication/login/LoginInfoEndpointTests.java | 2 +- .../login/saml/IdentityProviderConfiguratorTests.java | 2 +- .../identity/uaa/oauth/AccessControllerTests.java | 2 +- .../identity/uaa/oauth/CheckTokenEndpointTests.java | 3 +-- .../identity/uaa/oauth/ClientAdminBootstrapTests.java | 2 +- .../identity/uaa/oauth/ClientAdminEndpointsTests.java | 5 +++-- .../uaa/oauth/UaaAuthorizationRequestManagerTests.java | 2 +- .../uaa/oauth/UaaUserApprovalHandlerTests.java | 3 +-- .../oauth/UserManagedAuthzApprovalHandlerTests.java | 2 +- .../ZoneEndpointsClientDetailsValidatorTests.java | 2 +- .../oauth/approval/ApprovalsAdminEndpointsTests.java | 2 +- .../oauth/client/ClientDetailsModificationTests.java | 3 ++- .../uaa/oauth/token/UaaTokenServicesTests.java | 2 +- .../cloudfoundry/identity/uaa/rest/MessageTests.java | 10 ++++------ .../identity/uaa/util/DomainFilterTest.java | 2 +- .../zone/MultitenantJdbcClientDetailsServiceTests.java | 2 +- .../identity/uaa/login/ProfileController.java | 2 +- .../identity/uaa/oauth/TokenRevocationEndpoint.java | 2 +- .../identity/uaa/login/ProfileControllerTests.java | 2 +- .../identity/uaa/oauth}/client/ClientConstants.java | 2 +- .../uaa/oauth/client/ClientDetailsModification.java | 1 - .../uaa/oauth/client}/SecretChangeRequest.java | 2 +- .../identity/uaa/resources/ActionResult.java | 10 +++++----- .../identity/uaa/resources}/SearchResults.java | 2 +- .../identity/uaa/password/PasswordChangeEndpoint.java | 6 +++--- .../uaa/scim/endpoints/ScimGroupEndpoints.java | 2 +- .../identity/uaa/scim/endpoints/ScimUserEndpoints.java | 2 +- .../uaa/scim/endpoints/UserIdConversionEndpoints.java | 2 +- .../identity/uaa/performance/TestMySQLEmailSearch.java | 3 +-- .../uaa/scim/endpoints/ScimGroupEndpointsTests.java | 2 +- .../uaa/scim/endpoints/ScimUserEndpointsTests.java | 2 +- .../scim/endpoints/UserIdConversionEndpointsTests.java | 2 +- .../ClientAdminEndpointsIntegrationTests.java | 2 +- .../IdentityZoneEndpointsIntegrationTests.java | 2 +- .../identity/uaa/integration/LdapIntegationTests.java | 2 +- .../identity/uaa/integration/feature/SamlLoginIT.java | 2 +- .../uaa/integration/util/IntegrationTestUtils.java | 2 +- .../identity/uaa/login/LoginMockMvcTests.java | 2 +- .../mock/clients/ClientAdminEndpointsMockMvcTests.java | 4 ++-- .../identity/uaa/mock/token/TokenMvcMockTests.java | 2 +- .../identity/uaa/mock/util/MockMvcUtils.java | 2 +- .../mock/zones/IdentityZoneEndpointsMockMvcTests.java | 2 +- .../scim/endpoints/ScimGroupEndpointsMockMvcTests.java | 2 +- 57 files changed, 77 insertions(+), 80 deletions(-) rename {common/src/main/java/org/cloudfoundry/identity/uaa => payload/src/main/java/org/cloudfoundry/identity/uaa/oauth}/client/ClientConstants.java (95%) rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java (98%) rename {common/src/main/java/org/cloudfoundry/identity/uaa/oauth => payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client}/SecretChangeRequest.java (96%) rename common/src/main/java/org/cloudfoundry/identity/uaa/message/SimpleMessage.java => payload/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java (84%) rename {common/src/main/java/org/cloudfoundry/identity/uaa/rest => payload/src/main/java/org/cloudfoundry/identity/uaa/resources}/SearchResults.java (98%) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/UaaConfiguration.java b/common/src/main/java/org/cloudfoundry/identity/uaa/UaaConfiguration.java index f73e72db640..d201527adfd 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/UaaConfiguration.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/UaaConfiguration.java @@ -28,7 +28,7 @@ import javax.validation.constraints.Pattern; import org.cloudfoundry.identity.uaa.UaaConfiguration.OAuth.Client; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.config.CustomPropertyConstructor; import org.hibernate.validator.constraints.URL; import org.yaml.snakeyaml.TypeDescription; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java index 66a380ad511..25d5c16ca41 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java @@ -15,7 +15,7 @@ import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java index 13164607fa1..f7540310782 100755 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java @@ -28,7 +28,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrap.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrap.java index 0c5308cb7b0..30399d8417b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrap.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrap.java @@ -23,7 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.crypto.password.PasswordEncoder; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpoints.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpoints.java index 17d3c31991c..595a19e47f5 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpoints.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpoints.java @@ -28,14 +28,15 @@ import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.error.UaaException; -import org.cloudfoundry.identity.uaa.message.SimpleMessage; import org.cloudfoundry.identity.uaa.oauth.ClientDetailsValidator.Mode; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; +import org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest; +import org.cloudfoundry.identity.uaa.resources.ActionResult; import org.cloudfoundry.identity.uaa.rest.AttributeNameMapper; import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; import org.cloudfoundry.identity.uaa.rest.ResourceMonitor; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.rest.SearchResultsFactory; import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor; @@ -495,7 +496,7 @@ public SearchResults listClientDetails( @RequestMapping(value = "/oauth/clients/{client}/secret", method = RequestMethod.PUT) @ResponseBody - public SimpleMessage changeSecret(@PathVariable String client, @RequestBody SecretChangeRequest change) { + public ActionResult changeSecret(@PathVariable String client, @RequestBody SecretChangeRequest change) { ClientDetails clientDetails; try { @@ -514,7 +515,7 @@ public SimpleMessage changeSecret(@PathVariable String client, @RequestBody Secr clientSecretChanges.incrementAndGet(); - return new SimpleMessage("ok", "secret updated"); + return new ActionResult("ok", "secret updated"); } @ExceptionHandler(InvalidClientDetailsException.class) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManager.java index ef26661b742..577c77c35fd 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManager.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.user.UaaUser; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandler.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandler.java index 96046f88e54..eddc8c8a50b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandler.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandler.java @@ -19,7 +19,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.util.OAuth2Utils; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java index d9aedf6cc59..0eb52380273 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java @@ -27,7 +27,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java index b87f5616ebc..4198287169c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java @@ -3,7 +3,7 @@ import java.util.Collections; import org.apache.commons.lang.StringUtils; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.provider.ClientDetails; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java index c41230ba844..3d0241dd9a0 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java @@ -23,11 +23,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.error.ConvertingExceptionView; import org.cloudfoundry.identity.uaa.error.ExceptionReport; import org.cloudfoundry.identity.uaa.error.UaaException; -import org.cloudfoundry.identity.uaa.message.SimpleMessage; +import org.cloudfoundry.identity.uaa.resources.ActionResult; import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; @@ -202,11 +202,11 @@ private boolean isValidUser(String userId) { @RequestMapping(value = "/approvals", method = RequestMethod.DELETE) @ResponseBody @Override - public SimpleMessage revokeApprovals(@RequestParam(required = true) String clientId) { + public ActionResult revokeApprovals(@RequestParam(required = true) String clientId) { String username = getCurrentUserId(); logger.debug("Revoking all existing approvals for user: " + username + " and client " + clientId); approvalStore.revokeApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, username, clientId)); - return new SimpleMessage("ok", "Approvals of user " + username + " and client " + clientId + " revoked"); + return new ActionResult("ok", "Approvals of user " + username + " and client " + clientId + " revoked"); } @ExceptionHandler diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsControllerService.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsControllerService.java index e2a20dc79d8..f31ce79145e 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsControllerService.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsControllerService.java @@ -14,11 +14,11 @@ import java.util.List; -import org.cloudfoundry.identity.uaa.message.SimpleMessage; +import org.cloudfoundry.identity.uaa.resources.ActionResult; public interface ApprovalsControllerService { public List getApprovals(String filter, int startIndex, int count); public List updateApprovals(Approval[] approvals); public List updateClientApprovals(String clientId, Approval[] approvals); - public SimpleMessage revokeApprovals(String clientId); + public ActionResult revokeApprovals(String clientId); } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java index a0b80e1e3b1..e80e6dc33ae 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java @@ -19,7 +19,7 @@ import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/SearchResultsFactory.java b/common/src/main/java/org/cloudfoundry/identity/uaa/rest/SearchResultsFactory.java index df842139350..fe18e96d2f3 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/SearchResultsFactory.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/rest/SearchResultsFactory.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Map; -import org.cloudfoundry.identity.uaa.util.UaaPagingUtils; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java index 85d156da45e..055508cf182 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java @@ -14,7 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.util.StringUtils; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java index 8887a3f203d..245e9ddb1d1 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java @@ -3,7 +3,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.InMemoryExpiringCodeStore; import org.cloudfoundry.identity.uaa.constants.OriginKeys; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java index b880bcd3024..b0e134928e7 100755 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java @@ -14,7 +14,7 @@ import org.apache.commons.httpclient.params.HttpClientParams; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.config.YamlMapFactoryBean; import org.cloudfoundry.identity.uaa.config.YamlProcessor; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/AccessControllerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/AccessControllerTests.java index 3c9049d2104..567e6b67f19 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/AccessControllerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/AccessControllerTests.java @@ -18,7 +18,7 @@ import java.util.Map; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; import org.junit.Test; import org.mockito.Mockito; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java index 2b614dadb0d..802e5144084 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java @@ -13,7 +13,7 @@ package org.cloudfoundry.identity.uaa.oauth; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; @@ -33,7 +33,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.common.OAuth2AccessToken; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrapTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrapTests.java index 84936fc3201..48619667fdb 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrapTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrapTests.java @@ -13,7 +13,7 @@ package org.cloudfoundry.identity.uaa.oauth; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.zone.MultitenantJdbcClientDetailsService; import org.junit.After; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java index 4d011143aa5..bf16356aa06 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java @@ -35,9 +35,10 @@ import org.cloudfoundry.identity.uaa.oauth.ClientDetailsValidator.Mode; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; +import org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest; import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; import org.cloudfoundry.identity.uaa.rest.ResourceMonitor; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.StubSecurityContextAccessor; @@ -822,4 +823,4 @@ public void testUpdateClientWithAutoapproveScopesTrue() throws Exception { assertTrue(updated.isAutoApprove("foo.write")); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java index d1aa471ecf4..6bb8961b8d4 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java @@ -13,7 +13,7 @@ package org.cloudfoundry.identity.uaa.oauth; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.StubSecurityContextAccessor; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandlerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandlerTests.java index 676ab38bb42..434ff9bfdcd 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandlerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandlerTests.java @@ -19,7 +19,7 @@ import java.util.Arrays; import java.util.Collections; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.junit.Test; import org.mockito.Mockito; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -29,7 +29,6 @@ import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; -import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; /** * @author Dave Syer diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandlerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandlerTests.java index c24697f4284..6855840a79a 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandlerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandlerTests.java @@ -26,7 +26,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.oauth.approval.JdbcApprovalStore; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java index ed181d86944..a3ba403e5e1 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java @@ -4,7 +4,7 @@ import java.util.Collections; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.ClientDetailsValidator.Mode; import org.junit.Before; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java index 34b6d62304e..7d17b50a275 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java @@ -27,7 +27,7 @@ import static org.mockito.Mockito.when; import com.fasterxml.jackson.core.type.TypeReference; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModificationTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModificationTests.java index ee1d019e125..62d291dd6e8 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModificationTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModificationTests.java @@ -17,6 +17,7 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.Test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** @@ -44,4 +45,4 @@ public void testClientDetailsModificationDeserialize() throws Exception { assertTrue(details.isApprovalsDeleted()); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java index 27e0c6a0142..4d9b547b484 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java @@ -17,7 +17,7 @@ import org.cloudfoundry.identity.uaa.audit.event.TokenIssuedEvent; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/rest/MessageTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/rest/MessageTests.java index 01ac497fc96..523eec34b0f 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/rest/MessageTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/rest/MessageTests.java @@ -15,9 +15,7 @@ import static org.junit.Assert.assertEquals; -import java.io.StringWriter; - -import org.cloudfoundry.identity.uaa.message.SimpleMessage; +import org.cloudfoundry.identity.uaa.resources.ActionResult; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.Test; @@ -30,14 +28,14 @@ public class MessageTests { @Test public void testSerialize() throws Exception { - assertEquals("{\"status\":\"ok\",\"message\":\"done\"}", JsonUtils.writeValueAsString(new SimpleMessage("ok", "done"))); + assertEquals("{\"status\":\"ok\",\"message\":\"done\"}", JsonUtils.writeValueAsString(new ActionResult("ok", "done"))); } @Test public void testDeserialize() throws Exception { String value = "{\"status\":\"ok\",\"message\":\"done\"}"; - SimpleMessage message = JsonUtils.readValue(value, SimpleMessage.class); - assertEquals(new SimpleMessage("ok", "done"), message); + ActionResult message = JsonUtils.readValue(value, ActionResult.class); + assertEquals(new ActionResult("ok", "done"), message); } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java index 40b82251bc7..294a4886007 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java @@ -30,7 +30,7 @@ import static java.util.Collections.EMPTY_LIST; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LOGIN_SERVER; -import static org.cloudfoundry.identity.uaa.client.ClientConstants.ALLOWED_PROVIDERS; +import static org.cloudfoundry.identity.uaa.oauth.client.ClientConstants.ALLOWED_PROVIDERS; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java index 3a2c8f5398f..2f44920a7df 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.zone; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.flywaydb.core.Flyway; import org.junit.After; import org.junit.Before; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java index 54337ffbaa0..27e3a09df83 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java @@ -13,7 +13,7 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.springframework.beans.factory.annotation.Autowired; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java b/login/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java index 2025528c674..65723550c59 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java @@ -16,7 +16,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java index e7abe92feb7..31f67f81691 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java @@ -15,7 +15,7 @@ import org.cloudfoundry.identity.uaa.TestClassNullifier; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/client/ClientConstants.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/client/ClientConstants.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java index bc6d0cd2fd2..a4de155281a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/client/ClientConstants.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java @@ -10,7 +10,7 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.client; +package org.cloudfoundry.identity.uaa.oauth.client; public class ClientConstants { public static final String ALLOWED_PROVIDERS = "allowedproviders"; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java index 460be9a88bf..f62819c8871 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import org.cloudfoundry.identity.uaa.client.ClientConstants; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/SecretChangeRequest.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/SecretChangeRequest.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java index 793119ac379..10a50aa2465 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/SecretChangeRequest.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java @@ -10,7 +10,7 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth; +package org.cloudfoundry.identity.uaa.oauth.client; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/message/SimpleMessage.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java similarity index 84% rename from common/src/main/java/org/cloudfoundry/identity/uaa/message/SimpleMessage.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java index 9aba46dd585..930fdef43aa 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/message/SimpleMessage.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.message; +package org.cloudfoundry.identity.uaa.resources; import java.io.Serializable; @@ -21,17 +21,17 @@ * @author Dave Syer * */ -public class SimpleMessage implements Serializable { +public class ActionResult implements Serializable { private String status; private String message; @SuppressWarnings("unused") - private SimpleMessage() { + private ActionResult() { } - public SimpleMessage(String status, String message) { + public ActionResult(String status, String message) { this.status = status; this.message = message; } @@ -56,7 +56,7 @@ public int hashCode() { @Override public boolean equals(Object obj) { - return obj instanceof SimpleMessage && toString().equals(obj.toString()); + return obj instanceof ActionResult && toString().equals(obj.toString()); } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/SearchResults.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/SearchResults.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java index eff171a95ae..183bcf1f54b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/SearchResults.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java @@ -10,7 +10,7 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.rest; +package org.cloudfoundry.identity.uaa.resources; import java.util.ArrayList; import java.util.Collection; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordChangeEndpoint.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordChangeEndpoint.java index 8b5d9541e87..fbbb02c70c1 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordChangeEndpoint.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordChangeEndpoint.java @@ -17,7 +17,7 @@ import org.cloudfoundry.identity.uaa.error.ConvertingExceptionView; import org.cloudfoundry.identity.uaa.error.ExceptionReport; import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; -import org.cloudfoundry.identity.uaa.message.SimpleMessage; +import org.cloudfoundry.identity.uaa.resources.ActionResult; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.scim.exception.ScimException; @@ -81,14 +81,14 @@ void setSecurityContextAccessor(SecurityContextAccessor securityContextAccessor) @RequestMapping(value = "/Users/{userId}/password", method = RequestMethod.PUT) @ResponseBody - public SimpleMessage changePassword(@PathVariable String userId, @RequestBody PasswordChangeRequest change) { + public ActionResult changePassword(@PathVariable String userId, @RequestBody PasswordChangeRequest change) { checkPasswordChangeIsAllowed(userId, change.getOldPassword()); if (dao.checkPasswordMatches(userId, change.getPassword())) { throw new InvalidPasswordException("Your new password cannot be the same as the old password.", UNPROCESSABLE_ENTITY); } passwordValidator.validate(change.getPassword()); dao.changePassword(userId, change.getOldPassword(), change.getPassword()); - return new SimpleMessage("ok", "password updated"); + return new ActionResult("ok", "password updated"); } @ExceptionHandler diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java index f01c23d5ed0..c4b69768f60 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java @@ -17,7 +17,7 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.ConvertingExceptionView; import org.cloudfoundry.identity.uaa.error.ExceptionReport; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.rest.SearchResultsFactory; import org.cloudfoundry.identity.uaa.scim.ScimCore; import org.cloudfoundry.identity.uaa.scim.ScimGroup; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java index 07bb8a8eeaf..f0cb4294f37 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java @@ -22,7 +22,7 @@ import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.rest.AttributeNameMapper; import org.cloudfoundry.identity.uaa.rest.ResourceMonitor; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.rest.SearchResultsFactory; import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; import org.cloudfoundry.identity.uaa.scim.ScimCore; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java index 2396885f36d..2efd32f864f 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java @@ -18,7 +18,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimCore; import org.cloudfoundry.identity.uaa.scim.exception.ScimException; import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/performance/TestMySQLEmailSearch.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/performance/TestMySQLEmailSearch.java index bf94b3374d2..80c05b474bb 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/performance/TestMySQLEmailSearch.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/performance/TestMySQLEmailSearch.java @@ -14,13 +14,12 @@ package org.cloudfoundry.identity.uaa.performance; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.endpoints.ScimUserEndpoints; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.jdbc.ScimSearchQueryConverter; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; -import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java index 90657df8e27..75d514bc230 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java @@ -16,7 +16,7 @@ import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.ExceptionReportHttpMessageConverter; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.rest.jdbc.DefaultLimitSqlAdapter; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java index 033bb3bdc4c..f2844931ca4 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java @@ -19,7 +19,7 @@ import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.oauth.approval.JdbcApprovalStore; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; import org.cloudfoundry.identity.uaa.rest.jdbc.DefaultLimitSqlAdapter; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpointsTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpointsTests.java index 16dfcaff868..d2b52809af0 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpointsTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpointsTests.java @@ -21,7 +21,7 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.when; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.scim.exception.ScimException; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java index 5a611535960..b90b9410813 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java @@ -15,7 +15,7 @@ import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.oauth.InvalidClientDetailsException; -import org.cloudfoundry.identity.uaa.oauth.SecretChangeRequest; +import org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java index 99a4bd3303b..ecaabf006cc 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/IdentityZoneEndpointsIntegrationTests.java @@ -1,7 +1,7 @@ package org.cloudfoundry.identity.uaa.integration; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java index c611bb37918..506e815e016 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java @@ -15,7 +15,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index 5dd17d6af6b..3590e695a42 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -17,7 +17,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index a135a9ee7bc..9ac48303585 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -21,7 +21,7 @@ import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index 5317ba9c1d8..c11c16a50ee 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -16,7 +16,7 @@ import org.cloudfoundry.identity.uaa.authentication.WhitelistLogoutHandler; import org.cloudfoundry.identity.uaa.authentication.login.LoginInfoEndpoint; import org.cloudfoundry.identity.uaa.authentication.login.Prompt; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.codestore.JdbcExpiringCodeStore; import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java index 0a0e0a71027..3737d83ab7f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java @@ -6,7 +6,7 @@ import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.oauth.InvalidClientDetailsException; -import org.cloudfoundry.identity.uaa.oauth.SecretChangeRequest; +import org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest; import org.cloudfoundry.identity.uaa.oauth.UaaScopes; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; @@ -18,7 +18,7 @@ import org.cloudfoundry.identity.uaa.oauth.event.ClientUpdateEvent; import org.cloudfoundry.identity.uaa.oauth.event.SecretChangeEvent; import org.cloudfoundry.identity.uaa.oauth.event.SecretFailureEvent; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java index 815afdc3c24..88d756c03bd 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java @@ -17,7 +17,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authorization.UaaAuthorizationEndpoint; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index 5266b5838b1..d5dd44220a2 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -27,7 +27,7 @@ import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index bc0867fd256..d337ea6aa89 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -6,7 +6,7 @@ import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; import org.cloudfoundry.identity.uaa.audit.event.GroupModifiedEvent; import org.cloudfoundry.identity.uaa.audit.event.UserModifiedEvent; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.SamlConfig; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index 2f413d190d7..01c28c786eb 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -15,7 +15,7 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; From 64893211416391d6a6275b808df29d584ea0d033 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Thu, 19 Nov 2015 10:50:28 -0800 Subject: [PATCH 007/103] Create payload classes for previously weakly typed endpoints. [#107504490] https://www.pivotaltracker.com/story/show/107504490 Signed-off-by: Jeremy Coffield --- .../uaa/audit/event/AbstractUaaEvent.java | 6 +- .../login/RemoteAuthenticationEndpoint.java | 43 +-- .../uaa/oauth/CheckTokenEndpoint.java | 13 +- .../uaa/oauth/RemoteTokenServices.java | 5 +- .../uaa/oauth/UaaUserTokenConverter.java | 6 +- ...sitiveOAuth2SecurityExpressionMethods.java | 4 +- .../uaa/oauth/token/TokenKeyEndpoint.java | 30 +- .../uaa/oauth/token/UaaTokenServices.java | 54 ++-- .../identity/uaa/openid/UserInfoEndpoint.java | 42 +-- .../uaa/oauth/CheckTokenEndpointTests.java | 85 ++--- .../uaa/oauth/RemoteTokenServicesTests.java | 19 +- .../uaa/oauth/UaaUserTokenConverterTests.java | 6 +- .../matchers/OAuth2AccessTokenMatchers.java | 40 +-- .../matchers/OAuth2RefreshTokenMatchers.java | 40 +-- .../uaa/openid/UserInfoEndpointTests.java | 11 +- .../identity/uaa/codestore/ExpiringCode.java | 0 .../uaa/invitations/InvitationsRequest.java | 0 .../uaa/invitations/InvitationsResponse.java | 0 .../uaa/login/AuthenticationResponse.java | 70 ++++ .../identity/uaa/login/AutologinRequest.java | 0 .../identity/uaa/login/AutologinResponse.java | 0 .../uaa/oauth/token/ClaimConstants.java | 4 +- .../identity/uaa/oauth/token/Claims.java | 301 ++++++++++++++++++ .../oauth/token/VerificationKeyResponse.java | 97 ++++++ .../token/VerificationKeysListResponse.java | 35 ++ .../uaa/profile/PasswordChangeRequest.java | 39 +++ .../uaa/profile/PasswordChangeResponse.java | 61 ++++ .../identity/uaa/profile/PasswordReset.java | 34 -- .../uaa/profile/PasswordResetResponse.java | 43 +++ .../uaa/profile/UserInfoResponse.java | 93 ++++++ .../scim/endpoints/PasswordResetEndpoint.java | 42 +-- .../uaa/integration/LdapIntegationTests.java | 14 +- .../feature/OpenIdTokenGrantsIT.java | 6 +- .../uaa/integration/feature/SamlLoginIT.java | 6 +- .../identity/uaa/login/BootstrapTests.java | 4 +- .../mock/audit/AuditCheckMockMvcTests.java | 4 +- .../uaa/mock/token/TokenMvcMockTests.java | 40 +-- 37 files changed, 995 insertions(+), 302 deletions(-) rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java (100%) rename {login => payload}/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java (100%) rename {login => payload}/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java (100%) create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java (100%) rename {common => payload}/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java (100%) rename common/src/main/java/org/cloudfoundry/identity/uaa/oauth/Claims.java => payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java (96%) create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java delete mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordReset.java create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java create mode 100644 payload/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java b/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java index fa7dacaba38..3350079e15e 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java @@ -18,7 +18,7 @@ import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.cloudfoundry.identity.uaa.audit.UaaAuditService; -import org.cloudfoundry.identity.uaa.oauth.Claims; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -122,8 +122,8 @@ protected String getOrigin(Principal principal) { String tokenString = ((OAuth2AuthenticationDetails)authentication.getDetails()).getTokenValue(); Jwt token = JwtHelper.decode(tokenString); Map claims = JsonUtils.readValue(token.getClaims(), new TypeReference>() {}); - String issuer = claims.get(Claims.ISS).toString(); - String subject = claims.get(Claims.SUB).toString(); + String issuer = claims.get(ClaimConstants.ISS).toString(); + String subject = claims.get(ClaimConstants.SUB).toString(); builder.append(", sub=").append(subject).append(", ").append("iss=").append(issuer); } catch (Exception e) { builder.append(", "); diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java index 4ab0cc7a9fa..714c2faa248 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java @@ -25,6 +25,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.login.AuthenticationResponse; import org.springframework.http.HttpEntity; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -65,10 +66,10 @@ public RemoteAuthenticationEndpoint(AuthenticationManager authenticationManager) @RequestMapping(value = { "/authenticate" }, method = RequestMethod.POST) @ResponseBody - public HttpEntity> authenticate(HttpServletRequest request, + public HttpEntity authenticate(HttpServletRequest request, @RequestParam(value = "username", required = true) String username, @RequestParam(value = "password", required = true) String password) { - Map responseBody = new HashMap<>(); + AuthenticationResponse response = new AuthenticationResponse(); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); token.setDetails(new UaaAuthenticationDetails(request)); @@ -76,38 +77,38 @@ public HttpEntity> authenticate(HttpServletRequest request, HttpStatus status = HttpStatus.UNAUTHORIZED; try { Authentication a = authenticationManager.authenticate(token); - responseBody.put("username", a.getName()); + response.setUsername(a.getName()); if (a.getPrincipal() != null && a.getPrincipal() instanceof UaaPrincipal) { - responseBody.put("email", ((UaaPrincipal) a.getPrincipal()).getEmail()); + response.setEmail(((UaaPrincipal) a.getPrincipal()).getEmail()); } - processAdditionalInformation(responseBody, a); + processAdditionalInformation(response, a); status = HttpStatus.OK; } catch (AccountNotVerifiedException e) { - responseBody.put("error", "account not verified"); + response.setError("account not verified"); status = HttpStatus.FORBIDDEN; } catch (AuthenticationException e) { - responseBody.put("error", "authentication failed"); + response.setError("authentication failed"); } catch (Exception e) { logger.debug("Failed to authenticate user ", e); - responseBody.put("error", "error"); + response.setError("error"); status = HttpStatus.INTERNAL_SERVER_ERROR; } - return new ResponseEntity<>(responseBody, status); + return new ResponseEntity<>(response, status); } @RequestMapping(value = { "/authenticate" }, method = RequestMethod.POST, params = {"source","origin", UaaAuthenticationDetails.ADD_NEW}) @ResponseBody - public HttpEntity> authenticate(HttpServletRequest request, + public HttpEntity authenticate(HttpServletRequest request, @RequestParam(value = "username", required = true) String username, @RequestParam(value = OriginKeys.ORIGIN, required = true) String origin, @RequestParam(value = "email", required = false) String email) { - Map responseBody = new HashMap<>(); + AuthenticationResponse response = new AuthenticationResponse(); HttpStatus status = HttpStatus.UNAUTHORIZED; if (!hasClientOauth2Authentication()) { - responseBody.put("error", "authentication failed"); - return new ResponseEntity<>(responseBody, status); + response.setError("authentication failed"); + return new ResponseEntity<>(response, status); } Map userInfo = new HashMap<>(); @@ -120,26 +121,26 @@ public HttpEntity> authenticate(HttpServletRequest request, AuthzAuthenticationRequest token = new AuthzAuthenticationRequest(userInfo, new UaaAuthenticationDetails(request)); try { Authentication a = loginAuthenticationManager.authenticate(token); - responseBody.put("username", a.getName()); - processAdditionalInformation(responseBody, a); + response.setUsername(a.getName()); + processAdditionalInformation(response, a); status = HttpStatus.OK; } catch (AuthenticationException e) { - responseBody.put("error", "authentication failed"); + response.setError("authentication failed"); } catch (Exception e) { logger.debug("Failed to authenticate user ", e); - responseBody.put("error", "error"); + response.setError("error"); status = HttpStatus.INTERNAL_SERVER_ERROR; } - return new ResponseEntity<>(responseBody, status); + return new ResponseEntity<>(response, status); } - private void processAdditionalInformation(Map responseBody, Authentication a) { + private void processAdditionalInformation(AuthenticationResponse response, Authentication a) { if (hasClientOauth2Authentication()) { UaaPrincipal principal = getPrincipal(a); if (principal!=null) { - responseBody.put(OriginKeys.ORIGIN, principal.getOrigin()); - responseBody.put("user_id", principal.getId()); + response.setOrigin(principal.getOrigin()); + response.setUserId(principal.getId()); } } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java index 128fd767241..6dedb6b5dab 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java @@ -14,9 +14,9 @@ import java.util.Map; -import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.oauth.token.Claims; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.http.ResponseEntity; @@ -61,7 +61,7 @@ public void afterPropertiesSet() throws Exception { @RequestMapping(value = "/check_token") @ResponseBody - public Map checkToken(@RequestParam("token") String value) { + public Claims checkToken(@RequestParam("token") String value) { OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value); if (token == null) { @@ -79,12 +79,12 @@ public void afterPropertiesSet() throws Exception { } - Map response = getClaimsForToken(value); + Claims response = getClaimsForToken(value); return response; } - private Map getClaimsForToken(String token) { + private Claims getClaimsForToken(String token) { Jwt tokenJwt = null; try { tokenJwt = JwtHelper.decode(token); @@ -92,10 +92,9 @@ private Map getClaimsForToken(String token) { throw new InvalidTokenException("Invalid token (could not decode): " + token); } - Map claims = null; + Claims claims = null; try { - claims = JsonUtils.readValue(tokenJwt.getClaims(), new TypeReference>() { - }); + claims = JsonUtils.readValue(tokenJwt.getClaims(), Claims.class); } catch (JsonUtils.JsonUtilException e) { throw new IllegalStateException("Cannot read token claims", e); } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServices.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServices.java index 5763459a32f..b7160862c32 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServices.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServices.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -166,9 +167,9 @@ public OAuth2Authentication loadAuthentication(String accessToken) throws Authen } } - if (map.containsKey(Claims.ADDITIONAL_AZ_ATTR)) { + if (map.containsKey(ClaimConstants.ADDITIONAL_AZ_ATTR)) { try { - requestParameters.put(Claims.ADDITIONAL_AZ_ATTR, JsonUtils.writeValueAsString(map.get(Claims.ADDITIONAL_AZ_ATTR))); + requestParameters.put(ClaimConstants.ADDITIONAL_AZ_ATTR, JsonUtils.writeValueAsString(map.get(ClaimConstants.ADDITIONAL_AZ_ATTR))); } catch (JsonUtils.JsonUtilException e) { throw new IllegalStateException("Cannot convert access token to JSON", e); } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserTokenConverter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserTokenConverter.java index a694c201a0e..d1a8877237f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserTokenConverter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserTokenConverter.java @@ -13,9 +13,9 @@ package org.cloudfoundry.identity.uaa.oauth; -import static org.cloudfoundry.identity.uaa.oauth.Claims.EMAIL; -import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_ID; -import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_NAME; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EMAIL; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ID; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_NAME; import java.util.LinkedHashMap; import java.util.Map; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/ContextSensitiveOAuth2SecurityExpressionMethods.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/ContextSensitiveOAuth2SecurityExpressionMethods.java index 9c999014d2c..413dc687145 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/ContextSensitiveOAuth2SecurityExpressionMethods.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/ContextSensitiveOAuth2SecurityExpressionMethods.java @@ -14,7 +14,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.oauth.Claims; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -143,6 +143,6 @@ private String getZoneIdFromToken(String token) { } catch (JsonUtils.JsonUtilException e) { throw new IllegalStateException("Cannot read token claims", e); } - return (String)claims.get(Claims.ZONE_ID); + return (String)claims.get(ClaimConstants.ZONE_ID); } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpoint.java index 7b2a3fccdff..971ced127a2 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpoint.java @@ -59,31 +59,31 @@ public void setSignerProvider(SignerProvider signerProvider) { * Get the verification key for the token signatures. The principal has to * be provided only if the key is secret * (shared not public). - * + * * @param principal the currently authenticated user if there is one * @return the key used to verify tokens */ @RequestMapping(value = "/token_key", method = RequestMethod.GET) @ResponseBody - public Map getKey(Principal principal) { + public VerificationKeyResponse getKey(Principal principal) { if ((principal == null || principal instanceof AnonymousAuthenticationToken) && !signerProvider.isPublic()) { throw new AccessDeniedException("You need to authenticate to see a shared key"); } - Map result = new LinkedHashMap(); - result.put("alg", signerProvider.getSigner().algorithm()); - result.put("value", signerProvider.getVerifierKey()); + VerificationKeyResponse result = new VerificationKeyResponse(); + result.setAlgorithm(signerProvider.getSigner().algorithm()); + result.setKey(signerProvider.getVerifierKey()); //new values per OpenID and JWK spec - result.put("kty", signerProvider.getType()); - result.put("use", "sig"); + result.setType(signerProvider.getType()); + result.setUse("sig"); if (signerProvider.isPublic() && "RSA".equals(signerProvider.getType())) { SignatureVerifier verifier = signerProvider.getVerifier(); - if (verifier!=null && verifier instanceof RsaVerifier) { - RSAPublicKey rsaKey = extractRsaPublicKey((RsaVerifier) verifier) ; - if (rsaKey!=null) { + if (verifier != null && verifier instanceof RsaVerifier) { + RSAPublicKey rsaKey = extractRsaPublicKey((RsaVerifier) verifier); + if (rsaKey != null) { String n = new String(Base64.encode(rsaKey.getModulus().toByteArray())); String e = new String(Base64.encode(rsaKey.getPublicExponent().toByteArray())); - result.put("n", n); - result.put("e", e); + result.setModulus(n); + result.setExponent(e); } } } @@ -101,9 +101,9 @@ public Map getKey(Principal principal) { */ @RequestMapping(value = "/token_keys", method = RequestMethod.GET) @ResponseBody - public Map>> getKeys(Principal principal) { - Map>> result = new LinkedHashMap<>(); - result.put("keys", Collections.singletonList(getKey(principal))); + public VerificationKeysListResponse getKeys(Principal principal) { + VerificationKeysListResponse result = new VerificationKeysListResponse(); + result.setKeys(Collections.singletonList(getKey(principal))); return result; } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java index e80e6dc33ae..03c05ec3855 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java @@ -88,33 +88,33 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import static org.cloudfoundry.identity.uaa.oauth.Claims.ADDITIONAL_AZ_ATTR; -import static org.cloudfoundry.identity.uaa.oauth.Claims.AUD; -import static org.cloudfoundry.identity.uaa.oauth.Claims.AUTHORITIES; -import static org.cloudfoundry.identity.uaa.oauth.Claims.AUTH_TIME; -import static org.cloudfoundry.identity.uaa.oauth.Claims.AZP; -import static org.cloudfoundry.identity.uaa.oauth.Claims.CID; -import static org.cloudfoundry.identity.uaa.oauth.Claims.CLIENT_ID; -import static org.cloudfoundry.identity.uaa.oauth.Claims.EMAIL; -import static org.cloudfoundry.identity.uaa.oauth.Claims.EXP; -import static org.cloudfoundry.identity.uaa.oauth.Claims.FAMILY_NAME; -import static org.cloudfoundry.identity.uaa.oauth.Claims.GIVEN_NAME; -import static org.cloudfoundry.identity.uaa.oauth.Claims.GRANT_TYPE; -import static org.cloudfoundry.identity.uaa.oauth.Claims.IAT; -import static org.cloudfoundry.identity.uaa.oauth.Claims.ISS; -import static org.cloudfoundry.identity.uaa.oauth.Claims.JTI; -import static org.cloudfoundry.identity.uaa.oauth.Claims.NONCE; -import static org.cloudfoundry.identity.uaa.oauth.Claims.ORIGIN; -import static org.cloudfoundry.identity.uaa.oauth.Claims.PHONE_NUMBER; -import static org.cloudfoundry.identity.uaa.oauth.Claims.PROFILE; -import static org.cloudfoundry.identity.uaa.oauth.Claims.REVOCATION_SIGNATURE; -import static org.cloudfoundry.identity.uaa.oauth.Claims.ROLES; -import static org.cloudfoundry.identity.uaa.oauth.Claims.SCOPE; -import static org.cloudfoundry.identity.uaa.oauth.Claims.SUB; -import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_ATTRIBUTES; -import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_ID; -import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_NAME; -import static org.cloudfoundry.identity.uaa.oauth.Claims.ZONE_ID; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ADDITIONAL_AZ_ATTR; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AUD; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AUTHORITIES; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AUTH_TIME; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.AZP; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.CID; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.CLIENT_ID; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EMAIL; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EXP; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.FAMILY_NAME; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.GIVEN_NAME; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.GRANT_TYPE; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.IAT; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ISS; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.JTI; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.NONCE; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ORIGIN; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.PHONE_NUMBER; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.PROFILE; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.REVOCATION_SIGNATURE; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ROLES; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SCOPE; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SUB; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ATTRIBUTES; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ID; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_NAME; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ZONE_ID; /** diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpoint.java index 66b4cf461f0..80ceda973d7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpoint.java @@ -12,28 +12,19 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.openid; -import static org.cloudfoundry.identity.uaa.oauth.Claims.EMAIL; -import static org.cloudfoundry.identity.uaa.oauth.Claims.FAMILY_NAME; -import static org.cloudfoundry.identity.uaa.oauth.Claims.GIVEN_NAME; -import static org.cloudfoundry.identity.uaa.oauth.Claims.NAME; -import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_ID; -import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_NAME; - -import java.security.Principal; -import java.util.LinkedHashMap; -import java.util.Map; - import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.profile.UserInfoResponse; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.stereotype.Controller; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; +import java.security.Principal; + /** * Controller that sends user info to clients wishing to authenticate. * @@ -55,7 +46,7 @@ public void afterPropertiesSet() throws Exception { @RequestMapping(value = "/userinfo") @ResponseBody - public Map loginInfo(Principal principal) { + public UserInfoResponse loginInfo(Principal principal) { OAuth2Authentication authentication = (OAuth2Authentication) principal; UaaPrincipal uaaPrincipal = extractUaaPrincipal(authentication); return getResponse(uaaPrincipal); @@ -69,25 +60,16 @@ protected UaaPrincipal extractUaaPrincipal(OAuth2Authentication authentication) throw new IllegalStateException("User authentication could not be converted to UaaPrincipal"); } - protected Map getResponse(UaaPrincipal principal) { + protected UserInfoResponse getResponse(UaaPrincipal principal) { UaaUser user = userDatabase.retrieveUserById(principal.getId()); - Map response = new LinkedHashMap() { - @Override - public String put(String key, String value) { - if (StringUtils.hasText(value)) { - return super.put(key, value); - } - return null; - } - }; - response.put(USER_ID, user.getId()); - response.put(USER_NAME, user.getUsername()); - response.put(GIVEN_NAME, user.getGivenName()); - response.put(FAMILY_NAME, user.getFamilyName()); - response.put(NAME, (user.getGivenName() != null ? user.getGivenName() : "") - + (user.getFamilyName() != null ? " " + user.getFamilyName() : "")); - response.put(EMAIL, user.getEmail()); + UserInfoResponse response = new UserInfoResponse(); + response.setUserId(user.getId()); + response.setUsername(user.getUsername()); + response.setGivenName(user.getGivenName()); + response.setFamilyName(user.getFamilyName()); + response.setEmail(user.getEmail()); // TODO: other attributes return response; } } + diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java index 802e5144084..24bbc3230a8 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java @@ -15,6 +15,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.oauth.approval.Approval; +import org.cloudfoundry.identity.uaa.oauth.token.Claims; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; @@ -442,9 +443,9 @@ public void testSwitchVerifierKey() throws Exception { @Test public void testUserIdInResult() { - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals("olds", result.get("user_name")); - assertEquals("12345", result.get("user_id")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertEquals("olds", result.getUserName()); + assertEquals("12345", result.getUserId()); } @Test @@ -452,9 +453,9 @@ public void testIssuerInResults() throws Exception { tokenServices.setIssuer("http://some.other.issuer"); tokenServices.afterPropertiesSet(); accessToken = tokenServices.createAccessToken(authentication); - Map result = endpoint.checkToken(accessToken.getValue()); - assertNotNull("iss field is not present", result.get("iss")); - assertEquals("http://some.other.issuer/oauth/token",result.get("iss")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertNotNull("iss field is not present", result.getIss()); + assertEquals("http://some.other.issuer/oauth/token",result.getIss()); } @Test @@ -465,9 +466,9 @@ public void testIssuerInResultsInNonDefaultZone() throws Exception { tokenServices.setIssuer("http://some.other.issuer"); tokenServices.afterPropertiesSet(); accessToken = tokenServices.createAccessToken(authentication); - Map result = endpoint.checkToken(accessToken.getValue()); - assertNotNull("iss field is not present", result.get("iss")); - assertEquals("http://subdomain.some.other.issuer/oauth/token", result.get("iss")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertNotNull("iss field is not present", result.getIss()); + assertEquals("http://subdomain.some.other.issuer/oauth/token", result.getIss()); } finally { IdentityZoneHolder.clear(); } @@ -476,8 +477,8 @@ public void testIssuerInResultsInNonDefaultZone() throws Exception { @Test public void testValidateAudParameter() { - Map result = endpoint.checkToken(accessToken.getValue()); - List aud = (List)result.get(Claims.AUD); + Claims result = endpoint.checkToken(accessToken.getValue()); + List aud = result.getAud(); assertEquals(2, aud.size()); assertTrue(aud.contains("scim")); assertTrue(aud.contains("client")); @@ -485,63 +486,63 @@ public void testValidateAudParameter() { @Test public void testClientId() { - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals("client", result.get(Claims.AZP)); - assertEquals("client", result.get(Claims.CID)); - assertEquals("client", result.get(Claims.CLIENT_ID)); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertEquals("client", result.getAzp()); + assertEquals("client", result.getCid()); + assertEquals("client", result.getClientId()); } @Test public void validateAuthTime() { - Map result = endpoint.checkToken(accessToken.getValue()); - assertNotNull(result.get(Claims.AUTH_TIME)); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertNotNull(result.getAuthTime()); } @Test public void validatateIssuedAtIsSmallerThanExpiredAt() { - Map result = endpoint.checkToken(accessToken.getValue()); - Integer iat = (Integer)result.get(Claims.IAT); + Claims result = endpoint.checkToken(accessToken.getValue()); + Integer iat = result.getIat(); assertNotNull(iat); - Integer exp = (Integer)result.get(Claims.EXP); + Integer exp = result.getExp(); assertNotNull(exp); assertTrue(iat result = endpoint.checkToken(accessToken.getValue()); - assertEquals("olds@vmware.com", result.get("email")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertEquals("olds@vmware.com", result.getEmail()); } @Test public void testClientIdInResult() { - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals("client", result.get("client_id")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertEquals("client", result.getClientId()); } @Test public void testClientIdInAud() { - Map result = endpoint.checkToken(accessToken.getValue()); - assertTrue(((List)result.get(Claims.AUD)).contains("client")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertTrue(result.getAud().contains("client")); } @Test public void testExpiryResult() { - Map result = endpoint.checkToken(accessToken.getValue()); - assertTrue(expiresIn + System.currentTimeMillis() / 1000 >= Integer.parseInt(String.valueOf(result.get("exp")))); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertTrue(expiresIn + System.currentTimeMillis() / 1000 >= result.getExp()); } @Test public void testUserAuthoritiesNotInResult() { - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals(null, result.get("user_authorities")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertEquals(null, result.getAuthorities()); } @Test public void testClientAuthoritiesNotInResult() { - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals(null, result.get("client_authorities")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertEquals(null, result.getAuthorities()); } @Test(expected = InvalidTokenException.class) @@ -554,7 +555,7 @@ public void testExpiredToken() throws Exception { tokenServices.setClientDetailsService(clientDetailsService); accessToken = tokenServices.createAccessToken(authentication); Thread.sleep(1000); - Map result = endpoint.checkToken(accessToken.getValue()); + Claims result = endpoint.checkToken(accessToken.getValue()); } @Test(expected = InvalidTokenException.class) @@ -562,8 +563,8 @@ public void testUpdatedApprovals() { Date thirtySecondsAhead = new Date(System.currentTimeMillis() + 30000); approvalStore.addApproval(new Approval(userId, "client", "read", thirtySecondsAhead, ApprovalStatus.APPROVED, new Date())); - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals(null, result.get("client_authorities")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertEquals(null, result.getAuthorities()); } @Test(expected = InvalidTokenException.class) @@ -575,8 +576,8 @@ public void testDeniedApprovals() { oneSecondAgo)); approvalStore.addApproval(new Approval(userId, "client", "read", thirtySecondsAhead, ApprovalStatus.DENIED, oneSecondAgo)); - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals(null, result.get("client_authorities")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertEquals(null, result.getAuthorities()); } @Test(expected = InvalidTokenException.class) @@ -585,8 +586,8 @@ public void testExpiredApprovals() { new Date())); approvalStore.addApproval(new Approval(userId, "client", "read", new Date(), ApprovalStatus.APPROVED, new Date())); - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals(null, result.get("client_authorities")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertEquals(null, result.getAuthorities()); } @Test @@ -594,9 +595,9 @@ public void testClientOnly() { authentication = new OAuth2Authentication(new AuthorizationRequest("client", Collections.singleton("scim.read")).createOAuth2Request(), null); accessToken = tokenServices.createAccessToken(authentication); - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals("client", result.get("client_id")); - assertEquals("client", result.get("user_id")); + Claims result = endpoint.checkToken(accessToken.getValue()); + assertEquals("client", result.getClientId()); + assertEquals("client", result.getUserId()); } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServicesTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServicesTests.java index 85109df64e9..367cbc2af4f 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServicesTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServicesTests.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.Test; import org.springframework.http.HttpEntity; @@ -48,11 +49,11 @@ public class RemoteTokenServicesTests { public RemoteTokenServicesTests() { services.setClientId("client"); services.setClientSecret("secret"); - body.put(Claims.CLIENT_ID, "remote"); - body.put(Claims.USER_NAME, "olds"); - body.put(Claims.EMAIL, "olds@vmware.com"); - body.put(Claims.ISS, "http://some.issuer.com"); - body.put(Claims.USER_ID, "HDGFJSHGDF"); + body.put(ClaimConstants.CLIENT_ID, "remote"); + body.put(ClaimConstants.USER_NAME, "olds"); + body.put(ClaimConstants.EMAIL, "olds@vmware.com"); + body.put(ClaimConstants.ISS, "http://some.issuer.com"); + body.put(ClaimConstants.USER_ID, "HDGFJSHGDF"); services.setRestTemplate(new RestTemplate() { @SuppressWarnings("unchecked") @Override @@ -71,7 +72,7 @@ public void testTokenRetrieval() throws Exception { assertEquals("olds", result.getUserAuthentication().getName()); assertEquals("HDGFJSHGDF", ((RemoteUserAuthentication) result.getUserAuthentication()).getId()); assertNotNull(result.getOAuth2Request().getRequestParameters()); - assertNull(result.getOAuth2Request().getRequestParameters().get(Claims.ISS)); + assertNull(result.getOAuth2Request().getRequestParameters().get(ClaimConstants.ISS)); } @Test @@ -83,7 +84,7 @@ public void testTokenRetrievalWithClaims() throws Exception { assertEquals("olds", result.getUserAuthentication().getName()); assertEquals("HDGFJSHGDF", ((RemoteUserAuthentication) result.getUserAuthentication()).getId()); assertNotNull(result.getOAuth2Request().getRequestParameters()); - assertNotNull(result.getOAuth2Request().getRequestParameters().get(Claims.ISS)); + assertNotNull(result.getOAuth2Request().getRequestParameters().get(ClaimConstants.ISS)); } @Test @@ -105,12 +106,12 @@ public void testTokenRetrievalWithUserAuthorities() throws Exception { @Test public void testTokenRetrievalWithAdditionalAuthorizationAttributes() throws Exception { Map additionalAuthorizationAttributesMap = Collections.singletonMap("test", 1); - body.put(Claims.ADDITIONAL_AZ_ATTR, additionalAuthorizationAttributesMap); + body.put(ClaimConstants.ADDITIONAL_AZ_ATTR, additionalAuthorizationAttributesMap); OAuth2Authentication result = services.loadAuthentication("FOO"); assertNotNull(result); assertEquals(JsonUtils.writeValueAsString(additionalAuthorizationAttributesMap), result.getOAuth2Request() - .getRequestParameters().get(Claims.ADDITIONAL_AZ_ATTR)); + .getRequestParameters().get(ClaimConstants.ADDITIONAL_AZ_ATTR)); } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaUserTokenConverterTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaUserTokenConverterTests.java index bf8dcc61887..640551394a3 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaUserTokenConverterTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaUserTokenConverterTests.java @@ -13,9 +13,9 @@ package org.cloudfoundry.identity.uaa.oauth; -import static org.cloudfoundry.identity.uaa.oauth.Claims.EMAIL; -import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_ID; -import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_NAME; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EMAIL; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ID; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_NAME; import static org.junit.Assert.assertEquals; import java.util.Map; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2AccessTokenMatchers.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2AccessTokenMatchers.java index 462233e6560..df05e00151f 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2AccessTokenMatchers.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2AccessTokenMatchers.java @@ -4,7 +4,7 @@ import java.util.Map; -import org.cloudfoundry.identity.uaa.oauth.Claims; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.hamcrest.Description; import org.hamcrest.Factory; import org.hamcrest.Matcher; @@ -40,77 +40,77 @@ protected void describeMismatchSafely(OAuth2AccessToken accessToken, Description @Factory public static Matcher issuerUri(Matcher issuerUri) { - return new OAuth2AccessTokenMatchers(Claims.ISS, issuerUri); + return new OAuth2AccessTokenMatchers(ClaimConstants.ISS, issuerUri); } @Factory public static Matcher clientId(Matcher clientId) { - return new OAuth2AccessTokenMatchers(Claims.CLIENT_ID, clientId); + return new OAuth2AccessTokenMatchers(ClaimConstants.CLIENT_ID, clientId); } @Factory public static Matcher userId(Matcher userId) { - return new OAuth2AccessTokenMatchers(Claims.USER_ID, userId); + return new OAuth2AccessTokenMatchers(ClaimConstants.USER_ID, userId); } @Factory public static Matcher subject(Matcher clientId) { - return new OAuth2AccessTokenMatchers(Claims.SUB, clientId); + return new OAuth2AccessTokenMatchers(ClaimConstants.SUB, clientId); } @Factory public static Matcher cid(Matcher clientId) { - return new OAuth2AccessTokenMatchers(Claims.CID, clientId); + return new OAuth2AccessTokenMatchers(ClaimConstants.CID, clientId); } @Factory public static Matcher scope(Matcher scopes) { - return new OAuth2AccessTokenMatchers(Claims.SCOPE, scopes); + return new OAuth2AccessTokenMatchers(ClaimConstants.SCOPE, scopes); } @Factory public static Matcher audience(Matcher resourceIds) { - return new OAuth2AccessTokenMatchers(Claims.AUD, resourceIds); + return new OAuth2AccessTokenMatchers(ClaimConstants.AUD, resourceIds); } @Factory public static Matcher jwtId(Matcher jti) { - return new OAuth2AccessTokenMatchers(Claims.JTI, jti); + return new OAuth2AccessTokenMatchers(ClaimConstants.JTI, jti); } @Factory public static Matcher issuedAt(Matcher iat) { - return new OAuth2AccessTokenMatchers(Claims.IAT, iat); + return new OAuth2AccessTokenMatchers(ClaimConstants.IAT, iat); } @Factory public static Matcher expiry(Matcher expiry) { - return new OAuth2AccessTokenMatchers(Claims.EXP, expiry); + return new OAuth2AccessTokenMatchers(ClaimConstants.EXP, expiry); } @Factory public static Matcher username(Matcher username) { - return new OAuth2AccessTokenMatchers(Claims.USER_NAME, username); + return new OAuth2AccessTokenMatchers(ClaimConstants.USER_NAME, username); } @Factory public static Matcher zoneId(Matcher zoneId) { - return new OAuth2AccessTokenMatchers(Claims.ZONE_ID, zoneId); + return new OAuth2AccessTokenMatchers(ClaimConstants.ZONE_ID, zoneId); } @Factory public static Matcher origin(Matcher origin) { - return new OAuth2AccessTokenMatchers(Claims.ORIGIN, origin); + return new OAuth2AccessTokenMatchers(ClaimConstants.ORIGIN, origin); } @Factory public static Matcher revocationSignature(Matcher signature) { - return new OAuth2AccessTokenMatchers(Claims.REVOCATION_SIGNATURE, signature); + return new OAuth2AccessTokenMatchers(ClaimConstants.REVOCATION_SIGNATURE, signature); } @Factory public static Matcher email(Matcher email) { - return new OAuth2AccessTokenMatchers(Claims.EMAIL, email); + return new OAuth2AccessTokenMatchers(ClaimConstants.EMAIL, email); } @Factory @@ -120,9 +120,9 @@ public static Matcher validFor(Matcher validFor) { @Override protected boolean matchesSafely(OAuth2AccessToken token) { Map claims = getClaims(token); - assertTrue(((Integer) claims.get(Claims.IAT)) > 0); - assertTrue(((Integer) claims.get(Claims.EXP)) > 0); - return validFor.matches(((Integer) claims.get(Claims.EXP)) - ((Integer) claims.get(Claims.IAT))); + assertTrue(((Integer) claims.get(ClaimConstants.IAT)) > 0); + assertTrue(((Integer) claims.get(ClaimConstants.EXP)) > 0); + return validFor.matches(((Integer) claims.get(ClaimConstants.EXP)) - ((Integer) claims.get(ClaimConstants.IAT))); } @Override @@ -134,7 +134,7 @@ public void describeTo(Description description) { protected void describeMismatchSafely(OAuth2AccessToken accessToken, Description mismatchDescription) { if (accessToken != null) { Map claims = getClaims(accessToken); - mismatchDescription.appendText(" was ").appendValue(((Integer) claims.get(Claims.EXP)) - ((Integer) claims.get(Claims.IAT))); + mismatchDescription.appendText(" was ").appendValue(((Integer) claims.get(ClaimConstants.EXP)) - ((Integer) claims.get(ClaimConstants.IAT))); } } }; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2RefreshTokenMatchers.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2RefreshTokenMatchers.java index ba0cd0c8f37..81b11839073 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2RefreshTokenMatchers.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2RefreshTokenMatchers.java @@ -4,7 +4,7 @@ import java.util.Map; -import org.cloudfoundry.identity.uaa.oauth.Claims; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.hamcrest.Description; import org.hamcrest.Factory; import org.hamcrest.Matcher; @@ -40,77 +40,77 @@ protected void describeMismatchSafely(OAuth2RefreshToken accessToken, Descriptio @Factory public static Matcher issuerUri(Matcher issuerUri) { - return new OAuth2RefreshTokenMatchers(Claims.ISS, issuerUri); + return new OAuth2RefreshTokenMatchers(ClaimConstants.ISS, issuerUri); } @Factory public static Matcher clientId(Matcher clientId) { - return new OAuth2RefreshTokenMatchers(Claims.CLIENT_ID, clientId); + return new OAuth2RefreshTokenMatchers(ClaimConstants.CLIENT_ID, clientId); } @Factory public static Matcher userId(Matcher userId) { - return new OAuth2RefreshTokenMatchers(Claims.USER_ID, userId); + return new OAuth2RefreshTokenMatchers(ClaimConstants.USER_ID, userId); } @Factory public static Matcher subject(Matcher clientId) { - return new OAuth2RefreshTokenMatchers(Claims.SUB, clientId); + return new OAuth2RefreshTokenMatchers(ClaimConstants.SUB, clientId); } @Factory public static Matcher cid(Matcher clientId) { - return new OAuth2RefreshTokenMatchers(Claims.CID, clientId); + return new OAuth2RefreshTokenMatchers(ClaimConstants.CID, clientId); } @Factory public static Matcher scope(Matcher scopes) { - return new OAuth2RefreshTokenMatchers(Claims.SCOPE, scopes); + return new OAuth2RefreshTokenMatchers(ClaimConstants.SCOPE, scopes); } @Factory public static Matcher audience(Matcher resourceIds) { - return new OAuth2RefreshTokenMatchers(Claims.AUD, resourceIds); + return new OAuth2RefreshTokenMatchers(ClaimConstants.AUD, resourceIds); } @Factory public static Matcher jwtId(Matcher jti) { - return new OAuth2RefreshTokenMatchers(Claims.JTI, jti); + return new OAuth2RefreshTokenMatchers(ClaimConstants.JTI, jti); } @Factory public static Matcher issuedAt(Matcher iat) { - return new OAuth2RefreshTokenMatchers(Claims.IAT, iat); + return new OAuth2RefreshTokenMatchers(ClaimConstants.IAT, iat); } @Factory public static Matcher expiry(Matcher expiry) { - return new OAuth2RefreshTokenMatchers(Claims.EXP, expiry); + return new OAuth2RefreshTokenMatchers(ClaimConstants.EXP, expiry); } @Factory public static Matcher username(Matcher username) { - return new OAuth2RefreshTokenMatchers(Claims.USER_NAME, username); + return new OAuth2RefreshTokenMatchers(ClaimConstants.USER_NAME, username); } @Factory public static Matcher zoneId(Matcher zoneId) { - return new OAuth2RefreshTokenMatchers(Claims.ZONE_ID, zoneId); + return new OAuth2RefreshTokenMatchers(ClaimConstants.ZONE_ID, zoneId); } @Factory public static Matcher origin(Matcher origin) { - return new OAuth2RefreshTokenMatchers(Claims.ORIGIN, origin); + return new OAuth2RefreshTokenMatchers(ClaimConstants.ORIGIN, origin); } @Factory public static Matcher revocationSignature(Matcher signature) { - return new OAuth2RefreshTokenMatchers(Claims.REVOCATION_SIGNATURE, signature); + return new OAuth2RefreshTokenMatchers(ClaimConstants.REVOCATION_SIGNATURE, signature); } @Factory public static Matcher email(Matcher email) { - return new OAuth2RefreshTokenMatchers(Claims.EMAIL, email); + return new OAuth2RefreshTokenMatchers(ClaimConstants.EMAIL, email); } @Factory @@ -120,9 +120,9 @@ public static Matcher validFor(Matcher validFor) { @Override protected boolean matchesSafely(OAuth2RefreshToken token) { Map claims = getClaims(token); - assertTrue(((Integer) claims.get(Claims.IAT)) > 0); - assertTrue(((Integer) claims.get(Claims.EXP)) > 0); - return validFor.matches(((Integer) claims.get(Claims.EXP)) - ((Integer) claims.get(Claims.IAT))); + assertTrue(((Integer) claims.get(ClaimConstants.IAT)) > 0); + assertTrue(((Integer) claims.get(ClaimConstants.EXP)) > 0); + return validFor.matches(((Integer) claims.get(ClaimConstants.EXP)) - ((Integer) claims.get(ClaimConstants.IAT))); } @Override @@ -134,7 +134,7 @@ public void describeTo(Description description) { protected void describeMismatchSafely(OAuth2RefreshToken accessToken, Description mismatchDescription) { if (accessToken != null) { Map claims = getClaims(accessToken); - mismatchDescription.appendText(" was ").appendValue(((Integer) claims.get(Claims.EXP)) - ((Integer) claims.get(Claims.IAT))); + mismatchDescription.appendText(" was ").appendValue(((Integer) claims.get(ClaimConstants.EXP)) - ((Integer) claims.get(ClaimConstants.IAT))); } } }; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java index b396b6a047f..7e9d412c950 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java @@ -20,6 +20,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.profile.UserInfoResponse; import org.cloudfoundry.identity.uaa.user.InMemoryUaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserTestFactory; @@ -47,17 +48,17 @@ public void testSunnyDay() { UaaUser user = userDatabase.retrieveUserByName("olds", OriginKeys.UAA); UaaAuthentication authentication = UaaAuthenticationTestFactory.getAuthentication(user.getId(), "olds", "olds@vmware.com"); - Map map = endpoint.loginInfo(new OAuth2Authentication(null, authentication)); - assertEquals("olds", map.get("user_name")); - assertEquals("Dale Olds", map.get("name")); - assertEquals("olds@vmware.com", map.get("email")); + UserInfoResponse map = endpoint.loginInfo(new OAuth2Authentication(null, authentication)); + assertEquals("olds", map.getUsername()); + assertEquals("Dale Olds", map.getFullName()); + assertEquals("olds@vmware.com", map.getEmail()); } @Test(expected = UsernameNotFoundException.class) public void testMissingUser() { UaaAuthentication authentication = UaaAuthenticationTestFactory.getAuthentication("nonexist-id", "Dale", "olds@vmware.com"); - Map map = endpoint.loginInfo(new OAuth2Authentication(null, authentication)); + UserInfoResponse map = endpoint.loginInfo(new OAuth2Authentication(null, authentication)); } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java similarity index 100% rename from login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java similarity index 100% rename from login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java new file mode 100644 index 00000000000..8e23bd6ab5f --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java @@ -0,0 +1,70 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.login; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) +public class AuthenticationResponse { + private String error; + private String username; + private String origin; + @JsonProperty("user_id") private String userId; + private String email; + + public void setError(String error) { + this.error = error; + } + + public String getError() { + return error; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public String getOrigin() { + return origin; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserId() { + return userId; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getEmail() { + return email; + } +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/Claims.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/Claims.java rename to payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java index 2946d291422..042eb08207d 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/Claims.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java @@ -10,7 +10,7 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth; +package org.cloudfoundry.identity.uaa.oauth.token; /** *

@@ -22,7 +22,7 @@ * @author Dave Syer * */ -public class Claims { +public class ClaimConstants { public static final String USER_ID = "user_id"; public static final String USER_NAME = "user_name"; public static final String NAME = "name"; diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java new file mode 100644 index 00000000000..df434bd8376 --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java @@ -0,0 +1,301 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.oauth.token; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class Claims { + + @JsonProperty(ClaimConstants.USER_ID) + private String userId; + @JsonProperty(ClaimConstants.USER_NAME) + private String userName; + @JsonProperty(ClaimConstants.NAME) + private String name; + @JsonProperty(ClaimConstants.GIVEN_NAME) + private String givenName; + @JsonProperty(ClaimConstants.FAMILY_NAME) + private String familyName; + @JsonProperty(ClaimConstants.PHONE_NUMBER) + private String phoneNumber; + @JsonProperty(ClaimConstants.EMAIL) + private String email; + @JsonProperty(ClaimConstants.CLIENT_ID) + private String clientId; + @JsonProperty(ClaimConstants.EXP) + private Integer exp; + @JsonProperty(ClaimConstants.AUTHORITIES) + private List authorities; + @JsonProperty(ClaimConstants.SCOPE) + private List scope; + @JsonProperty(ClaimConstants.JTI) + private String jti; + @JsonProperty(ClaimConstants.AUD) + private List aud; + @JsonProperty(ClaimConstants.SUB) + private String sub; + @JsonProperty(ClaimConstants.ISS) + private String iss; + @JsonProperty(ClaimConstants.IAT) + private Integer iat; + @JsonProperty(ClaimConstants.CID) + private String cid; + @JsonProperty(ClaimConstants.GRANT_TYPE) + private String grantType; + @JsonProperty(ClaimConstants.ADDITIONAL_AZ_ATTR) + private String azAttr; + @JsonProperty(ClaimConstants.AZP) + private String azp; + @JsonProperty(ClaimConstants.AUTH_TIME) + private Long authTime; + @JsonProperty(ClaimConstants.ZONE_ID) + private String zid; + @JsonProperty(ClaimConstants.REVOCATION_SIGNATURE) + private String revSig; + @JsonProperty(ClaimConstants.NONCE) + private String nonce; + @JsonProperty(ClaimConstants.ORIGIN) + private String origin; + @JsonProperty(ClaimConstants.ROLES) + private String roles; + @JsonProperty(ClaimConstants.PROFILE) + private String profile; + @JsonProperty(ClaimConstants.USER_ATTRIBUTES) + private String userAttributes; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGivenName() { + return givenName; + } + + public void setGivenName(String givenName) { + this.givenName = givenName; + } + + public String getFamilyName() { + return familyName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + public String getPhoneNumber() { + return phoneNumber; + } + + public void setPhoneNumber(String phoneNumber) { + this.phoneNumber = phoneNumber; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Integer getExp() { + return exp; + } + + public void setExp(Integer exp) { + this.exp = exp; + } + + public List getAuthorities() { + return authorities; + } + + public void setAuthorities(List authorities) { + this.authorities = authorities; + } + + public List getScope() { + return scope; + } + + public void setScope(List scope) { + this.scope = scope; + } + + public String getJti() { + return jti; + } + + public void setJti(String jti) { + this.jti = jti; + } + + public List getAud() { return aud; } + + public void setAud(List aud) { + this.aud = aud; + } + + public String getSub() { + return sub; + } + + public void setSub(String sub) { + this.sub = sub; + } + + public String getIss() { + return iss; + } + + public void setIss(String iss) { + this.iss = iss; + } + + public Integer getIat() { + return iat; + } + + public void setIat(Integer iat) { + this.iat = iat; + } + + public String getCid() { + return cid; + } + + public void setCid(String cid) { + this.cid = cid; + } + + public String getGrantType() { + return grantType; + } + + public void setGrantType(String grantType) { + this.grantType = grantType; + } + + public String getAzAttr() { + return azAttr; + } + + public void setAzAttr(String azAttr) { + this.azAttr = azAttr; + } + + public String getAzp() { + return azp; + } + + public void setAzp(String azp) { + this.azp = azp; + } + + public Long getAuthTime() { + return authTime; + } + + public void setAuthTime(Long authTime) { + this.authTime = authTime; + } + + public String getZid() { + return zid; + } + + public void setZid(String zid) { + this.zid = zid; + } + + public String getRevSig() { + return revSig; + } + + public void setRevSig(String revSig) { + this.revSig = revSig; + } + + public String getNonce() { + return nonce; + } + + public void setNonce(String nonce) { + this.nonce = nonce; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public String getRoles() { + return roles; + } + + public void setRoles(String roles) { + this.roles = roles; + } + + public String getProfile() { + return profile; + } + + public void setProfile(String profile) { + this.profile = profile; + } + + public String getUserAttributes() { + return userAttributes; + } + + public void setUserAttributes(String userAttributes) { + this.userAttributes = userAttributes; + } +} diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java new file mode 100644 index 00000000000..d1740210c00 --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java @@ -0,0 +1,97 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.oauth.token; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; + +/** + * Created by pivotal on 11/18/15. + */ +@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) +public class VerificationKeyResponse { + + @JsonProperty("alg") + private String algorithm; + + @JsonProperty("key") + private String key; + + @JsonProperty("kty") + private String type; + + @JsonProperty("use") + private String use; + + @JsonProperty("n") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String modulus; + + @JsonProperty("e") + @JsonInclude(JsonInclude.Include.NON_NULL) + private String exponent; + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + public String getAlgorithm() { + return algorithm; + } + + public void setKey(String key) { + this.key = key; + } + + public String getKey() { + return key; + } + + public void setType(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setUse(String use) { + this.use = use; + } + + public String getUse() { + return use; + } + + public void setModulus(String modulus) { + this.modulus = modulus; + } + + public String getModulus() { + return modulus; + } + + public void setExponent(String exponent) { + this.exponent = exponent; + } + + public String getExponent() { + return exponent; + } + +} + diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java new file mode 100644 index 00000000000..15ac2e31ab5 --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java @@ -0,0 +1,35 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.oauth.token; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; + +import java.util.List; + +/** + * Created by pivotal on 11/18/15. + */ +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) +public class VerificationKeysListResponse { + private List keys; + + public List getKeys() { + return keys; + } + + public void setKeys(List keys) { + this.keys = keys; + } +} diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java new file mode 100644 index 00000000000..48cef4099ae --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java @@ -0,0 +1,39 @@ +package org.cloudfoundry.identity.uaa.profile; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) +public class PasswordChangeRequest { + + @JsonProperty("code") + private String changeCode; + + @JsonProperty("new_password") + private String newPassword; + + public PasswordChangeRequest() { } + + public PasswordChangeRequest(String changeCode, String newPassword) { + this.changeCode = changeCode; + this.newPassword = newPassword; + } + + public String getChangeCode() { + return changeCode; + } + + public void setChangeCode(String changeCode) { + this.changeCode = changeCode; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java new file mode 100644 index 00000000000..770ee577fb0 --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java @@ -0,0 +1,61 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.profile; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) +public class PasswordChangeResponse { + + @JsonProperty("code") private String loginCode; + @JsonProperty("user_id") private String userId; + private String username; + private String email; + + public void setLoginCode(String loginCode) { + this.loginCode = loginCode; + } + + public String getLoginCode() { + return loginCode; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserId() { + return userId; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getUsername() { + return username; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getEmail() { + return email; + } +} diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordReset.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordReset.java deleted file mode 100644 index e10b88b6066..00000000000 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordReset.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cloudfoundry.identity.uaa.profile; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class PasswordReset { - - private String code; - - @JsonProperty("new_password") - private String newPassword; - - public PasswordReset() { } - - public PasswordReset(String code, String newPassword) { - this.code = code; - this.newPassword = newPassword; - } - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - public String getNewPassword() { - return newPassword; - } - - public void setNewPassword(String newPassword) { - this.newPassword = newPassword; - } -} diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java new file mode 100644 index 00000000000..3b639c46f67 --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java @@ -0,0 +1,43 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.profile; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) +public class PasswordResetResponse { + + @JsonProperty("code") private String changeCode; + @JsonProperty("user_id") private String userId; + + public void setChangeCode(String changeCode) { + this.changeCode = changeCode; + } + + public String getChangeCode() { + return changeCode; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserId() { + return userId; + } +} diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java new file mode 100644 index 00000000000..827ba636ec7 --- /dev/null +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java @@ -0,0 +1,93 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.profile; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.EMAIL; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.FAMILY_NAME; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.GIVEN_NAME; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.NAME; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ID; +import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_NAME; + +/** + * Created by pivotal on 11/18/15. + */ +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) +public class UserInfoResponse { + + @JsonProperty(USER_ID) + private String userId; + + @JsonProperty(USER_NAME) + private String username; + + @JsonProperty(GIVEN_NAME) + private String givenName; + + @JsonProperty(FAMILY_NAME) + private String familyName; + + @JsonProperty(EMAIL) + private String email; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getGivenName() { + return givenName; + } + + public void setGivenName(String givenName) { + this.givenName = givenName; + } + + public String getFamilyName() { + return familyName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + @JsonProperty(NAME) + public String getFullName() { + return (getGivenName() != null ? getGivenName() : "") + + (getFamilyName() != null ? " " + getFamilyName() : ""); + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java index c7a3423d1c7..7e17016c24e 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java @@ -24,7 +24,9 @@ import org.cloudfoundry.identity.uaa.login.NotFoundException; import org.cloudfoundry.identity.uaa.login.ResetPasswordService; import org.cloudfoundry.identity.uaa.login.ResetPasswordService.ResetPasswordResponse; -import org.cloudfoundry.identity.uaa.profile.PasswordReset; +import org.cloudfoundry.identity.uaa.profile.PasswordChangeResponse; +import org.cloudfoundry.identity.uaa.profile.PasswordChangeRequest; +import org.cloudfoundry.identity.uaa.profile.PasswordResetResponse; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.scim.exception.ScimException; @@ -76,9 +78,9 @@ public void setMessageConverters(HttpMessageConverter[] messageConverters) { } @RequestMapping(value = "/password_resets", method = RequestMethod.POST) - public ResponseEntity> resetPassword(@RequestBody String email, - @RequestParam(required=false, value = "client_id") String clientId, - @RequestParam(required=false, value = "redirect_uri") String redirectUri) throws IOException { + public ResponseEntity resetPassword(@RequestBody String email, + @RequestParam(required = false, value = "client_id") String clientId, + @RequestParam(required = false, value = "redirect_uri") String redirectUri) throws IOException { if (clientId == null) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication instanceof OAuth2Authentication) { @@ -86,14 +88,14 @@ public ResponseEntity> resetPassword(@RequestBody String emai clientId = oAuth2Authentication.getOAuth2Request().getClientId(); } } - Map response = new HashMap<>(); + PasswordResetResponse response = new PasswordResetResponse(); try { ForgotPasswordInfo forgotPasswordInfo = resetPasswordService.forgotPassword(email, clientId, redirectUri); - response.put("code", forgotPasswordInfo.getResetPasswordCode().getCode()); - response.put("user_id", forgotPasswordInfo.getUserId()); + response.setChangeCode(forgotPasswordInfo.getResetPasswordCode().getCode()); + response.setUserId(forgotPasswordInfo.getUserId()); return new ResponseEntity<>(response, CREATED); } catch (ConflictException e) { - response.put("user_id", e.getUserId()); + response.setUserId(e.getUserId()); return new ResponseEntity<>(response, CONFLICT); } catch (NotFoundException e) { return new ResponseEntity<>(NOT_FOUND); @@ -101,19 +103,19 @@ public ResponseEntity> resetPassword(@RequestBody String emai } @RequestMapping(value = "/password_change", method = RequestMethod.POST) - public ResponseEntity> changePassword(@RequestBody PasswordReset passwordReset) { - ResponseEntity> responseEntity; - if (passwordReset.getCode() != null) { + public ResponseEntity changePassword(@RequestBody PasswordChangeRequest passwordChangeRequest) { + ResponseEntity responseEntity; + if (passwordChangeRequest.getChangeCode() != null) { try { - ResetPasswordResponse response = resetPasswordService.resetPassword(passwordReset.getCode(), passwordReset.getNewPassword()); - ScimUser user = response.getUser(); - ExpiringCode loginCode = getCode(user.getId(), user.getUserName(), response.getClientId()); - Map responseBody = new HashMap<>(); - responseBody.put("user_id", user.getId()); - responseBody.put("username", user.getUserName()); - responseBody.put("email", user.getPrimaryEmail()); - responseBody.put("code", loginCode.getCode()); - return new ResponseEntity<>(responseBody, OK); + ResetPasswordResponse reset = resetPasswordService.resetPassword(passwordChangeRequest.getChangeCode(), passwordChangeRequest.getNewPassword()); + ScimUser user = reset.getUser(); + ExpiringCode loginCode = getCode(user.getId(), user.getUserName(), reset.getClientId()); + PasswordChangeResponse response = new PasswordChangeResponse(); + response.setUserId(user.getId()); + response.setUsername(user.getUserName()); + response.setEmail(user.getPrimaryEmail()); + response.setLoginCode(loginCode.getCode()); + return new ResponseEntity<>(response, OK); } catch (BadCredentialsException e) { return new ResponseEntity<>(UNAUTHORIZED); } catch (ScimResourceNotFoundException e) { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java index 506e815e016..0055b83e7d0 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java @@ -19,7 +19,7 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.oauth.Claims; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -156,14 +156,14 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() throws Exception { Jwt idTokenClaims = JwtHelper.decode(idToken); Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() {}); - assertNotNull(claims.get(Claims.USER_ATTRIBUTES)); - Map> userAttributes = (Map>) claims.get(Claims.USER_ATTRIBUTES); + assertNotNull(claims.get(ClaimConstants.USER_ATTRIBUTES)); + Map> userAttributes = (Map>) claims.get(ClaimConstants.USER_ATTRIBUTES); assertThat(userAttributes.get(COST_CENTERS), containsInAnyOrder(DENVER_CO)); assertThat(userAttributes.get(MANAGERS), containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); - assertNotNull(claims.get(Claims.ROLES)); - List roles = (List) claims.get(Claims.ROLES); + assertNotNull(claims.get(ClaimConstants.ROLES)); + List roles = (List) claims.get(ClaimConstants.ROLES); assertThat(roles, containsInAnyOrder("marissaniner", "marissaniner2")); //no user_attribute scope provided @@ -180,8 +180,8 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() throws Exception { idTokenClaims = JwtHelper.decode(idToken); claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() {}); - assertNull(claims.get(Claims.USER_ATTRIBUTES)); - assertNull(claims.get(Claims.ROLES)); + assertNull(claims.get(ClaimConstants.USER_ATTRIBUTES)); + assertNull(claims.get(ClaimConstants.ROLES)); } protected boolean doesSupportZoneDNS_and_isLdapEnabled() { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OpenIdTokenGrantsIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OpenIdTokenGrantsIT.java index a73ba5d5a5f..20d78b5807e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OpenIdTokenGrantsIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OpenIdTokenGrantsIT.java @@ -15,7 +15,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.oauth.Claims; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; @@ -194,8 +194,8 @@ private void validateToken(String paramName, Map params, String[] scopes, String Assert.assertThat(claims.get("client_id"), is("cf")); Assert.assertThat(claims.get("cid"), is("cf")); Assert.assertThat(claims.get("user_name"), is(user.getUserName())); - Assert.assertThat(((List) claims.get(Claims.SCOPE)), containsInAnyOrder(scopes)); - Assert.assertThat(((List) claims.get(Claims.AUD)), containsInAnyOrder(aud)); + Assert.assertThat(((List) claims.get(ClaimConstants.SCOPE)), containsInAnyOrder(scopes)); + Assert.assertThat(((List) claims.get(ClaimConstants.AUD)), containsInAnyOrder(aud)); } @Test diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index 3590e695a42..b2d3868e0cd 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -25,7 +25,7 @@ import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.test.LoginServerClassRunner; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.oauth.Claims; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; @@ -736,8 +736,8 @@ public void testSamlLogin_Custom_User_Attributes_In_ID_Token() throws Exception Jwt idTokenClaims = JwtHelper.decode(idToken); Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() {}); - assertNotNull(claims.get(Claims.USER_ATTRIBUTES)); - Map> userAttributes = (Map>) claims.get(Claims.USER_ATTRIBUTES); + assertNotNull(claims.get(ClaimConstants.USER_ATTRIBUTES)); + Map> userAttributes = (Map>) claims.get(ClaimConstants.USER_ATTRIBUTES); assertThat(userAttributes.get(COST_CENTERS), containsInAnyOrder(DENVER_CO)); assertThat(userAttributes.get(MANAGERS), containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index 5c932dfc974..1a6663e16b0 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -26,7 +26,7 @@ import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.util.FakeJavaMailSender; -import org.cloudfoundry.identity.uaa.oauth.Claims; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices; import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenStore; import org.cloudfoundry.identity.uaa.rest.jdbc.SimpleSearchQueryConverter; @@ -414,7 +414,7 @@ public void testBootstrappedIdps_and_ExcludedClaims() throws Exception { //we have provided 4 here, but the original login.yml may add, but not remove some assertTrue(samlProviders.getIdentityProviderDefinitions().size() >= 4); - assertThat(context.getBean(UaaTokenServices.class).getExcludedClaims(), containsInAnyOrder(Claims.AUTHORITIES)); + assertThat(context.getBean(UaaTokenServices.class).getExcludedClaims(), containsInAnyOrder(ClaimConstants.AUTHORITIES)); //verify that they got loaded in the DB for (SamlIdentityProviderDefinition def : samlProviders.getIdentityProviderDefinitions()) { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java index 62d09e1c43b..50da8d62364 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java @@ -41,7 +41,7 @@ import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; -import org.cloudfoundry.identity.uaa.profile.PasswordReset; +import org.cloudfoundry.identity.uaa.profile.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.event.ScimEventPublisher; import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; import org.cloudfoundry.identity.uaa.test.TestClient; @@ -427,7 +427,7 @@ public void changePassword_ReturnsSuccess_WithValidExpiringCode() throws Excepti String loginToken = testClient.getClientCredentialsOAuthAccessToken("login", "loginsecret", "oauth.login"); String expiringCode = requestExpiringCode(testUser.getUserName(), loginToken); - PasswordReset pwch = new PasswordReset(expiringCode, "Koala2"); + PasswordChangeRequest pwch = new PasswordChangeRequest(expiringCode, "Koala2"); MockHttpServletRequestBuilder changePasswordPost = post("/password_change") .accept(MediaType.APPLICATION_JSON_VALUE) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java index 88d756c03bd..3f632204983 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java @@ -22,7 +22,7 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.oauth.Claims; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.oauth.DisableIdTokenResponseTypeFilter; import org.cloudfoundry.identity.uaa.oauth.token.SignerProvider; import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices; @@ -858,7 +858,7 @@ public void testOpenIdToken() throws Exception { .param(OAuth2Utils.SCOPE, "openid") .param(OAuth2Utils.STATE, state) .param(OAuth2Utils.CLIENT_ID, authCodeClientId) - .param(Claims.NONCE, "testnonce") + .param(ClaimConstants.NONCE, "testnonce") .param(OAuth2Utils.REDIRECT_URI, TEST_REDIRECT_URI); result = getMockMvc().perform(oauthTokenPost).andExpect(status().is3xxRedirection()).andReturn(); @@ -890,7 +890,7 @@ public void testOpenIdToken() throws Exception { //nonce must be in id_token if was in auth request, see http://openid.net/specs/openid-connect-core-1_0.html#IDToken Map claims = getClaimsForToken((String) token.get("id_token")); - assertEquals("testnonce", claims.get(Claims.NONCE)); + assertEquals("testnonce", claims.get(ClaimConstants.NONCE)); //hybrid flow defined in - response_types=code token id_token //http://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth @@ -1102,22 +1102,22 @@ public void testOpenIdToken() throws Exception { private void validateOpenIdConnectToken(String token, String userId, String clientId) { Map result = getClaimsForToken(token); - String iss = (String)result.get(Claims.ISS); + String iss = (String)result.get(ClaimConstants.ISS); assertEquals(uaaTokenServices.getTokenEndpoint(), iss); - String sub = (String)result.get(Claims.SUB); + String sub = (String)result.get(ClaimConstants.SUB); assertEquals(userId, sub); - List aud = (List)result.get(Claims.AUD); + List aud = (List)result.get(ClaimConstants.AUD); assertTrue(aud.contains(clientId)); - Integer exp = (Integer)result.get(Claims.EXP); + Integer exp = (Integer)result.get(ClaimConstants.EXP); assertNotNull(exp); - Integer iat = (Integer)result.get(Claims.IAT); + Integer iat = (Integer)result.get(ClaimConstants.IAT); assertNotNull(iat); assertTrue(exp>iat); - List openid = (List)result.get(Claims.SCOPE); + List openid = (List)result.get(ClaimConstants.SCOPE); Assert.assertThat(openid, containsInAnyOrder("openid")); //TODO OpenID - Integer auth_time = (Integer)result.get(Claims.AUTH_TIME); + Integer auth_time = (Integer)result.get(ClaimConstants.AUTH_TIME); assertNotNull(auth_time); @@ -1180,7 +1180,7 @@ public void test_Token_Expiry_Time() throws Exception { Jwt tokenJwt = JwtHelper.decode(token); Map claims = JsonUtils.readValue(tokenJwt.getClaims(), new TypeReference>() {}); - Integer expirationTime = (Integer)claims.get(Claims.EXP); + Integer expirationTime = (Integer)claims.get(ClaimConstants.EXP); Calendar nineYearsAhead = new GregorianCalendar(); nineYearsAhead.setTimeInMillis(System.currentTimeMillis()); @@ -1253,7 +1253,7 @@ public void testWildcardPasswordGrant() throws Exception { set1.remove("openid"); set1.remove("profile"); set1.remove("roles"); - set1.remove(Claims.USER_ATTRIBUTES); + set1.remove(ClaimConstants.USER_ATTRIBUTES); validatePasswordGrantToken( clientId, userId, @@ -1775,15 +1775,15 @@ public void testGetClientCredentialsTokenForDefaultIdentityZone() throws Excepti assertNotNull(bodyMap.get("access_token")); Jwt jwt = JwtHelper.decode((String)bodyMap.get("access_token")); Map claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference>() {}); - assertNotNull(claims.get(Claims.AUTHORITIES)); - assertNotNull(claims.get(Claims.AZP)); + assertNotNull(claims.get(ClaimConstants.AUTHORITIES)); + assertNotNull(claims.get(ClaimConstants.AZP)); } @Test public void testGetClientCredentials_WithAuthoritiesExcluded_ForDefaultIdentityZone() throws Exception { Set originalExclude = getWebApplicationContext().getBean(UaaTokenServices.class).getExcludedClaims(); try { - getWebApplicationContext().getBean(UaaTokenServices.class).setExcludedClaims(new HashSet<>(Arrays.asList(Claims.AUTHORITIES, Claims.AZP))); + getWebApplicationContext().getBean(UaaTokenServices.class).setExcludedClaims(new HashSet<>(Arrays.asList(ClaimConstants.AUTHORITIES, ClaimConstants.AZP))); String clientId = "testclient" + new RandomValueStringGenerator().generate(); String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); @@ -1801,8 +1801,8 @@ public void testGetClientCredentials_WithAuthoritiesExcluded_ForDefaultIdentityZ assertNotNull(bodyMap.get("access_token")); Jwt jwt = JwtHelper.decode((String)bodyMap.get("access_token")); Map claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference>() {}); - assertNull(claims.get(Claims.AUTHORITIES)); - assertNull(claims.get(Claims.AZP)); + assertNull(claims.get(ClaimConstants.AUTHORITIES)); + assertNull(claims.get(ClaimConstants.AZP)); }finally { getWebApplicationContext().getBean(UaaTokenServices.class).setExcludedClaims(originalExclude); } @@ -2096,9 +2096,9 @@ public void testRevocablePasswordGrantTokenForDefaultZone() throws Exception { String token = (String)tokenResponse.get(tokenKey); Jwt jwt = JwtHelper.decode(token); Map claims = JsonUtils.readValue(jwt.getClaims(), new TypeReference>(){}); - assertNotNull("Token revocation signature must exist", claims.get(Claims.REVOCATION_SIGNATURE)); - assertTrue("Token revocation signature must be a string", claims.get(Claims.REVOCATION_SIGNATURE) instanceof String); - assertTrue("Token revocation signature must have data", StringUtils.hasText((String) claims.get(Claims.REVOCATION_SIGNATURE))); + assertNotNull("Token revocation signature must exist", claims.get(ClaimConstants.REVOCATION_SIGNATURE)); + assertTrue("Token revocation signature must be a string", claims.get(ClaimConstants.REVOCATION_SIGNATURE) instanceof String); + assertTrue("Token revocation signature must have data", StringUtils.hasText((String) claims.get(ClaimConstants.REVOCATION_SIGNATURE))); } private ScimUser setUpUser(String username) { From 87b85f4975695748416c1ee585af99a596039e86 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Thu, 19 Nov 2015 13:50:40 -0800 Subject: [PATCH 008/103] Refactor out ClientDetailsModification constructors [#107504490] https://www.pivotaltracker.com/story/show/107504490 Signed-off-by: Paul Warren --- .../uaa/oauth/ClientAdminEndpointsTests.java | 4 +- .../oauth/token/TokenKeyEndpointTests.java | 34 +++++-- .../identity/uaa/oauth/approval/Approval.java | 84 ++++++++--------- .../client/ClientDetailsModification.java | 14 +-- .../ClientAdminEndpointsIntegrationTests.java | 32 +++++-- .../InvitationsEndpointMockMvcTests.java | 5 +- .../login/InvitationsServiceMockMvcTests.java | 4 +- .../ClientAdminEndpointsMockMvcTests.java | 89 +++++++++++-------- .../identity/uaa/mock/util/MockMvcUtils.java | 31 +++---- .../ScimGroupEndpointsMockMvcTests.java | 2 +- .../ScimUserEndpointsMockMvcTests.java | 4 +- .../endpoints/ScimUserLookupMockMvcTests.java | 5 +- 12 files changed, 178 insertions(+), 130 deletions(-) diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java index bf16356aa06..d556d0f71b5 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java @@ -249,7 +249,9 @@ public void testMultipleCreateClientDetailsEmptyArray() throws Exception { @Test(expected = InvalidClientDetailsException.class) public void testMultipleCreateClientDetailsNonExistent() throws Exception { - ClientDetailsModification nonexist = new ClientDetailsModification("unknown","","","",""); + ClientDetailsModification detailsModification = new ClientDetailsModification(); + detailsModification.setClientId("unknown"); + ClientDetailsModification nonexist = detailsModification; endpoints.createClientDetailsTx(new ClientDetailsModification[]{nonexist}); } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java index 0b2362c198d..ba64ec3de6a 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java @@ -14,6 +14,8 @@ import static org.junit.Assert.assertEquals; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.Before; import org.junit.Test; import org.springframework.security.access.AccessDeniedException; @@ -21,6 +23,9 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; +import java.util.HashMap; +import java.util.Map; + /** * @author Dave Syer * @author Luke Taylor @@ -39,18 +44,33 @@ public void setUp() throws Exception { @Test public void sharedSecretIsReturnedFromTokenKeyEndpoint() throws Exception { signerProvider.setVerifierKey("someKey"); - assertEquals("{alg=HMACSHA256, value=someKey, kty=MAC, use=sig}", - tokenEnhancer.getKey(new UsernamePasswordAuthenticationToken("foo", "bar")).toString()); + VerificationKeyResponse response = tokenEnhancer.getKey(new UsernamePasswordAuthenticationToken("foo", "bar")); + assertEquals("HMACSHA256", response.getAlgorithm()); + assertEquals("someKey", response.getKey()); + assertEquals("MAC", response.getType()); + assertEquals("sig", response.getUse()); } @Test(expected = AccessDeniedException.class) public void sharedSecretCannotBeAnonymouslyRetrievedFromTokenKeyEndpoint() throws Exception { signerProvider.setVerifierKey("someKey"); - assertEquals( - "{alg=HMACSHA256, value=someKey}", - tokenEnhancer.getKey( - new AnonymousAuthenticationToken("anon", "anonymousUser", AuthorityUtils - .createAuthorityList("ROLE_ANONYMOUS"))).toString()); + assertEquals("{alg=HMACSHA256, value=someKey}", + tokenEnhancer.getKey( + new AnonymousAuthenticationToken("anon", "anonymousUser", AuthorityUtils + .createAuthorityList("ROLE_ANONYMOUS"))).toString()); } + @Test + public void responseIsBackwardCompatibleWithMap() { + signerProvider.setVerifierKey("someKey"); + VerificationKeyResponse response = tokenEnhancer.getKey(new UsernamePasswordAuthenticationToken("foo", "bar")); + + String serialized = JsonUtils.writeValueAsString(response); + + Map deserializedMap = JsonUtils.readValue(serialized, Map.class); + assertEquals("HMACSHA256", deserializedMap.get("alg")); + assertEquals("someKey", deserializedMap.get("key")); + assertEquals("MAC", deserializedMap.get("kty")); + assertEquals("sig", deserializedMap.get("use")); + } } diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java index 3818c936b60..ac052851d9b 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java @@ -27,26 +27,56 @@ @JsonDeserialize(using = ApprovalsJsonDeserializer.class) public class Approval { + public enum ApprovalStatus { + APPROVED, + DENIED; + } + private String userId; private String clientId; private String scope; - public enum ApprovalStatus { - APPROVED, - DENIED; + private ApprovalStatus status; + + private Date expiresAt; + + private Date lastUpdatedAt; + + public Approval(String userId, String clientId, String scope, int expiresIn, ApprovalStatus status) { + this(userId, clientId, scope, new Date(), status, new Date()); + Calendar expiresAt = Calendar.getInstance(); + expiresAt.add(Calendar.MILLISECOND, expiresIn); + setExpiresAt(expiresAt.getTime()); } - private ApprovalStatus status; + public Approval(String userId, String clientId, String scope, Date expiresAt, ApprovalStatus status) { + this(userId, clientId, scope, expiresAt, status, new Date()); + } - public ApprovalStatus getStatus() { - return status; + public Approval(String userId, String clientId, String scope, Date expiresAt, ApprovalStatus status, + Date lastUpdatedAt) { + this.userId = userId; + this.clientId = clientId; + this.scope = scope; + this.expiresAt = expiresAt; + this.status = status; + this.lastUpdatedAt = lastUpdatedAt; } - private Date expiresAt; + public Approval() { + } - private Date lastUpdatedAt; + public Approval(Approval approval) { + this(approval.getUserId(), + approval.getClientId(), + approval.getScope(), + approval.getExpiresAt(), + approval.getStatus(), + approval.getLastUpdatedAt() + ); + } public String getUserId() { return userId; @@ -64,6 +94,10 @@ public void setClientId(String clientId) { this.clientId = clientId == null ? "" : clientId; } + public ApprovalStatus getStatus() { + return status; + } + public String getScope() { return scope; } @@ -102,40 +136,6 @@ public boolean isCurrentlyActive() { return expiresAt != null && expiresAt.after(new Date()); } - public Approval(String userId, String clientId, String scope, int expiresIn, ApprovalStatus status) { - this(userId, clientId, scope, new Date(), status, new Date()); - Calendar expiresAt = Calendar.getInstance(); - expiresAt.add(Calendar.MILLISECOND, expiresIn); - setExpiresAt(expiresAt.getTime()); - } - - public Approval(String userId, String clientId, String scope, Date expiresAt, ApprovalStatus status) { - this(userId, clientId, scope, expiresAt, status, new Date()); - } - - public Approval(String userId, String clientId, String scope, Date expiresAt, ApprovalStatus status, - Date lastUpdatedAt) { - this.userId = userId; - this.clientId = clientId; - this.scope = scope; - this.expiresAt = expiresAt; - this.status = status; - this.lastUpdatedAt = lastUpdatedAt; - } - - public Approval() { - } - - public Approval(Approval approval) { - this(approval.getUserId(), - approval.getClientId(), - approval.getScope(), - approval.getExpiresAt(), - approval.getStatus(), - approval.getLastUpdatedAt() - ); - } - @Override public int hashCode() { final int prime = 31; diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java index f62819c8871..548521b6836 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java @@ -4,9 +4,15 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import java.util.Collection; +import java.util.List; +import java.util.Set; + @JsonInclude(JsonInclude.Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class ClientDetailsModification extends BaseClientDetails { @@ -24,12 +30,8 @@ public class ClientDetailsModification extends BaseClientDetails { public ClientDetailsModification() { } - public ClientDetailsModification(String clientId, String resourceIds, String scopes, String grantTypes, String authorities, String redirectUris) { - super(clientId, resourceIds, scopes, grantTypes, authorities, redirectUris); - } - - public ClientDetailsModification(String clientId, String resourceIds, String scopes, String grantTypes, String authorities) { - super(clientId, resourceIds, scopes, grantTypes, authorities); + public static Set derp(String wat) { + return null; } public ClientDetailsModification(ClientDetails prototype) { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java index b90b9410813..f28d3e45b94 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java @@ -109,13 +109,17 @@ public void testCreateClients() throws Exception { public ClientDetailsModification[] doCreateClients() throws Exception { headers = getAuthenticatedHeaders(getClientCredentialsAccessToken("clients.admin,clients.read,clients.write,clients.secret")); headers.add("Accept", "application/json"); - String grantTypes = "client_credentials"; RandomValueStringGenerator gen = new RandomValueStringGenerator(); String[] ids = new String[5]; ClientDetailsModification[] clients = new ClientDetailsModification[ids.length]; for (int i=0; i singletonMap("foo", Arrays.asList("bar"))); } @@ -433,7 +437,7 @@ public void testCreateExistingClientFails() throws Exception { @Test public void testClientApprovalsDeleted() throws Exception { //create client - BaseClientDetails client = createClient("client_credentials,password"); + BaseClientDetails client = createClient("client_credentials","password"); assertNotNull(getClient(client.getClientId())); //issue a user token for this client OAuth2AccessToken userToken = getUserAccessToken(client.getClientId(), "secret", testAccounts.getUserName(), testAccounts.getPassword(),"oauth.approvals"); @@ -462,7 +466,7 @@ public void testClientApprovalsDeleted() throws Exception { @Test public void testClientTxApprovalsDeleted() throws Exception { //create client - BaseClientDetails client = createClient("client_credentials,password"); + BaseClientDetails client = createClient("client_credentials","password"); assertNotNull(getClient(client.getClientId())); //issue a user token for this client OAuth2AccessToken userToken = getUserAccessToken(client.getClientId(), "secret", testAccounts.getUserName(), testAccounts.getPassword(),"oauth.approvals"); @@ -490,7 +494,7 @@ public void testClientTxApprovalsDeleted() throws Exception { @Test public void testClientTxModifyApprovalsDeleted() throws Exception { //create client - ClientDetailsModification client = createClient("client_credentials,password"); + ClientDetailsModification client = createClient("client_credentials","password"); assertNotNull(getClient(client.getClientId())); //issue a user token for this client OAuth2AccessToken userToken = getUserAccessToken(client.getClientId(), "secret", testAccounts.getUserName(), testAccounts.getPassword(),"oauth.approvals"); @@ -552,8 +556,13 @@ private Approval[] addApprovals(String token, String clientId) throws Exception return response.getBody(); } - private ClientDetailsModification createClient(String grantTypes) throws Exception { - ClientDetailsModification client = new ClientDetailsModification(new RandomValueStringGenerator().generate(), "", "oauth.approvals,foo,bar",grantTypes, "uaa.none"); + private ClientDetailsModification createClient(String... grantTypes) throws Exception { + ClientDetailsModification detailsModification = new ClientDetailsModification(); + detailsModification.setClientId(new RandomValueStringGenerator().generate()); + detailsModification.setScope(Arrays.asList("oauth.approvals", "foo", "bar")); + detailsModification.setAuthorizedGrantTypes(Arrays.asList(grantTypes)); + detailsModification.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("uaa.none")); + ClientDetailsModification client = detailsModification; client.setClientSecret("secret"); client.setAdditionalInformation(Collections.singletonMap("foo", Arrays.asList("bar"))); ResponseEntity result = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients"), @@ -562,8 +571,13 @@ private ClientDetailsModification createClient(String grantTypes) throws Excepti return client; } - private ClientDetailsModification createApprovalsClient(String grantTypes) throws Exception { - ClientDetailsModification client = new ClientDetailsModification(new RandomValueStringGenerator().generate(), "", "oauth.login,oauth.approvals,foo,bar",grantTypes, "uaa.none"); + private ClientDetailsModification createApprovalsClient(String... grantTypes) throws Exception { + ClientDetailsModification detailsModification = new ClientDetailsModification(); + detailsModification.setClientId(new RandomValueStringGenerator().generate()); + detailsModification.setScope(Arrays.asList("oauth.login","oauth.approvals","foo","bar")); + detailsModification.setAuthorizedGrantTypes(Arrays.asList(grantTypes)); + detailsModification.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList("uaa.none")); + ClientDetailsModification client = detailsModification; client.setClientSecret("secret"); client.setAdditionalInformation(Collections. singletonMap("foo", Arrays.asList("bar"))); ResponseEntity result = serverRunning.getRestTemplate().exchange(serverRunning.getUrl("/oauth/clients"), diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java index c19dcd0806f..44dca87f990 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpointMockMvcTests.java @@ -25,6 +25,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import java.util.Arrays; +import java.util.Collections; import java.util.Map; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.ORIGIN; @@ -65,7 +66,7 @@ public void setUp() throws Exception { clientId = generator.generate().toLowerCase(); clientSecret = generator.generate().toLowerCase(); authorities = "scim.read,scim.invite"; - clientDetails = utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, "oauth", "scim.read,scim.invite", Arrays.asList(new MockMvcUtils.GrantType[]{MockMvcUtils.GrantType.client_credentials, MockMvcUtils.GrantType.password}), authorities); + clientDetails = utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("scim.read","scim.invite"), Arrays.asList(new String[]{"client_credentials", "password"}), authorities); scimInviteToken = utils().getClientCredentialsOAuthAccessToken(getMockMvc(), clientId, clientSecret, "scim.read scim.invite", null); domain = generator.generate().toLowerCase()+".com"; IdentityProvider uaaProvider = getWebApplicationContext().getBean(IdentityProviderProvisioning.class).retrieveByOrigin(UAA, IdentityZone.getUaa().getId()); @@ -116,7 +117,7 @@ public void invite_User_Within_Zone() throws Exception { String zonedClientId = "zonedClientId"; String zonedClientSecret = "zonedClientSecret"; - BaseClientDetails zonedClientDetails = (BaseClientDetails)utils().createClient(this.getMockMvc(), result.getZoneAdminToken(), zonedClientId, zonedClientSecret, "oauth", "scim.read,scim.invite", Arrays.asList(new MockMvcUtils.GrantType[]{MockMvcUtils.GrantType.client_credentials, MockMvcUtils.GrantType.password}), authorities, null, result.getIdentityZone()); + BaseClientDetails zonedClientDetails = (BaseClientDetails)utils().createClient(this.getMockMvc(), result.getZoneAdminToken(), zonedClientId, zonedClientSecret, Collections.singleton("oauth"), Arrays.asList("scim.read","scim.invite"), Arrays.asList("client_credentials", "password"), authorities, null, result.getIdentityZone()); zonedClientDetails.setClientSecret(zonedClientSecret); String zonedScimInviteToken = utils().getClientCredentialsOAuthAccessToken(getMockMvc(), zonedClientDetails.getClientId(), zonedClientDetails.getClientSecret(), "scim.read scim.invite", subdomain); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java index 6164c46d05f..3e569a484ee 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java @@ -42,6 +42,8 @@ import java.net.URL; import java.util.Arrays; +import java.util.Collections; + import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; @@ -81,7 +83,7 @@ public void setUp() throws Exception { clientId = generator.generate().toLowerCase(); clientSecret = generator.generate().toLowerCase(); authorities = "scim.read,scim.invite"; - MockMvcUtils.utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, "oauth", "scim.read,scim.invite", Arrays.asList(new MockMvcUtils.GrantType[]{MockMvcUtils.GrantType.client_credentials, MockMvcUtils.GrantType.password}), authorities, REDIRECT_URI, IdentityZone.getUaa()); + MockMvcUtils.utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("scim.read","scim.invite"), Arrays.asList(new String[]{"client_credentials", "password"}), authorities, Collections.singleton(REDIRECT_URI), IdentityZone.getUaa()); userInviteToken = MockMvcUtils.utils().getScimInviteUserToken(getMockMvc(), clientId, clientSecret); getWebApplicationContext().getBean(JdbcTemplate.class).update("delete from expiring_code_store"); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java index 3737d83ab7f..593fbaf2e4d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java @@ -33,6 +33,7 @@ import org.mockito.ArgumentCaptor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails; @@ -47,6 +48,7 @@ import java.util.Collections; import java.util.Date; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; @@ -158,7 +160,7 @@ private void setupAdminUserToken() throws Exception { @Test public void testCreateClient() throws Exception { - createClient(adminToken, new RandomValueStringGenerator().generate(), "client_credentials"); + createClient(adminToken, new RandomValueStringGenerator().generate(), Collections.singleton("client_credentials")); verify(applicationEventPublisher, times(1)).publishEvent(captor.capture()); assertEquals(AuditEventType.ClientCreateSuccess, captor.getValue().getAuditEvent().getType()); } @@ -166,7 +168,7 @@ public void testCreateClient() throws Exception { @Test public void testCreateClientAsAdminUser() throws Exception { setupAdminUserToken(); - createClient(adminUserToken, new RandomValueStringGenerator().generate(), "client_credentials"); + createClient(adminUserToken, new RandomValueStringGenerator().generate(), Collections.singleton("client_credentials")); verify(applicationEventPublisher, times(2)).publishEvent(captor.capture()); for (AbstractUaaEvent event : captor.getAllValues()) { assertEquals(AuditEventType.ClientCreateSuccess, event.getAuditEvent().getType()); @@ -182,7 +184,9 @@ public void createClient_withClientAdminToken_withAuthoritiesExcluded() throws E testAccounts.getAdminClientId(), testAccounts.getAdminClientSecret(), "clients.admin"); - ClientDetailsModification client = createBaseClient("test-client-id", "client_credentials", "password.write,scim.write,scim.read", "foo,bar,oauth.approvals"); + List authorities = Arrays.asList("password.write", "scim.write", "scim.read"); + List scopes = Arrays.asList("foo","bar","oauth.approvals"); + ClientDetailsModification client = createBaseClient("test-client-id", Collections.singleton("client_credentials"), authorities, scopes); MockHttpServletRequestBuilder createClientPost = post("/oauth/clients") .header("Authorization", "Bearer " + clientAdminToken) .accept(APPLICATION_JSON) @@ -209,8 +213,9 @@ public void test_Read_Restricted_Scopes() throws Exception { @Test public void testCreate_RestrictedClient_Fails() throws Exception { String id = new RandomValueStringGenerator().generate(); - BaseClientDetails clientWithAuthorities = createBaseClient(id, "client_credentials,password", StringUtils.collectionToCommaDelimitedString(new UaaScopes().getUaaScopes()), ""); - BaseClientDetails clientWithScopes = createBaseClient(id,"client_credentials,password", "", StringUtils.collectionToCommaDelimitedString(new UaaScopes().getUaaScopes())); + List grantTypes = Arrays.asList("client_credentials", "password"); + BaseClientDetails clientWithAuthorities = createBaseClient(id, grantTypes, new UaaScopes().getUaaScopes(), null); + BaseClientDetails clientWithScopes = createBaseClient(id, grantTypes, null, new UaaScopes().getUaaScopes()); MockHttpServletRequestBuilder createClientPost = post("/oauth/clients/restricted") .header("Authorization", "Bearer " + adminToken) @@ -230,7 +235,8 @@ public void testCreate_RestrictedClient_Fails() throws Exception { @Test public void testCreate_RestrictedClient_Succeeds() throws Exception { String id = new RandomValueStringGenerator().generate(); - BaseClientDetails client = createBaseClient(id, "client_credentials,password", "openid", "openid"); + List scopes = Collections.singletonList("openid"); + BaseClientDetails client = createBaseClient(id, Arrays.asList("client_credentials", "password"), scopes, scopes); MockHttpServletRequestBuilder createClientPost = post("/oauth/clients/restricted") .header("Authorization", "Bearer " + adminToken) @@ -577,7 +583,7 @@ public void testAddUpdateDeleteClientsTxSuccess() throws Exception { public void testAddUpdateDeleteClientsTxDeleteFailedRollback() throws Exception { ClientDetailsModification[] details = new ClientDetailsModification[15]; for (int i=0; i<5; i++) { - details[i] = (ClientDetailsModification)createClient(adminToken,null,"password"); + details[i] = (ClientDetailsModification)createClient(adminToken,null,Collections.singleton("password")); details[i].setRefreshTokenValiditySeconds(120); details[i].setAction(ClientDetailsModification.UPDATE); } @@ -633,7 +639,7 @@ public void testAddUpdateDeleteClientsTxDeleteFailedRollback() throws Exception @Test public void testApprovalsAreDeleted() throws Exception { - ClientDetails details = createClient(adminToken, new RandomValueStringGenerator().generate(), "password"); + ClientDetails details = createClient(adminToken, new RandomValueStringGenerator().generate(), Collections.singleton("password")); String userToken = testClient.getUserOAuthAccessToken( details.getClientId(), "secret", @@ -674,7 +680,7 @@ public void testApprovalsAreDeleted() throws Exception { @Test public void testApprovalsAreDeleted2() throws Exception { - ClientDetails details = createClient(adminToken, new RandomValueStringGenerator().generate(), "password"); + ClientDetails details = createClient(adminToken, new RandomValueStringGenerator().generate(), Collections.singleton("password")); String userToken = testClient.getUserOAuthAccessToken( details.getClientId(), "secret", @@ -707,7 +713,7 @@ public void testApprovalsAreDeleted2() throws Exception { @Test public void testModifyApprovalsAreDeleted() throws Exception { - ClientDetails details = createClient(adminToken, new RandomValueStringGenerator().generate(), "password"); + ClientDetails details = createClient(adminToken, new RandomValueStringGenerator().generate(), Collections.singleton("password")); ((ClientDetailsModification)details).setAction(ClientDetailsModification.DELETE); String userToken = testClient.getUserOAuthAccessToken( details.getClientId(), @@ -747,7 +753,7 @@ public void testModifyApprovalsAreDeleted() throws Exception { public void testSecretChangeTxApprovalsNotDeleted() throws Exception { int count = 3; //create clients - ClientDetailsModification[] clients = createBaseClients(count, "client_credentials,password"); + ClientDetailsModification[] clients = createBaseClients(count, Arrays.asList("client_credentials", "password")); for (ClientDetailsModification c : clients) { c.setAction(c.ADD); } @@ -822,7 +828,7 @@ public void testSecretChangeEvent() throws Exception { testAccounts.getAdminClientSecret(), "clients.admin,uaa.admin,clients.secret"); String id = "secretchangeevent"; - ClientDetails c = createClient(token, id, "client_credentials"); + ClientDetails c = createClient(token, id, Collections.singleton("client_credentials")); SecretChangeRequest request = new SecretChangeRequest(id, "secret", "newsecret"); MockHttpServletRequestBuilder modifyClientsPost = put("/oauth/clients/" + id + "/secret") .header("Authorization", "Bearer " + token) @@ -840,8 +846,8 @@ public void testSecretChangeEvent() throws Exception { @Test public void testFailedSecretChangeEvent() throws Exception { - String scopes = "oauth.approvals,clients.secret"; - BaseClientDetails client = createBaseClient(null, "password,client_credentials", scopes, scopes); + List scopes = Arrays.asList("oauth.approvals","clients.secret"); + BaseClientDetails client = createBaseClient(null, Arrays.asList("password", "client_credentials"), scopes, scopes); MockHttpServletRequestBuilder createClientPost = post("/oauth/clients") .header("Authorization", "Bearer " + adminToken) .accept(APPLICATION_JSON) @@ -870,7 +876,7 @@ public void testFailedSecretChangeEvent() throws Exception { public void testSecretChangeModifyTxApprovalsDeleted() throws Exception { int count = 3; //create clients - ClientDetailsModification[] clients = createBaseClients(count, "client_credentials,password"); + ClientDetailsModification[] clients = createBaseClients(count, Arrays.asList("client_credentials","password")); for (ClientDetailsModification c : clients) { c.setAction(c.ADD); } @@ -957,7 +963,7 @@ public void testSecretChangeModifyTxApprovalsDeleted() throws Exception { @Test public void testSecretChangeModifyTxApprovalsNotDeleted() throws Exception { //create clients - ClientDetailsModification[] clients = createBaseClients(3, "client_credentials,password"); + ClientDetailsModification[] clients = createBaseClients(3, Arrays.asList("client_credentials","password")); for (ClientDetailsModification c : clients) { c.setAction(c.ADD); } @@ -1026,7 +1032,7 @@ public void testClientsAdminPermissions() throws Exception { ClientDetails adminsClient = createClientAdminsClient(adminToken); //create clients - ClientDetailsModification[] clients = createBaseClients(3, "client_credentials,refresh_token"); + ClientDetailsModification[] clients = createBaseClients(3, Arrays.asList("client_credentials","refresh_token")); for (ClientDetailsModification c : clients) { c.setScope(Collections.singletonList("oauth.approvals")); c.setAction(c.ADD); @@ -1051,7 +1057,7 @@ public void testNonClientsAdminPermissions() throws Exception { ClientDetails adminsClient = createReadWriteClient(adminToken); //create clients - ClientDetailsModification[] clients = createBaseClients(3, "client_credentials,refresh_token"); + ClientDetailsModification[] clients = createBaseClients(3, Arrays.asList("client_credentials","refresh_token")); for (ClientDetailsModification c : clients) { c.setScope(Collections.singletonList("oauth.approvals")); c.setAction(c.ADD); @@ -1077,7 +1083,7 @@ public void testCreateAsAdminPermissions() throws Exception { ClientDetails adminsClient = createClientAdminsClient(adminToken); //create clients - ClientDetailsModification[] clients = createBaseClients(1, "client_credentials,refresh_token"); + ClientDetailsModification[] clients = createBaseClients(1, Arrays.asList("client_credentials","refresh_token")); for (ClientDetailsModification c : clients) { c.setScope(Collections.singletonList("oauth.approvals")); c.setAction(c.ADD); @@ -1102,7 +1108,7 @@ public void testCreateAsReadPermissions() throws Exception { ClientDetails adminsClient = createReadWriteClient(adminToken); //create clients - ClientDetailsModification[] clients = createBaseClients(1, "client_credentials,refresh_token"); + ClientDetailsModification[] clients = createBaseClients(1, Arrays.asList("client_credentials","refresh_token")); for (ClientDetailsModification c : clients) { c.setScope(Collections.singletonList("oauth.approvals")); c.setAction(c.ADD); @@ -1127,7 +1133,7 @@ public void testCreateAsWritePermissions() throws Exception { ClientDetails adminsClient = createReadWriteClient(adminToken); //create clients - ClientDetailsModification[] clients = createBaseClients(1, "client_credentials,refresh_token"); + ClientDetailsModification[] clients = createBaseClients(1, Arrays.asList("client_credentials","refresh_token")); for (ClientDetailsModification c : clients) { c.setScope(Collections.singletonList("oauth.approvals")); c.setAction(c.ADD); @@ -1190,8 +1196,8 @@ public void testGetClientDetailsSortedByLastModified() throws Exception{ @Test public void testClientWithDotInID() throws Exception { - ClientDetails details = createClient(adminToken, "testclient", "client_credentials"); - ClientDetails detailsv2 = createClient(adminToken, "testclient.v2", "client_credentials"); + ClientDetails details = createClient(adminToken, "testclient", Collections.singleton("client_credentials")); + ClientDetails detailsv2 = createClient(adminToken, "testclient.v2", Collections.singleton("client_credentials")); assertEquals("testclient.v2", detailsv2.getClientId()); } @@ -1228,7 +1234,7 @@ private Approval[] addApprovals(String token, String clientId) throws Exception return approvals; } - private ClientDetails createClient(String token, String id, String grantTypes) throws Exception { + private ClientDetails createClient(String token, String id, Collection grantTypes) throws Exception { BaseClientDetails client = createBaseClient(id,grantTypes); MockHttpServletRequestBuilder createClientPost = post("/oauth/clients") .header("Authorization", "Bearer " + token) @@ -1257,8 +1263,8 @@ private ClientDetails getClient(String id) throws Exception { } private ClientDetails createClientAdminsClient(String token) throws Exception { - String scopes = "oauth.approvals,clients.admin"; - BaseClientDetails client = createBaseClient(null, "password,client_credentials", scopes, scopes); + List scopes = Arrays.asList("oauth.approvals","clients.admin"); + BaseClientDetails client = createBaseClient(null, Arrays.asList("password","client_credentials"), scopes, scopes); MockHttpServletRequestBuilder createClientPost = post("/oauth/clients") .header("Authorization", "Bearer " + token) .accept(APPLICATION_JSON) @@ -1269,8 +1275,8 @@ private ClientDetails createClientAdminsClient(String token) throws Exception { } private ClientDetails createReadWriteClient(String token) throws Exception { - String scopes = "oauth.approvals,clients.read,clients.write"; - BaseClientDetails client = createBaseClient(null, "password,client_credentials", scopes, scopes); + List scopes = Arrays.asList("oauth.approvals","clients.read","clients.write"); + BaseClientDetails client = createBaseClient(null, Arrays.asList("password","client_credentials"), scopes, scopes); MockHttpServletRequestBuilder createClientPost = post("/oauth/clients") .header("Authorization", "Bearer " + token) .accept(APPLICATION_JSON) @@ -1281,8 +1287,8 @@ private ClientDetails createReadWriteClient(String token) throws Exception { } private ClientDetails createAdminClient(String token) throws Exception { - String scopes = "uaa.admin,oauth.approvals,clients.read,clients.write"; - BaseClientDetails client = createBaseClient(null, "password,client_credentials", scopes, scopes); + List scopes = Arrays.asList("uaa.admin","oauth.approvals","clients.read","clients.write"); + BaseClientDetails client = createBaseClient(null, Arrays.asList("password","client_credentials"), scopes, scopes); MockHttpServletRequestBuilder createClientPost = post("/oauth/clients") .header("Authorization", "Bearer " + token) @@ -1294,8 +1300,8 @@ private ClientDetails createAdminClient(String token) throws Exception { } private ClientDetails createApprovalsLoginClient(String token) throws Exception { - String scopes = "uaa.admin,oauth.approvals,oauth.login"; - BaseClientDetails client = createBaseClient(null, "password,client_credentials", scopes, scopes); + List scopes = Arrays.asList("uaa.admin","oauth.approvals","oauth.login"); + BaseClientDetails client = createBaseClient(null, Arrays.asList("password","client_credentials"), scopes, scopes); MockHttpServletRequestBuilder createClientPost = post("/oauth/clients") .header("Authorization", "Bearer " + token) @@ -1309,27 +1315,32 @@ private ClientDetails createApprovalsLoginClient(String token) throws Exception - private ClientDetailsModification createBaseClient(String id, String grantTypes) { - return createBaseClient(id, grantTypes, "uaa.none", "foo,bar,oauth.approvals"); + private ClientDetailsModification createBaseClient(String id, Collection grantTypes) { + return createBaseClient(id, grantTypes, Collections.singletonList("uaa.none"), Arrays.asList("foo","bar","oauth.approvals")); } - private ClientDetailsModification createBaseClient(String id, String grantTypes, String authorities, String scopes) { + private ClientDetailsModification createBaseClient(String id, Collection grantTypes, List authorities, List scopes) { if (id==null) { id = new RandomValueStringGenerator().generate(); } if (grantTypes==null) { - grantTypes = "client_credentials"; + grantTypes = Collections.singleton("client_credentials"); } - ClientDetailsModification client = new ClientDetailsModification(id, "", scopes, grantTypes, authorities); + ClientDetailsModification detailsModification = new ClientDetailsModification(); + detailsModification.setClientId(id); + detailsModification.setScope(scopes); + detailsModification.setAuthorizedGrantTypes(grantTypes); + detailsModification.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", authorities))); + ClientDetailsModification client = detailsModification; client.setClientSecret("secret"); client.setAdditionalInformation(Collections. singletonMap("foo", Arrays.asList("bar"))); return client; } - private ClientDetailsModification[] createBaseClients(int length, String grantTypes) { + private ClientDetailsModification[] createBaseClients(int length, Collection grantTypes) { ClientDetailsModification[] result = new ClientDetailsModification[length]; for (int i=0; i grantTypes, String authorities) throws Exception { + public ClientDetails createClient(MockMvc mockMvc, String adminAccessToken, String id, String secret, Collection resourceIds, List scopes, List grantTypes, String authorities) throws Exception { return createClient(mockMvc, adminAccessToken, id, secret, resourceIds, scopes, grantTypes, authorities, null, IdentityZone.getUaa()); } - public ClientDetails createClient(MockMvc mockMvc, String adminAccessToken, String id, String secret, String resourceIds, String scopes, List grantTypes, String authorities, String redirectUris, IdentityZone zone) throws Exception { - ClientDetailsModification client = new ClientDetailsModification(id, resourceIds, scopes, commaDelineatedGrantTypes(grantTypes), authorities, redirectUris); + + public ClientDetails createClient(MockMvc mockMvc, String adminAccessToken, String id, String secret, Collection resourceIds, Collection scopes, Collection grantTypes, String authorities, Set redirectUris, IdentityZone zone) throws Exception { + ClientDetailsModification detailsModification = new ClientDetailsModification(); + detailsModification.setClientId(id); + detailsModification.setResourceIds(resourceIds); + detailsModification.setScope(scopes); + detailsModification.setAuthorizedGrantTypes(grantTypes); + detailsModification.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(authorities)); + detailsModification.setRegisteredRedirectUri(redirectUris); + ClientDetailsModification client = detailsModification; client.setClientSecret(secret); return createClient(mockMvc,adminAccessToken, client, zone); } @@ -811,21 +821,6 @@ public static CookieCsrfPostProcessor cookieCsrf() { } } - public enum GrantType { - password, client_credentials, authorization_code, implicit - } - - private static String commaDelineatedGrantTypes(List grantTypes) { - StringBuilder grantTypeCommaDelineated = new StringBuilder(); - for (int i = 0; i < grantTypes.size(); i++) { - if (i > 0) { - grantTypeCommaDelineated.append(","); - } - grantTypeCommaDelineated.append(grantTypes.get(i).name()); - } - return grantTypeCommaDelineated.toString(); - } - public static class PredictableGenerator extends RandomValueStringGenerator { public AtomicInteger counter = new AtomicInteger(1); @Override diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index 01c28c786eb..f80ae577f41 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -100,7 +100,7 @@ public void setUp() throws Exception { String clientId = generator.generate().toLowerCase(); String clientSecret = generator.generate().toLowerCase(); String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create"; - utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, "oauth", "foo,bar", Collections.singletonList(MockMvcUtils.GrantType.client_credentials), authorities); + utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("foo","bar"), Collections.singletonList("client_credentials"), authorities); scimReadToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.read password.write"); scimWriteToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.write password.write"); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java index 746f2997c2f..3221f17343b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java @@ -89,7 +89,7 @@ public void setUp() throws Exception { String clientId = generator.generate().toLowerCase(); String clientSecret = generator.generate().toLowerCase(); String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create"; - clientDetails = utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, "oauth", "foo,bar", Collections.singletonList(MockMvcUtils.GrantType.client_credentials), authorities); + clientDetails = utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("foo","bar"), Collections.singletonList("client_credentials"), authorities); scimReadWriteToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.read scim.write password.write"); scimCreateToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.create"); usersRepository = getWebApplicationContext().getBean(ScimUserProvisioning.class); @@ -206,7 +206,7 @@ public void verification_link_in_non_default_zone() throws Exception { MockMvcUtils.IdentityZoneCreationResult zoneResult = utils().createOtherIdentityZoneAndReturnResult(subdomain, getMockMvc(), getWebApplicationContext(), null); String zonedClientId = "zonedClientId"; String zonedClientSecret = "zonedClientSecret"; - BaseClientDetails zonedClientDetails = (BaseClientDetails)utils().createClient(this.getMockMvc(), zoneResult.getZoneAdminToken(), zonedClientId, zonedClientSecret, "oauth", null, Arrays.asList(new MockMvcUtils.GrantType[]{MockMvcUtils.GrantType.client_credentials}), "scim.create", null, zoneResult.getIdentityZone()); + BaseClientDetails zonedClientDetails = (BaseClientDetails)utils().createClient(this.getMockMvc(), zoneResult.getZoneAdminToken(), zonedClientId, zonedClientSecret, Collections.singleton("oauth"), null, Arrays.asList(new String[]{"client_credentials"}), "scim.create", null, zoneResult.getIdentityZone()); zonedClientDetails.setClientSecret(zonedClientSecret); String zonedScimCreateToken = utils().getClientCredentialsOAuthAccessToken(getMockMvc(), zonedClientDetails.getClientId(), zonedClientDetails.getClientSecret(), "scim.create", subdomain); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java index 2c26a84c4c2..59332bed374 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserLookupMockMvcTests.java @@ -29,6 +29,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -71,8 +72,8 @@ public void setUp() throws Exception { originalEnabled = getWebApplicationContext().getBean(UserIdConversionEndpoints.class).isEnabled(); getWebApplicationContext().getBean(UserIdConversionEndpoints.class).setEnabled(true); - String scopes = "scim.userids,scim.me"; - utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, "scim", scopes, Arrays.asList(new MockMvcUtils.GrantType[]{MockMvcUtils.GrantType.client_credentials, MockMvcUtils.GrantType.password}), "uaa.none"); + List scopes = Arrays.asList("scim.userids","scim.me"); + utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("scim"), scopes, Arrays.asList(new String[]{"client_credentials", "password"}), "uaa.none"); scimLookupIdUserToken = testClient.getUserOAuthAccessToken(clientId, clientSecret, user.getUserName(), "secr3T", "scim.userids"); if (testUsers==null) { testUsers = createUsers(adminToken, testUserCount); From 75c50596ad06d3f494c88f8811351ad62d4de608 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Thu, 19 Nov 2015 15:32:30 -0800 Subject: [PATCH 009/103] Fix JSON property name and a test helper method. [#107504490] https://www.pivotaltracker.com/story/show/107504490 Signed-off-by: Paul Warren --- .../identity/uaa/oauth/token/TokenKeyEndpointTests.java | 2 +- .../identity/uaa/oauth/token/VerificationKeyResponse.java | 2 +- .../uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java index ba64ec3de6a..8c4374fcc64 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java @@ -69,7 +69,7 @@ public void responseIsBackwardCompatibleWithMap() { Map deserializedMap = JsonUtils.readValue(serialized, Map.class); assertEquals("HMACSHA256", deserializedMap.get("alg")); - assertEquals("someKey", deserializedMap.get("key")); + assertEquals("someKey", deserializedMap.get("value")); assertEquals("MAC", deserializedMap.get("kty")); assertEquals("sig", deserializedMap.get("use")); } diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java index d1740210c00..0ef25a4d681 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java @@ -28,7 +28,7 @@ public class VerificationKeyResponse { @JsonProperty("alg") private String algorithm; - @JsonProperty("key") + @JsonProperty("value") private String key; @JsonProperty("kty") diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java index 593fbaf2e4d..7ef2145b285 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java @@ -1330,7 +1330,9 @@ private ClientDetailsModification createBaseClient(String id, Collection detailsModification.setClientId(id); detailsModification.setScope(scopes); detailsModification.setAuthorizedGrantTypes(grantTypes); - detailsModification.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", authorities))); + if(authorities != null) { + detailsModification.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", authorities))); + } ClientDetailsModification client = detailsModification; client.setClientSecret("secret"); client.setAdditionalInformation(Collections. singletonMap("foo", Arrays.asList("bar"))); From cc4e085338dbcc6ea5e13efa3e3b997d3448a3f5 Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Thu, 19 Nov 2015 15:19:02 -0800 Subject: [PATCH 010/103] Introduce builder for SamlIdentityProviderDefinition [#107504490] https://www.pivotaltracker.com/story/show/107504490 Signed-off-by: Jeremy Coffield --- .../login/LoginInfoEndpointTests.java | 24 +-- .../IdentityProviderConfiguratorTests.java | 94 +++++---- .../SamlIdentityProviderDefinitionTests.java | 19 +- .../uaa/login/saml/SamlRedirectUtilsTest.java | 18 +- .../identity/uaa/util/DomainFilterTest.java | 20 +- .../InvitationsControllerTest.java | 9 +- .../SamlIdentityProviderDefinition.java | 191 ++++++++++++------ .../login/InvitationsServiceMockMvcTests.java | 18 +- .../identity/uaa/login/LoginMockMvcTests.java | 54 +++-- ...IdentityProviderEndpointsMockMvcTests.java | 77 +++---- 10 files changed, 316 insertions(+), 208 deletions(-) diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java index 245e9ddb1d1..d8203fa93f9 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java @@ -473,21 +473,15 @@ private List getIdps() { } private SamlIdentityProviderDefinition createIdentityProviderDefinition(String idpEntityAlias, String zoneId) { - SamlIdentityProviderDefinition idp1 = new SamlIdentityProviderDefinition( - "metadataLocation for "+idpEntityAlias, - idpEntityAlias, - "nameID for "+idpEntityAlias, - 0, - true, - true, - "link text for "+idpEntityAlias, - "icon url for "+idpEntityAlias, - zoneId, - true, - null, - null, - null - ); + SamlIdentityProviderDefinition idp1 = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation("metadataLocation for " + idpEntityAlias) + .setIdpEntityAlias(idpEntityAlias) + .setNameID("nameID for " + idpEntityAlias) + .setMetadataTrustCheck(true) + .setLinkText("link text for " + idpEntityAlias) + .setIconUrl("icon url for " + idpEntityAlias) + .setZoneId(zoneId) + .build(); idp1.setIdpEntityAlias(idpEntityAlias); idp1.setShowSamlLink(true); idp1.setZoneId(zoneId); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java index b0e134928e7..e1cce70514d 100755 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java @@ -183,28 +183,26 @@ public static void initializeOpenSAML() throws Exception { public void setUp() throws Exception { conf = new SamlIdentityProviderConfigurator(); conf.setParserPool(new BasicParserPool()); - singleAdd = new SamlIdentityProviderDefinition( - String.format(xmlWithoutID, new RandomValueStringGenerator().generate()), - singleAddAlias, - "sample-nameID", - 1, - true, - true, - "sample-link-test", - "sample-icon-url" - ,"uaa" - ); - singleAddWithoutHeader = new SamlIdentityProviderDefinition( - String.format(xmlWithoutHeader, new RandomValueStringGenerator().generate()), - singleAddAlias, - "sample-nameID", - 1, - true, - true, - "sample-link-test", - "sample-icon-url", - "uaa" - ); + singleAdd = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(xmlWithoutID, new RandomValueStringGenerator().generate())) + .setIdpEntityAlias(singleAddAlias) + .setNameID("sample-nameID") + .setAssertionConsumerIndex(1) + .setMetadataTrustCheck(true) + .setLinkText("sample-link-test") + .setIconUrl("sample-icon-url") + .setZoneId("uaa") + .build(); + singleAddWithoutHeader = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(xmlWithoutHeader, new RandomValueStringGenerator().generate())) + .setIdpEntityAlias(singleAddAlias) + .setNameID("sample-nameID") + .setAssertionConsumerIndex(1) + .setMetadataTrustCheck(true) + .setLinkText("sample-link-test") + .setIconUrl("sample-icon-url") + .setZoneId("uaa") + .build(); } private static Map> parseYaml(String sampleYaml) { @@ -307,7 +305,16 @@ public void testGetIdentityProviderDefinitionsForZone() throws Exception { String zoneId = UUID.randomUUID().toString(); IdentityZone zone = MultitenancyFixture.identityZone(zoneId, "test-zone"); - SamlIdentityProviderDefinition samlIdentityProviderDefinition = new SamlIdentityProviderDefinition(xml, "zoneIdpAlias","sample-nameID",1,true,true,"sample-link-test","sample-icon-url", zoneId); + SamlIdentityProviderDefinition samlIdentityProviderDefinition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(xml) + .setIdpEntityAlias("zoneIdpAlias") + .setNameID("sample-nameID") + .setAssertionConsumerIndex(1) + .setMetadataTrustCheck(true) + .setLinkText("sample-link-test") + .setIconUrl("sample-icon-url") + .setZoneId(zoneId) + .build(); conf.addSamlIdentityProviderDefinition(samlIdentityProviderDefinition); List idps = conf.getIdentityProviderDefinitionsForZone(zone); @@ -333,7 +340,16 @@ public void testGetIdentityProviderDefinititonsForAllowedProviders() throws Exce public void testReturnAllIdpsInZoneForClientWithNoAllowedProviders() throws Exception { conf.setIdentityProviders(sampleData); conf.afterPropertiesSet(); - SamlIdentityProviderDefinition samlIdentityProviderDefinitionInOtherZone = new SamlIdentityProviderDefinition(xml, "zoneIdpAlias","sample-nameID",1,true,true,"sample-link-test","sample-icon-url", "other-zone-id"); + SamlIdentityProviderDefinition samlIdentityProviderDefinitionInOtherZone = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(xml) + .setIdpEntityAlias("zoneIdpAlias") + .setNameID("sample-nameID") + .setAssertionConsumerIndex(1) + .setMetadataTrustCheck(true) + .setLinkText("sample-link-test") + .setIconUrl("sample-icon-url") + .setZoneId("other-zone-id") + .build(); try { conf.addSamlIdentityProviderDefinition(samlIdentityProviderDefinitionInOtherZone); } catch (MetadataProviderException e) { @@ -348,7 +364,16 @@ public void testReturnNoIdpsInZoneForClientWithNoAllowedProviders() throws Excep conf.setIdentityProviders(sampleData); conf.afterPropertiesSet(); String xmlMetadata = String.format(xmlWithoutID, new RandomValueStringGenerator().generate()); - SamlIdentityProviderDefinition samlIdentityProviderDefinitionInOtherZone = new SamlIdentityProviderDefinition(xmlMetadata, "zoneIdpAlias","sample-nameID",1,true,true,"sample-link-test","sample-icon-url", "other-zone-id"); + SamlIdentityProviderDefinition samlIdentityProviderDefinitionInOtherZone = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(xmlMetadata) + .setIdpEntityAlias("zoneIdpAlias") + .setNameID("sample-nameID") + .setAssertionConsumerIndex(1) + .setMetadataTrustCheck(true) + .setLinkText("sample-link-test") + .setIconUrl("sample-icon-url") + .setZoneId("other-zone-id") + .build(); conf.addSamlIdentityProviderDefinition(samlIdentityProviderDefinitionInOtherZone); List clientIdps = conf.getIdentityProviderDefinitions(null, IdentityZoneHolder.get()); @@ -464,17 +489,14 @@ public void testDuplicate_EntityID_IsRejected() throws Exception { conf.afterPropertiesSet(); testGetIdentityProviderDefinitions(3, false); - SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition( - "http://simplesamlphp.identity.cf-app.com/saml2/idp/metadata.php", - "simplesamlphp-url-2", - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - 0, - false, - true, - "Link Text", - null, - IdentityZone.getUaa().getId() - ); + SamlIdentityProviderDefinition def = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation("http://simplesamlphp.identity.cf-app.com/saml2/idp/metadata.php") + .setIdpEntityAlias("simplesamlphp-url-2") + .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") + .setLinkText("Link Text") + .setZoneId(IdentityZone.getUaa().getId()) + .setShowSamlLink(true) + .build(); //duplicate entityID - different alias ExtendedMetadataDelegate[] delegate = null; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java index 1bf5d8baab1..5a28edd2d39 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java @@ -20,15 +20,16 @@ public class SamlIdentityProviderDefinitionTests { @Before public void createDefinition() { - definition = new SamlIdentityProviderDefinition("location", - "alias", - "nameID", - 0, - true, - false, - "link test", - "url", - "zoneId"); + definition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation("location") + .setIdpEntityAlias("alias") + .setNameID("nameID") + .setMetadataTrustCheck(true) + .setShowSamlLink(false) + .setLinkText("link test") + .setIconUrl("url") + .setZoneId("zoneId") + .build(); } @Test diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtilsTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtilsTest.java index e31dc9a0cee..af38356c96c 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtilsTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtilsTest.java @@ -24,16 +24,14 @@ public class SamlRedirectUtilsTest { @Test public void testGetIdpRedirectUrl() throws Exception { SamlIdentityProviderDefinition definition = - new SamlIdentityProviderDefinition( - "http://some.meta.data", - "simplesamlphp-url", - "nameID", - 0, - true, - true, - "link text", - null, - IdentityZone.getUaa().getId()); + SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation("http://some.meta.data") + .setIdpEntityAlias("simplesamlphp-url") + .setNameID("nameID") + .setMetadataTrustCheck(true) + .setLinkText("link text") + .setZoneId(IdentityZone.getUaa().getId()) + .build(); String url = SamlRedirectUtils.getIdpRedirectUrl(definition, "login.identity.cf-app.com"); Assert.assertEquals("saml/discovery?returnIDParam=idp&entityID=login.identity.cf-app.com&idp=simplesamlphp-url&isPassive=true", url); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java index 294a4886007..b1042a09708 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java @@ -91,8 +91,24 @@ public void setUp() throws Exception { client = new BaseClientDetails("clientid","", "", "","",""); uaaDef = new UaaIdentityProviderDefinition(null, null); ldapDef = new LdapIdentityProviderDefinition(); - samlDef1 = new SamlIdentityProviderDefinition(idpMetaData,"","",0,true,true,"","", IdentityZone.getUaa().getId()); - samlDef2 = new SamlIdentityProviderDefinition(idpMetaData,"","",0,true,true,"","", IdentityZone.getUaa().getId()); + samlDef1 = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(idpMetaData) + .setIdpEntityAlias("") + .setNameID("") + .setMetadataTrustCheck(true) + .setLinkText("") + .setIconUrl("") + .setZoneId(IdentityZone.getUaa().getId()) + .build(); + samlDef2 = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(idpMetaData) + .setIdpEntityAlias("") + .setNameID("") + .setMetadataTrustCheck(true) + .setLinkText("") + .setIconUrl("") + .setZoneId(IdentityZone.getUaa().getId()) + .build(); configureTestData(); } diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java b/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java index 06d68090810..494c8f1e6b6 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java @@ -159,7 +159,14 @@ public void acceptInvitePage_for_unverifiedSamlUser() throws Exception { when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); when(expiringCodeStore.generateCode(anyString(), anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); IdentityProvider provider = new IdentityProvider(); - SamlIdentityProviderDefinition definition = new SamlIdentityProviderDefinition("http://test.saml.com", "test-saml", "test", 0, false, true, "testsaml", "test.com", IdentityZone.getUaa().getId()); + SamlIdentityProviderDefinition definition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation("http://test.saml.com") + .setIdpEntityAlias("test-saml") + .setNameID("test") + .setLinkText("testsaml") + .setIconUrl("test.com") + .setZoneId(IdentityZone.getUaa().getId()) + .build(); provider.setConfig(definition); provider.setType(OriginKeys.SAML); when(providerProvisioning.retrieveByOrigin(eq("test-saml"), anyString())).thenReturn(provider); diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java index 9bea27ccf4a..beb30bca407 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java @@ -38,7 +38,7 @@ public enum MetadataLocation { URL, DATA, UNKNOWN - }; + } private String metaDataLocation; private String idpEntityAlias; @@ -52,70 +52,27 @@ public enum MetadataLocation { private String iconUrl; private boolean addShadowUserOnLogin = true; - public SamlIdentityProviderDefinition clone() { - return new SamlIdentityProviderDefinition(metaDataLocation, - idpEntityAlias, - nameID, - assertionConsumerIndex, - metadataTrustCheck, - showSamlLink, - linkText, - iconUrl, - zoneId, - addShadowUserOnLogin, - getEmailDomain() != null ? new ArrayList<>(getEmailDomain()) : null, - getExternalGroupsWhitelist() != null ? new ArrayList<>(getExternalGroupsWhitelist()) : null, - getAttributeMappings() != null ? new HashMap(getAttributeMappings()) : null); - } - public SamlIdentityProviderDefinition() {} - public SamlIdentityProviderDefinition(String metaDataLocation, - String idpEntityAlias, - String nameID, - int assertionConsumerIndex, - boolean metadataTrustCheck, - boolean showSamlLink, - String linkText, - String iconUrl, - String zoneId) { - this.metaDataLocation = metaDataLocation; - this.idpEntityAlias = idpEntityAlias; - this.nameID = nameID; - this.assertionConsumerIndex = assertionConsumerIndex; - this.metadataTrustCheck = metadataTrustCheck; - this.showSamlLink = showSamlLink; - this.linkText = linkText; - this.iconUrl = iconUrl; - this.zoneId = zoneId; - } - - public SamlIdentityProviderDefinition(String metaDataLocation, - String idpEntityAlias, - String nameID, - int assertionConsumerIndex, - boolean metadataTrustCheck, - boolean showSamlLink, - String linkText, - String iconUrl, - String zoneId, - boolean addShadowUserOnLogin, - List emailDomain, - List externalGroupsWhitelist, - Map attributeMappings) { - this.metaDataLocation = metaDataLocation; - this.idpEntityAlias = idpEntityAlias; - this.nameID = nameID; - this.assertionConsumerIndex = assertionConsumerIndex; - this.metadataTrustCheck = metadataTrustCheck; - this.showSamlLink = showSamlLink; - this.linkText = linkText; - this.iconUrl = iconUrl; - this.zoneId = zoneId; - this.addShadowUserOnLogin = addShadowUserOnLogin; - setEmailDomain(emailDomain); - setExternalGroupsWhitelist(externalGroupsWhitelist); - setAttributeMappings(attributeMappings); + public SamlIdentityProviderDefinition clone() { + List emailDomain = getEmailDomain() != null ? new ArrayList<>(getEmailDomain()) : null; + List externalGroupsWhitelist = getExternalGroupsWhitelist() != null ? new ArrayList<>(getExternalGroupsWhitelist()) : null; + Map attributeMappings = getAttributeMappings() != null ? new HashMap(getAttributeMappings()) : null; + return Builder.get() + .setMetaDataLocation(metaDataLocation) + .setIdpEntityAlias(idpEntityAlias) + .setNameID(nameID) + .setAssertionConsumerIndex(assertionConsumerIndex) + .setMetadataTrustCheck(metadataTrustCheck) + .setShowSamlLink(showSamlLink) + .setLinkText(linkText) + .setIconUrl(iconUrl) + .setZoneId(zoneId) + .setAddShadowUserOnLogin(addShadowUserOnLogin) + .setEmailDomain(emailDomain) + .setExternalGroupsWhitelist(externalGroupsWhitelist) + .setAttributeMappings(attributeMappings) + .build(); } @JsonIgnore @@ -301,4 +258,112 @@ public String toString() { ", addShadowUserOnLogin='" + addShadowUserOnLogin + '\'' + '}'; } + + public static class Builder { + + private String metaDataLocation; + private String idpEntityAlias; + private String zoneId; + private String nameID; + private int assertionConsumerIndex; + private boolean metadataTrustCheck; + private boolean showSamlLink; + private String linkText; + private String iconUrl; + private boolean addShadowUserOnLogin = true; + private List emailDomain; + private List externalGroupsWhitelist; + private Map attributeMappings; + + private Builder(){} + + public static Builder get() { + return new Builder(); + } + + public SamlIdentityProviderDefinition build() { + SamlIdentityProviderDefinition def = new SamlIdentityProviderDefinition(); + + def.setMetaDataLocation(metaDataLocation); + def.setIdpEntityAlias(idpEntityAlias); + def.setZoneId(zoneId); + def.setNameID(nameID); + def.setAssertionConsumerIndex(assertionConsumerIndex); + def.setMetadataTrustCheck(metadataTrustCheck); + def.setShowSamlLink(showSamlLink); + def.setLinkText(linkText); + def.setIconUrl(iconUrl); + def.setAddShadowUserOnLogin(addShadowUserOnLogin); + def.setEmailDomain(emailDomain); + def.setExternalGroupsWhitelist(externalGroupsWhitelist); + def.setAttributeMappings(attributeMappings); + + return def; + } + + public Builder setAttributeMappings(Map attributeMappings) { + this.attributeMappings = attributeMappings; + return this; + } + + public Builder setMetaDataLocation(String metaDataLocation) { + this.metaDataLocation = metaDataLocation; + return this; + } + + public Builder setIdpEntityAlias(String idpEntityAlias) { + this.idpEntityAlias = idpEntityAlias; + return this; + } + + public Builder setZoneId(String zoneId) { + this.zoneId = zoneId; + return this; + } + + public Builder setNameID(String nameID) { + this.nameID = nameID; + return this; + } + + public Builder setAssertionConsumerIndex(int assertionConsumerIndex) { + this.assertionConsumerIndex = assertionConsumerIndex; + return this; + } + + public Builder setMetadataTrustCheck(boolean metadataTrustCheck) { + this.metadataTrustCheck = metadataTrustCheck; + return this; + } + + public Builder setShowSamlLink(boolean showSamlLink) { + this.showSamlLink = showSamlLink; + return this; + } + + public Builder setLinkText(String linkText) { + this.linkText = linkText; + return this; + } + + public Builder setIconUrl(String iconUrl) { + this.iconUrl = iconUrl; + return this; + } + + public Builder setAddShadowUserOnLogin(boolean addShadowUserOnLogin) { + this.addShadowUserOnLogin = addShadowUserOnLogin; + return this; + } + + public Builder setEmailDomain(List emailDomain) { + this.emailDomain = emailDomain; + return this; + } + + public Builder setExternalGroupsWhitelist(List externalGroupsWhitelist) { + this.externalGroupsWhitelist = externalGroupsWhitelist; + return this; + } + } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java index 3e569a484ee..7df2b5a96fe 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/InvitationsServiceMockMvcTests.java @@ -318,17 +318,13 @@ protected IdentityProvider createIdentityProvider(IdentityZoneCreationResult zon } protected SamlIdentityProviderDefinition getSamlIdentityProviderDefinition(IdentityZoneCreationResult zone, String entityID) { - return new SamlIdentityProviderDefinition( - String.format(utils.IDP_META_DATA, entityID), - entityID, - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - 0, - false, - true, - "Test Saml Provider", - null, - zone.getIdentityZone().getId() - ); + return SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(utils.IDP_META_DATA, entityID)) + .setIdpEntityAlias(entityID) + .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") + .setLinkText("Test Saml Provider") + .setZoneId(zone.getIdentityZone().getId()) + .build(); } public URL inviteUser(String email, String userInviteToken, String subdomain, String clientId, String expectedOrigin) throws Exception { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index c11c16a50ee..684f3221e0f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -618,7 +618,13 @@ public void testSamlLoginLinksShowActiveProviders() throws Exception { String zoneAdminToken = identityZoneCreationResult.getZoneAdminToken(); String metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); - SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition = new SamlIdentityProviderDefinition(metadata, activeAlias, null, 0, false, true, "Active SAML Provider", null, identityZone.getId()); + SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(metadata) + .setIdpEntityAlias(activeAlias) + .setLinkText("Active SAML Provider") + .setShowSamlLink(true) + .setZoneId(identityZone.getId()) + .build(); IdentityProvider activeIdentityProvider = new IdentityProvider(); activeIdentityProvider.setType(OriginKeys.SAML); activeIdentityProvider.setName("Active SAML Provider"); @@ -628,7 +634,12 @@ public void testSamlLoginLinksShowActiveProviders() throws Exception { mockMvcUtils.createIdpUsingWebRequest(getMockMvc(), identityZone.getId(), zoneAdminToken, activeIdentityProvider, status().isCreated()); metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); - SamlIdentityProviderDefinition inactiveSamlIdentityProviderDefinition = new SamlIdentityProviderDefinition(metadata, inactiveAlias, null, 0, false, true, "You should not see me", null, identityZone.getId()); + SamlIdentityProviderDefinition inactiveSamlIdentityProviderDefinition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(metadata) + .setIdpEntityAlias(inactiveAlias) + .setLinkText("You should not see me") + .setZoneId(identityZone.getId()) + .build(); IdentityProvider inactiveIdentityProvider = new IdentityProvider(); inactiveIdentityProvider.setType(OriginKeys.SAML); inactiveIdentityProvider.setName("Inactive SAML Provider"); @@ -655,7 +666,12 @@ public void testSamlRedirectWhenTheOnlyProvider() throws Exception { String zoneAdminToken = identityZoneCreationResult.getZoneAdminToken(); String metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); - SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition = new SamlIdentityProviderDefinition(metadata, alias, null, 0, false, true, "Active SAML Provider", null, identityZone.getId()); + SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(metadata) + .setIdpEntityAlias(alias) + .setLinkText("Active SAML Provider") + .setZoneId(identityZone.getId()) + .build(); IdentityProvider activeIdentityProvider = new IdentityProvider(); activeIdentityProvider.setType(OriginKeys.SAML); activeIdentityProvider.setName("Active SAML Provider"); @@ -709,17 +725,12 @@ public void testNoCreateAccountLinksWhenUAAisNotAllowedProvider() throws Excepti IdentityZone identityZone = identityZoneCreationResult.getIdentityZone(); String zoneAdminToken = identityZoneCreationResult.getZoneAdminToken(); - SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition3 = new SamlIdentityProviderDefinition( - String.format(IdentityProviderConfiguratorTests.xmlWithoutID,"http://example3.com/saml/metadata"), - alias3, - null, - 0, - false, - true, - "Active3 SAML Provider", - null, - identityZone.getId() - ); + SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition3 = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://example3.com/saml/metadata")) + .setIdpEntityAlias(alias3) + .setLinkText("Active3 SAML Provider") + .setZoneId(identityZone.getId()) + .build(); IdentityProvider activeIdentityProvider3 = new IdentityProvider(); activeIdentityProvider3.setType(OriginKeys.SAML); activeIdentityProvider3.setName("Active 3 SAML Provider"); @@ -728,7 +739,12 @@ public void testNoCreateAccountLinksWhenUAAisNotAllowedProvider() throws Excepti activeIdentityProvider3.setOriginKey(alias3); activeIdentityProvider3 = mockMvcUtils.createIdpUsingWebRequest(getMockMvc(), identityZone.getId(), zoneAdminToken, activeIdentityProvider3, status().isCreated()); - SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition2 = new SamlIdentityProviderDefinition(String.format(IdentityProviderConfiguratorTests.xmlWithoutID,"http://example2.com/saml/metadata"), alias2, null, 0, false, true, "Active2 SAML Provider", null, identityZone.getId()); + SamlIdentityProviderDefinition activeSamlIdentityProviderDefinition2 = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://example2.com/saml/metadata")) + .setIdpEntityAlias(alias2) + .setLinkText("Active2 SAML Provider") + .setZoneId(identityZone.getId()) + .build(); IdentityProvider activeIdentityProvider2 = new IdentityProvider(); activeIdentityProvider2.setType(OriginKeys.SAML); activeIdentityProvider2.setName("Active 2 SAML Provider"); @@ -784,7 +800,13 @@ public void testDeactivatedProviderIsRemovedFromSamlLoginLinks() throws Exceptio String zoneAdminToken = identityZoneCreationResult.getZoneAdminToken(); String metadata = String.format(MockMvcUtils.IDP_META_DATA, new RandomValueStringGenerator().generate()); - SamlIdentityProviderDefinition samlIdentityProviderDefinition = new SamlIdentityProviderDefinition(metadata, alias, null, 0, false, true, "SAML Provider", null, identityZone.getId()); + SamlIdentityProviderDefinition samlIdentityProviderDefinition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(metadata) + .setIdpEntityAlias(alias) + .setLinkText("SAML Provider") + .setShowSamlLink(true) + .setZoneId(identityZone.getId()) + .build(); IdentityProvider identityProvider = new IdentityProvider(); identityProvider.setType(OriginKeys.SAML); identityProvider.setName("SAML Provider"); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java index 2e41e313670..17c914c5dbf 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java @@ -108,7 +108,10 @@ public void testCreateSamlProvider() throws Exception { provider.setIdentityZoneId(IdentityZone.getUaa().getId()); provider.setType(OriginKeys.SAML); provider.setOriginKey(origin); - SamlIdentityProviderDefinition samlDefinition = new SamlIdentityProviderDefinition(metadata, null, null, 0, false, true, "Test SAML Provider", null, null); + SamlIdentityProviderDefinition samlDefinition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(metadata) + .setLinkText("Test SAML Provider") + .build(); samlDefinition.setEmailDomain(Arrays.asList("test.com", "test2.com")); List externalGroupsWhitelist = new ArrayList<>(); externalGroupsWhitelist.add("value"); @@ -285,17 +288,13 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Other_Zone() throws IdentityProvider identityProvider = MultitenancyFixture.identityProvider(origin1, zone.getId()); identityProvider.setType(OriginKeys.SAML); - SamlIdentityProviderDefinition providerDefinition = new SamlIdentityProviderDefinition( - String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://www.okta.com/"+identityProvider.getOriginKey()), - identityProvider.getOriginKey(), - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - 0, - false, - true, - "IDPEndpointsMockTests Saml Provider:"+identityProvider.getOriginKey(), - null, - zone.getId() - ); + SamlIdentityProviderDefinition providerDefinition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://www.okta.com/" + identityProvider.getOriginKey())) + .setIdpEntityAlias(identityProvider.getOriginKey()) + .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") + .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) + .setZoneId(zone.getId()) + .build(); identityProvider.setConfig(providerDefinition); IdentityProvider createdIDP = createIdentityProvider(zone.getId(), identityProvider, userAccessToken, status().isCreated()); @@ -307,17 +306,13 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Other_Zone() throws assertEquals(identityProvider.getConfig().getZoneId(), createdIDP.getConfig().getZoneId()); identityProvider.setOriginKey(origin2); - providerDefinition = new SamlIdentityProviderDefinition( - providerDefinition.getMetaDataLocation(), - identityProvider.getOriginKey(), - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - 0, - false, - true, - "IDPEndpointsMockTests Saml Provider:"+identityProvider.getOriginKey(), - null, - zone.getId() - ); + providerDefinition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(providerDefinition.getMetaDataLocation()) + .setIdpEntityAlias(identityProvider.getOriginKey()) + .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") + .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) + .setZoneId(zone.getId()) + .build(); identityProvider.setConfig(providerDefinition); createIdentityProvider(zone.getId(), identityProvider, userAccessToken, status().isConflict()); @@ -336,17 +331,13 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Default_Zone() throw IdentityProvider identityProvider = MultitenancyFixture.identityProvider(origin1, IdentityZone.getUaa().getId()); identityProvider.setType(OriginKeys.SAML); - SamlIdentityProviderDefinition providerDefinition = new SamlIdentityProviderDefinition( - String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://www.okta.com/"+identityProvider.getOriginKey()), - identityProvider.getOriginKey(), - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - 0, - false, - true, - "IDPEndpointsMockTests Saml Provider:"+identityProvider.getOriginKey(), - null, - IdentityZone.getUaa().getId() - ); + SamlIdentityProviderDefinition providerDefinition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://www.okta.com/" + identityProvider.getOriginKey())) + .setIdpEntityAlias(identityProvider.getOriginKey()) + .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") + .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) + .setZoneId(IdentityZone.getUaa().getId()) + .build(); identityProvider.setConfig(providerDefinition); IdentityProvider createdIDP = createIdentityProvider(null, identityProvider, userAccessToken, status().isCreated()); @@ -356,17 +347,13 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Default_Zone() throw assertEquals(identityProvider.getOriginKey(), createdIDP.getOriginKey()); identityProvider.setOriginKey(origin2); - providerDefinition = new SamlIdentityProviderDefinition( - providerDefinition.getMetaDataLocation(), - identityProvider.getOriginKey(), - "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", - 0, - false, - true, - "IDPEndpointsMockTests Saml Provider:"+identityProvider.getOriginKey(), - null, - IdentityZone.getUaa().getId() - ); + providerDefinition = SamlIdentityProviderDefinition.Builder.get() + .setMetaDataLocation(providerDefinition.getMetaDataLocation()) + .setIdpEntityAlias(identityProvider.getOriginKey()) + .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") + .setLinkText("IDPEndpointsMockTests Saml Provider:" + identityProvider.getOriginKey()) + .setZoneId(IdentityZone.getUaa().getId()) + .build(); identityProvider.setConfig(providerDefinition); createIdentityProvider(null, identityProvider, userAccessToken, status().isConflict()); From 1a3047a50567b739a2b05906ad6767b2462d7234 Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Fri, 20 Nov 2015 10:00:44 -0800 Subject: [PATCH 011/103] Add builder-style setters and remove complex constructors on Approval [#107504490] https://www.pivotaltracker.com/story/show/107504490 Signed-off-by: Jeremy Coffield --- .../UserManagedAuthzApprovalHandler.java | 27 ++- .../approval/ApprovalsAdminEndpoints.java | 7 +- .../uaa/oauth/approval/JdbcApprovalStore.java | 9 +- .../event/ApprovalModifiedEventTest.java | 9 +- .../uaa/oauth/CheckTokenEndpointTests.java | 61 +++-- .../UserManagedAuthzApprovalHandlerTests.java | 210 +++++++++++++++--- .../uaa/oauth/approval/ApprovalTests.java | 195 +++++++++++++--- .../ApprovalsAdminEndpointsTests.java | 147 ++++++++++-- .../approval/JdbcApprovalStoreTests.java | 78 +++++-- .../oauth/token/UaaTokenServicesTests.java | 187 +++++++++++++--- .../identity/uaa/login/DescribedApproval.java | 9 +- .../identity/uaa/oauth/approval/Approval.java | 83 +++---- .../endpoints/ScimUserEndpointsTests.java | 55 ++++- .../ClientAdminEndpointsIntegrationTests.java | 24 +- .../mock/audit/AuditCheckMockMvcTests.java | 9 +- .../ClientAdminEndpointsMockMvcTests.java | 46 ++-- 16 files changed, 929 insertions(+), 227 deletions(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java index 0eb52380273..4447227a1ae 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java @@ -126,12 +126,22 @@ public boolean isApproved(AuthorizationRequest authorizationRequest, Authenticat for (String requestedScope : requestedScopes) { if (approvedScopes.contains(requestedScope)) { - approvalStore.addApproval(new Approval(getUserId(userAuthentication), authorizationRequest - .getClientId(), requestedScope, expiry, APPROVED)); + Approval approval = new Approval() + .setUserId(getUserId(userAuthentication)) + .setClientId(authorizationRequest.getClientId()) + .setScope(requestedScope) + .setExpiresAt(expiry) + .setStatus(APPROVED); + approvalStore.addApproval(approval); } else { - approvalStore.addApproval(new Approval(getUserId(userAuthentication), authorizationRequest - .getClientId(), requestedScope, expiry, DENIED)); + Approval approval = new Approval() + .setUserId(getUserId(userAuthentication)) + .setClientId(authorizationRequest.getClientId()) + .setScope(requestedScope) + .setExpiresAt(expiry) + .setStatus(DENIED); + approvalStore.addApproval(approval); } } @@ -141,8 +151,13 @@ public boolean isApproved(AuthorizationRequest authorizationRequest, Authenticat for (String requestedScope : requestedScopes) { if (!autoApprovedScopes.contains(requestedScope)) { - approvalStore.addApproval(new Approval(getUserId(userAuthentication), authorizationRequest - .getClientId(), requestedScope, expiry, DENIED)); + Approval approval = new Approval() + .setUserId(getUserId(userAuthentication)) + .setClientId(authorizationRequest.getClientId()) + .setScope(requestedScope) + .setExpiresAt(expiry) + .setStatus(DENIED); + approvalStore.addApproval(approval); } } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java index 3d0241dd9a0..6efeca06ee7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java @@ -42,6 +42,7 @@ import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.stereotype.Controller; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.View; @@ -153,14 +154,13 @@ public List updateApprovals(@RequestBody Approval[] approvals) { logger.debug("Updating approvals for user: " + currentUserId); approvalStore.revokeApprovals(String.format(USER_FILTER_TEMPLATE, currentUserId)); for (Approval approval : approvals) { - if (approval.getUserId() !=null && !isValidUser(approval.getUserId())) { - logger.warn(String.format("Error[2] %s attemting to update approvals for %s", currentUserId, approval.getUserId())); + if (StringUtils.hasText(approval.getUserId()) && !isValidUser(approval.getUserId())) { + logger.warn(String.format("Error[2] %s attempting to update approvals for %s", currentUserId, approval.getUserId())); throw new UaaException("unauthorized_operation", "Cannot update approvals for another user. Set user_id to null to update for existing user.", HttpStatus.UNAUTHORIZED.value()); } else { approval.setUserId(currentUserId); } - approval.setLastUpdatedAt(new Date()); approvalStore.addApproval(approval); } return approvalStore.getApprovals(String.format(USER_FILTER_TEMPLATE, currentUserId)); @@ -181,7 +181,6 @@ public List updateClientApprovals(@PathVariable String clientId, @Requ } else { approval.setUserId(currentUserId); } - approval.setLastUpdatedAt(new Date()); approvalStore.addApproval(approval); } return approvalStore.getApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, currentUserId, clientId)); diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java index 516704affa9..1f2853b1850 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java @@ -223,7 +223,14 @@ public Approval mapRow(ResultSet rs, int rowNum) throws SQLException { String status = rs.getString(5); Date lastUpdatedAt = rs.getTimestamp(6); - return new Approval(userName, clientId, scope, expiresAt, ApprovalStatus.valueOf(status), lastUpdatedAt); + Approval approval = new Approval() + .setUserId(userName) + .setClientId(clientId) + .setScope(scope) + .setExpiresAt(expiresAt) + .setStatus(ApprovalStatus.valueOf(status)) + .setLastUpdatedAt(lastUpdatedAt); + return approval; } } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java index 74527019714..0031432b8cb 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java @@ -7,6 +7,8 @@ import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ApprovalModifiedEventTest { @Test(expected = IllegalArgumentException.class) @@ -16,7 +18,12 @@ public void testRaisesWithBadSource() throws Exception { @Test public void testAuditEvent() throws Exception { - Approval approval = new Approval("mruser", "app", "cloud_controller.read", 1000, Approval.ApprovalStatus.APPROVED); + Approval approval = new Approval() + .setUserId("mruser") + .setClientId("app") + .setScope("cloud_controller.read") + .setExpiresAt(Approval.timeFromNow(1000)) + .setStatus(Approval.ApprovalStatus.APPROVED); ApprovalModifiedEvent event = new ApprovalModifiedEvent(approval, null); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java index 24bbc3230a8..a1027e03ed2 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java @@ -230,10 +230,20 @@ public void setUp() { Date oneSecondAgo = new Date(System.currentTimeMillis() - 1000); Date thirtySecondsAhead = new Date(System.currentTimeMillis() + 30000); - approvalStore.addApproval(new Approval(userId, "client", "read", thirtySecondsAhead, ApprovalStatus.APPROVED, - oneSecondAgo)); - approvalStore.addApproval(new Approval(userId, "client", "write", thirtySecondsAhead, ApprovalStatus.APPROVED, - oneSecondAgo)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("client") + .setScope("read") + .setExpiresAt(thirtySecondsAhead) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneSecondAgo)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("client") + .setScope("write") + .setExpiresAt(thirtySecondsAhead) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneSecondAgo)); tokenServices.setApprovalStore(approvalStore); tokenServices.setTokenPolicy(new TokenPolicy(43200, 2592000)); @@ -561,8 +571,12 @@ public void testExpiredToken() throws Exception { @Test(expected = InvalidTokenException.class) public void testUpdatedApprovals() { Date thirtySecondsAhead = new Date(System.currentTimeMillis() + 30000); - approvalStore.addApproval(new Approval(userId, "client", "read", thirtySecondsAhead, ApprovalStatus.APPROVED, - new Date())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("client") + .setScope("read") + .setExpiresAt(thirtySecondsAhead) + .setStatus(ApprovalStatus.APPROVED)); Claims result = endpoint.checkToken(accessToken.getValue()); assertEquals(null, result.getAuthorities()); } @@ -571,21 +585,38 @@ public void testUpdatedApprovals() { public void testDeniedApprovals() { Date oneSecondAgo = new Date(System.currentTimeMillis() - 1000); Date thirtySecondsAhead = new Date(System.currentTimeMillis() + 30000); - approvalStore.revokeApproval(new Approval(userId, "client", "read", thirtySecondsAhead, - ApprovalStatus.APPROVED, - oneSecondAgo)); - approvalStore.addApproval(new Approval(userId, "client", "read", thirtySecondsAhead, ApprovalStatus.DENIED, - oneSecondAgo)); + approvalStore.revokeApproval(new Approval() + .setUserId(userId) + .setClientId("client") + .setScope("read") + .setExpiresAt(thirtySecondsAhead) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneSecondAgo)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("client") + .setScope("read") + .setExpiresAt(thirtySecondsAhead) + .setStatus(ApprovalStatus.DENIED) + .setLastUpdatedAt(oneSecondAgo)); Claims result = endpoint.checkToken(accessToken.getValue()); assertEquals(null, result.getAuthorities()); } @Test(expected = InvalidTokenException.class) public void testExpiredApprovals() { - approvalStore.revokeApproval(new Approval(userId, "client", "read", new Date(), ApprovalStatus.APPROVED, - new Date())); - approvalStore.addApproval(new Approval(userId, "client", "read", new Date(), ApprovalStatus.APPROVED, - new Date())); + approvalStore.revokeApproval(new Approval() + .setUserId(userId) + .setClientId("client") + .setScope("read") + .setExpiresAt(new Date()) + .setStatus(ApprovalStatus.APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("client") + .setScope("read") + .setExpiresAt(new Date()) + .setStatus(ApprovalStatus.APPROVED)); Claims result = endpoint.checkToken(accessToken.getValue()); assertEquals(null, result.getAuthorities()); } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandlerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandlerTests.java index 6855840a79a..69053541439 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandlerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandlerTests.java @@ -139,8 +139,18 @@ public void testNoRequestedScopesButSomeApprovedScopes() { long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000); Date nextWeek = new Date(theFuture); - approvalStore.addApproval(new Approval(userAuthentication.getId(), "foo", "cloud_controller.read",nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userAuthentication.getId(), "foo", "cloud_controller.write",nextWeek, DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userAuthentication.getId()) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userAuthentication.getId()) + .setClientId("foo") + .setScope("cloud_controller.write") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); // The request is approved because the user has not requested any scopes assertTrue(handler.isApproved(request, userAuthentication)); @@ -160,8 +170,18 @@ public void testRequestedScopesDontMatchApprovalsAtAll() { long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000); Date nextWeek = new Date(theFuture); - approvalStore.addApproval(new Approval(userAuthentication.getId(), "foo", "cloud_controller.read",nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userAuthentication.getId(), "foo", "cloud_controller.write",nextWeek, DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userAuthentication.getId()) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userAuthentication.getId()) + .setClientId("foo") + .setScope("cloud_controller.write") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); // The request is not approved because the user has not yet approved the // scopes requested @@ -181,8 +201,18 @@ public void testOnlySomeRequestedScopeMatchesApproval() { long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000); Date nextWeek = new Date(theFuture); - approvalStore.addApproval(new Approval(userAuthentication.getId(), "foo", "cloud_controller.read",nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userAuthentication.getId(), "foo", "cloud_controller.write",nextWeek, DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userAuthentication.getId()) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userAuthentication.getId()) + .setClientId("foo") + .setScope("cloud_controller.write") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); // The request is not approved because the user has not yet approved all // the scopes requested @@ -214,8 +244,18 @@ public void testOnlySomeRequestedScopeMatchesDeniedApprovalButScopeAutoApproved( ) ); - approvalStore.addApproval(new Approval(userAuthentication.getId(), "foo", "cloud_controller.read",nextWeek, DENIED)); - approvalStore.addApproval(new Approval(userAuthentication.getId(), "foo", "openid", nextWeek, DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userAuthentication.getId()) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userAuthentication.getId()) + .setClientId("foo") + .setScope("openid") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); assertTrue(handler.isApproved(request, userAuthentication)); assertEquals(new HashSet<>(Arrays.asList(new String[]{"cloud_controller.read", "openid"})),request.getScope()); @@ -238,8 +278,18 @@ public void testRequestedScopesMatchApprovalButAdditionalScopesRequested() { long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000); Date nextWeek = new Date(theFuture); - approvalStore.addApproval(new Approval(userAuthentication.getId(), "foo", "cloud_controller.read",nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userAuthentication.getId(), "foo", "cloud_controller.write",nextWeek, DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userAuthentication.getId()) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userAuthentication.getId()) + .setClientId("foo") + .setScope("cloud_controller.write") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); // The request is not approved because the user has not yet approved all // the scopes requested @@ -262,9 +312,24 @@ public void testAllRequestedScopesMatchApproval() { long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000); Date nextWeek = new Date(theFuture); - approvalStore.addApproval(new Approval(userId, "foo", "openid", nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.read",nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.write",nextWeek, APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("openid") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.write") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); // The request is approved because the user has approved all the scopes // requested @@ -288,9 +353,24 @@ public void testRequestedScopesMatchApprovalButSomeDenied() { long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000); Date nextWeek = new Date(theFuture); - approvalStore.addApproval(new Approval(userId, "foo", "openid", nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.read",nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.write",nextWeek, DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("openid") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.write") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); // The request is approved because the user has acted on all requested // scopes @@ -323,9 +403,24 @@ public void testRequestedScopesMatchApprovalSomeDeniedButDeniedScopesAutoApprove }, Collections.singletonMap(ClientConstants.AUTO_APPROVE,(Object) Collections.singletonList("cloud_controller.write")))); - approvalStore.addApproval(new Approval(userId, "foo", "openid", nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.read",nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.write",nextWeek, DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("openid") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.write") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); // The request is not approved because the user has denied some of the // scopes requested @@ -364,10 +459,30 @@ public void testRequestedScopesMatchApprovalSomeDeniedButDeniedScopesAutoApprove }, Collections.singletonMap(ClientConstants.AUTO_APPROVE,(Object) Arrays.asList("space.*.developer", "cloud_controller.write")))); - approvalStore.addApproval(new Approval(userId, "foo", "openid", nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.read",nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.write",nextWeek, DENIED)); - approvalStore.addApproval(new Approval(userId, "foo", "space.1.developer",nextWeek, DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("openid") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.write") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("space.1.developer") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); // The request is not approved because the user has denied some of the // scopes requested @@ -405,10 +520,30 @@ public void testRequestedScopesMatchByWildcard() { }, Collections.singletonMap(ClientConstants.AUTO_APPROVE, (Object) "true"))); - approvalStore.addApproval(new Approval(userId, "foo", "openid", nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.read",nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.write",nextWeek, DENIED)); - approvalStore.addApproval(new Approval(userId, "foo", "space.1.developer",nextWeek, DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("openid") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.write") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("space.1.developer") + .setExpiresAt(nextWeek) + .setStatus(DENIED)); // The request is not approved because the user has denied some of the // scopes requested @@ -429,9 +564,24 @@ public void testSomeRequestedScopesMatchApproval() { long theFuture = System.currentTimeMillis() + (86400 * 7 * 1000); Date nextWeek = new Date(theFuture); - approvalStore.addApproval(new Approval(userId, "foo", "openid", nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.read",nextWeek, APPROVED)); - approvalStore.addApproval(new Approval(userId, "foo", "cloud_controller.write",nextWeek, APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("openid") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.read") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId("foo") + .setScope("cloud_controller.write") + .setExpiresAt(nextWeek) + .setStatus(APPROVED)); // The request is approved because the user has approved all the scopes // requested diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalTests.java index 4dd90f21b94..71fb4986467 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalTests.java @@ -16,6 +16,7 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; +import java.util.Date; import java.util.List; import org.junit.Test; @@ -24,45 +25,181 @@ public class ApprovalTests { @Test public void testHashCode() throws Exception { - assertTrue(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).hashCode() == new Approval("u1", - "c1", "s1", 500, Approval.ApprovalStatus.DENIED).hashCode()); - assertFalse(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).hashCode() == new Approval( - "u1", "c2", "s1", 100, Approval.ApprovalStatus.DENIED).hashCode()); - assertFalse(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).hashCode() == new Approval( - "u1", "c1", "s2", 100, Approval.ApprovalStatus.DENIED).hashCode()); - assertFalse(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).hashCode() == new Approval( - "u2", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).hashCode()); - assertFalse(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).hashCode() == new Approval( - "u1", "c1", "s1", 100, Approval.ApprovalStatus.APPROVED).hashCode()); + assertTrue(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).hashCode() == new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(500)) + .setStatus(Approval.ApprovalStatus.DENIED).hashCode()); + assertFalse(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).hashCode() == new Approval() + .setUserId("u1") + .setClientId("c2") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).hashCode()); + assertFalse(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).hashCode() == new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s2") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).hashCode()); + assertFalse(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).hashCode() == new Approval() + .setUserId("u2") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).hashCode()); + assertFalse(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).hashCode() == new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.APPROVED).hashCode()); } @Test public void testEquals() throws Exception { - assertTrue(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).equals(new Approval("u1", "c1", - "s1", 500, Approval.ApprovalStatus.DENIED))); - assertFalse(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).equals(new Approval("u1", "c2", - "s1", 100, Approval.ApprovalStatus.DENIED))); - assertFalse(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).equals(new Approval("u1", "c1", - "s2", 100, Approval.ApprovalStatus.DENIED))); - assertFalse(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).equals(new Approval("u2", "c1", - "s1", 100, Approval.ApprovalStatus.DENIED))); - assertFalse(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED).equals(new Approval("u1", "c1", - "s1", 100, Approval.ApprovalStatus.APPROVED))); + assertTrue(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).equals(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(500)) + .setStatus(Approval.ApprovalStatus.DENIED))); + assertFalse(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).equals(new Approval() + .setUserId("u1") + .setClientId("c2") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED))); + assertFalse(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).equals(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s2") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED))); + assertFalse(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).equals(new Approval() + .setUserId("u2") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED))); + assertFalse(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED).equals(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.APPROVED))); - List approvals = Arrays.asList(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.APPROVED), - new Approval("u1", "c1", "s2", 100, Approval.ApprovalStatus.APPROVED), - new Approval("u1", "c1", "s3", 100, Approval.ApprovalStatus.APPROVED), - new Approval("u1", "c2", "s1", 100, Approval.ApprovalStatus.APPROVED), - new Approval("u1", "c2", "s2", 100, Approval.ApprovalStatus.DENIED) + List approvals = Arrays.asList(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.APPROVED), + new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s2") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.APPROVED), + new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s3") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.APPROVED), + new Approval() + .setUserId("u1") + .setClientId("c2") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.APPROVED), + new Approval() + .setUserId("u1") + .setClientId("c2") + .setScope("s2") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED) ); - assertTrue(approvals.contains(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.APPROVED))); - assertFalse(approvals.contains(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.DENIED))); + assertTrue(approvals.contains(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.APPROVED))); + assertFalse(approvals.contains(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(100)) + .setStatus(Approval.ApprovalStatus.DENIED))); } @Test public void testExpiry() { int THIRTY_MINTUES = 30 * 60 * 1000; - assertTrue(new Approval("u1", "c1", "s1", THIRTY_MINTUES, Approval.ApprovalStatus.APPROVED).isCurrentlyActive()); - assertFalse(new Approval("u1", "c1", "s1", -1, Approval.ApprovalStatus.APPROVED).isCurrentlyActive()); + assertTrue(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(THIRTY_MINTUES)) + .setStatus(Approval.ApprovalStatus.APPROVED).isCurrentlyActive()); + int expiresIn = -1; + assertFalse(new Approval() + .setUserId("u1") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(expiresIn)) + .setStatus(Approval.ApprovalStatus.APPROVED).isCurrentlyActive()); } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java index 7d17b50a275..7dc95dce972 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java @@ -84,7 +84,12 @@ public void initApprovalsAdminEndpointsTests() { } private void addApproval(String userName, String clientId, String scope, int expiresIn, ApprovalStatus status) { - dao.addApproval(new Approval(userName, clientId, scope, expiresIn, status)); + dao.addApproval(new Approval() + .setUserId(userName) + .setClientId(clientId) + .setScope(scope) + .setExpiresAt(Approval.timeFromNow(expiresIn)) + .setStatus(status)); } private SecurityContextAccessor mockSecurityContextAccessor(String userName, String id) { @@ -116,7 +121,12 @@ public void canGetApprovals() { @Test public void testApprovalsDeserializationIsCaseInsensitive() throws Exception { Set approvals = new HashSet<>(); - approvals.add(new Approval("test-user-id", "testclientid", "scope", new Date(), Approval.ApprovalStatus.APPROVED)); + approvals.add(new Approval() + .setUserId("test-user-id") + .setClientId("testclientid") + .setScope("scope") + .setExpiresAt(new Date()) + .setStatus(ApprovalStatus.APPROVED)); Set deserializedApprovals = JsonUtils.readValue("[{\"userid\":\"test-user-id\",\"clientid\":\"testclientid\",\"scope\":\"scope\",\"status\":\"APPROVED\",\"expiresat\":\"2015-08-25T14:35:42.512Z\",\"lastupdatedat\":\"2015-08-25T14:35:42.512Z\"}]", new TypeReference>() { }); assertEquals(approvals, deserializedApprovals); @@ -143,23 +153,83 @@ public void canUpdateApprovals() { addApproval(marissa.getId(), "c1", "uaa.admin", 12000, DENIED); addApproval(marissa.getId(), "c1", "openid", 6000, APPROVED); - Approval[] app = new Approval[] { new Approval(marissa.getId(), "c1", "uaa.user", 2000, APPROVED), - new Approval(marissa.getId(), "c1", "dash.user", 2000, APPROVED), - new Approval(marissa.getId(), "c1", "openid", 2000, DENIED), - new Approval(marissa.getId(), "c1", "cloud_controller.read", 2000, APPROVED) }; + Approval[] app = new Approval[] {new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("uaa.user") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(APPROVED), + new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("dash.user") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(APPROVED), + new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("openid") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(DENIED), + new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("cloud_controller.read") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(APPROVED)}; List response = endpoints.updateApprovals(app); assertEquals(4, response.size()); - assertTrue(response.contains(new Approval(marissa.getId(), "c1", "uaa.user", 2000, APPROVED))); - assertTrue(response.contains(new Approval(marissa.getId(), "c1", "dash.user", 2000, APPROVED))); - assertTrue(response.contains(new Approval(marissa.getId(), "c1", "openid", 2000, DENIED))); - assertTrue(response.contains(new Approval(marissa.getId(), "c1", "cloud_controller.read", 2000, APPROVED))); + assertTrue(response.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("uaa.user") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(APPROVED))); + assertTrue(response.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("dash.user") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(APPROVED))); + assertTrue(response.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("openid") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(DENIED))); + assertTrue(response.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("cloud_controller.read") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(APPROVED))); List updatedApprovals = endpoints.getApprovals("user_id eq \""+marissa.getId()+"\"", 1, 100); assertEquals(4, updatedApprovals.size()); - assertTrue(updatedApprovals.contains(new Approval(marissa.getId(), "c1", "dash.user", 2000, APPROVED))); - assertTrue(updatedApprovals.contains(new Approval(marissa.getId(), "c1", "openid", 2000, DENIED))); - assertTrue(updatedApprovals.contains(new Approval(marissa.getId(), "c1", "cloud_controller.read", 2000, APPROVED))); - assertTrue(updatedApprovals.contains(new Approval(marissa.getId(), "c1", "uaa.user", 2000, APPROVED))); + assertTrue(updatedApprovals.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("dash.user") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(APPROVED))); + assertTrue(updatedApprovals.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("openid") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(DENIED))); + assertTrue(updatedApprovals.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("cloud_controller.read") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(APPROVED))); + assertTrue(updatedApprovals.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("uaa.user") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(APPROVED))); } public void attemptingToCreateDuplicateApprovalsExtendsValidity() { @@ -171,9 +241,24 @@ public void attemptingToCreateDuplicateApprovalsExtendsValidity() { List updatedApprovals = endpoints.getApprovals("user_id eq \""+marissa.getId()+"\"", 1, 100); assertEquals(3, updatedApprovals.size()); - assertTrue(updatedApprovals.contains(new Approval(marissa.getId(), "c1", "uaa.user", 6000, APPROVED))); - assertTrue(updatedApprovals.contains(new Approval(marissa.getId(), "c1", "uaa.admin", 12000, DENIED))); - assertTrue(updatedApprovals.contains(new Approval(marissa.getId(), "c1", "openid", 10000, APPROVED))); + assertTrue(updatedApprovals.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("uaa.user") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(APPROVED))); + assertTrue(updatedApprovals.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("uaa.admin") + .setExpiresAt(Approval.timeFromNow(12000)) + .setStatus(DENIED))); + assertTrue(updatedApprovals.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("openid") + .setExpiresAt(Approval.timeFromNow(10000)) + .setStatus(APPROVED))); } public void attemptingToCreateAnApprovalWithADifferentStatusUpdatesApproval() { @@ -185,9 +270,24 @@ public void attemptingToCreateAnApprovalWithADifferentStatusUpdatesApproval() { List updatedApprovals = endpoints.getApprovals("user_id eq \""+marissa.getId()+"\"", 1, 100); assertEquals(4, updatedApprovals.size()); - assertTrue(updatedApprovals.contains(new Approval(marissa.getId(), "c1", "uaa.user", 6000, APPROVED))); - assertTrue(updatedApprovals.contains(new Approval(marissa.getId(), "c1", "uaa.admin", 12000, DENIED))); - assertTrue(updatedApprovals.contains(new Approval(marissa.getId(), "c1", "openid", 18000, DENIED))); + assertTrue(updatedApprovals.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("uaa.user") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(APPROVED))); + assertTrue(updatedApprovals.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("uaa.admin") + .setExpiresAt(Approval.timeFromNow(12000)) + .setStatus(DENIED))); + assertTrue(updatedApprovals.contains(new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("openid") + .setExpiresAt(Approval.timeFromNow(18000)) + .setStatus(DENIED))); } @Test(expected = UaaException.class) @@ -196,7 +296,12 @@ public void userCannotUpdateApprovalsForAnotherUser() { addApproval(marissa.getId(), "c1", "uaa.admin", 12000, DENIED); addApproval(marissa.getId(), "c1", "openid", 6000, APPROVED); endpoints.setSecurityContextAccessor(mockSecurityContextAccessor("vidya", "123456")); - endpoints.updateApprovals(new Approval[] { new Approval(marissa.getId(), "c1", "uaa.user", 2000, APPROVED) }); + endpoints.updateApprovals(new Approval[] {new Approval() + .setUserId(marissa.getId()) + .setClientId("c1") + .setScope("uaa.user") + .setExpiresAt(Approval.timeFromNow(2000)) + .setStatus(APPROVED)}); } @Test diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStoreTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStoreTests.java index f95125bb4a7..a11a89c4770 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStoreTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStoreTests.java @@ -59,7 +59,13 @@ public void initJdbcApprovalStoreTests() { private void addApproval(String userName, String clientId, String scope, long expiresIn, ApprovalStatus status) { Date expiresAt = new Timestamp(new Date().getTime() + expiresIn); Date lastUpdatedAt = new Date(); - Approval newApproval = new Approval(userName, clientId, scope, expiresAt, status, lastUpdatedAt); + Approval newApproval = new Approval() + .setUserId(userName) + .setClientId(clientId) + .setScope(scope) + .setExpiresAt(expiresAt) + .setStatus(status) + .setLastUpdatedAt(lastUpdatedAt); dao.addApproval(newApproval); } @@ -79,18 +85,23 @@ public void testAddAndGetApproval() { ApprovalStatus status = APPROVED; Date expiresAt = new Timestamp(new Date().getTime() + expiresIn); - Approval newApproval = new Approval(userName, clientId, scope, expiresAt, status, lastUpdatedAt); + Approval newApproval = new Approval() + .setUserId(userName) + .setClientId(clientId) + .setScope(scope) + .setExpiresAt(expiresAt) + .setStatus(status) + .setLastUpdatedAt(lastUpdatedAt); dao.addApproval(newApproval); List approvals = dao.getApprovals(userName, clientId); - Approval approval = approvals.get(0); - assertEquals(clientId, approval.getClientId()); - assertEquals(userName, approval.getUserId()); - assertEquals(Math.round(expiresAt.getTime() / 1000), Math.round(approval.getExpiresAt().getTime() / 1000)); + assertEquals(clientId, approvals.get(0).getClientId()); + assertEquals(userName, approvals.get(0).getUserId()); + assertEquals(Math.round(expiresAt.getTime() / 1000), Math.round(approvals.get(0).getExpiresAt().getTime() / 1000)); assertEquals(Math.round(lastUpdatedAt.getTime() / 1000), - Math.round(approval.getLastUpdatedAt().getTime() / 1000)); - assertEquals(scope, approval.getScope()); - assertEquals(status, approval.getStatus()); + Math.round(approvals.get(0).getLastUpdatedAt().getTime() / 1000)); + assertEquals(scope, approvals.get(0).getScope()); + assertEquals(status, approvals.get(0).getStatus()); } @Test @@ -103,7 +114,12 @@ public void canGetApprovals() { @Test public void canAddApproval() { - assertTrue(dao.addApproval(new Approval("u2", "c2", "dash.user", 12000, APPROVED))); + assertTrue(dao.addApproval(new Approval() + .setUserId("u2") + .setClientId("c2") + .setScope("dash.user") + .setExpiresAt(Approval.timeFromNow(12000)) + .setStatus(APPROVED))); List apps = dao.getApprovals("u2", "c2"); assertEquals(1, apps.size()); Approval app = apps.iterator().next(); @@ -134,11 +150,21 @@ public void canRevokeSingleApproval() { @Test public void addSameApprovalRepeatedlyUpdatesExpiry() { - assertTrue(dao.addApproval(new Approval("u2", "c2", "dash.user", 6000, APPROVED))); + assertTrue(dao.addApproval(new Approval() + .setUserId("u2") + .setClientId("c2") + .setScope("dash.user") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(APPROVED))); Approval app = dao.getApprovals("u2", "c2").iterator().next(); assertEquals(Math.round(app.getExpiresAt().getTime() / 1000), Math.round((new Date().getTime() + 6000) / 1000)); - assertTrue(dao.addApproval(new Approval("u2", "c2", "dash.user", 8000, APPROVED))); + assertTrue(dao.addApproval(new Approval() + .setUserId("u2") + .setClientId("c2") + .setScope("dash.user") + .setExpiresAt(Approval.timeFromNow(8000)) + .setStatus(APPROVED))); app = dao.getApprovals("u2", "c2").iterator().next(); assertEquals(Math.round(app.getExpiresAt().getTime() / 1000), Math.round((new Date().getTime() + 8000) / 1000)); } @@ -146,11 +172,21 @@ public void addSameApprovalRepeatedlyUpdatesExpiry() { @Test @Ignore //this test has issues public void addSameApprovalDifferentStatusRepeatedlyOnlyUpdatesStatus() { - assertTrue(dao.addApproval(new Approval("u2", "c2", "dash.user", 6000, APPROVED))); + assertTrue(dao.addApproval(new Approval() + .setUserId("u2") + .setClientId("c2") + .setScope("dash.user") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(APPROVED))); Approval app = dao.getApprovals("u2", "c2").iterator().next(); assertEquals(Math.round(app.getExpiresAt().getTime() / 1000), Math.round((new Date().getTime() + 6000) / 1000)); - assertTrue(dao.addApproval(new Approval("u2", "c2", "dash.user", 8000, DENIED))); + assertTrue(dao.addApproval(new Approval() + .setUserId("u2") + .setClientId("c2") + .setScope("dash.user") + .setExpiresAt(Approval.timeFromNow(8000)) + .setStatus(DENIED))); app = dao.getApprovals("u2", "c2").iterator().next(); assertEquals(Math.round(app.getExpiresAt().getTime() / 1000), Math.round((new Date().getTime() + 6000) / 1000)); assertEquals(DENIED, app.getStatus()); @@ -161,7 +197,12 @@ public void canRefreshApproval() { Approval app = dao.getApprovals("u1", "c1").iterator().next(); Date now = new Date(); - dao.refreshApproval(new Approval(app.getUserId(), app.getClientId(), app.getScope(), now, APPROVED)); + dao.refreshApproval(new Approval() + .setUserId(app.getUserId()) + .setClientId(app.getClientId()) + .setScope(app.getScope()) + .setExpiresAt(now) + .setStatus(APPROVED)); app = dao.getApprovals("u1", "c1").iterator().next(); assertEquals(Math.round(now.getTime() / 1000), Math.round(app.getExpiresAt().getTime() / 1000)); } @@ -188,7 +229,12 @@ public void canPurgeExpiredApprovals() throws InterruptedException { public void testAddingAndUpdatingAnApprovalPublishesEvents() throws Exception { UaaTestAccounts testAccounts = UaaTestAccounts.standard(null); - Approval approval = new Approval(testAccounts.getUserName(), "app", "cloud_controller.read", 1000, ApprovalStatus.APPROVED); + Approval approval = new Approval() + .setUserId(testAccounts.getUserName()) + .setClientId("app") + .setScope("cloud_controller.read") + .setExpiresAt(Approval.timeFromNow(1000)) + .setStatus(ApprovalStatus.APPROVED); eventPublisher.clearEvents(); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java index 4d9b547b484..74e57c205a8 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java @@ -451,8 +451,20 @@ private OAuth2AccessToken getOAuth2AccessToken() { Calendar updatedAt = Calendar.getInstance(); updatedAt.add(Calendar.MILLISECOND, -1000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED, updatedAt.getTime())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED, updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); authorizationRequest.setResourceIds(new HashSet<>(resourceIds)); @@ -571,7 +583,13 @@ public void testCreateAccessTokenRefreshGrantSomeScopesAutoApproved() throws Int Calendar updatedAt = Calendar.getInstance(); updatedAt.add(Calendar.MILLISECOND, -1000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); authorizationRequest.setResourceIds(new HashSet<>(resourceIds)); @@ -623,7 +641,13 @@ public void testCreateAccessTokenRefreshGrantNoScopesAutoApprovedIncompleteAppro Calendar updatedAt = Calendar.getInstance(); updatedAt.add(Calendar.MILLISECOND, -1000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); authorizationRequest.setResourceIds(new HashSet<>(resourceIds)); @@ -668,8 +692,20 @@ public void testCreateAccessTokenRefreshGrantAllScopesAutoApprovedButApprovalDen Calendar updatedAt = Calendar.getInstance(); updatedAt.add(Calendar.MILLISECOND, -1000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,updatedAt.getTime())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.DENIED,updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.DENIED) + .setLastUpdatedAt(updatedAt.getTime())); AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); authorizationRequest.setResourceIds(new HashSet<>(resourceIds)); @@ -850,8 +886,20 @@ public void testCreateAccessTokenAuthcodeGrantNarrowerScopes() { Calendar updatedAt = Calendar.getInstance(); updatedAt.add(Calendar.MILLISECOND, -1000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,updatedAt.getTime())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); // First Request AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); @@ -891,8 +939,18 @@ public void testCreateAccessTokenAuthcodeGrantExpandedScopes() { Calendar expiresAt = Calendar.getInstance(); expiresAt.add(Calendar.MILLISECOND, 3000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED, new Date())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED, new Date())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); // First Request AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); authorizationRequest.setResourceIds(new HashSet<>(resourceIds)); @@ -949,8 +1007,18 @@ public void testUserUpdatedAfterRefreshTokenIssued() { Calendar expiresAt = Calendar.getInstance(); expiresAt.add(Calendar.MILLISECOND, 3000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); authorizationRequest.setResourceIds(new HashSet<>(resourceIds)); Map azParameters = new HashMap<>(authorizationRequest.getRequestParameters()); @@ -979,8 +1047,18 @@ public void testRefreshTokenExpiry() { Calendar expiresAt = Calendar.getInstance(); expiresAt.add(Calendar.MILLISECOND, 3000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); BaseClientDetails clientDetails = cloneClient(defaultClient); // Back date the refresh token. Crude way to do this but i'm not sure of @@ -1022,8 +1100,18 @@ public void testRefreshTokenAfterApprovalsChanged() { Calendar expiresAt = Calendar.getInstance(); expiresAt.add(Calendar.MILLISECOND, 3000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); AuthorizationRequest refreshAuthorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); refreshAuthorizationRequest.setResourceIds(new HashSet<>(resourceIds)); @@ -1039,8 +1127,18 @@ public void testRefreshTokenAfterApprovalsExpired() { Calendar expiresAt = Calendar.getInstance(); expiresAt.add(Calendar.MILLISECOND, -3000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); authorizationRequest.setResourceIds(new HashSet<>(resourceIds)); @@ -1066,8 +1164,18 @@ public void testRefreshTokenAfterApprovalsDenied() { Calendar expiresAt = Calendar.getInstance(); expiresAt.add(Calendar.MILLISECOND, -3000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.DENIED, new Date())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,new Date())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.DENIED)); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED)); AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); authorizationRequest.setResourceIds(new HashSet<>(resourceIds)); @@ -1093,7 +1201,12 @@ public void testRefreshTokenAfterApprovalsMissing() { Calendar expiresAt = Calendar.getInstance(); expiresAt.add(Calendar.MILLISECOND, -3000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.DENIED,new Date())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.DENIED)); AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); authorizationRequest.setResourceIds(new HashSet<>(resourceIds)); @@ -1149,8 +1262,20 @@ public void testReadAccessToken() { Calendar updatedAt = Calendar.getInstance(); updatedAt.add(Calendar.MILLISECOND, -1000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,updatedAt.getTime())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED,updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken accessToken = tokenServices.createAccessToken(authentication); @@ -1171,8 +1296,20 @@ public void testReadAccessTokenForDeletedUserId() { Calendar updatedAt = Calendar.getInstance(); updatedAt.add(Calendar.MILLISECOND, -1000); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, readScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED, updatedAt.getTime())); - approvalStore.addApproval(new Approval(userId, CLIENT_ID, writeScope.get(0), expiresAt.getTime(), ApprovalStatus.APPROVED, updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(readScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); + approvalStore.addApproval(new Approval() + .setUserId(userId) + .setClientId(CLIENT_ID) + .setScope(writeScope.get(0)) + .setExpiresAt(expiresAt.getTime()) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(updatedAt.getTime())); OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken accessToken = tokenServices.createAccessToken(authentication); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/DescribedApproval.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/DescribedApproval.java index 0320c67ac93..62f88cfef31 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/DescribedApproval.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/DescribedApproval.java @@ -21,8 +21,15 @@ public class DescribedApproval extends Approval { public DescribedApproval() { } + public DescribedApproval(Approval approval) { - super(approval); + this + .setLastUpdatedAt(approval.getLastUpdatedAt()) + .setUserId(approval.getUserId()) + .setStatus(approval.getStatus()) + .setExpiresAt(approval.getExpiresAt()) + .setScope(approval.getScope()) + .setClientId(approval.getClientId()); } @JsonIgnore diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java index ac052851d9b..569ee593cb9 100644 --- a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java +++ b/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.identity.uaa.impl.JsonDateDeserializer; @@ -27,71 +28,48 @@ @JsonDeserialize(using = ApprovalsJsonDeserializer.class) public class Approval { + public Approval() { + } + public enum ApprovalStatus { APPROVED, - DENIED; + DENIED } - private String userId; + private String userId = ""; - private String clientId; + private String clientId = ""; - private String scope; + private String scope = ""; private ApprovalStatus status; private Date expiresAt; - private Date lastUpdatedAt; - - public Approval(String userId, String clientId, String scope, int expiresIn, ApprovalStatus status) { - this(userId, clientId, scope, new Date(), status, new Date()); - Calendar expiresAt = Calendar.getInstance(); - expiresAt.add(Calendar.MILLISECOND, expiresIn); - setExpiresAt(expiresAt.getTime()); - } - - public Approval(String userId, String clientId, String scope, Date expiresAt, ApprovalStatus status) { - this(userId, clientId, scope, expiresAt, status, new Date()); - } + private Date lastUpdatedAt = new Date(); - public Approval(String userId, String clientId, String scope, Date expiresAt, ApprovalStatus status, - Date lastUpdatedAt) { - this.userId = userId; - this.clientId = clientId; - this.scope = scope; - this.expiresAt = expiresAt; - this.status = status; - this.lastUpdatedAt = lastUpdatedAt; - } - - public Approval() { - } - - public Approval(Approval approval) { - this(approval.getUserId(), - approval.getClientId(), - approval.getScope(), - approval.getExpiresAt(), - approval.getStatus(), - approval.getLastUpdatedAt() - ); + public static Date timeFromNow(int timeTill) { + Calendar timeOf = Calendar.getInstance(); + timeOf.add(Calendar.MILLISECOND, timeTill); + return timeOf.getTime(); } public String getUserId() { return userId; } - public void setUserId(String userId) { + public Approval setUserId(String userId) { this.userId = userId == null ? "" : userId; + return this; } public String getClientId() { return clientId; } - public void setClientId(String clientId) { + public Approval setClientId(String clientId) { this.clientId = clientId == null ? "" : clientId; + return this; } public ApprovalStatus getStatus() { @@ -102,33 +80,39 @@ public String getScope() { return scope; } - public void setScope(String scope) { + public Approval setScope(String scope) { this.scope = scope == null ? "" : scope; + return this; } - @JsonSerialize(using = JsonDateSerializer.class, include = JsonSerialize.Inclusion.NON_NULL) + @JsonSerialize(using = JsonDateSerializer.class) + @JsonProperty("expiresAt") public Date getExpiresAt() { - return expiresAt; - } - - @JsonDeserialize(using = JsonDateDeserializer.class) - public void setExpiresAt(Date expiresAt) { if (expiresAt == null) { Calendar thirtyMinFromNow = Calendar.getInstance(); thirtyMinFromNow.add(Calendar.MINUTE, 30); expiresAt = thirtyMinFromNow.getTime(); } + return expiresAt; + } + + @JsonDeserialize(using = JsonDateDeserializer.class) + @JsonProperty("expiresAt") + public Approval setExpiresAt(Date expiresAt) { this.expiresAt = expiresAt; + return this; } - @JsonSerialize(using = JsonDateSerializer.class, include = JsonSerialize.Inclusion.NON_NULL) + @JsonSerialize(using = JsonDateSerializer.class) public Date getLastUpdatedAt() { return lastUpdatedAt; } @JsonDeserialize(using = JsonDateDeserializer.class) - public void setLastUpdatedAt(Date lastUpdatedAt) { + public Approval setLastUpdatedAt(Date lastUpdatedAt) { + if (lastUpdatedAt == null) throw new IllegalArgumentException("lastUpdatedAt cannot be null"); this.lastUpdatedAt = lastUpdatedAt; + return this; } @JsonIgnore @@ -163,8 +147,9 @@ public String toString() { lastUpdatedAt); } - public void setStatus(ApprovalStatus status) { + public Approval setStatus(ApprovalStatus status) { this.status = status; + return this; } } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java index f2844931ca4..1d2dc9057fa 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java @@ -67,6 +67,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -244,8 +245,12 @@ public void groupsIsSyncedCorrectlyOnGet() { public void approvalsIsSyncedCorrectlyOnCreate() { ScimUser user = new ScimUser(null, "vidya", "Vidya", "V"); user.addEmail("vidya@vmware.com"); - user.setApprovals(Collections.singleton(new Approval("vidya", "c1", "s1", 6000, - Approval.ApprovalStatus.APPROVED))); + user.setApprovals(Collections.singleton(new Approval() + .setUserId("vidya") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(Approval.ApprovalStatus.APPROVED))); ScimUser created = endpoints.createUser(user, new MockHttpServletResponse()); assertNotNull(created.getApprovals()); @@ -258,14 +263,32 @@ public void approvalsIsSyncedCorrectlyOnUpdate() { ScimUser user = new ScimUser(null, "vidya", "Vidya", "V"); user.addEmail("vidya@vmware.com"); - user.setApprovals(Collections.singleton(new Approval("vidya", "c1", "s1", 6000, - Approval.ApprovalStatus.APPROVED))); + user.setApprovals(Collections.singleton(new Approval() + .setUserId("vidya") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(Approval.ApprovalStatus.APPROVED))); ScimUser created = endpoints.createUser(user, new MockHttpServletResponse()); - am.addApproval(new Approval(created.getId(), "c1", "s1", 6000, Approval.ApprovalStatus.APPROVED)); - am.addApproval(new Approval(created.getId(), "c1", "s2", 6000, Approval.ApprovalStatus.DENIED)); - - created.setApprovals(Collections.singleton(new Approval("vidya", "c1", "s1", 6000, - Approval.ApprovalStatus.APPROVED))); + am.addApproval(new Approval() + .setUserId(created.getId()) + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(Approval.ApprovalStatus.APPROVED)); + am.addApproval(new Approval() + .setUserId(created.getId()) + .setClientId("c1") + .setScope("s2") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(Approval.ApprovalStatus.DENIED)); + + created.setApprovals(Collections.singleton(new Approval() + .setUserId("vidya") + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(Approval.ApprovalStatus.APPROVED))); ScimUser updated = endpoints.updateUser(created, created.getId(), "*", new MockHttpServletResponse()); assertEquals(2, updated.getApprovals().size()); } @@ -274,8 +297,18 @@ public void approvalsIsSyncedCorrectlyOnUpdate() { public void approvalsIsSyncedCorrectlyOnGet() { assertEquals(0, endpoints.getUser(joel.getId(), new MockHttpServletResponse()).getApprovals().size()); - am.addApproval(new Approval(joel.getId(), "c1", "s1", 6000, Approval.ApprovalStatus.APPROVED)); - am.addApproval(new Approval(joel.getId(), "c1", "s2", 6000, Approval.ApprovalStatus.DENIED)); + am.addApproval(new Approval() + .setUserId(joel.getId()) + .setClientId("c1") + .setScope("s1") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(Approval.ApprovalStatus.APPROVED)); + am.addApproval(new Approval() + .setUserId(joel.getId()) + .setClientId("c1") + .setScope("s2") + .setExpiresAt(Approval.timeFromNow(6000)) + .setStatus(Approval.ApprovalStatus.DENIED)); assertEquals(2, endpoints.getUser(joel.getId(), new MockHttpServletResponse()).getApprovals().size()); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java index f28d3e45b94..941cdb53047 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ClientAdminEndpointsIntegrationTests.java @@ -539,9 +539,27 @@ private Approval[] addApprovals(String token, String clientId) throws Exception Date oneMinuteAgo = new Date(System.currentTimeMillis() - 60000); Date expiresAt = new Date(System.currentTimeMillis() + 60000); Approval[] approvals = new Approval[] { - new Approval(null, clientId, "cloud_controller.read", expiresAt, Approval.ApprovalStatus.APPROVED,oneMinuteAgo), - new Approval(null, clientId, "openid", expiresAt, Approval.ApprovalStatus.APPROVED,oneMinuteAgo), - new Approval(null, clientId, "password.write", expiresAt, Approval.ApprovalStatus.APPROVED,oneMinuteAgo) + new Approval() + .setUserId(null) + .setClientId(clientId) + .setScope("cloud_controller.read") + .setExpiresAt(expiresAt) + .setStatus(Approval.ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneMinuteAgo), + new Approval() + .setUserId(null) + .setClientId(clientId) + .setScope("openid") + .setExpiresAt(expiresAt) + .setStatus(Approval.ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneMinuteAgo), + new Approval() + .setUserId(null) + .setClientId(clientId) + .setScope("password.write") + .setExpiresAt(expiresAt) + .setStatus(Approval.ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneMinuteAgo) }; HttpHeaders headers = getAuthenticatedHeaders(token); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java index 50da8d62364..252fae5fdf5 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java @@ -66,6 +66,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import java.util.Arrays; +import java.util.Date; import java.util.List; import java.util.Map; @@ -487,12 +488,18 @@ public void clientAuthenticationFailureClientNotFound() throws Exception { ClientAuthenticationFailureEvent event1 = (ClientAuthenticationFailureEvent)captor.getAllValues().get(1); assertEquals("login", event1.getClientId()); } + @Test public void testUserApprovalAdded() throws Exception { clientRegistrationService.updateClientDetails(new BaseClientDetails("login", "oauth", "oauth.approvals", "password", "oauth.login")); String marissaToken = testClient.getUserOAuthAccessToken("login", "loginsecret", testUser.getUserName(), testPassword, "oauth.approvals"); - Approval[] approvals = {new Approval(null, "app", "cloud_controller.read", 1000, Approval.ApprovalStatus.APPROVED)}; + Approval[] approvals = {new Approval() + .setUserId(null) + .setClientId("app") + .setScope("cloud_controller.read") + .setExpiresAt(Approval.timeFromNow(1000)) + .setStatus(Approval.ApprovalStatus.APPROVED)}; MockHttpServletRequestBuilder approvalsPut = put("/approvals") .accept(MediaType.APPLICATION_JSON_VALUE) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java index 7ef2145b285..06085b4d7c2 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/clients/ClientAdminEndpointsMockMvcTests.java @@ -253,7 +253,7 @@ public void testCreate_RestrictedClient_Succeeds() throws Exception { getMockMvc().perform(createClientPost).andExpect(status().isOk()); client.setScope(new UaaScopes().getUaaScopes()); - createClientPost = put("/oauth/clients/restricted/"+id) + createClientPost = put("/oauth/clients/restricted/" + id) .header("Authorization", "Bearer " + adminToken) .accept(APPLICATION_JSON) .contentType(APPLICATION_JSON) @@ -321,7 +321,7 @@ public void test_InZone_ClientWrite_Using_ZonesDotClientsDotAdmin() throws Excep client = MockMvcUtils.utils().createClient(getMockMvc(), adminToken, client); client.setClientSecret("secret"); - String zonesClientsAdminToken = MockMvcUtils.utils().getClientOAuthAccessToken(getMockMvc(), client.getClientId(), client.getClientSecret(), "zones."+id+".clients.admin"); + String zonesClientsAdminToken = MockMvcUtils.utils().getClientOAuthAccessToken(getMockMvc(), client.getClientId(), client.getClientSecret(), "zones." + id + ".clients.admin"); BaseClientDetails newclient = new BaseClientDetails(clientId, "", "openid","authorization_code",""); newclient.setClientSecret("secret"); @@ -740,11 +740,11 @@ public void testModifyApprovalsAreDeleted() throws Exception { ClientDetails approvalsClient = createApprovalsLoginClient(adminToken); String loginToken = testClient.getUserOAuthAccessToken( - approvalsClient.getClientId(), - "secret", - testUser.getUserName(), - testPassword, - "oauth.approvals"); + approvalsClient.getClientId(), + "secret", + testUser.getUserName(), + testPassword, + "oauth.approvals"); approvals = getApprovals(loginToken, details.getClientId()); assertEquals(0, approvals.length); } @@ -1133,7 +1133,7 @@ public void testCreateAsWritePermissions() throws Exception { ClientDetails adminsClient = createReadWriteClient(adminToken); //create clients - ClientDetailsModification[] clients = createBaseClients(1, Arrays.asList("client_credentials","refresh_token")); + ClientDetailsModification[] clients = createBaseClients(1, Arrays.asList("client_credentials", "refresh_token")); for (ClientDetailsModification c : clients) { c.setScope(Collections.singletonList("oauth.approvals")); c.setAction(c.ADD); @@ -1221,9 +1221,27 @@ private Approval[] addApprovals(String token, String clientId) throws Exception Date oneMinuteAgo = new Date(System.currentTimeMillis() - 60000); Date expiresAt = new Date(System.currentTimeMillis() + 60000); Approval[] approvals = new Approval[] { - new Approval(null, clientId, "cloud_controller.read", expiresAt, ApprovalStatus.APPROVED,oneMinuteAgo), - new Approval(null, clientId, "openid", expiresAt, ApprovalStatus.APPROVED,oneMinuteAgo), - new Approval(null, clientId, "password.write", expiresAt, ApprovalStatus.APPROVED,oneMinuteAgo)}; + new Approval() + .setUserId(null) + .setClientId(clientId) + .setScope("cloud_controller.read") + .setExpiresAt(expiresAt) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneMinuteAgo), + new Approval() + .setUserId(null) + .setClientId(clientId) + .setScope("openid") + .setExpiresAt(expiresAt) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneMinuteAgo), + new Approval() + .setUserId(null) + .setClientId(clientId) + .setScope("password.write") + .setExpiresAt(expiresAt) + .setStatus(ApprovalStatus.APPROVED) + .setLastUpdatedAt(oneMinuteAgo)}; MockHttpServletRequestBuilder put = put("/approvals/"+clientId) .header("Authorization", "Bearer " + token) @@ -1263,8 +1281,8 @@ private ClientDetails getClient(String id) throws Exception { } private ClientDetails createClientAdminsClient(String token) throws Exception { - List scopes = Arrays.asList("oauth.approvals","clients.admin"); - BaseClientDetails client = createBaseClient(null, Arrays.asList("password","client_credentials"), scopes, scopes); + List scopes = Arrays.asList("oauth.approvals", "clients.admin"); + BaseClientDetails client = createBaseClient(null, Arrays.asList("password", "client_credentials"), scopes, scopes); MockHttpServletRequestBuilder createClientPost = post("/oauth/clients") .header("Authorization", "Bearer " + token) .accept(APPLICATION_JSON) @@ -1316,7 +1334,7 @@ private ClientDetails createApprovalsLoginClient(String token) throws Exception private ClientDetailsModification createBaseClient(String id, Collection grantTypes) { - return createBaseClient(id, grantTypes, Collections.singletonList("uaa.none"), Arrays.asList("foo","bar","oauth.approvals")); + return createBaseClient(id, grantTypes, Collections.singletonList("uaa.none"), Arrays.asList("foo", "bar", "oauth.approvals")); } private ClientDetailsModification createBaseClient(String id, Collection grantTypes, List authorities, List scopes) { From da65f4f3db7dc275987eed476f6731dc16d1ea5f Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Fri, 20 Nov 2015 10:43:29 -0800 Subject: [PATCH 012/103] Only consider non-null userId for validity - Approval forces null userId to be an empty string Signed-off-by: Paul Warren --- .../identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java index 6efeca06ee7..8b034ea0e44 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java @@ -174,7 +174,7 @@ public List updateClientApprovals(@PathVariable String clientId, @Requ logger.debug("Updating approvals for user: " + currentUserId); approvalStore.revokeApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, currentUserId, clientId)); for (Approval approval : approvals) { - if (approval.getUserId() !=null && !isValidUser(approval.getUserId())) { + if (StringUtils.hasText(approval.getUserId()) && !isValidUser(approval.getUserId())) { logger.warn(String.format("Error[1] %s attemting to update approvals for %s.", currentUserId, approval.getUserId())); throw new UaaException("unauthorized_operation", "Cannot update approvals for another user. Set user_id to null to update for existing user.", HttpStatus.UNAUTHORIZED.value()); From d4c4b9bb71300d9fd4f5f8a63225d7c625107c8e Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Mon, 23 Nov 2015 15:50:55 -0800 Subject: [PATCH 013/103] Rename payload -> models [#107504490] https://www.pivotaltracker.com/story/show/107504490 Signed-off-by: Jonathan Lo --- .gitignore | 2 -- build.gradle | 4 ++-- client-lib/build.gradle | 8 ++++---- common/build.gradle | 4 ++-- login/build.gradle | 6 +++--- {payload => models}/build.gradle | 4 ++-- {payload => models}/build_properties.gradle | 0 .../cloudfoundry/identity/uaa/codestore/ExpiringCode.java | 0 .../cloudfoundry/identity/uaa/constants/OriginKeys.java | 0 .../identity/uaa/impl/JsonDateDeserializer.java | 0 .../identity/uaa/impl/JsonDateSerializer.java | 0 .../identity/uaa/invitations/InvitationsRequest.java | 0 .../identity/uaa/invitations/InvitationsResponse.java | 0 .../identity/uaa/login/AuthenticationResponse.java | 0 .../cloudfoundry/identity/uaa/login/AutologinRequest.java | 0 .../identity/uaa/login/AutologinResponse.java | 0 .../identity/uaa/oauth/approval/Approval.java | 0 .../oauth/approval/impl/ApprovalsJsonDeserializer.java | 0 .../identity/uaa/oauth/client/ClientConstants.java | 0 .../uaa/oauth/client/ClientDetailsModification.java | 0 .../identity/uaa/oauth/client/SecretChangeRequest.java | 0 .../identity/uaa/oauth/token/ClaimConstants.java | 0 .../org/cloudfoundry/identity/uaa/oauth/token/Claims.java | 0 .../identity/uaa/oauth/token/VerificationKeyResponse.java | 0 .../uaa/oauth/token/VerificationKeysListResponse.java | 0 .../cloudfoundry/identity/uaa/profile/EmailChange.java | 0 .../identity/uaa/profile/EmailChangeResponse.java | 0 .../identity/uaa/profile/PasswordChangeRequest.java | 0 .../identity/uaa/profile/PasswordChangeResponse.java | 0 .../identity/uaa/profile/PasswordResetResponse.java | 0 .../identity/uaa/profile/UserInfoResponse.java | 0 .../uaa/provider/AbstractIdentityProviderDefinition.java | 0 .../uaa/provider/ExternalIdentityProviderDefinition.java | 0 .../identity/uaa/provider/IdentityProvider.java | 0 .../uaa/provider/KeystoneIdentityProviderDefinition.java | 0 .../uaa/provider/LdapIdentityProviderDefinition.java | 0 .../cloudfoundry/identity/uaa/provider/LockoutPolicy.java | 0 .../identity/uaa/provider/PasswordPolicy.java | 0 .../uaa/provider/SamlIdentityProviderDefinition.java | 0 .../uaa/provider/UaaIdentityProviderDefinition.java | 0 .../cloudfoundry/identity/uaa/resources/ActionResult.java | 0 .../identity/uaa/resources/SearchResults.java | 0 .../java/org/cloudfoundry/identity/uaa/scim/ScimCore.java | 0 .../org/cloudfoundry/identity/uaa/scim/ScimGroup.java | 0 .../identity/uaa/scim/ScimGroupExternalMember.java | 0 .../cloudfoundry/identity/uaa/scim/ScimGroupMember.java | 0 .../java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java | 0 .../java/org/cloudfoundry/identity/uaa/scim/ScimUser.java | 0 .../identity/uaa/scim/impl/ScimGroupJsonDeserializer.java | 0 .../identity/uaa/scim/impl/ScimGroupJsonSerializer.java | 0 .../identity/uaa/scim/impl/ScimUserJsonDeserializer.java | 0 .../org/cloudfoundry/identity/uaa/util/JsonUtils.java | 0 .../org/cloudfoundry/identity/uaa/util/ObjectUtils.java | 0 .../org/cloudfoundry/identity/uaa/zone/IdentityZone.java | 0 .../identity/uaa/zone/IdentityZoneConfiguration.java | 0 .../java/org/cloudfoundry/identity/uaa/zone/KeyPair.java | 0 .../org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java | 0 .../org/cloudfoundry/identity/uaa/zone/SamlConfig.java | 0 .../org/cloudfoundry/identity/uaa/zone/TokenPolicy.java | 0 {payload => models}/src/main/resources/.gitignore | 0 scim/build.gradle | 4 ++-- settings.gradle | 6 +++--- uaa/build.gradle | 2 +- 63 files changed, 19 insertions(+), 21 deletions(-) rename {payload => models}/build.gradle (87%) rename {payload => models}/build_properties.gradle (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java (100%) rename {payload => models}/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java (100%) rename {payload => models}/src/main/resources/.gitignore (100%) diff --git a/.gitignore b/.gitignore index febb0c72b48..0af4766d13c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,8 +27,6 @@ coverage.ec .gradle build/ /classes/ -payload/src/main/resources/build.properties -payload/src/main/resources/git.properties bin phantomjsdriver.log diff --git a/build.gradle b/build.gradle index b4471cf074c..54cd3594840 100644 --- a/build.gradle +++ b/build.gradle @@ -308,7 +308,7 @@ project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> apply plugin: 'coveralls' -Project identityPayload = subprojects.find { it.name.equals('cloudfoundry-identity-payload') } +Project identityModels = subprojects.find { it.name.equals('cloudfoundry-identity-models') } Project identityCommon = subprojects.find { it.name.equals('cloudfoundry-identity-common') } Project identityScim = subprojects.find { it.name.equals('cloudfoundry-identity-scim') } Project identityLogin = subprojects.find { it.name.equals('cloudfoundry-identity-login') } @@ -317,7 +317,7 @@ Project identityUaa = subprojects.find { it.name.equals('cloudfoundry-identity-u cobertura { coverageFormats = ['xml', 'html'] coverageSourceDirs = [ - identityPayload.sourceSets.main.java.srcDirs, + identityModels.sourceSets.main.java.srcDirs, identityCommon.sourceSets.main.java.srcDirs, identityScim.sourceSets.main.java.srcDirs, identityLogin.sourceSets.main.java.srcDirs, diff --git a/client-lib/build.gradle b/client-lib/build.gradle index a922f39ca78..7370b93ae25 100644 --- a/client-lib/build.gradle +++ b/client-lib/build.gradle @@ -1,13 +1,13 @@ -Project identityPayload = parent.subprojects.find { it.name.equals('cloudfoundry-identity-payload') } +Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } -description = 'CloudFoundry Identity Common Jar' +description = 'CloudFoundry Identity Client Library Jar' dependencies { - compile identityPayload + compile identityModels } processResources { //maven replaces project.artifactId in the log4j.properties file //https://www.pivotaltracker.com/story/show/74344574 - filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-common') : line } + filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-clientlib') : line } } diff --git a/common/build.gradle b/common/build.gradle index 32a361ccee9..615febe6fde 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,9 +1,9 @@ -Project identityPayload = parent.subprojects.find { it.name.equals('cloudfoundry-identity-payload') } +Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } description = 'CloudFoundry Identity Common Jar' dependencies { - compile identityPayload + compile identityModels compile group: 'org.passay', name: 'passay', version:'1.0' compile group: 'com.google.guava', name: 'guava', version: '18.0' compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version:parent.bcpkixVersion diff --git a/login/build.gradle b/login/build.gradle index 85ba143616c..93d906938b9 100644 --- a/login/build.gradle +++ b/login/build.gradle @@ -1,6 +1,6 @@ Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } Project identityScim = parent.subprojects.find { it.name.equals('cloudfoundry-identity-scim') } -Project identityPayload = parent.subprojects.find { it.name.equals('cloudfoundry-identity-payload') } +Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } description = 'CloudFoundry Identity Login' @@ -11,7 +11,7 @@ dependencies { compile(identityScim) { exclude(module: 'jna') } - compile(identityPayload) + compile(identityModels) compile group: 'org.springframework', name: 'spring-context-support', version:springVersion provided group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' @@ -39,4 +39,4 @@ project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> classpath = files(test.classpath.collect(rewriteInstrumentedLibs)) } } -} \ No newline at end of file +} diff --git a/payload/build.gradle b/models/build.gradle similarity index 87% rename from payload/build.gradle rename to models/build.gradle index a5f640ffe8d..88c4ba1fc5c 100644 --- a/payload/build.gradle +++ b/models/build.gradle @@ -1,4 +1,4 @@ -description = 'CloudFoundry Identity Payload Data Objects JAR' +description = 'CloudFoundry Identity Models JAR' dependencies { compile group: 'javax.validation', name: 'validation-api', version: parent.validationAPIVersion @@ -21,5 +21,5 @@ apply from: file('build_properties.gradle') processResources { //maven replaces project.artifactId in the log4j.properties file //https://www.pivotaltracker.com/story/show/74344574 - filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-payload') : line } + filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-models') : line } } diff --git a/payload/build_properties.gradle b/models/build_properties.gradle similarity index 100% rename from payload/build_properties.gradle rename to models/build_properties.gradle diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java b/models/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java b/models/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java b/models/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java b/models/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java b/models/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java b/models/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java b/models/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java b/models/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java b/models/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java b/models/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java b/models/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java b/models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java b/models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java b/models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java b/models/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java b/models/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java b/models/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java b/models/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java b/models/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java b/models/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java b/models/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java b/models/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java b/models/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java b/models/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java diff --git a/payload/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java b/models/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java similarity index 100% rename from payload/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java diff --git a/payload/src/main/resources/.gitignore b/models/src/main/resources/.gitignore similarity index 100% rename from payload/src/main/resources/.gitignore rename to models/src/main/resources/.gitignore diff --git a/scim/build.gradle b/scim/build.gradle index d5c2d52a740..c6d68fd86ff 100644 --- a/scim/build.gradle +++ b/scim/build.gradle @@ -1,11 +1,11 @@ Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } -Project identityPayload = parent.subprojects.find { it.name.equals('cloudfoundry-identity-payload') } +Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } description = 'CloudFoundry Identity SCIM' dependencies { compile identityCommon - compile identityPayload + compile identityModels compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version:'2.1.2.RELEASE' provided group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' testCompile identityCommon.configurations.testCompile.dependencies diff --git a/settings.gradle b/settings.gradle index 637cdf64cc9..03b303cdc0e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ rootProject.name = 'cloudfoundry-identity-parent' -include ':cloudfoundry-identity-payload' +include ':cloudfoundry-identity-models' include ':cloudfoundry-identity-client-lib' include ':cloudfoundry-identity-common' include ':cloudfoundry-identity-scim' @@ -9,7 +9,7 @@ include ':cloudfoundry-identity-samples:cloudfoundry-identity-api' include ':cloudfoundry-identity-samples:cloudfoundry-identity-app' include ':cloudfoundry-identity-samples' -project(':cloudfoundry-identity-payload').projectDir = "$rootDir/payload" as File +project(':cloudfoundry-identity-models').projectDir = "$rootDir/models" as File project(':cloudfoundry-identity-client-lib').projectDir = "$rootDir/client-lib" as File project(':cloudfoundry-identity-common').projectDir = "$rootDir/common" as File project(':cloudfoundry-identity-scim').projectDir = "$rootDir/scim" as File @@ -17,4 +17,4 @@ project(':cloudfoundry-identity-login').projectDir = "$rootDir/login" as File project(':cloudfoundry-identity-uaa').projectDir = "$rootDir/uaa" as File project(':cloudfoundry-identity-samples:cloudfoundry-identity-api').projectDir = "$rootDir/samples/api" as File project(':cloudfoundry-identity-samples:cloudfoundry-identity-app').projectDir = "$rootDir/samples/app" as File -project(':cloudfoundry-identity-samples').projectDir = "$rootDir/samples" as File \ No newline at end of file +project(':cloudfoundry-identity-samples').projectDir = "$rootDir/samples" as File diff --git a/uaa/build.gradle b/uaa/build.gradle index e504d46a43c..bad3c8b75c5 100644 --- a/uaa/build.gradle +++ b/uaa/build.gradle @@ -1,4 +1,4 @@ -Project identityPayload = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } +Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } Project identityScim = parent.subprojects.find { it.name.equals('cloudfoundry-identity-scim') } Project identityLogin = parent.subprojects.find { it.name.equals('cloudfoundry-identity-login') } From f50f913820abcc7a5c02f5427494ec248a8b06a4 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 24 Nov 2015 07:55:03 -0700 Subject: [PATCH 014/103] Simplest example of client-sdk API Retrieve client_credentials token within a UAA context https://www.pivotaltracker.com/story/show/107504674 [#107504674] --- client-lib/build.gradle | 20 +++- .../identity/client/UaaContext.java | 34 +++++++ .../identity/client/UaaContextFactory.java | 91 +++++++++++++++++ .../identity/client/UaaContextImpl.java | 63 ++++++++++++ .../identity/client/token/GrantType.java | 23 +++++ .../client/token/TokenAuthentication.java | 31 ++++++ .../identity/client/token/TokenRequest.java | 99 +++++++++++++++++++ ...ClientCredentialsTokenIntegrationTest.java | 54 ++++++++++ .../client/token/TokenRequestTest.java | 32 ++++++ common/build.gradle | 4 +- .../UaaAuthorizationEndpoint.java | 8 +- .../uaa/oauth/token/UaaTokenServices.java | 8 +- .../oauth/token/UaaTokenServicesTests.java | 74 +++++++------- gradle.properties | 2 +- models/build.gradle | 4 + .../uaa/oauth/token/CompositeAccessToken.java | 59 +++++------ shared_versions.gradle | 1 + 17 files changed, 528 insertions(+), 79 deletions(-) create mode 100644 client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContext.java create mode 100644 client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java create mode 100644 client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextImpl.java create mode 100644 client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java create mode 100644 client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenAuthentication.java create mode 100644 client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java create mode 100644 client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientCredentialsTokenIntegrationTest.java create mode 100644 client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java rename common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OpenIdToken.java => models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java (77%) diff --git a/client-lib/build.gradle b/client-lib/build.gradle index 7370b93ae25..445d21263b6 100644 --- a/client-lib/build.gradle +++ b/client-lib/build.gradle @@ -4,10 +4,28 @@ description = 'CloudFoundry Identity Client Library Jar' dependencies { compile identityModels + testCompile group: 'junit', name: 'junit', version:parent.junitVersion + testCompile identityModels.configurations.testCompile.dependencies + testCompile identityModels.sourceSets.test.output } processResources { //maven replaces project.artifactId in the log4j.properties file //https://www.pivotaltracker.com/story/show/74344574 - filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-clientlib') : line } + filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}', 'cloudfoundry-identity-clientlib') : line } } + +test { + exclude 'org/cloudfoundry/identity/client/integration/*.class' +} + +task integrationTest(type: Test) { + dependsOn parent.cargoStartLocal, parent.resetCoverage + + finalizedBy parent.flushCoverageData + + filter { + includeTestsMatching "org.cloudfoundry.identity.client.integration.*" + } +} + diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContext.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContext.java new file mode 100644 index 00000000000..617f5ee90fd --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContext.java @@ -0,0 +1,34 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.client; + +import org.cloudfoundry.identity.client.token.TokenRequest; +import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken; +import org.springframework.web.client.RestTemplate; + +public interface UaaContext { + + boolean hasAccessToken(); + boolean hasIdToken(); + boolean hasRefreshToken(); + + CompositeAccessToken getToken(); + + TokenRequest getTokenRequest(); + + RestTemplate getRestTemplate(); + + +} diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java new file mode 100644 index 00000000000..8064a866467 --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java @@ -0,0 +1,91 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.client; + + +import org.cloudfoundry.identity.client.token.TokenRequest; +import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken; +import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.common.AuthenticationScheme; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; +import org.springframework.web.util.UriComponentsBuilder; + +import java.net.URI; + +public class UaaContextFactory { + + private final URI uaaUri; + + private UaaContextFactory(URI uaaUri) { + this.uaaUri = uaaUri; + } + + private String tokenPath = "/oauth/token"; + private String authorizePath = "/oauth/authorize"; + + public static UaaContextFactory factory(URI uaaURI) { + return new UaaContextFactory(uaaURI); + } + + public UaaContextFactory tokenPath(String path) { + this.tokenPath = path; + return this; + } + + public UaaContextFactory authorizePath(String path) { + this.authorizePath = path; + return this; + } + + public TokenRequest tokenRequest() { + UriComponentsBuilder tokenURI = UriComponentsBuilder.newInstance(); + tokenURI.uri(uaaUri); + tokenURI.path(tokenPath); + + UriComponentsBuilder authorizationURI = UriComponentsBuilder.newInstance(); + authorizationURI.uri(uaaUri); + authorizationURI.path(authorizePath); + + return new TokenRequest(tokenURI.build().toUri(), authorizationURI.build().toUri()); + } + + + + public UaaContext authenticate(TokenRequest request) { + if (request == null) { + throw new NullPointerException(TokenRequest.class.getName() + " cannot be null."); + } + switch (request.getGrantType()) { + case CLIENT_CREDENTIALS: return authenticateClientCredentials(request); + default: throw new UnsupportedGrantTypeException("Not implemented:"+request.getGrantType()); + } + } + + protected UaaContext authenticateClientCredentials(TokenRequest request) { + ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); + details.setClientId(request.getClientId()); + details.setClientSecret(request.getClientSecret()); + details.setAccessTokenUri(request.getTokenEndpoint().toString()); + details.setClientAuthenticationScheme(AuthenticationScheme.header); + OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext()); + OAuth2AccessToken token = template.getAccessToken(); + CompositeAccessToken result = new CompositeAccessToken(token); + return new UaaContextImpl(request, template, result); + } + +} diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextImpl.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextImpl.java new file mode 100644 index 00000000000..87d94ef7ceb --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextImpl.java @@ -0,0 +1,63 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.client; + +import org.cloudfoundry.identity.client.token.TokenRequest; +import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken; +import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +public class UaaContextImpl implements UaaContext { + private CompositeAccessToken token; + private TokenRequest request; + private OAuth2RestTemplate template; + + public UaaContextImpl(TokenRequest request, OAuth2RestTemplate template, CompositeAccessToken token) { + this.request = request; + this.template = template; + this.token = token; + } + + @Override + public boolean hasAccessToken() { + return token!=null; + } + + @Override + public boolean hasIdToken() { + return token!=null && StringUtils.hasText(token.getIdTokenValue()); + } + + @Override + public boolean hasRefreshToken() { + return token!=null && token.getRefreshToken()!=null; + } + + @Override + public TokenRequest getTokenRequest() { + return request; + } + + @Override + public RestTemplate getRestTemplate() { + return template; + } + + @Override + public CompositeAccessToken getToken() { + return token; + } +} diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java new file mode 100644 index 00000000000..75f0ca84fb4 --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java @@ -0,0 +1,23 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.client.token; + +public enum GrantType { + CLIENT_CREDENTIALS, + PASSWORD, + IMPLICIT, + AUTHORIZATION_CODE, + REFRESH +} diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenAuthentication.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenAuthentication.java new file mode 100644 index 00000000000..d7078a79f16 --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenAuthentication.java @@ -0,0 +1,31 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.client.token; + +import org.springframework.security.core.Authentication; + +import java.util.Set; + +public interface TokenAuthentication extends Authentication { + + boolean hasAccessToken(); + + boolean hasIdToken(); + + boolean hasRefreshToken(); + + Set getScopes(); + +} diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java new file mode 100644 index 00000000000..e274a959421 --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java @@ -0,0 +1,99 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.client.token; + +import java.net.URI; + +public class TokenRequest { + + private GrantType grantType; + private String clientId; + private String clientSecret; + private String username; + private String password; + private URI tokenEndpoint; + private URI authorizationEndpoint; + + public TokenRequest(URI tokenEndpoint, URI authorizationEndpoint) { + this.tokenEndpoint = tokenEndpoint; + } + + public boolean isValid() { + return false; + } + + public URI getTokenEndpoint() { + return tokenEndpoint; + } + + public TokenRequest setTokenEndpoint(URI tokenEndpoint) { + this.tokenEndpoint = tokenEndpoint; + return this; + } + + public String getClientId() { + return clientId; + } + + public TokenRequest setClientId(String clientId) { + this.clientId = clientId; + return this; + } + + public String getClientSecret() { + return clientSecret; + } + + public TokenRequest setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + return this; + } + + public GrantType getGrantType() { + return grantType; + } + + public TokenRequest setGrantType(GrantType grantType) { + this.grantType = grantType; + return this; + } + + public String getPassword() { + return password; + } + + public TokenRequest setPassword(String password) { + this.password = password; + return this; + } + + public String getUsername() { + return username; + } + + public TokenRequest setUsername(String username) { + this.username = username; + return this; + } + + public URI getAuthorizationEndpoint() { + return authorizationEndpoint; + } + + public TokenRequest setAuthorizationEndpoint(URI authorizationEndpoint) { + this.authorizationEndpoint = authorizationEndpoint; + return this; + } +} diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientCredentialsTokenIntegrationTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientCredentialsTokenIntegrationTest.java new file mode 100644 index 00000000000..22c697761b6 --- /dev/null +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientCredentialsTokenIntegrationTest.java @@ -0,0 +1,54 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.client.integration; + + +import org.cloudfoundry.identity.client.UaaContext; +import org.cloudfoundry.identity.client.UaaContextFactory; +import org.cloudfoundry.identity.client.token.GrantType; +import org.cloudfoundry.identity.client.token.TokenRequest; +import org.junit.Test; + +import java.net.URI; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class ClientCredentialsTokenIntegrationTest { + + public static String uaaURI = "http://localhost:8080/uaa"; + + @Test + public void test_admin_client_token() throws Exception { + UaaContextFactory factory = + UaaContextFactory.factory(new URI(uaaURI)) + .authorizePath("/oauth/authorize") + .tokenPath("/oauth/token"); + + TokenRequest request = factory.tokenRequest() + .setClientId("admin") + .setClientSecret("adminsecret") + .setGrantType(GrantType.CLIENT_CREDENTIALS); + + UaaContext context = factory.authenticate(request); + assertNotNull(context); + assertTrue(context.hasAccessToken()); + assertFalse(context.hasIdToken()); + assertFalse(context.hasRefreshToken()); + assertTrue(context.getToken().getScope().contains("uaa.admin")); + } + +} diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java new file mode 100644 index 00000000000..64f54aed356 --- /dev/null +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java @@ -0,0 +1,32 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.client.token; + +import org.junit.Before; +import org.junit.Test; + + +public class TokenRequestTest { + + @Before + public void setUp() throws Exception { + + } + + @Test + public void testIsValid() throws Exception { + + } +} \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle index 615febe6fde..4ce2be78f5b 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -44,8 +44,6 @@ dependencies { exclude(module: 'httpclient') exclude(module: 'wink-client-apache-httpclient') } - compile group: 'org.slf4j', name: 'slf4j-log4j12', version:'1.7.7' - compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.7' compile group: 'org.hibernate', name: 'hibernate-validator', version:'4.3.1.Final' compile group: 'org.aspectj', name: 'aspectjrt', version:'1.6.9' compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version:parent.jacksonVersion @@ -63,7 +61,7 @@ dependencies { provided group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' testCompile group: 'org.springframework', name: 'spring-test', version:parent.springVersion - testCompile group: 'junit', name: 'junit', version:'4.11' + testCompile group: 'junit', name: 'junit', version:parent.junitVersion testCompile group: 'org.hamcrest', name: 'hamcrest-all', version:'1.3' testCompile group: 'com.jayway.jsonpath', name: 'json-path', version:'0.9.1' testCompile group: 'com.jayway.jsonpath', name: 'json-path-assert', version:'0.9.1' diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java index 1686352746d..d8d92f80155 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java @@ -13,7 +13,7 @@ package org.cloudfoundry.identity.uaa.authorization; -import org.cloudfoundry.identity.uaa.oauth.token.OpenIdToken; +import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.InsufficientAuthenticationException; @@ -361,9 +361,9 @@ private String appendAccessToken(AuthorizationRequest authorizationRequest, } - if (accessToken instanceof OpenIdToken && - authorizationRequest.getResponseTypes().contains(OpenIdToken.ID_TOKEN)) { - url.append("&").append(OpenIdToken.ID_TOKEN).append("=").append(encode(((OpenIdToken) accessToken).getIdTokenValue())); + if (accessToken instanceof CompositeAccessToken && + authorizationRequest.getResponseTypes().contains(CompositeAccessToken.ID_TOKEN)) { + url.append("&").append(CompositeAccessToken.ID_TOKEN).append("=").append(encode(((CompositeAccessToken) accessToken).getIdTokenValue())); } if (authorizationRequest.getResponseTypes().contains("code")) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java index 03c05ec3855..d71586453a6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServices.java @@ -350,7 +350,7 @@ private OAuth2AccessToken createAccessToken(String userId, Set externalGroupsForIdToken, Map> userAttributesForIdToken) throws AuthenticationException { String tokenId = UUID.randomUUID().toString(); - OpenIdToken accessToken = new OpenIdToken(tokenId); + CompositeAccessToken accessToken = new CompositeAccessToken(tokenId); if (validitySeconds > 0) { accessToken.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); } @@ -401,7 +401,7 @@ private OAuth2AccessToken createAccessToken(String userId, return accessToken; } - private void populateIdToken(OpenIdToken token, + private void populateIdToken(CompositeAccessToken token, Map accessTokenValues, Set scopes, Set responseTypes, @@ -410,7 +410,7 @@ private void populateIdToken(OpenIdToken token, Set externalGroupsForIdToken, UaaUser user, Map> userAttributesForIdToken) { - if (forceIdTokenCreation || (scopes.contains("openid") && responseTypes.contains(OpenIdToken.ID_TOKEN))) { + if (forceIdTokenCreation || (scopes.contains("openid") && responseTypes.contains(CompositeAccessToken.ID_TOKEN))) { try { Map clone = new HashMap<>(accessTokenValues); clone.remove(AUTHORITIES); @@ -900,7 +900,7 @@ public OAuth2AccessToken readAccessToken(String accessToken) { Map claims = getClaimsForToken(accessToken); // Expiry is verified by check_token - OpenIdToken token = new OpenIdToken(accessToken); + CompositeAccessToken token = new CompositeAccessToken(accessToken); token.setTokenType(OAuth2AccessToken.BEARER_TYPE); Integer exp = (Integer) claims.get(EXP); if (null != exp) { diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java index 74e57c205a8..4a7f4bfed70 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java @@ -230,7 +230,7 @@ public void setUp() throws Exception { tokenServices.setApprovalStore(approvalStore); tokenServices.setApplicationEventPublisher(publisher); tokenServices.afterPropertiesSet(); - + OAuth2AccessTokenMatchers.signer = signerProvider; OAuth2RefreshTokenMatchers.signer = signerProvider; } @@ -278,7 +278,7 @@ public void testCreateAccessTokenForAClient() { assertThat(accessToken, issuerUri(is(ISSUER_URI))); assertThat(accessToken, zoneId(is(IdentityZoneHolder.get().getId()))); assertThat(accessToken.getRefreshToken(), is(nullValue())); - + this.assertCommonEventProperties(accessToken, CLIENT_ID, expectedJson); } @@ -306,7 +306,7 @@ public void testCreateAccessTokenForAClientInAnotherIdentityZone() { this.assertCommonClientAccessTokenProperties(accessToken); assertThat(accessToken, issuerUri(is("http://"+subdomain+".localhost:8080/uaa/oauth/token"))); assertThat(accessToken.getRefreshToken(), is(nullValue())); - + Assert.assertEquals(1, publisher.getEventCount()); this.assertCommonEventProperties(accessToken, CLIENT_ID, expectedJson); @@ -336,12 +336,12 @@ public void testCreateAccessTokenAuthcodeGrant() { assertThat(accessToken, issuerUri(is(ISSUER_URI))); assertThat(accessToken, scope(is(requestedAuthScopes))); assertThat(accessToken, validFor(is(60 * 60 * 12))); - + OAuth2RefreshToken refreshToken = accessToken.getRefreshToken(); this.assertCommonUserRefreshTokenProperties(refreshToken); assertThat(refreshToken, OAuth2RefreshTokenMatchers.issuerUri(is(ISSUER_URI))); assertThat(refreshToken, OAuth2RefreshTokenMatchers.validFor(is(60 * 60 * 24 * 30))); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(requestedAuthScopes)); } @@ -366,7 +366,7 @@ public void testCreateAccessTokenPasswordGrant() { this.assertCommonUserRefreshTokenProperties(refreshToken); assertThat(refreshToken, OAuth2RefreshTokenMatchers.issuerUri(is(ISSUER_URI))); assertThat(refreshToken, OAuth2RefreshTokenMatchers.validFor(is(60 * 60 * 24 * 30))); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(requestedAuthScopes)); } @@ -378,7 +378,7 @@ public void testCreateRevocableAccessTokenPasswordGrant() { azParameters.put(GRANT_TYPE, PASSWORD); authorizationRequest.setRequestParameters(azParameters); Authentication userAuthentication = defaultUserAuthentication; - + OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken accessToken = tokenServices.createAccessToken(authentication); @@ -391,7 +391,7 @@ public void testCreateRevocableAccessTokenPasswordGrant() { this.assertCommonUserRefreshTokenProperties(refreshToken); assertThat(refreshToken, OAuth2RefreshTokenMatchers.issuerUri(is(ISSUER_URI))); assertThat(refreshToken, OAuth2RefreshTokenMatchers.validFor(is(60 * 60 * 24 * 30))); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(requestedAuthScopes)); } @@ -494,7 +494,7 @@ public void testCreateAccessTokenRefreshGrantAllScopesAutoApproved() throws Inte OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken accessToken = tokenServices.createAccessToken(authentication); - + this.assertCommonUserAccessTokenProperties(accessToken); assertThat(accessToken, issuerUri(is(ISSUER_URI))); assertThat(accessToken, scope(is(requestedAuthScopes))); @@ -504,7 +504,7 @@ public void testCreateAccessTokenRefreshGrantAllScopesAutoApproved() throws Inte this.assertCommonUserRefreshTokenProperties(refreshToken); assertThat(refreshToken, OAuth2RefreshTokenMatchers.issuerUri(is(ISSUER_URI))); assertThat(refreshToken, OAuth2RefreshTokenMatchers.validFor(is(60 * 60 * 24 * 30))); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(requestedAuthScopes)); AuthorizationRequest refreshAuthorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); @@ -516,7 +516,7 @@ public void testCreateAccessTokenRefreshGrantAllScopesAutoApproved() throws Inte OAuth2AccessToken refreshedAccessToken = tokenServices.refreshAccessToken(accessToken.getRefreshToken().getValue(), requestFactory.createTokenRequest(refreshAuthorizationRequest,"refresh_token")); assertEquals(refreshedAccessToken.getRefreshToken().getValue(), accessToken.getRefreshToken().getValue()); - + this.assertCommonUserAccessTokenProperties(refreshedAccessToken); assertThat(refreshedAccessToken, issuerUri(is(ISSUER_URI))); assertThat(refreshedAccessToken, scope(is(requestedAuthScopes))); @@ -542,7 +542,7 @@ public void testCreateAccessTokenRefreshGrantSomeScopesAutoApprovedDowngradedReq OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken accessToken = tokenServices.createAccessToken(authentication); - + this.assertCommonUserAccessTokenProperties(accessToken); assertThat(accessToken, issuerUri(is(ISSUER_URI))); assertThat(accessToken, scope(is(requestedAuthScopes))); @@ -552,7 +552,7 @@ public void testCreateAccessTokenRefreshGrantSomeScopesAutoApprovedDowngradedReq this.assertCommonUserRefreshTokenProperties(refreshToken); assertThat(refreshToken, OAuth2RefreshTokenMatchers.issuerUri(is(ISSUER_URI))); assertThat(refreshToken, OAuth2RefreshTokenMatchers.validFor(is(60 * 60 * 24 * 30))); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(requestedAuthScopes)); AuthorizationRequest refreshAuthorizationRequest = new AuthorizationRequest(CLIENT_ID,readScope); @@ -564,7 +564,7 @@ public void testCreateAccessTokenRefreshGrantSomeScopesAutoApprovedDowngradedReq OAuth2AccessToken refreshedAccessToken = tokenServices.refreshAccessToken(accessToken.getRefreshToken().getValue(), requestFactory.createTokenRequest(refreshAuthorizationRequest,"refresh_token")); assertEquals(refreshedAccessToken.getRefreshToken().getValue(), accessToken.getRefreshToken().getValue()); - + this.assertCommonUserAccessTokenProperties(refreshedAccessToken); assertThat(refreshedAccessToken, issuerUri(is(ISSUER_URI))); assertThat(refreshedAccessToken, validFor(is(60 * 60 * 12))); @@ -600,7 +600,7 @@ public void testCreateAccessTokenRefreshGrantSomeScopesAutoApproved() throws Int OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken accessToken = tokenServices.createAccessToken(authentication); - + this.assertCommonUserAccessTokenProperties(accessToken); assertThat(accessToken, issuerUri(is(ISSUER_URI))); assertThat(accessToken, scope(is(requestedAuthScopes))); @@ -610,7 +610,7 @@ public void testCreateAccessTokenRefreshGrantSomeScopesAutoApproved() throws Int this.assertCommonUserRefreshTokenProperties(refreshToken); assertThat(refreshToken, OAuth2RefreshTokenMatchers.issuerUri(is(ISSUER_URI))); assertThat(refreshToken, OAuth2RefreshTokenMatchers.validFor(is(60 * 60 * 24 * 30))); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(requestedAuthScopes)); AuthorizationRequest refreshAuthorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); @@ -622,7 +622,7 @@ public void testCreateAccessTokenRefreshGrantSomeScopesAutoApproved() throws Int OAuth2AccessToken refreshedAccessToken = tokenServices.refreshAccessToken(accessToken.getRefreshToken().getValue(), requestFactory.createTokenRequest(refreshAuthorizationRequest,"refresh_token")); assertEquals(refreshedAccessToken.getRefreshToken().getValue(), accessToken.getRefreshToken().getValue()); - + this.assertCommonUserAccessTokenProperties(refreshedAccessToken); assertThat(refreshedAccessToken, issuerUri(is(ISSUER_URI))); assertThat(refreshedAccessToken, validFor(is(60 * 60 * 12))); @@ -658,7 +658,7 @@ public void testCreateAccessTokenRefreshGrantNoScopesAutoApprovedIncompleteAppro OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken accessToken = tokenServices.createAccessToken(authentication); - + this.assertCommonUserAccessTokenProperties(accessToken); assertThat(accessToken, issuerUri(is(ISSUER_URI))); assertThat(accessToken, scope(is(requestedAuthScopes))); @@ -668,7 +668,7 @@ public void testCreateAccessTokenRefreshGrantNoScopesAutoApprovedIncompleteAppro this.assertCommonUserRefreshTokenProperties(refreshToken); assertThat(refreshToken, OAuth2RefreshTokenMatchers.issuerUri(is(ISSUER_URI))); assertThat(refreshToken, OAuth2RefreshTokenMatchers.validFor(is(60 * 60 * 24 * 30))); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(requestedAuthScopes)); AuthorizationRequest refreshAuthorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); @@ -716,7 +716,7 @@ public void testCreateAccessTokenRefreshGrantAllScopesAutoApprovedButApprovalDen OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken accessToken = tokenServices.createAccessToken(authentication); - + this.assertCommonUserAccessTokenProperties(accessToken); assertThat(accessToken, issuerUri(is(ISSUER_URI))); assertThat(accessToken, scope(is(requestedAuthScopes))); @@ -726,7 +726,7 @@ public void testCreateAccessTokenRefreshGrantAllScopesAutoApprovedButApprovalDen this.assertCommonUserRefreshTokenProperties(refreshToken); assertThat(refreshToken, OAuth2RefreshTokenMatchers.issuerUri(is(ISSUER_URI))); assertThat(refreshToken, OAuth2RefreshTokenMatchers.validFor(is(60 * 60 * 24 * 30))); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(requestedAuthScopes)); AuthorizationRequest refreshAuthorizationRequest = new AuthorizationRequest(CLIENT_ID,requestedAuthScopes); @@ -755,7 +755,7 @@ public void testCreateAccessTokenImplicitGrant() { assertThat(accessToken, issuerUri(is(ISSUER_URI))); assertThat(accessToken, validFor(is(60 * 60 * 12))); assertThat(accessToken.getRefreshToken(), is(nullValue())); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(requestedAuthScopes)); } @@ -790,7 +790,7 @@ public void create_id_token_without_profile_scope() throws Exception { private Jwt getIdToken(List scopes) { AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID, scopes); - authorizationRequest.setResponseTypes(new HashSet<>(Arrays.asList(OpenIdToken.ID_TOKEN))); + authorizationRequest.setResponseTypes(new HashSet<>(Arrays.asList(CompositeAccessToken.ID_TOKEN))); UaaPrincipal uaaPrincipal = new UaaPrincipal(defaultUser.getId(), defaultUser.getUsername(), defaultUser.getEmail(), defaultUser.getOrigin(), defaultUser.getExternalId(), defaultUser.getZoneId()); UaaAuthentication userAuthentication = new UaaAuthentication(uaaPrincipal, null, defaultUserAuthorities, new HashSet<>(Arrays.asList("group1", "group2")),Collections.EMPTY_MAP, null, true, System.currentTimeMillis(), System.currentTimeMillis() + 1000l * 60l); @@ -802,7 +802,7 @@ private Jwt getIdToken(List scopes) { Jwt tokenJwt = JwtHelper.decodeAndVerify(accessToken.getValue(), signerProvider.getVerifier()); assertNotNull(tokenJwt); - return JwtHelper.decodeAndVerify(((OpenIdToken) accessToken).getIdTokenValue(), signerProvider.getVerifier()); + return JwtHelper.decodeAndVerify(((CompositeAccessToken) accessToken).getIdTokenValue(), signerProvider.getVerifier()); } @Test @@ -823,7 +823,7 @@ public void testCreateAccessWithNonExistingScopes() { assertThat(accessToken, scope(is(scopesThatDontExist))); assertThat(accessToken, validFor(is(60 * 60 * 12))); assertThat(accessToken.getRefreshToken(), is(nullValue())); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(scopesThatDontExist)); } @@ -859,7 +859,7 @@ public void createAccessToken_forUser_inanotherzone() { this.assertCommonUserRefreshTokenProperties(refreshToken); assertThat(refreshToken, OAuth2RefreshTokenMatchers.issuerUri(is("http://test-zone-subdomain.localhost:8080/uaa/oauth/token"))); assertThat(refreshToken, OAuth2RefreshTokenMatchers.validFor(is(9600))); - + this.assertCommonEventProperties(accessToken, userId, buildJsonString(requestedAuthScopes)); } @@ -1397,21 +1397,21 @@ public void testCreateAccessTokenAuthcodeGrantAdditionalAuthorizationAttributes( OAuth2Authentication authentication = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication); OAuth2AccessToken token = tokenServices.createAccessToken(authentication); - + OAuth2AccessTokenMatchers.signer = signerProvider; this.assertCommonUserAccessTokenProperties(token); assertThat(token, issuerUri(is(ISSUER_URI))); assertThat(token, scope(is(requestedAuthScopes))); assertThat(token, validFor(is(60 * 60 * 12))); - + OAuth2RefreshTokenMatchers.signer = signerProvider; OAuth2RefreshToken refreshToken = token.getRefreshToken(); this.assertCommonUserRefreshTokenProperties(refreshToken); assertThat(refreshToken, OAuth2RefreshTokenMatchers.issuerUri(is(ISSUER_URI))); assertThat(refreshToken, OAuth2RefreshTokenMatchers.validFor(is(60 * 60 * 24 * 30))); - + this.assertCommonEventProperties(token, userId, buildJsonString(requestedAuthScopes)); - + Map azMap = new LinkedHashMap<>(); azMap.put("external_group", "domain\\group1"); azMap.put("external_id", "abcd1234"); @@ -1421,7 +1421,7 @@ public void testCreateAccessTokenAuthcodeGrantAdditionalAuthorizationAttributes( private BaseClientDetails cloneClient(BaseClientDetails client) { return new BaseClientDetails(client); } - + @SuppressWarnings("unchecked") private void assertCommonClientAccessTokenProperties(OAuth2AccessToken accessToken) { assertThat(accessToken, allOf(clientId(is(CLIENT_ID)), @@ -1436,13 +1436,13 @@ private void assertCommonClientAccessTokenProperties(OAuth2AccessToken accessTok expiry(is(greaterThan(0))), validFor(is(60 * 60 * 1)))); } - + @SuppressWarnings({ "unused", "unchecked" }) private void assertCommonUserAccessTokenProperties(OAuth2AccessToken accessToken) { - assertThat(accessToken, allOf(username(is(username)), + assertThat(accessToken, allOf(username(is(username)), clientId(is(CLIENT_ID)), subject(is(userId)), - audience(is(resourceIds)), + audience(is(resourceIds)), origin(is(OriginKeys.UAA)), revocationSignature(is(not(nullValue()))), cid(is(CLIENT_ID)), @@ -1453,7 +1453,7 @@ private void assertCommonUserAccessTokenProperties(OAuth2AccessToken accessToken expiry(is(greaterThan(0))) )); } - + @SuppressWarnings("unchecked") private void assertCommonUserRefreshTokenProperties(OAuth2RefreshToken refreshToken) { assertThat(refreshToken, allOf(/*issuer(is(issuerUri)),*/ @@ -1469,10 +1469,10 @@ private void assertCommonUserRefreshTokenProperties(OAuth2RefreshToken refreshTo ) ); } - + private void assertCommonEventProperties(OAuth2AccessToken accessToken, String expectedPrincipalId, String expectedData) { Assert.assertEquals(1, publisher.getEventCount()); - + TokenIssuedEvent event = publisher.getLatestEvent(); Assert.assertEquals(accessToken, event.getSource()); Assert.assertEquals(mockAuthentication, event.getAuthentication()); diff --git a/gradle.properties b/gradle.properties index b40eaeaafdb..cd92d6b0851 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.7.2-SNAPSHOT +version=3.0.0-SNAPSHOT diff --git a/models/build.gradle b/models/build.gradle index 88c4ba1fc5c..a0429c43526 100644 --- a/models/build.gradle +++ b/models/build.gradle @@ -14,6 +14,10 @@ dependencies { exclude(module: 'spring-security-config') } + compile group: 'org.slf4j', name: 'slf4j-log4j12', version:'1.7.7' + compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.7' + + } apply from: file('build_properties.gradle') diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OpenIdToken.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java similarity index 77% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OpenIdToken.java rename to models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java index a08691a6b7c..2be5471df21 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OpenIdToken.java +++ b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java @@ -1,23 +1,18 @@ -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ package org.cloudfoundry.identity.uaa.oauth.token; -import java.io.IOException; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; @@ -35,9 +30,15 @@ import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.util.Assert; -@JsonSerialize(using = OpenIdToken.OpenIdTokenJackson1Serializer.class) -@JsonDeserialize(using = OpenIdToken.OpenIdTokenJackson1Deserializer.class) -public class OpenIdToken extends DefaultOAuth2AccessToken { +import java.io.IOException; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +@JsonSerialize(using = CompositeAccessToken.CompositeAccessTokenSerializer.class) +@JsonDeserialize(using = CompositeAccessToken.CompositeAccessTokenDeserializer.class) +public class CompositeAccessToken extends DefaultOAuth2AccessToken { public static String ID_TOKEN = "id_token"; @@ -51,17 +52,17 @@ public void setIdTokenValue(String idTokenValue) { private String idTokenValue; - public OpenIdToken(String accessTokenValue) { + public CompositeAccessToken(String accessTokenValue) { super(accessTokenValue); } - public OpenIdToken(OAuth2AccessToken accessToken) { + public CompositeAccessToken(OAuth2AccessToken accessToken) { super(accessToken); } - public static final class OpenIdTokenJackson1Serializer extends StdSerializer { + public static final class CompositeAccessTokenSerializer extends StdSerializer { - public OpenIdTokenJackson1Serializer() { + public CompositeAccessTokenSerializer() { super(OAuth2AccessToken.class); } @@ -71,8 +72,8 @@ public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerPro jgen.writeStartObject(); jgen.writeStringField(OAuth2AccessToken.ACCESS_TOKEN, token.getValue()); jgen.writeStringField(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType()); - if (token instanceof OpenIdToken && ((OpenIdToken)token).getIdTokenValue()!=null) { - jgen.writeStringField(ID_TOKEN, ((OpenIdToken) token).getIdTokenValue()); + if (token instanceof CompositeAccessToken && ((CompositeAccessToken)token).getIdTokenValue()!=null) { + jgen.writeStringField(ID_TOKEN, ((CompositeAccessToken) token).getIdTokenValue()); } OAuth2RefreshToken refreshToken = token.getRefreshToken(); if (refreshToken != null) { @@ -101,9 +102,9 @@ public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerPro } } - public final class OpenIdTokenJackson1Deserializer extends StdDeserializer { + public final class CompositeAccessTokenDeserializer extends StdDeserializer { - public OpenIdTokenJackson1Deserializer() { + public CompositeAccessTokenDeserializer() { super(OAuth2AccessToken.class); } @@ -146,7 +147,7 @@ public OAuth2AccessToken deserialize(JsonParser jp, DeserializationContext ctxt) // TODO What should occur if a required parameter (tokenValue or tokenType) is missing? - OpenIdToken accessToken = new OpenIdToken(tokenValue); + CompositeAccessToken accessToken = new CompositeAccessToken(tokenValue); accessToken.setIdTokenValue(idTokenValue); accessToken.setTokenType(tokenType); if (expiresIn != null) { diff --git a/shared_versions.gradle b/shared_versions.gradle index c917005692d..f092f6e064c 100644 --- a/shared_versions.gradle +++ b/shared_versions.gradle @@ -12,4 +12,5 @@ ext { jacksonVersion = '2.5.3' flywayVersion = '3.2.1' validationAPIVersion = '1.0.0.GA' + junitVersion = '4.11' } From 65e9c76a9ae0bcf7db3b02fcfb9d68b1fcef5ee5 Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Tue, 24 Nov 2015 17:04:24 -0800 Subject: [PATCH 015/103] Replace Cobertura with JaCoCo [finishes #107261988] https://www.pivotaltracker.com/story/show/107261988 Signed-off-by: Jeremy Coffield --- .travis.yml | 2 +- build.gradle | 130 +++++++----------- client-lib/build.gradle | 4 +- .../identity/uaa/coverage/CoverageConfig.java | 32 ----- .../uaa/coverage/CoverageController.java | 36 ----- login/build.gradle | 10 -- samples/api/build.gradle | 4 +- samples/app/build.gradle | 4 +- scim/build.gradle | 12 +- uaa/build.gradle | 27 +--- .../main/webapp/WEB-INF/spring-servlet.xml | 1 - 11 files changed, 52 insertions(+), 210 deletions(-) delete mode 100644 common/src/main/java/org/cloudfoundry/identity/uaa/coverage/CoverageConfig.java delete mode 100644 common/src/main/java/org/cloudfoundry/identity/uaa/coverage/CoverageController.java diff --git a/.travis.yml b/.travis.yml index 34d027c5a15..7b595a5cb8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ install: - if [ "$TESTENV" = "keystone,default" ]; then ./scripts/keystone/configure-manifest.sh; fi script: -- ./gradlew -Dspring.profiles.active=$TESTENV cobertura +- ./gradlew -Dspring.profiles.active=$TESTENV jacocoRootReport after_success: - ./gradlew coveralls - for i in $(find $HOME/build/cloudfoundry/uaa/ -name reports -type d); do rm -rf $i; done diff --git a/build.gradle b/build.gradle index 54cd3594840..871393e7881 100644 --- a/build.gradle +++ b/build.gradle @@ -12,8 +12,7 @@ buildscript { dependencies { classpath group: 'org.gradle.api.plugins', name: 'gradle-cargo-plugin', version: '1.5' classpath group: 'org.jfrog.buildinfo', name: 'build-info-extractor-gradle', version: '2.2.4' - classpath group: 'net.saliman', name: 'gradle-cobertura-plugin', version: '2.2.8' - classpath group: 'org.kt3k.gradle.plugin', name: 'coveralls-gradle-plugin', version: '0.4.1' + classpath group: 'org.kt3k.gradle.plugin', name: 'coveralls-gradle-plugin', version: '2.4.0' classpath group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version:'1.1.8' classpath group: 'postgresql', name: 'postgresql', version:'9.1-901.jdbc3' classpath group: 'org.flywaydb', name: 'flyway-gradle-plugin', version: flywayVersion @@ -44,7 +43,7 @@ allprojects { group = 'org.cloudfoundry.identity' version = uaaVersion - apply plugin: 'cobertura' + apply plugin: 'jacoco' apply plugin: 'propdeps' apply plugin: 'propdeps-maven' apply plugin: 'propdeps-idea' @@ -55,21 +54,6 @@ allprojects { repositories { mavenCentral() } - - ext { - runningWithCoverage = { - allprojects.collect { it.tasks.findByName('instrument').enabled }.find() - } - - rewriteInstrumentedLibs = { - Boolean instrumentable = it.name.startsWith("cloudfoundry-identity-common-") || it.name.startsWith("cloudfoundry-identity-scim-") || it.name.startsWith("cloudfoundry-identity-login-") - if (instrumentable) { - file(it.absolutePath.replaceFirst("libs", "instrumented_libs")) - } else { - it - } - } - } } subprojects { @@ -166,35 +150,17 @@ subprojects { // } sourceSets { - instrumented - } - - task copyInstrumentedClasses(type: Copy, dependsOn: instrument) { - from "${buildDir}/instrumented_classes" - into sourceSets.instrumented.output.classesDir - onlyIf { runningWithCoverage() } } - task makeInstrumentedSourceSet(type: Copy, dependsOn: copyInstrumentedClasses) { - from sourceSets.main.output.resourcesDir - into sourceSets.instrumented.output.resourcesDir - onlyIf { runningWithCoverage() } - } - - task instrumentedJar(type: Jar, dependsOn: makeInstrumentedSourceSet) { - from sourceSets.instrumented.output - destinationDir = file("$buildDir/instrumented_libs") - onlyIf { runningWithCoverage() } - } -} - -configurations { - coberturaJar -} - -dependencies { - coberturaJar("net.sourceforge.cobertura:cobertura:2.0.3") { - exclude(group: "org.mortbay.jetty") + jacocoTestReport { + additionalSourceDirs = files(sourceSets.main.allSource.srcDirs) + sourceDirectories = files(sourceSets.main.allSource.srcDirs) + classDirectories = files(sourceSets.main.output) + reports { + html.enabled = true + xml.enabled = true + csv.enabled = false + } } } @@ -224,11 +190,6 @@ task prepareDatabase { dependsOn { databaseType().equals('hsqldb') ? null : flywayClean } } -task resetCoverage(type: Delete) { - delete integrationCoverageFile - onlyIf { runningWithCoverage() } -} - apply plugin: 'cargo' task cleanCargoConfDir { @@ -238,12 +199,6 @@ task cleanCargoConfDir { cargoStartLocal.dependsOn assemble, prepareDatabase cargoRunLocal.dependsOn cleanCargoConfDir, assemble -task flushCoverageData(type: Exec) { - commandLine "curl", "-s", "-v", "-X", "POST", "http://localhost:8080/uaa/healthz/coverage/flush" - finalizedBy cargoStopLocal - onlyIf { runningWithCoverage() } -} - task run(dependsOn: cargoRunLocal) cargo { @@ -262,7 +217,6 @@ cargo { local { systemProperties { - property 'net.sourceforge.cobertura.datafile', integrationCoverageFile property 'spring.profiles.active', System.getProperty('spring.profiles.active', 'default') } @@ -277,21 +231,13 @@ cargo { project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> cargo { deployable { - if (runningWithCoverage()) { - file = file('uaa/build/instrumented_libs/cloudfoundry-identity-uaa-' + version + '.war') - } else { - file = file('uaa/build/libs/cloudfoundry-identity-uaa-' + version + '.war') - } + file = file('uaa/build/libs/cloudfoundry-identity-uaa-' + version + '.war') context = 'uaa' } local { // jvmArgs = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" - if (runningWithCoverage()) { - extraClasspath = files(configurations.coberturaJar.files) - } - systemProperties { //property 'uaa.allowUnverifiedUsers', 'false' property 'smtp.host', 'localhost' @@ -306,7 +252,7 @@ project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> } } -apply plugin: 'coveralls' +apply plugin: 'com.github.kt3k.coveralls' Project identityModels = subprojects.find { it.name.equals('cloudfoundry-identity-models') } Project identityCommon = subprojects.find { it.name.equals('cloudfoundry-identity-common') } @@ -314,23 +260,39 @@ Project identityScim = subprojects.find { it.name.equals('cloudfoundry-identity- Project identityLogin = subprojects.find { it.name.equals('cloudfoundry-identity-login') } Project identityUaa = subprojects.find { it.name.equals('cloudfoundry-identity-uaa') } -cobertura { - coverageFormats = ['xml', 'html'] - coverageSourceDirs = [ - identityModels.sourceSets.main.java.srcDirs, - identityCommon.sourceSets.main.java.srcDirs, - identityScim.sourceSets.main.java.srcDirs, - identityLogin.sourceSets.main.java.srcDirs, - identityUaa.sourceSets.main.java.srcDirs - ] - coverageMergeDatafiles = [ - new File("common/build/cobertura/cobertura.ser"), - new File("scim/build/cobertura/cobertura.ser"), - new File("login/build/cobertura/cobertura.ser"), - new File("uaa/build/cobertura/cobertura.ser"), - integrationCoverageFile - ] - coverageExcludes = ['.*org.cloudfoundry.identity.uaa.coverage.CoverageController'] +def publishedProjects = subprojects + +task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) { + dependsOn = subprojects.test + additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs) + sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs) + classDirectories = files(subprojects.sourceSets.main.output) + executionData = files(subprojects.jacocoTestReport.executionData) + reports { + html.enabled = true + xml.enabled = true + csv.enabled = false + } + onlyIf = { + true + } + doFirst { + executionData = files(executionData.findAll { + it.exists() + }) + } +} + +coveralls { + sourceDirs = publishedProjects.sourceSets.main.allSource.srcDirs.flatten() + jacocoReportPath = "${buildDir}/reports/jacoco/jacocoRootReport/jacocoRootReport.xml" +} + +tasks.coveralls { + group = 'Coverage reports' + description = 'Uploads the aggregated coverage report to Coveralls' + + dependsOn jacocoRootReport } assemble.dependsOn subprojects.assemble diff --git a/client-lib/build.gradle b/client-lib/build.gradle index 445d21263b6..9b8396b51d1 100644 --- a/client-lib/build.gradle +++ b/client-lib/build.gradle @@ -20,9 +20,7 @@ test { } task integrationTest(type: Test) { - dependsOn parent.cargoStartLocal, parent.resetCoverage - - finalizedBy parent.flushCoverageData + dependsOn parent.cargoStartLocal filter { includeTestsMatching "org.cloudfoundry.identity.client.integration.*" diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/coverage/CoverageConfig.java b/common/src/main/java/org/cloudfoundry/identity/uaa/coverage/CoverageConfig.java deleted file mode 100644 index 0511b72255c..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/coverage/CoverageConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.cloudfoundry.identity.uaa.coverage; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Condition; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.type.AnnotatedTypeMetadata; - -@Configuration -public class CoverageConfig { - - public static final String COBERTURA_PROJECT_DATA_CLASSNAME = "net.sourceforge.cobertura.coveragedata.ProjectData"; - - @Bean - @Conditional(CoverageConfig.CoberturaCondition.class) - public CoverageController coverageController() { - return new CoverageController(); - } - - public static class CoberturaCondition implements Condition{ - @Override - public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - try { - Class.forName(COBERTURA_PROJECT_DATA_CLASSNAME); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - } -} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/coverage/CoverageController.java b/common/src/main/java/org/cloudfoundry/identity/uaa/coverage/CoverageController.java deleted file mode 100644 index fdf2f1f6d90..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/coverage/CoverageController.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * Cloud Foundry - * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - *******************************************************************************/ -package org.cloudfoundry.identity.uaa.coverage; - -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - -import java.lang.reflect.InvocationTargetException; - -@Controller -@RequestMapping("/healthz/coverage") -public class CoverageController { - - @RequestMapping(value = "flush", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity saveGlobalProjectData() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { - String methodName = "saveGlobalProjectData"; - Class saveClass = Class.forName(CoverageConfig.COBERTURA_PROJECT_DATA_CLASSNAME); - java.lang.reflect.Method saveMethod = saveClass.getDeclaredMethod(methodName, new Class[0]); - saveMethod.invoke(null, new Object[0]); - return new ResponseEntity<>(HttpStatus.OK); - } -} diff --git a/login/build.gradle b/login/build.gradle index 93d906938b9..d80298a53b9 100644 --- a/login/build.gradle +++ b/login/build.gradle @@ -27,16 +27,6 @@ dependencies { } -test.dependsOn identityCommon.instrumentedJar, identityScim.instrumentedJar - processResources { filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-login') : line } } - -project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> - if (runningWithCoverage()) { - test { - classpath = files(test.classpath.collect(rewriteInstrumentedLibs)) - } - } -} diff --git a/samples/api/build.gradle b/samples/api/build.gradle index 78617b25f46..7927b6aa797 100644 --- a/samples/api/build.gradle +++ b/samples/api/build.gradle @@ -34,9 +34,7 @@ test { } task integrationTest(type: Test) { - dependsOn parent.parent.cargoStartLocal, parent.parent.resetCoverage - - finalizedBy parent.parent.flushCoverageData + dependsOn parent.parent.cargoStartLocal filter { includeTestsMatching "org.cloudfoundry.identity.api.web.*IntegrationTests" diff --git a/samples/app/build.gradle b/samples/app/build.gradle index 9d3bdd14928..9afff76c496 100644 --- a/samples/app/build.gradle +++ b/samples/app/build.gradle @@ -30,9 +30,7 @@ test { } task integrationTest(type: Test) { - dependsOn parent.parent.cargoStartLocal, parent.parent.resetCoverage - - finalizedBy parent.parent.flushCoverageData + dependsOn parent.parent.cargoStartLocal filter { includeTestsMatching "org.cloudfoundry.identity.app.integration.*" diff --git a/scim/build.gradle b/scim/build.gradle index c6d68fd86ff..3f5a3c5d8f6 100644 --- a/scim/build.gradle +++ b/scim/build.gradle @@ -12,19 +12,9 @@ dependencies { testCompile identityCommon.sourceSets.test.output } -test.dependsOn identityCommon.instrumentedJar - processResources { //maven replaces project.artifactId in the log4j.properties file //https://www.pivotaltracker.com/story/show/74344574 from(new File('../common/src/main/resources/log4j.properties')) filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-scim') : line } -} - -project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> - if (runningWithCoverage()) { - test { - classpath = files(test.classpath.collect(rewriteInstrumentedLibs)) - } - } -} +} \ No newline at end of file diff --git a/uaa/build.gradle b/uaa/build.gradle index bad3c8b75c5..4d4baccbf03 100644 --- a/uaa/build.gradle +++ b/uaa/build.gradle @@ -72,41 +72,16 @@ dependencies { } test { - dependsOn identityCommon.instrumentedJar, identityScim.instrumentedJar, identityLogin.instrumentedJar exclude 'org/cloudfoundry/identity/uaa/integration/*.class' exclude '**/*IT.class' systemProperty "mock.suite.test", "true" } task integrationTest(type: Test) { - dependsOn parent.cargoStartLocal, parent.resetCoverage - - finalizedBy parent.flushCoverageData + dependsOn parent.cargoStartLocal filter { includeTestsMatching "org.cloudfoundry.identity.uaa.integration.*" includeTestsMatching "*IT" } } - -task instrumentedWar(type: War, dependsOn: instrument) { - dependsOn identityCommon.tasks.findByName('instrumentedJar'), - identityScim.tasks.findByName('instrumentedJar'), - identityLogin.tasks.findByName('instrumentedJar') - - destinationDir = file("$buildDir/instrumented_libs") - classpath = war.classpath - .minus(files('/classes')).plus(files('/instrumented_classes')) - .collect(rewriteInstrumentedLibs) - onlyIf { runningWithCoverage() } -} - -assemble.dependsOn instrumentedWar - -project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> - if (runningWithCoverage()) { - test { - classpath = files(test.classpath.collect(rewriteInstrumentedLibs)) - } - } -} diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index f0135621729..2b5d29fa222 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -284,7 +284,6 @@ - From 39826fd3846e285e934b49e0940dd9dea761930a Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Tue, 24 Nov 2015 20:48:37 -0800 Subject: [PATCH 016/103] Fix develop --- uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml index 5b24926ba03..ae8487cc753 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml @@ -297,20 +297,20 @@ - + - + - + From 8f556f630718ba98bca29bb9971380cfd81e0cf3 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 24 Nov 2015 11:44:06 -0700 Subject: [PATCH 017/103] Implement password grant https://www.pivotaltracker.com/story/show/107504674 [#107504674] --- .../identity/client/UaaContextFactory.java | 69 +++++++++- .../identity/client/token/GrantType.java | 2 +- .../identity/client/token/TokenRequest.java | 64 ++++++++- ...ava => ClientAPITokenIntegrationTest.java} | 54 +++++++- .../client/token/TokenRequestTest.java | 37 +++++- .../uaa/oauth/token/CompositeAccessToken.java | 124 +----------------- .../CompositeAccessTokenDeserializer.java | 91 +++++++++++++ .../token/CompositeAccessTokenSerializer.java | 70 ++++++++++ 8 files changed, 377 insertions(+), 134 deletions(-) rename client-lib/src/test/java/org/cloudfoundry/identity/client/integration/{ClientCredentialsTokenIntegrationTest.java => ClientAPITokenIntegrationTest.java} (54%) create mode 100644 models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java create mode 100644 models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java index 8064a866467..de0d82f6579 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java @@ -17,15 +17,30 @@ import org.cloudfoundry.identity.client.token.TokenRequest; import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken; +import org.springframework.http.HttpHeaders; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.client.token.AccessTokenRequest; +import org.springframework.security.oauth2.client.token.RequestEnhancer; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; -import org.springframework.security.oauth2.common.AuthenticationScheme; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider; +import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpMessageConverterExtractor; +import org.springframework.web.client.ResponseExtractor; import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Objects; + +import static org.springframework.security.oauth2.common.AuthenticationScheme.header; public class UaaContextFactory { @@ -70,22 +85,72 @@ public UaaContext authenticate(TokenRequest request) { if (request == null) { throw new NullPointerException(TokenRequest.class.getName() + " cannot be null."); } + if (!request.isValid()) { + throw new IllegalArgumentException("Invalid token request."); + } switch (request.getGrantType()) { case CLIENT_CREDENTIALS: return authenticateClientCredentials(request); + case PASSWORD: return authenticatePassword(request); default: throw new UnsupportedGrantTypeException("Not implemented:"+request.getGrantType()); } } + protected UaaContext authenticatePassword(TokenRequest request) { + ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider() { + @Override + protected ResponseExtractor getResponseExtractor() { + getRestTemplate(); // force initialization + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + return new HttpMessageConverterExtractor(CompositeAccessToken.class, Arrays.asList(converter)); + } + }; + provider.setTokenRequestEnhancer(new PasswordTokenRequestEnhancer(request)); + ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails(); + details.setUsername(request.getUsername()); + details.setPassword(request.getPassword()); + details.setClientId(request.getClientId()); + details.setClientSecret(request.getClientSecret()); + if (!Objects.isNull(request.getScopes())) { + details.setScope(new LinkedList(request.getScopes())); + } + details.setClientAuthenticationScheme(header); + details.setAccessTokenUri(request.getTokenEndpoint().toString()); + OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext()); + template.setAccessTokenProvider(provider); + OAuth2AccessToken token = template.getAccessToken(); + return new UaaContextImpl(request, template, (CompositeAccessToken) token); + } + protected UaaContext authenticateClientCredentials(TokenRequest request) { + if (!request.isValid()) { + + } ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); details.setClientId(request.getClientId()); details.setClientSecret(request.getClientSecret()); details.setAccessTokenUri(request.getTokenEndpoint().toString()); - details.setClientAuthenticationScheme(AuthenticationScheme.header); + details.setClientAuthenticationScheme(header); OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext()); OAuth2AccessToken token = template.getAccessToken(); CompositeAccessToken result = new CompositeAccessToken(token); return new UaaContextImpl(request, template, result); } + public static class PasswordTokenRequestEnhancer implements RequestEnhancer { + private final TokenRequest request; + + public PasswordTokenRequestEnhancer(TokenRequest request) { + this.request = request; + } + + @Override + public void enhance(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, MultiValueMap form, HttpHeaders headers) { + if (this.request.wantsIdToken()) { + form.put(OAuth2Utils.RESPONSE_TYPE, Arrays.asList("id_token token")); + } + } + + + } + } diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java index 75f0ca84fb4..6d0f2b54c50 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java @@ -19,5 +19,5 @@ public enum GrantType { PASSWORD, IMPLICIT, AUTHORIZATION_CODE, - REFRESH + REFRESH_TOKEN } diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java index e274a959421..6488d2c8b25 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java @@ -15,7 +15,17 @@ package org.cloudfoundry.identity.client.token; import java.net.URI; - +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * A token request contains all the information needed to retrieve a token from the UAA. + * + */ public class TokenRequest { private GrantType grantType; @@ -23,15 +33,40 @@ public class TokenRequest { private String clientSecret; private String username; private String password; + private Set scopes; private URI tokenEndpoint; private URI authorizationEndpoint; + private boolean idToken = false; public TokenRequest(URI tokenEndpoint, URI authorizationEndpoint) { this.tokenEndpoint = tokenEndpoint; } public boolean isValid() { - return false; + if (grantType==null) { + return false; + } + switch (grantType) { + case CLIENT_CREDENTIALS: + return !isNull( + Arrays.asList( + tokenEndpoint, + clientId, + clientSecret + ) + ); + case PASSWORD: + return !isNull( + Arrays.asList( + tokenEndpoint, + clientId, + clientSecret, + username, + password + ) + ); + default: return false; + } } public URI getTokenEndpoint() { @@ -96,4 +131,29 @@ public TokenRequest setAuthorizationEndpoint(URI authorizationEndpoint) { this.authorizationEndpoint = authorizationEndpoint; return this; } + + public TokenRequest withIdToken() { + idToken = true; + return this; + } + + public boolean wantsIdToken() { + return idToken; + } + + public TokenRequest setScopes(Collection scopes) { + this.scopes = scopes==null ? null : new HashSet<>(scopes); + return this; + } + + public Set getScopes() { + return scopes; + } + + protected boolean isNull(List objects) { + if (Objects.isNull(objects)) { + return true; + } + return objects.stream().filter(o -> Objects.isNull(o)).count() > 0; + } } diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientCredentialsTokenIntegrationTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java similarity index 54% rename from client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientCredentialsTokenIntegrationTest.java rename to client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java index 22c697761b6..af7e192a4bb 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientCredentialsTokenIntegrationTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java @@ -19,6 +19,7 @@ import org.cloudfoundry.identity.client.UaaContextFactory; import org.cloudfoundry.identity.client.token.GrantType; import org.cloudfoundry.identity.client.token.TokenRequest; +import org.junit.Before; import org.junit.Test; import java.net.URI; @@ -27,17 +28,22 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -public class ClientCredentialsTokenIntegrationTest { +public class ClientAPITokenIntegrationTest { public static String uaaURI = "http://localhost:8080/uaa"; - @Test - public void test_admin_client_token() throws Exception { - UaaContextFactory factory = + private UaaContextFactory factory; + + @Before + public void setUp() throws Exception { + factory = UaaContextFactory.factory(new URI(uaaURI)) - .authorizePath("/oauth/authorize") - .tokenPath("/oauth/token"); + .authorizePath("/oauth/authorize") + .tokenPath("/oauth/token"); + } + @Test + public void test_admin_client_token() throws Exception { TokenRequest request = factory.tokenRequest() .setClientId("admin") .setClientSecret("adminsecret") @@ -51,4 +57,40 @@ public void test_admin_client_token() throws Exception { assertTrue(context.getToken().getScope().contains("uaa.admin")); } + @Test + public void test_password_token_no_id_token() throws Exception { + TokenRequest request = factory.tokenRequest() + .setClientId("cf") + .setClientSecret("") + .setGrantType(GrantType.PASSWORD) + .setUsername("marissa") + .setPassword("koala"); + + UaaContext context = factory.authenticate(request); + assertNotNull(context); + assertTrue(context.hasAccessToken()); + assertFalse(context.hasIdToken()); + assertTrue(context.hasRefreshToken()); + assertTrue(context.getToken().getScope().contains("openid")); + } + + @Test + public void test_password_token_with_id_token() throws Exception { + TokenRequest request = factory.tokenRequest() + .withIdToken() + .setClientId("cf") + .setClientSecret("") + .setGrantType(GrantType.PASSWORD) + .setUsername("marissa") + .setPassword("koala"); + + UaaContext context = factory.authenticate(request); + assertNotNull(context); + assertTrue(context.hasAccessToken()); + assertTrue(context.hasIdToken()); + assertTrue(context.hasRefreshToken()); + assertTrue(context.getToken().getScope().contains("openid")); + } + + } diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java index 64f54aed356..67a760b9143 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java @@ -17,16 +17,51 @@ import org.junit.Before; import org.junit.Test; +import java.net.URI; +import java.util.Arrays; + +import static java.util.Collections.EMPTY_LIST; +import static org.cloudfoundry.identity.client.token.GrantType.CLIENT_CREDENTIALS; +import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + public class TokenRequestTest { + private TokenRequest request; + @Before public void setUp() throws Exception { + URI turi = new URI("http://localhost:8080/uaa/oauth/token"); + URI auri = new URI("http://localhost:8080/uaa/oauth/authorize"); + request = new TokenRequest(turi, auri); + } + @Test + public void test_is_client_credentials_grant_valid() throws Exception { + assertFalse(request.isValid()); + assertFalse(request.setGrantType(CLIENT_CREDENTIALS).isValid()); + assertFalse(request.setClientId("client_id").isValid()); + assertTrue(request.setClientSecret("client_secret").isValid()); } @Test - public void testIsValid() throws Exception { + public void test_is_password_grant_valid() throws Exception { + assertFalse(request.isValid()); + assertFalse(request.setGrantType(PASSWORD).isValid()); + assertFalse(request.setClientId("client_id").isValid()); + assertFalse(request.setClientSecret("client_secret").isValid()); + assertFalse(request.setUsername("username").isValid()); + assertTrue(request.setPassword("password").isValid()); + } + + @Test + public void test_is_null_function() { + assertTrue(request.isNull(null)); + assertFalse(request.isNull(EMPTY_LIST)); + assertTrue(request.isNull(Arrays.asList("1",null,"2"))); + assertFalse(request.isNull(Arrays.asList("1","2","3"))); } } \ No newline at end of file diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java index 2be5471df21..4b2b56c0383 100644 --- a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java +++ b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java @@ -13,31 +13,13 @@ */ package org.cloudfoundry.identity.uaa.oauth.token; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; -import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.OAuth2RefreshToken; -import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.util.Assert; -import java.io.IOException; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -@JsonSerialize(using = CompositeAccessToken.CompositeAccessTokenSerializer.class) -@JsonDeserialize(using = CompositeAccessToken.CompositeAccessTokenDeserializer.class) +@JsonSerialize(using = CompositeAccessTokenSerializer.class) +@JsonDeserialize(using = CompositeAccessTokenDeserializer.class) public class CompositeAccessToken extends DefaultOAuth2AccessToken { public static String ID_TOKEN = "id_token"; @@ -60,106 +42,4 @@ public CompositeAccessToken(OAuth2AccessToken accessToken) { super(accessToken); } - public static final class CompositeAccessTokenSerializer extends StdSerializer { - - public CompositeAccessTokenSerializer() { - super(OAuth2AccessToken.class); - } - - @Override - public void serialize(OAuth2AccessToken token, JsonGenerator jgen, SerializerProvider provider) throws IOException { - - jgen.writeStartObject(); - jgen.writeStringField(OAuth2AccessToken.ACCESS_TOKEN, token.getValue()); - jgen.writeStringField(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType()); - if (token instanceof CompositeAccessToken && ((CompositeAccessToken)token).getIdTokenValue()!=null) { - jgen.writeStringField(ID_TOKEN, ((CompositeAccessToken) token).getIdTokenValue()); - } - OAuth2RefreshToken refreshToken = token.getRefreshToken(); - if (refreshToken != null) { - jgen.writeStringField(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue()); - } - Date expiration = token.getExpiration(); - if (expiration != null) { - long now = System.currentTimeMillis(); - jgen.writeNumberField(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000); - } - Set scope = token.getScope(); - if (scope != null && !scope.isEmpty()) { - StringBuffer scopes = new StringBuffer(); - for (String s : scope) { - Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + ""); - scopes.append(s); - scopes.append(" "); - } - jgen.writeStringField(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1)); - } - Map additionalInformation = token.getAdditionalInformation(); - for (String key : additionalInformation.keySet()) { - jgen.writeObjectField(key, additionalInformation.get(key)); - } - jgen.writeEndObject(); - } - } - - public final class CompositeAccessTokenDeserializer extends StdDeserializer { - - public CompositeAccessTokenDeserializer() { - super(OAuth2AccessToken.class); - } - - @Override - public OAuth2AccessToken deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - - String idTokenValue = null; - String tokenValue = null; - String tokenType = null; - String refreshToken = null; - Long expiresIn = null; - Set scope = null; - Map additionalInformation = new LinkedHashMap(); - - // TODO What should occur if a parameter exists twice - while (jp.nextToken() != JsonToken.END_OBJECT) { - String name = jp.getCurrentName(); - jp.nextToken(); - if (OAuth2AccessToken.ACCESS_TOKEN.equals(name)) { - tokenValue = jp.getText(); - } else if (ID_TOKEN.equals(name)) { - idTokenValue = jp.getText(); - } else if (OAuth2AccessToken.TOKEN_TYPE.equals(name)) { - tokenType = jp.getText(); - } else if (OAuth2AccessToken.REFRESH_TOKEN.equals(name)) { - refreshToken = jp.getText(); - } else if (OAuth2AccessToken.EXPIRES_IN.equals(name)) { - try { - expiresIn = jp.getLongValue(); - } catch (JsonParseException e) { - expiresIn = Long.valueOf(jp.getText()); - } - } else if (OAuth2AccessToken.SCOPE.equals(name)) { - String text = jp.getText(); - scope = OAuth2Utils.parseParameterList(text); - } else { - additionalInformation.put(name, jp.readValueAs(Object.class)); - } - } - - // TODO What should occur if a required parameter (tokenValue or tokenType) is missing? - - CompositeAccessToken accessToken = new CompositeAccessToken(tokenValue); - accessToken.setIdTokenValue(idTokenValue); - accessToken.setTokenType(tokenType); - if (expiresIn != null) { - accessToken.setExpiration(new Date(System.currentTimeMillis() + (expiresIn * 1000))); - } - if (refreshToken != null) { - accessToken.setRefreshToken(new DefaultOAuth2RefreshToken(refreshToken)); - } - accessToken.setScope(scope); - accessToken.setAdditionalInformation(additionalInformation); - - return accessToken; - } - } } diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java new file mode 100644 index 00000000000..e9bfc9ddc22 --- /dev/null +++ b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java @@ -0,0 +1,91 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.oauth.token; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.util.OAuth2Utils; + +import java.io.IOException; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +public final class CompositeAccessTokenDeserializer extends StdDeserializer { + + public CompositeAccessTokenDeserializer() { + super(CompositeAccessToken.class); + } + + @Override + public CompositeAccessToken deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + + String idTokenValue = null; + String tokenValue = null; + String tokenType = null; + String refreshToken = null; + Long expiresIn = null; + Set scope = null; + Map additionalInformation = new LinkedHashMap(); + + // TODO What should occur if a parameter exists twice + while (jp.nextToken() != JsonToken.END_OBJECT) { + String name = jp.getCurrentName(); + jp.nextToken(); + if (OAuth2AccessToken.ACCESS_TOKEN.equals(name)) { + tokenValue = jp.getText(); + } else if (CompositeAccessToken.ID_TOKEN.equals(name)) { + idTokenValue = jp.getText(); + } else if (OAuth2AccessToken.TOKEN_TYPE.equals(name)) { + tokenType = jp.getText(); + } else if (OAuth2AccessToken.REFRESH_TOKEN.equals(name)) { + refreshToken = jp.getText(); + } else if (OAuth2AccessToken.EXPIRES_IN.equals(name)) { + try { + expiresIn = jp.getLongValue(); + } catch (JsonParseException e) { + expiresIn = Long.valueOf(jp.getText()); + } + } else if (OAuth2AccessToken.SCOPE.equals(name)) { + String text = jp.getText(); + scope = OAuth2Utils.parseParameterList(text); + } else { + additionalInformation.put(name, jp.readValueAs(Object.class)); + } + } + + // TODO What should occur if a required parameter (tokenValue or tokenType) is missing? + + CompositeAccessToken accessToken = new CompositeAccessToken(tokenValue); + accessToken.setIdTokenValue(idTokenValue); + accessToken.setTokenType(tokenType); + if (expiresIn != null) { + accessToken.setExpiration(new Date(System.currentTimeMillis() + (expiresIn * 1000))); + } + if (refreshToken != null) { + accessToken.setRefreshToken(new DefaultOAuth2RefreshToken(refreshToken)); + } + accessToken.setScope(scope); + accessToken.setAdditionalInformation(additionalInformation); + + return accessToken; + } +} diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java new file mode 100644 index 00000000000..5d0e9c90b24 --- /dev/null +++ b/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java @@ -0,0 +1,70 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.oauth.token; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2RefreshToken; +import org.springframework.util.Assert; + +import java.io.IOException; +import java.util.Date; +import java.util.Map; +import java.util.Set; + + +public final class CompositeAccessTokenSerializer extends StdSerializer { + + public CompositeAccessTokenSerializer() { + super(CompositeAccessToken.class); + } + + @Override + public void serialize(CompositeAccessToken token, JsonGenerator jgen, SerializerProvider provider) throws IOException { + + jgen.writeStartObject(); + jgen.writeStringField(OAuth2AccessToken.ACCESS_TOKEN, token.getValue()); + jgen.writeStringField(OAuth2AccessToken.TOKEN_TYPE, token.getTokenType()); + if (token instanceof CompositeAccessToken && ((CompositeAccessToken) token).getIdTokenValue() != null) { + jgen.writeStringField(CompositeAccessToken.ID_TOKEN, ((CompositeAccessToken) token).getIdTokenValue()); + } + OAuth2RefreshToken refreshToken = token.getRefreshToken(); + if (refreshToken != null) { + jgen.writeStringField(OAuth2AccessToken.REFRESH_TOKEN, refreshToken.getValue()); + } + Date expiration = token.getExpiration(); + if (expiration != null) { + long now = System.currentTimeMillis(); + jgen.writeNumberField(OAuth2AccessToken.EXPIRES_IN, (expiration.getTime() - now) / 1000); + } + Set scope = token.getScope(); + if (scope != null && !scope.isEmpty()) { + StringBuffer scopes = new StringBuffer(); + for (String s : scope) { + Assert.hasLength(s, "Scopes cannot be null or empty. Got " + scope + ""); + scopes.append(s); + scopes.append(" "); + } + jgen.writeStringField(OAuth2AccessToken.SCOPE, scopes.substring(0, scopes.length() - 1)); + } + Map additionalInformation = token.getAdditionalInformation(); + for (String key : additionalInformation.keySet()) { + jgen.writeObjectField(key, additionalInformation.get(key)); + } + jgen.writeEndObject(); + } +} From 76babfb5938225e2c34012b690ecbb8e56506064 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 24 Nov 2015 11:50:03 -0700 Subject: [PATCH 018/103] Use lambda instead of new class. --- .../identity/client/UaaContextFactory.java | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java index de0d82f6579..cd2a541cd21 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java @@ -95,7 +95,7 @@ public UaaContext authenticate(TokenRequest request) { } } - protected UaaContext authenticatePassword(TokenRequest request) { + protected UaaContext authenticatePassword(final TokenRequest tokenRequest) { ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider() { @Override protected ResponseExtractor getResponseExtractor() { @@ -104,21 +104,31 @@ protected ResponseExtractor getResponseExtractor() { return new HttpMessageConverterExtractor(CompositeAccessToken.class, Arrays.asList(converter)); } }; - provider.setTokenRequestEnhancer(new PasswordTokenRequestEnhancer(request)); + provider.setTokenRequestEnhancer( //add id_token to the response type if requested. + (AccessTokenRequest request, + OAuth2ProtectedResourceDetails resource, + MultiValueMap form, + HttpHeaders headers) -> { + if (tokenRequest.wantsIdToken()) { + form.put(OAuth2Utils.RESPONSE_TYPE, Arrays.asList("id_token token")); + } + + } + ); ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails(); - details.setUsername(request.getUsername()); - details.setPassword(request.getPassword()); - details.setClientId(request.getClientId()); - details.setClientSecret(request.getClientSecret()); - if (!Objects.isNull(request.getScopes())) { - details.setScope(new LinkedList(request.getScopes())); + details.setUsername(tokenRequest.getUsername()); + details.setPassword(tokenRequest.getPassword()); + details.setClientId(tokenRequest.getClientId()); + details.setClientSecret(tokenRequest.getClientSecret()); + if (!Objects.isNull(tokenRequest.getScopes())) { + details.setScope(new LinkedList(tokenRequest.getScopes())); } details.setClientAuthenticationScheme(header); - details.setAccessTokenUri(request.getTokenEndpoint().toString()); + details.setAccessTokenUri(tokenRequest.getTokenEndpoint().toString()); OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext()); template.setAccessTokenProvider(provider); OAuth2AccessToken token = template.getAccessToken(); - return new UaaContextImpl(request, template, (CompositeAccessToken) token); + return new UaaContextImpl(tokenRequest, template, (CompositeAccessToken) token); } protected UaaContext authenticateClientCredentials(TokenRequest request) { @@ -145,9 +155,7 @@ public PasswordTokenRequestEnhancer(TokenRequest request) { @Override public void enhance(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, MultiValueMap form, HttpHeaders headers) { - if (this.request.wantsIdToken()) { - form.put(OAuth2Utils.RESPONSE_TYPE, Arrays.asList("id_token token")); - } + } From f361ee04e6129cd01aaa70718db31850df5ea10d Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 24 Nov 2015 16:04:35 -0700 Subject: [PATCH 019/103] wip - stub out auth code process --- .../identity/client/UaaContext.java | 30 ++++ .../identity/client/UaaContextFactory.java | 167 +++++++++++++----- .../identity/client/UaaContextImpl.java | 18 ++ .../identity/client/token/GrantType.java | 3 + .../client/token/TokenAuthentication.java | 31 ---- .../identity/client/token/TokenRequest.java | 134 +++++++++++++- .../ClientAPITokenIntegrationTest.java | 52 +++++- .../client/token/TokenRequestTest.java | 20 ++- 8 files changed, 369 insertions(+), 86 deletions(-) delete mode 100644 client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenAuthentication.java diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContext.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContext.java index 617f5ee90fd..b53e75d8c81 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContext.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContext.java @@ -20,14 +20,44 @@ public interface UaaContext { + /** + * Returns true if the context is authenticated and has an access token + * @return true if the context is authenticated and has an access token + */ boolean hasAccessToken(); + + /** + * Returns true if the context contains an OpenID Connect id_token. + * The token can be retrieved by {@link CompositeAccessToken#getIdTokenValue()} + * @return true if the context contains an OpenID Connect id_token + */ boolean hasIdToken(); + + /** + * Returns true if the context has a refresh token + * The token can be retrieved by {@link CompositeAccessToken#getRefreshToken()} + * @return true if the context has a refresh token + */ boolean hasRefreshToken(); + /** + * Returns the token for this context. A token object will always contain an access token and may + * contain an OpenID Connect id_token and/or a refresh token + * @return the token for this context + */ CompositeAccessToken getToken(); + /** + * Returns the token request that was used to acquire the token + * @return the token request that was used to acquire the token + */ TokenRequest getTokenRequest(); + /** + * Returns a {@link org.springframework.security.oauth2.client.OAuth2RestTemplate} + * that has the access token enabled on this object. + * @return the rest template that can be used to invoke UAA APIs + */ RestTemplate getRestTemplate(); diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java index cd2a541cd21..a670e0f73f2 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java @@ -21,10 +21,12 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext; import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; import org.springframework.security.oauth2.client.token.AccessTokenRequest; -import org.springframework.security.oauth2.client.token.RequestEnhancer; +import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider; import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.security.oauth2.common.OAuth2AccessToken; @@ -40,47 +42,80 @@ import java.util.LinkedList; import java.util.Objects; +import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE; import static org.springframework.security.oauth2.common.AuthenticationScheme.header; public class UaaContextFactory { - private final URI uaaUri; - - private UaaContextFactory(URI uaaUri) { - this.uaaUri = uaaUri; + /** + * UAA Base URI + */ + private final URI uaaURI; + + /** + * Instantiates a context factory to authenticate against the UAA + * @param uaaURI the UAA base URI + */ + private UaaContextFactory(URI uaaURI) { + this.uaaURI = uaaURI; } private String tokenPath = "/oauth/token"; private String authorizePath = "/oauth/authorize"; + /** + * Instantiates a context factory to authenticate against the UAA + * The default token path, /oauth/token, and authorize path, /oauth/authorize are set. + * @param uaaURI the UAA base URI + */ public static UaaContextFactory factory(URI uaaURI) { return new UaaContextFactory(uaaURI); } + /** + * Sets the token endpoint path. If not invoked, the default is /oauth/token + * @param path the path for the token endpoint. + * @return this mutable object + */ public UaaContextFactory tokenPath(String path) { this.tokenPath = path; return this; } + /** + * Sets the authorize endpoint path. If not invoked, the default is /oauth/authorize + * @param path the path for the authorize endpoint. + * @return this mutable object + */ public UaaContextFactory authorizePath(String path) { this.authorizePath = path; return this; } + /** + * Creates a new {@link TokenRequest} object. + * The object will have the token an authorize endpoints already configured. + * @return the new token request that can be used for an access token request. + */ public TokenRequest tokenRequest() { UriComponentsBuilder tokenURI = UriComponentsBuilder.newInstance(); - tokenURI.uri(uaaUri); + tokenURI.uri(uaaURI); tokenURI.path(tokenPath); - UriComponentsBuilder authorizationURI = UriComponentsBuilder.newInstance(); - authorizationURI.uri(uaaUri); + authorizationURI.uri(uaaURI); authorizationURI.path(authorizePath); - return new TokenRequest(tokenURI.build().toUri(), authorizationURI.build().toUri()); } - + /** + * Authenticates the client and optionally the user and retrieves an access token + * @param request - a fully configured token request + * @return an authenticated UAA context with + * @throws NullPointerException if the request object is null + * @throws IllegalArgumentException if the token request is invalid + * @see {@link TokenRequest#isValid()} + */ public UaaContext authenticate(TokenRequest request) { if (request == null) { throw new NullPointerException(TokenRequest.class.getName() + " cannot be null."); @@ -91,10 +126,33 @@ public UaaContext authenticate(TokenRequest request) { switch (request.getGrantType()) { case CLIENT_CREDENTIALS: return authenticateClientCredentials(request); case PASSWORD: return authenticatePassword(request); + case AUTHORIZATION_CODE: return authenticateAuthCode(request); default: throw new UnsupportedGrantTypeException("Not implemented:"+request.getGrantType()); } } + /** + * Not yet implemented + * @param tokenRequest + * @return + */ + protected UaaContext authenticateAuthCode(final TokenRequest tokenRequest) { + AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); + details.setPreEstablishedRedirectUri(tokenRequest.getRedirectUriRedirectUri().toString()); + configureResourceDetails(tokenRequest, details); + setClientCredentials(tokenRequest, details); + setRequestScopes(tokenRequest, details); + OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext()); + template.getAccessToken(); + throw new UnsupportedOperationException(AUTHORIZATION_CODE +" is not yet implemented"); + } + + + /** + * Performs a {@link org.cloudfoundry.identity.client.token.GrantType#PASSWORD authentication} + * @param tokenRequest - a configured TokenRequest + * @return an authenticated {@link UaaContext} + */ protected UaaContext authenticatePassword(final TokenRequest tokenRequest) { ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider() { @Override @@ -104,6 +162,19 @@ protected ResponseExtractor getResponseExtractor() { return new HttpMessageConverterExtractor(CompositeAccessToken.class, Arrays.asList(converter)); } }; + enhanceForIdTokenRetrieval(tokenRequest, provider); + ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails(); + configureResourceDetails(tokenRequest, details); + setUserCredentials(tokenRequest, details); + setClientCredentials(tokenRequest, details); + setRequestScopes(tokenRequest, details); + OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext()); + template.setAccessTokenProvider(provider); + OAuth2AccessToken token = template.getAccessToken(); + return new UaaContextImpl(tokenRequest, template, (CompositeAccessToken) token); + } + + protected void enhanceForIdTokenRetrieval(TokenRequest tokenRequest, OAuth2AccessTokenSupport provider) { provider.setTokenRequestEnhancer( //add id_token to the response type if requested. (AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, @@ -112,53 +183,67 @@ protected ResponseExtractor getResponseExtractor() { if (tokenRequest.wantsIdToken()) { form.put(OAuth2Utils.RESPONSE_TYPE, Arrays.asList("id_token token")); } - } ); - ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails(); - details.setUsername(tokenRequest.getUsername()); - details.setPassword(tokenRequest.getPassword()); - details.setClientId(tokenRequest.getClientId()); - details.setClientSecret(tokenRequest.getClientSecret()); - if (!Objects.isNull(tokenRequest.getScopes())) { - details.setScope(new LinkedList(tokenRequest.getScopes())); - } - details.setClientAuthenticationScheme(header); - details.setAccessTokenUri(tokenRequest.getTokenEndpoint().toString()); - OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext()); - template.setAccessTokenProvider(provider); - OAuth2AccessToken token = template.getAccessToken(); - return new UaaContextImpl(tokenRequest, template, (CompositeAccessToken) token); } - protected UaaContext authenticateClientCredentials(TokenRequest request) { - if (!request.isValid()) { + /** + * Performs a {@link org.cloudfoundry.identity.client.token.GrantType#CLIENT_CREDENTIALS authentication} + * @param request - a configured TokenRequest + * @return an authenticated {@link UaaContext} + */ - } + protected UaaContext authenticateClientCredentials(TokenRequest request) { ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); - details.setClientId(request.getClientId()); - details.setClientSecret(request.getClientSecret()); - details.setAccessTokenUri(request.getTokenEndpoint().toString()); - details.setClientAuthenticationScheme(header); + configureResourceDetails(request, details); + setClientCredentials(request, details); + setRequestScopes(request, details); OAuth2RestTemplate template = new OAuth2RestTemplate(details,new DefaultOAuth2ClientContext()); OAuth2AccessToken token = template.getAccessToken(); CompositeAccessToken result = new CompositeAccessToken(token); return new UaaContextImpl(request, template, result); } - public static class PasswordTokenRequestEnhancer implements RequestEnhancer { - private final TokenRequest request; - - public PasswordTokenRequestEnhancer(TokenRequest request) { - this.request = request; - } - - @Override - public void enhance(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, MultiValueMap form, HttpHeaders headers) { + /** + * Sets the token endpoint on the resource details + * Sets the authentication scheme to be {@link org.springframework.security.oauth2.common.AuthenticationScheme#header} + * @param tokenRequest the token request containing the token endpoint + * @param details the details object that will be configured + */ + protected void configureResourceDetails(TokenRequest tokenRequest, BaseOAuth2ProtectedResourceDetails details) { + details.setAuthenticationScheme(header); + details.setAccessTokenUri(tokenRequest.getTokenEndpoint().toString()); + } + /** + * Sets the requested scopes on the resource details, if and only if the requested scopes are not null + * @param tokenRequest the token request containing the requested scopes, if any + * @param details the details object that will be configured + */ + protected void setRequestScopes(TokenRequest tokenRequest, BaseOAuth2ProtectedResourceDetails details) { + if (!Objects.isNull(tokenRequest.getScopes())) { + details.setScope(new LinkedList(tokenRequest.getScopes())); } + } + /** + * Sets the client_id and client_secret on the resource details object + * @param tokenRequest the token request containing the client_id and client_secret + * @param details the details object that. will be configured + */ + protected void setClientCredentials(TokenRequest tokenRequest, BaseOAuth2ProtectedResourceDetails details) { + details.setClientId(tokenRequest.getClientId()); + details.setClientSecret(tokenRequest.getClientSecret()); + } + /** + * Sets the username and password on the resource details object + * @param tokenRequest the token request containing the client_id and client_secret + * @param details the details object that. will be configured + */ + protected void setUserCredentials(TokenRequest tokenRequest, ResourceOwnerPasswordResourceDetails details) { + details.setUsername(tokenRequest.getUsername()); + details.setPassword(tokenRequest.getPassword()); } } diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextImpl.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextImpl.java index 87d94ef7ceb..8d17f9e440e 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextImpl.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextImpl.java @@ -31,31 +31,49 @@ public UaaContextImpl(TokenRequest request, OAuth2RestTemplate template, Composi this.token = token; } + /** + * {@inheritDoc} + */ @Override public boolean hasAccessToken() { return token!=null; } + /** + * {@inheritDoc} + */ @Override public boolean hasIdToken() { return token!=null && StringUtils.hasText(token.getIdTokenValue()); } + /** + * {@inheritDoc} + */ @Override public boolean hasRefreshToken() { return token!=null && token.getRefreshToken()!=null; } + /** + * {@inheritDoc} + */ @Override public TokenRequest getTokenRequest() { return request; } + /** + * {@inheritDoc} + */ @Override public RestTemplate getRestTemplate() { return template; } + /** + * {@inheritDoc} + */ @Override public CompositeAccessToken getToken() { return token; diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java index 6d0f2b54c50..abdd296da57 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java @@ -14,6 +14,9 @@ package org.cloudfoundry.identity.client.token; +/** + * Represent the standard Oauth 2 grant types + */ public enum GrantType { CLIENT_CREDENTIALS, PASSWORD, diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenAuthentication.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenAuthentication.java deleted file mode 100644 index d7078a79f16..00000000000 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenAuthentication.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * ***************************************************************************** - * Cloud Foundry - * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - * ***************************************************************************** - */ - -package org.cloudfoundry.identity.client.token; - -import org.springframework.security.core.Authentication; - -import java.util.Set; - -public interface TokenAuthentication extends Authentication { - - boolean hasAccessToken(); - - boolean hasIdToken(); - - boolean hasRefreshToken(); - - Set getScopes(); - -} diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java index 6488d2c8b25..9ab627fe586 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java @@ -24,6 +24,8 @@ /** * A token request contains all the information needed to retrieve a token from the UAA. + * the {@link #isValid()} method validates the token request object + * for each {@link GrantType} * */ public class TokenRequest { @@ -37,18 +39,29 @@ public class TokenRequest { private URI tokenEndpoint; private URI authorizationEndpoint; private boolean idToken = false; + private URI redirectUri; + /** + * Constructs a token request + * @param tokenEndpoint - required for all grant types + * @param authorizationEndpoint - maybe required only for {@link GrantType#AUTHORIZATION_CODE} and {@link GrantType#IMPLICIT} + */ public TokenRequest(URI tokenEndpoint, URI authorizationEndpoint) { this.tokenEndpoint = tokenEndpoint; + this.authorizationEndpoint = authorizationEndpoint; } + /** + * Returns true if this object contains enough information to retrieve a token + * @return true if this object contains enough information to retrieve a token + */ public boolean isValid() { if (grantType==null) { return false; } switch (grantType) { case CLIENT_CREDENTIALS: - return !isNull( + return !hasAnyNullValues( Arrays.asList( tokenEndpoint, clientId, @@ -56,7 +69,7 @@ public boolean isValid() { ) ); case PASSWORD: - return !isNull( + return !hasAnyNullValues( Arrays.asList( tokenEndpoint, clientId, @@ -65,92 +78,207 @@ public boolean isValid() { password ) ); + case AUTHORIZATION_CODE: + return !hasAnyNullValues( + Arrays.asList( + tokenEndpoint, + authorizationEndpoint, + clientId, + clientSecret, + username, + password, + redirectUri + ) + ); default: return false; } } + /** + * @return the token endpoint URI, for example http://localhost:8080/uaa/oauth/token + */ public URI getTokenEndpoint() { return tokenEndpoint; } + /** + * Sets the token endpoint. Must be the endpoint of the UAA server, for example http://localhost:8080/uaa/oauth/token + * @param tokenEndpoint a valid URI pointing to the UAA token endpoint + * @return this mutable object + */ public TokenRequest setTokenEndpoint(URI tokenEndpoint) { this.tokenEndpoint = tokenEndpoint; return this; } + /** + * Returns the client ID, if set, that will be used to authenticate the client + * @return the client ID if set + */ public String getClientId() { return clientId; } + /** + * Sets the client ID to be used for authentication during the token request + * @param clientId a string, no more than 255 characters identifying a valid client on the UAA + * @return this mutable object + */ public TokenRequest setClientId(String clientId) { this.clientId = clientId; return this; } + /** + * Returns the client secret, if set, that will be used to authenticate the client + * @return the client secret if set + */ public String getClientSecret() { return clientSecret; } + /** + * Sets the client secret to be used for authentication during the token request + * @param clientSecret a string representing the password for a valid client on the UAA + * @return this mutable object + */ public TokenRequest setClientSecret(String clientSecret) { this.clientSecret = clientSecret; return this; } + /** + * Returns the grant type for this token request. Null if none has been set. + * @return the grant type for this token request. Null if none has been set. + */ public GrantType getGrantType() { return grantType; } + /** + * Sets the grant type + * @param grantType a grant type + * @return this mutable object + */ public TokenRequest setGrantType(GrantType grantType) { this.grantType = grantType; return this; } + /** + * Returns the user password used during {@link GrantType#PASSWORD} token requests + * @return the user password used during {@link GrantType#PASSWORD} token requests + */ public String getPassword() { return password; } + /** + * Sets the user password used during {@link GrantType#PASSWORD} token requests + * @param password a clear text password + * @return this mutable object + */ public TokenRequest setPassword(String password) { this.password = password; return this; } + /** + * Returns the username to be used during {@link GrantType#PASSWORD} token requests + * @return the username to be used during {@link GrantType#PASSWORD} token requests + */ public String getUsername() { return username; } + /** + * Sets the username to be used during {@link GrantType#PASSWORD} token requests + * @param username the username to be used during {@link GrantType#PASSWORD} token requests + * @return this mutable object + */ public TokenRequest setUsername(String username) { this.username = username; return this; } + /** + * @return the authorize endpoint URI, for example http://localhost:8080/uaa/oauth/authorize + */ public URI getAuthorizationEndpoint() { return authorizationEndpoint; } + /** + * Sets the authorize endpoint URI, for example http://localhost:8080/uaa/oauth/authorize + * @param authorizationEndpoint the authorize endpoint URI, for example http://localhost:8080/uaa/oauth/authorize + * @return this mutable object + */ public TokenRequest setAuthorizationEndpoint(URI authorizationEndpoint) { this.authorizationEndpoint = authorizationEndpoint; return this; } + /** + * When invoked, this token request should return an id_token in addition to the access token + * @return this mutable object + */ public TokenRequest withIdToken() { idToken = true; return this; } + /** + * Returns true if an id_token has been reqeusted, {@link #withIdToken()} has been invoked. + * @return true if an id_token has been reqeusted + */ public boolean wantsIdToken() { return idToken; } + /** + * Sets the requested/narrowed scope list for this token request. + * Use this if you would like to limit the scopes in the access token + * Setting this to null indicates that you would like the access token to contain all available scopes + * @param scopes a set of strings representing requested scopes, or null to request all scopes + * @return this mutable object + */ public TokenRequest setScopes(Collection scopes) { this.scopes = scopes==null ? null : new HashSet<>(scopes); return this; } + /** + * Returns the list of requested scopes, or null if no scopes have been requested. + * @return the list of requested scopes, or null if no scopes have been requested. + */ public Set getScopes() { return scopes; } - protected boolean isNull(List objects) { + /** + * Returns the redirect_uri for an {@link GrantType#AUTHORIZATION_CODE} or {@link GrantType#IMPLICIT} token request + * @return the redirect_uri for an {@link GrantType#AUTHORIZATION_CODE} or {@link GrantType#IMPLICIT} token request + */ + public URI getRedirectUriRedirectUri() { + return redirectUri; + } + + /** + * Sets the redirect_uri for an {@link GrantType#AUTHORIZATION_CODE} or {@link GrantType#IMPLICIT} token request + * @param redirectUri the redirect_uri for an {@link GrantType#AUTHORIZATION_CODE} or {@link GrantType#IMPLICIT} token request + * @return this mutable object + */ + public TokenRequest setRedirectUri(URI redirectUri) { + this.redirectUri = redirectUri; + return this; + } + + /** + * Returns true if the list or any item in the list is null + * @param objects a list of items to be evaluated for null references + * @return true if the list or any item in the list is null + */ + protected boolean hasAnyNullValues(List objects) { if (Objects.isNull(objects)) { return true; } diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java index af7e192a4bb..3fef6b2d2d4 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java @@ -20,10 +20,12 @@ import org.cloudfoundry.identity.client.token.GrantType; import org.cloudfoundry.identity.client.token.TokenRequest; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.net.URI; +import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -44,12 +46,12 @@ public void setUp() throws Exception { @Test public void test_admin_client_token() throws Exception { - TokenRequest request = factory.tokenRequest() + TokenRequest clientCredentials = factory.tokenRequest() .setClientId("admin") .setClientSecret("adminsecret") .setGrantType(GrantType.CLIENT_CREDENTIALS); - UaaContext context = factory.authenticate(request); + UaaContext context = factory.authenticate(clientCredentials); assertNotNull(context); assertTrue(context.hasAccessToken()); assertFalse(context.hasIdToken()); @@ -58,15 +60,15 @@ public void test_admin_client_token() throws Exception { } @Test - public void test_password_token_no_id_token() throws Exception { - TokenRequest request = factory.tokenRequest() + public void test_password_token_without_id_token() throws Exception { + TokenRequest passwordGrant = factory.tokenRequest() .setClientId("cf") .setClientSecret("") .setGrantType(GrantType.PASSWORD) .setUsername("marissa") .setPassword("koala"); - UaaContext context = factory.authenticate(request); + UaaContext context = factory.authenticate(passwordGrant); assertNotNull(context); assertTrue(context.hasAccessToken()); assertFalse(context.hasIdToken()); @@ -76,7 +78,7 @@ public void test_password_token_no_id_token() throws Exception { @Test public void test_password_token_with_id_token() throws Exception { - TokenRequest request = factory.tokenRequest() + TokenRequest passwordGrant = factory.tokenRequest() .withIdToken() .setClientId("cf") .setClientSecret("") @@ -84,7 +86,7 @@ public void test_password_token_with_id_token() throws Exception { .setUsername("marissa") .setPassword("koala"); - UaaContext context = factory.authenticate(request); + UaaContext context = factory.authenticate(passwordGrant); assertNotNull(context); assertTrue(context.hasAccessToken()); assertTrue(context.hasIdToken()); @@ -92,5 +94,41 @@ public void test_password_token_with_id_token() throws Exception { assertTrue(context.getToken().getScope().contains("openid")); } + @Test + @Ignore //until we have decided if we want to be able to do this without a UI + public void test_auth_code_token_with_id_token() throws Exception { + TokenRequest authorizationCode = factory.tokenRequest() + .withIdToken() + .setGrantType(AUTHORIZATION_CODE) + .setRedirectUri(new URI("http://localhost/redirect")) + .setClientId("cf") + .setClientSecret("") + .setUsername("marissa") + .setPassword("koala"); + UaaContext context = factory.authenticate(authorizationCode); + assertNotNull(context); + assertTrue(context.hasAccessToken()); + assertFalse(context.hasIdToken()); + assertTrue(context.hasRefreshToken()); + assertTrue(context.getToken().getScope().contains("openid")); + } + + @Test + @Ignore //until we have decided if we want to be able to do this without a UI + public void test_auth_code_token_without_id_token() throws Exception { + TokenRequest authorizationCode = factory.tokenRequest() + .setGrantType(AUTHORIZATION_CODE) + .setRedirectUri(new URI("http://localhost/redirect")) + .setClientId("cf") + .setClientSecret("") + .setUsername("marissa") + .setPassword("koala"); + UaaContext context = factory.authenticate(authorizationCode); + assertNotNull(context); + assertTrue(context.hasAccessToken()); + assertFalse(context.hasIdToken()); + assertTrue(context.hasRefreshToken()); + assertTrue(context.getToken().getScope().contains("openid")); + } } diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java index 67a760b9143..a344eafa4fb 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java @@ -21,6 +21,7 @@ import java.util.Arrays; import static java.util.Collections.EMPTY_LIST; +import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE; import static org.cloudfoundry.identity.client.token.GrantType.CLIENT_CREDENTIALS; import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD; import static org.junit.Assert.assertFalse; @@ -56,12 +57,23 @@ public void test_is_password_grant_valid() throws Exception { assertTrue(request.setPassword("password").isValid()); } + @Test + public void test_is_auth_code_grant_valid() throws Exception { + assertFalse(request.isValid()); + assertFalse(request.setGrantType(AUTHORIZATION_CODE).isValid()); + assertFalse(request.setClientId("client_id").isValid()); + assertFalse(request.setClientSecret("client_secret").isValid()); + assertFalse(request.setUsername("username").isValid()); + assertFalse(request.setPassword("password").isValid()); + assertTrue(request.setRedirectUri(new URI("http://localhost:8080/test")).isValid()); + } + @Test public void test_is_null_function() { - assertTrue(request.isNull(null)); - assertFalse(request.isNull(EMPTY_LIST)); - assertTrue(request.isNull(Arrays.asList("1",null,"2"))); - assertFalse(request.isNull(Arrays.asList("1","2","3"))); + assertTrue(request.hasAnyNullValues(null)); + assertFalse(request.hasAnyNullValues(EMPTY_LIST)); + assertTrue(request.hasAnyNullValues(Arrays.asList("1", null, "2"))); + assertFalse(request.hasAnyNullValues(Arrays.asList("1", "2", "3"))); } } \ No newline at end of file From 71670c05c14034565471cad72b4a74b514532d50 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 25 Nov 2015 10:07:54 -0700 Subject: [PATCH 020/103] Remove duplicate run for now --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7b595a5cb8b..22cf051ad8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ install: script: - ./gradlew -Dspring.profiles.active=$TESTENV jacocoRootReport after_success: -- ./gradlew coveralls +#- ./gradlew coveralls - for i in $(find $HOME/build/cloudfoundry/uaa/ -name reports -type d); do rm -rf $i; done - /bin/df -h - /usr/bin/du -sh * From 4c134f37d9db165145a9919ff24103b2842bb10a Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 30 Nov 2015 16:39:36 -0700 Subject: [PATCH 021/103] Support authorization_code using a token as authentication https://www.pivotaltracker.com/story/show/108960042 [#108960042] --- .../identity/client/UaaContextFactory.java | 34 +++++++++++++++++++ .../identity/client/token/GrantType.java | 1 + .../identity/client/token/TokenRequest.java | 34 +++++++++++++++++++ .../ClientAPITokenIntegrationTest.java | 32 +++++++++++++++-- .../client/token/TokenRequestTest.java | 13 +++++++ docs/UAA-APIs.rst | 4 +-- 6 files changed, 114 insertions(+), 4 deletions(-) diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java index a670e0f73f2..c5373208af5 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java @@ -26,12 +26,14 @@ import org.springframework.security.oauth2.client.token.AccessTokenRequest; import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport; import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails; +import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider; import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.MultiValueMap; import org.springframework.web.client.HttpMessageConverterExtractor; import org.springframework.web.client.ResponseExtractor; @@ -40,6 +42,8 @@ import java.net.URI; import java.util.Arrays; import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.Objects; import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE; @@ -127,6 +131,7 @@ public UaaContext authenticate(TokenRequest request) { case CLIENT_CREDENTIALS: return authenticateClientCredentials(request); case PASSWORD: return authenticatePassword(request); case AUTHORIZATION_CODE: return authenticateAuthCode(request); + case AUTHORIZATION_CODE_WITH_TOKEN: return authenticateAuthCodeWithToken(request); default: throw new UnsupportedGrantTypeException("Not implemented:"+request.getGrantType()); } } @@ -147,6 +152,35 @@ protected UaaContext authenticateAuthCode(final TokenRequest tokenRequest) { throw new UnsupportedOperationException(AUTHORIZATION_CODE +" is not yet implemented"); } + protected UaaContext authenticateAuthCodeWithToken(final TokenRequest tokenRequest) { + AuthorizationCodeAccessTokenProvider provider = new AuthorizationCodeAccessTokenProvider() { + @Override + protected ResponseExtractor getResponseExtractor() { + getRestTemplate(); // force initialization + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + return new HttpMessageConverterExtractor(CompositeAccessToken.class, Arrays.asList(converter)); + } + }; + enhanceForIdTokenRetrieval(tokenRequest, provider); + AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); + details.setPreEstablishedRedirectUri(tokenRequest.getRedirectUriRedirectUri().toString()); + configureResourceDetails(tokenRequest, details); + setClientCredentials(tokenRequest, details); + setRequestScopes(tokenRequest, details); + details.setUserAuthorizationUri(tokenRequest.getAuthorizationEndpoint().toString()); + DefaultOAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext(); + String state = new RandomValueStringGenerator().generate(); + oAuth2ClientContext.getAccessTokenRequest().setStateKey(state); + oAuth2ClientContext.setPreservedState(state, details.getPreEstablishedRedirectUri()); + oAuth2ClientContext.getAccessTokenRequest().setCurrentUri(details.getPreEstablishedRedirectUri()); + Map> headers = (Map>) oAuth2ClientContext.getAccessTokenRequest().getHeaders(); + headers.put("Authorization", Arrays.asList("bearer " + tokenRequest.getAuthCodeAPIToken())); + OAuth2RestTemplate template = new OAuth2RestTemplate(details, oAuth2ClientContext); + template.setAccessTokenProvider(provider); + OAuth2AccessToken token = template.getAccessToken(); + return new UaaContextImpl(tokenRequest, template, (CompositeAccessToken) token); + } + /** * Performs a {@link org.cloudfoundry.identity.client.token.GrantType#PASSWORD authentication} diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java index abdd296da57..2bc7fe4cfc7 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java @@ -22,5 +22,6 @@ public enum GrantType { PASSWORD, IMPLICIT, AUTHORIZATION_CODE, + AUTHORIZATION_CODE_WITH_TOKEN, REFRESH_TOKEN } diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java index 9ab627fe586..a6bc9b9b5b7 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java @@ -40,6 +40,7 @@ public class TokenRequest { private URI authorizationEndpoint; private boolean idToken = false; private URI redirectUri; + private String authCodeAPIToken; /** * Constructs a token request @@ -90,6 +91,19 @@ public boolean isValid() { redirectUri ) ); + case AUTHORIZATION_CODE_WITH_TOKEN: + return !hasAnyNullValues( + Arrays.asList( + tokenEndpoint, + authorizationEndpoint, + clientId, + clientSecret, + username, + password, + redirectUri, + authCodeAPIToken + ) + ); default: return false; } } @@ -273,6 +287,26 @@ public TokenRequest setRedirectUri(URI redirectUri) { return this; } + /** + * Returns the UAA token that will be used if this token request is an + * {@link GrantType#AUTHORIZATION_CODE_WITH_TOKEN} grant. + * @return the token set or null if not set + */ + public String getAuthCodeAPIToken() { + return authCodeAPIToken; + } + + /** + * Sets the token used as authentication mechanism when using + * the {@link GrantType#AUTHORIZATION_CODE_WITH_TOKEN} grant. + * @param authCodeAPIToken - a valid UAA token + * @return this mutable object + */ + public TokenRequest setAuthCodeAPIToken(String authCodeAPIToken) { + this.authCodeAPIToken = authCodeAPIToken; + return this; + } + /** * Returns true if the list or any item in the list is null * @param objects a list of items to be evaluated for null references diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java index 3fef6b2d2d4..e60c6632a5f 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java @@ -24,8 +24,10 @@ import org.junit.Test; import java.net.URI; +import java.util.Arrays; import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE; +import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE_WITH_TOKEN; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -61,19 +63,23 @@ public void test_admin_client_token() throws Exception { @Test public void test_password_token_without_id_token() throws Exception { + UaaContext context = retrievePasswordToken(null); + assertTrue(context.getToken().getScope().contains("openid")); + } + + protected UaaContext retrievePasswordToken(String scopes) { TokenRequest passwordGrant = factory.tokenRequest() .setClientId("cf") .setClientSecret("") .setGrantType(GrantType.PASSWORD) .setUsername("marissa") .setPassword("koala"); - UaaContext context = factory.authenticate(passwordGrant); assertNotNull(context); assertTrue(context.hasAccessToken()); assertFalse(context.hasIdToken()); assertTrue(context.hasRefreshToken()); - assertTrue(context.getToken().getScope().contains("openid")); + return context; } @Test @@ -131,4 +137,26 @@ public void test_auth_code_token_without_id_token() throws Exception { assertTrue(context.getToken().getScope().contains("openid")); } + @Test + public void test_auth_code_token_using_api() throws Exception { + UaaContext passwordContext = retrievePasswordToken("uaa.user"); + assertTrue(passwordContext.getToken().getScope().contains("uaa.user")); + TokenRequest authorizationCode = factory.tokenRequest() + .setGrantType(AUTHORIZATION_CODE_WITH_TOKEN) + .setRedirectUri(new URI("http://localhost:8080/app/")) + .setClientId("app") + .setClientSecret("appclientsecret") + .setUsername("marissa") + .setPassword("koala") + .setScopes(Arrays.asList("openid")) + .setAuthCodeAPIToken(passwordContext.getToken().getValue()); + UaaContext context = factory.authenticate(authorizationCode); + assertNotNull(context); + assertTrue(context.hasAccessToken()); + //we receive an id_token because we request 'openid' explicitly + assertTrue(context.hasIdToken()); + assertTrue(context.hasRefreshToken()); + assertTrue(context.getToken().getScope().contains("openid")); + } + } diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java index a344eafa4fb..ade80840bf4 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java @@ -22,6 +22,7 @@ import static java.util.Collections.EMPTY_LIST; import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE; +import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE_WITH_TOKEN; import static org.cloudfoundry.identity.client.token.GrantType.CLIENT_CREDENTIALS; import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD; import static org.junit.Assert.assertFalse; @@ -68,6 +69,18 @@ public void test_is_auth_code_grant_valid() throws Exception { assertTrue(request.setRedirectUri(new URI("http://localhost:8080/test")).isValid()); } + @Test + public void test_is_auth_code_grant_api_valid() throws Exception { + assertFalse(request.isValid()); + assertFalse(request.setGrantType(AUTHORIZATION_CODE_WITH_TOKEN).isValid()); + assertFalse(request.setClientId("client_id").isValid()); + assertFalse(request.setClientSecret("client_secret").isValid()); + assertFalse(request.setUsername("username").isValid()); + assertFalse(request.setPassword("password").isValid()); + assertFalse(request.setAuthCodeAPIToken("some token").isValid()); + assertTrue(request.setRedirectUri(new URI("http://localhost:8080/test")).isValid()); + } + @Test public void test_is_null_function() { diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 868106f5435..f762874721c 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -47,7 +47,7 @@ Here is a summary of the different scopes that are known to the UAA. * **approvals.me** - not currently used * **openid** - Required to access the /userinfo endpoint. Intended for OpenID clients. * **groups.update** - Allows a group to be updated. Can also be accomplished with ``scim.write`` -* **uaa.user** - scope to indicate this is a user +* **uaa.user** - scope to indicate this is a user, also required in the token if using `API Authorization Requests Code: ``GET /oauth/authorize`` (non standard /oauth/authorize)`_ * **uaa.resource** - scope to indicate this is a resource server, used for the /check_token endpoint * **uaa.admin** - scope to indicate this is the super user * **uaa.none** - scope to indicate that this client will not be performing actions on behalf of a user @@ -755,7 +755,7 @@ Fields *Available Fields* :: ===================== ==================== ======== ======================================================================================================================================================================== accessTokenValidity int Optional How long the access token is valid for in seconds. refreshTokenValidity int Optional How long the refresh token is valid for seconds. - + SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig) ===================== ==================== ======== ======================================================================================================================================================================== requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``false``. From dc16f90f2a0da697f61d0cad5991310465c4acad Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Mon, 30 Nov 2015 17:03:45 -0800 Subject: [PATCH 022/103] Bump next prerelease/2.7.3 version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 42d447f3076..cd92d6b0851 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.7.3 +version=3.0.0-SNAPSHOT From 47603eba2b3af7a192d63729f024f5629d2c0d2c Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Mon, 30 Nov 2015 15:05:20 -0800 Subject: [PATCH 023/103] Expire one time token for the user if a new one is requested [#108973068] https://www.pivotaltracker.com/story/show/108973068 Signed-off-by: Madhura Bhave --- .../login/LoginInfoEndpoint.java | 20 ++++---- .../uaa/codestore/CodeStoreEndpoints.java | 2 +- .../uaa/codestore/ExpiringCodeStore.java | 10 +++- .../codestore/InMemoryExpiringCodeStore.java | 12 ++++- .../uaa/codestore/JdbcExpiringCodeStore.java | 24 ++++++--- ...7_5__Add_Intent_to_Expiring_Code_Store.sql | 1 + ...7_5__Add_Intent_to_Expiring_Code_Store.sql | 1 + ...7_5__Add_Intent_to_Expiring_Code_Store.sql | 1 + .../codestore/CodeStoreEndpointsTests.java | 16 +++--- .../uaa/codestore/ExpiringCodeStoreTests.java | 36 +++++++------ .../invitations/InvitationsController.java | 6 +-- .../uaa/invitations/InvitationsEndpoint.java | 2 +- .../uaa/login/EmailChangeEmailService.java | 2 +- .../uaa/login/ResetPasswordController.java | 2 +- .../InvitationsControllerTest.java | 28 ++++++----- .../AutologinAuthenticationManagerTest.java | 10 ++-- .../EmailAccountCreationServiceTests.java | 10 ++-- .../login/EmailChangeEmailServiceTest.java | 8 +-- .../login/EmailInvitationsServiceTests.java | 10 ++-- .../login/ResetPasswordControllerTest.java | 6 +-- .../identity/uaa/codestore/ExpiringCode.java | 16 ++++-- .../uaa/login/UaaResetPasswordService.java | 2 +- .../scim/endpoints/ChangeEmailEndpoints.java | 2 +- .../scim/endpoints/PasswordResetEndpoint.java | 2 +- .../identity/uaa/scim/util/ScimUtils.java | 4 +- .../login/UaaResetPasswordServiceTests.java | 13 +++-- .../endpoints/ChangeEmailEndpointsTest.java | 12 ++--- .../endpoints/PasswordResetEndpointTest.java | 38 +++++++------- .../integration/feature/InvitationsIT.java | 2 +- .../uaa/login/PasscodeMockMvcTests.java | 50 +++++++++++++++++++ .../ResetPasswordControllerMockMvcTests.java | 16 +++--- .../ExpiringCodeStoreMockMvcTests.java | 24 ++++----- ...erManagementSecurityFilterMockMvcTest.java | 2 +- 33 files changed, 244 insertions(+), 146 deletions(-) create mode 100644 common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql create mode 100644 common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql create mode 100644 common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java index 25d5c16ca41..a3747f275d4 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java @@ -381,7 +381,7 @@ public AutologinResponse generateAutologinCode(@RequestBody AutologinRequest req codeData.put(OriginKeys.ORIGIN, p.getOrigin()); } } - ExpiringCode expiringCode = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000)); + ExpiringCode expiringCode = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000), null); return new AutologinResponse(expiringCode.getCode()); } @@ -428,16 +428,18 @@ public String generatePasscode(Map model, Principal principal) PasscodeInformation pi = new PasscodeInformation(userId, username, null, origin, authorizationParameters); - ExpiringCode code = doGenerateCode(pi); + String intent = "PASSCODE " + pi.getUserId(); + + expiringCodeStore.expireByIntent(intent); + + ExpiringCode code = expiringCodeStore.generateCode( + JsonUtils.writeValueAsString(pi), + new Timestamp(System.currentTimeMillis() + (getCodeExpirationMillis())), + intent); + model.put("passcode", code.getCode()); - return "passcode"; - } - protected ExpiringCode doGenerateCode(Object o) throws IOException { - return expiringCodeStore.generateCode( - JsonUtils.writeValueAsString(o), - new Timestamp(System.currentTimeMillis() + (getCodeExpirationMillis())) - ); + return "passcode"; } protected Map getLinksInfo() { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpoints.java b/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpoints.java index 12f45418c9c..543242751b8 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpoints.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpoints.java @@ -52,7 +52,7 @@ public void setExpiringCodeStore(ExpiringCodeStore expiringCodeStore) { @ResponseBody public ExpiringCode generateCode(@RequestBody ExpiringCode expiringCode) { try { - return expiringCodeStore.generateCode(expiringCode.getData(), expiringCode.getExpiresAt()); + return expiringCodeStore.generateCode(expiringCode.getData(), expiringCode.getExpiresAt(), null); } catch (NullPointerException e) { throw new CodeStoreException("data and expiresAt are required.", HttpStatus.BAD_REQUEST); } catch (IllegalArgumentException e) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStore.java b/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStore.java index 483a9899089..494a427fa6a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStore.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStore.java @@ -22,11 +22,12 @@ public interface ExpiringCodeStore { * Generate and persist a one-time code with an expiry date. * * @param data JSON object to be associated with the code + * @param intent An optional key (not necessarily unique) for looking up codes * @return code the generated one-time code * @throws java.lang.NullPointerException if data or expiresAt is null * @throws java.lang.IllegalArgumentException if expiresAt is in the past */ - ExpiringCode generateCode(String data, Timestamp expiresAt); + ExpiringCode generateCode(String data, Timestamp expiresAt, String intent); /** * Retrieve a code and delete it if it exists. @@ -43,4 +44,11 @@ public interface ExpiringCodeStore { * @param generator Code generator */ void setGenerator(RandomValueStringGenerator generator); + + /** + * Remove all codes matching a given intent. + * + * @param intent Intent of codes to remove + */ + void expireByIntent(String intent); } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/InMemoryExpiringCodeStore.java b/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/InMemoryExpiringCodeStore.java index db443a74326..bffc1bbf962 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/InMemoryExpiringCodeStore.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/InMemoryExpiringCodeStore.java @@ -26,6 +26,7 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.util.Assert; public class InMemoryExpiringCodeStore implements ExpiringCodeStore { @@ -34,7 +35,7 @@ public class InMemoryExpiringCodeStore implements ExpiringCodeStore { private ConcurrentMap store = new ConcurrentHashMap(); @Override - public ExpiringCode generateCode(String data, Timestamp expiresAt) { + public ExpiringCode generateCode(String data, Timestamp expiresAt, String intent) { if (data == null || expiresAt == null) { throw new NullPointerException(); } @@ -45,7 +46,7 @@ public ExpiringCode generateCode(String data, Timestamp expiresAt) { String code = generator.generate(); - ExpiringCode expiringCode = new ExpiringCode(code, expiresAt, data); + ExpiringCode expiringCode = new ExpiringCode(code, expiresAt, data, intent); ExpiringCode duplicate = store.putIfAbsent(code, expiringCode); if (duplicate != null) { @@ -74,4 +75,11 @@ public ExpiringCode retrieveCode(String code) { public void setGenerator(RandomValueStringGenerator generator) { this.generator = generator; } + + @Override + public void expireByIntent(String intent) { + Assert.hasText(intent); + + store.values().stream().filter(c -> intent.equals(c.getIntent())).forEach(c -> store.remove(c.getCode())); + } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/JdbcExpiringCodeStore.java b/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/JdbcExpiringCodeStore.java index a6d7b2eff3f..829b0e5c954 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/JdbcExpiringCodeStore.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/JdbcExpiringCodeStore.java @@ -26,14 +26,16 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.util.Assert; public class JdbcExpiringCodeStore implements ExpiringCodeStore { public static final String tableName = "expiring_code_store"; - public static final String fields = "code, expiresat, data"; + public static final String fields = "code, expiresat, data, intent"; - public static final String insert = "insert into " + tableName + " (" + fields + ") values (?,?,?)"; + public static final String insert = "insert into " + tableName + " (" + fields + ") values (?,?,?,?)"; public static final String delete = "delete from " + tableName + " where code = ?"; + public static final String deleteIntent = "delete from " + tableName + " where intent = ?"; public static final String deleteExpired = "delete from " + tableName + " where expiresat < ?"; public static final String select = "select " + fields + " from " + tableName + " where code = ?"; public static final String SELECT_BY_EMAIL_AND_CLIENT_ID = "select " + fields + " from " + tableName + @@ -69,7 +71,7 @@ public void setDataSource(DataSource dataSource) { } @Override - public ExpiringCode generateCode(String data, Timestamp expiresAt) { + public ExpiringCode generateCode(String data, Timestamp expiresAt, String intent) { cleanExpiredEntries(); if (data == null || expiresAt == null) { @@ -85,9 +87,9 @@ public ExpiringCode generateCode(String data, Timestamp expiresAt) { count++; String code = generator.generate(); try { - int update = jdbcTemplate.update(insert, code, expiresAt.getTime(), data); + int update = jdbcTemplate.update(insert, code, expiresAt.getTime(), data, intent); if (update == 1) { - ExpiringCode expiringCode = new ExpiringCode(code, expiresAt, data); + ExpiringCode expiringCode = new ExpiringCode(code, expiresAt, data, intent); return expiringCode; } else { logger.warn("Unable to store expiring code:" + code); @@ -132,6 +134,13 @@ public void setGenerator(RandomValueStringGenerator generator) { this.generator = generator; } + @Override + public void expireByIntent(String intent) { + Assert.hasText(intent); + + jdbcTemplate.update(deleteIntent, intent); + } + public int cleanExpiredEntries() { long now = System.currentTimeMillis(); long lastCheck = lastExpired.get(); @@ -152,8 +161,9 @@ public ExpiringCode mapRow(ResultSet rs, int rowNum) throws SQLException { int pos = 1; String code = rs.getString(pos++); Timestamp expiresAt = new Timestamp(rs.getLong(pos++)); - String data = rs.getString(pos++).toString(); - return new ExpiringCode(code, expiresAt, data); + String data = rs.getString(pos++); + String intent = rs.getString(pos++); + return new ExpiringCode(code, expiresAt, data, intent); } } diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql new file mode 100644 index 00000000000..ef63d0063db --- /dev/null +++ b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql @@ -0,0 +1 @@ +ALTER TABLE expiring_code_store ADD COLUMN intent LONGVARCHAR DEFAULT NULL; diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql new file mode 100644 index 00000000000..dbe7b6d0d76 --- /dev/null +++ b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql @@ -0,0 +1 @@ +ALTER TABLE expiring_code_store ADD COLUMN intent LONGTEXT DEFAULT NULL; diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql new file mode 100644 index 00000000000..b8e32a862c4 --- /dev/null +++ b/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql @@ -0,0 +1 @@ +ALTER TABLE expiring_code_store ADD COLUMN intent TEXT DEFAULT NULL; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpointsTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpointsTests.java index 713077028e9..efeebac56bb 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpointsTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpointsTests.java @@ -44,7 +44,7 @@ public void initCodeStoreTests() throws Exception { public void testGenerateCode() throws Exception { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data); + ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data, null); ExpiringCode result = codeStoreEndpoints.generateCode(expiringCode); @@ -61,7 +61,7 @@ public void testGenerateCode() throws Exception { @Test public void testGenerateCodeWithNullData() throws Exception { Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, null); + ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, null, null); try { codeStoreEndpoints.generateCode(expiringCode); @@ -75,7 +75,7 @@ public void testGenerateCodeWithNullData() throws Exception { @Test public void testGenerateCodeWithNullExpiresAt() throws Exception { String data = "{}"; - ExpiringCode expiringCode = new ExpiringCode(null, null, data); + ExpiringCode expiringCode = new ExpiringCode(null, null, data, null); try { codeStoreEndpoints.generateCode(expiringCode); @@ -90,7 +90,7 @@ public void testGenerateCodeWithNullExpiresAt() throws Exception { public void testGenerateCodeWithExpiresAtInThePast() throws Exception { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() - 60000); - ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data); + ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data, null); try { codeStoreEndpoints.generateCode(expiringCode); @@ -109,7 +109,7 @@ public void testGenerateCodeWithDuplicateCode() throws Exception { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data); + ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data, null); try { codeStoreEndpoints.generateCode(expiringCode); @@ -125,7 +125,7 @@ public void testGenerateCodeWithDuplicateCode() throws Exception { public void testRetrieveCode() throws Exception { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data); + ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data, null); ExpiringCode generatedCode = codeStoreEndpoints.generateCode(expiringCode); ExpiringCode retrievedCode = codeStoreEndpoints.retrieveCode(generatedCode.getCode()); @@ -169,7 +169,7 @@ public void testStoreLargeData() throws Exception { Arrays.fill(oneMb, 'a'); String data = new String(oneMb); Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data); + ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data, null); ExpiringCode generatedCode = codeStoreEndpoints.generateCode(expiringCode); @@ -183,7 +183,7 @@ public void testStoreLargeData() throws Exception { public void testRetrieveCodeWithExpiredCode() throws Exception { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 1000); - ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data); + ExpiringCode expiringCode = new ExpiringCode(null, expiresAt, data, null); ExpiringCode generatedCode = codeStoreEndpoints.generateCode(expiringCode); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStoreTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStoreTests.java index c990973fefa..67c64a3b2d5 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStoreTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStoreTests.java @@ -16,8 +16,6 @@ import java.sql.Timestamp; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -28,7 +26,6 @@ import static org.mockito.Mockito.when; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.test.TestUtils; -import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -75,7 +72,7 @@ public void initExpiringCodeStoreTests() throws Exception { public void testGenerateCode() throws Exception { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode expiringCode = expiringCodeStore.generateCode(data, expiresAt); + ExpiringCode expiringCode = expiringCodeStore.generateCode(data, expiresAt, null); assertNotNull(expiringCode); @@ -91,21 +88,21 @@ public void testGenerateCode() throws Exception { public void testGenerateCodeWithNullData() throws Exception { String data = null; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 60000); - expiringCodeStore.generateCode(data, expiresAt); + expiringCodeStore.generateCode(data, expiresAt, null); } @Test(expected = NullPointerException.class) public void testGenerateCodeWithNullExpiresAt() throws Exception { String data = "{}"; Timestamp expiresAt = null; - expiringCodeStore.generateCode(data, expiresAt); + expiringCodeStore.generateCode(data, expiresAt, null); } @Test(expected = IllegalArgumentException.class) public void testGenerateCodeWithExpiresAtInThePast() throws Exception { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() - 60000); - expiringCodeStore.generateCode(data, expiresAt); + expiringCodeStore.generateCode(data, expiresAt, null); } @Test(expected = DataIntegrityViolationException.class) @@ -116,15 +113,15 @@ public void testGenerateCodeWithDuplicateCode() throws Exception { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 60000); - expiringCodeStore.generateCode(data, expiresAt); - expiringCodeStore.generateCode(data, expiresAt); + expiringCodeStore.generateCode(data, expiresAt, null); + expiringCodeStore.generateCode(data, expiresAt, null); } @Test public void testRetrieveCode() throws Exception { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode generatedCode = expiringCodeStore.generateCode(data, expiresAt); + ExpiringCode generatedCode = expiringCodeStore.generateCode(data, expiresAt, null); ExpiringCode retrievedCode = expiringCodeStore.retrieveCode(generatedCode.getCode()); @@ -151,7 +148,7 @@ public void testStoreLargeData() throws Exception { Arrays.fill(oneMb, 'a'); String aaaString = new String(oneMb); ExpiringCode expiringCode = expiringCodeStore.generateCode(aaaString, new Timestamp( - System.currentTimeMillis() + 60000)); + System.currentTimeMillis() + 60000), null); String code = expiringCode.getCode(); ExpiringCode actualCode = expiringCodeStore.retrieveCode(code); assertEquals(expiringCode, actualCode); @@ -161,13 +158,24 @@ public void testStoreLargeData() throws Exception { public void testExpiredCodeReturnsNull() throws Exception { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 1000); - ExpiringCode generatedCode = expiringCodeStore.generateCode(data, expiresAt); + ExpiringCode generatedCode = expiringCodeStore.generateCode(data, expiresAt, null); Thread.currentThread(); Thread.sleep(1001); ExpiringCode retrievedCode = expiringCodeStore.retrieveCode(generatedCode.getCode()); assertNull(retrievedCode); } + @Test + public void testExpireCodeByIntent() throws Exception { + ExpiringCode code = expiringCodeStore.generateCode("{}", new Timestamp(System.currentTimeMillis() + 60000), "Test Intent"); + + expiringCodeStore.expireByIntent("Test Intent"); + + ExpiringCode retrievedCode = expiringCodeStore.retrieveCode(code.getCode()); + + assertNull(retrievedCode); + } + @Test public void testDatabaseDown() throws Exception { if (JdbcExpiringCodeStore.class == expiringCodeStoreClass) { @@ -177,7 +185,7 @@ public void testDatabaseDown() throws Exception { try { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 10000000); - ExpiringCode generatedCode = expiringCodeStore.generateCode(data, expiresAt); + ExpiringCode generatedCode = expiringCodeStore.generateCode(data, expiresAt, null); fail("Database is down, should not generate a code"); } catch (DataAccessException x) { @@ -189,7 +197,7 @@ public void testDatabaseDown() throws Exception { @Test(expected = EmptyResultDataAccessException.class) public void testExpirationCleaner() throws Exception { if (JdbcExpiringCodeStore.class == expiringCodeStoreClass) { - jdbcTemplate.update(JdbcExpiringCodeStore.insert, "test", System.currentTimeMillis() - 1000, "{}"); + jdbcTemplate.update(JdbcExpiringCodeStore.insert, "test", System.currentTimeMillis() - 1000, "{}", null); ((JdbcExpiringCodeStore) expiringCodeStore).cleanExpiredEntries(); jdbcTemplate.queryForObject(JdbcExpiringCodeStore.select, new JdbcExpiringCodeStore.JdbcExpiringCodeMapper(), "test"); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java index c3b3a7982a8..9b1d534e8a4 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java @@ -127,7 +127,7 @@ public String acceptInvitePage(@RequestParam String code, Model model, HttpServl String origin = codeData.get(ORIGIN); try { IdentityProvider provider = providerProvisioning.retrieveByOrigin(origin, IdentityZoneHolder.get().getId()); - final String newCode = expiringCodeStore.generateCode(expiringCode.getData(), new Timestamp(System.currentTimeMillis() + (10 * 60 * 1000))).getCode(); + final String newCode = expiringCodeStore.generateCode(expiringCode.getData(), new Timestamp(System.currentTimeMillis() + (10 * 60 * 1000)), null).getCode(); UaaUser user = userDatabase.retrieveUserById(codeData.get("user_id")); if (user.isVerified()) { @@ -250,7 +250,7 @@ public String acceptLdapInvitation(@RequestParam("enterprise_username") String u return handleUnprocessableEntity(model, response, "error_message_code", "code_expired", "invitations/accept_enterprise.do"); } - String newCode = expiringCodeStore.generateCode(expiringCode.getData(), new Timestamp(System.currentTimeMillis() + (1000*60*10))).getCode(); + String newCode = expiringCodeStore.generateCode(expiringCode.getData(), new Timestamp(System.currentTimeMillis() + (1000*60*10)), null).getCode(); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); AuthenticationManager authenticationManager = null; @@ -274,7 +274,7 @@ public String acceptLdapInvitation(@RequestParam("enterprise_username") String u if (!user.getPrimaryEmail().equalsIgnoreCase(((ExtendedLdapUserDetails) authentication.getPrincipal()).getEmailAddress())) { model.addAttribute("email", data.get("email")); model.addAttribute(OriginKeys.LDAP, OriginKeys.LDAP); - model.addAttribute("code", expiringCodeStore.generateCode(expiringCode.getData(), new Timestamp(System.currentTimeMillis() + (10 * 60 * 1000))).getCode()); + model.addAttribute("code", expiringCodeStore.generateCode(expiringCode.getData(), new Timestamp(System.currentTimeMillis() + (10 * 60 * 1000)), null).getCode()); return handleUnprocessableEntity(model, response, "error_message", "invite.email_mismatch", "invitations/accept_invite"); } diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java index d45f283366f..013a7c50489 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java @@ -91,7 +91,7 @@ public ResponseEntity inviteUsers(@RequestBody InvitationsR data.put(REDIRECT_URI, redirectUri); data.put(ORIGIN, user.getOrigin()); Timestamp expiry = new Timestamp(System.currentTimeMillis() + (INVITATION_EXPIRY_DAYS * 24 * 60 * 60 * 1000)); - ExpiringCode code = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(data), expiry); + ExpiringCode code = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(data), expiry, null); String invitationLink = accountsUrl + "?code=" + code.getCode(); try { diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java index ba40928b873..1a9331ce562 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java @@ -87,7 +87,7 @@ private String generateExpiringCode(String userId, String newEmail, String clien codeData.put("redirect_uri", redirectUri); codeData.put("email", newEmail); - return codeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + EMAIL_CHANGE_LIFETIME)).getCode(); + return codeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + EMAIL_CHANGE_LIFETIME), null).getCode(); } @Override diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordController.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordController.java index ab10fbd5b43..f31a7cdc23b 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordController.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordController.java @@ -166,7 +166,7 @@ public String resetPasswordPage(Model model, return handleUnprocessableEntity(model, response, "message_code", "bad_code"); } else { Timestamp fiveMinutes = new Timestamp(System.currentTimeMillis()+(1000*60*5)); - model.addAttribute("code", codeStore.generateCode(expiringCode.getData(), fiveMinutes).getCode()); + model.addAttribute("code", codeStore.generateCode(expiringCode.getData(), fiveMinutes, null).getCode()); model.addAttribute("email", email); return "reset_password"; } diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java b/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java index 494c8f1e6b6..cc97f6184b3 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java @@ -57,12 +57,14 @@ import static com.google.common.collect.Lists.newArrayList; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -133,8 +135,8 @@ public void testAcceptInvitationsPage() throws Exception { codeData.put("email", "user@example.com"); codeData.put("client_id", "client-id"); codeData.put("redirect_uri", "blah.test.com"); - when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); - when(expiringCodeStore.generateCode(anyString(), anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); + when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); + when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); IdentityProvider provider = new IdentityProvider(); provider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(provider); @@ -156,8 +158,8 @@ public void testAcceptInvitationsPage() throws Exception { @Test public void acceptInvitePage_for_unverifiedSamlUser() throws Exception { Map codeData = getInvitationsCode("test-saml"); - when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); - when(expiringCodeStore.generateCode(anyString(), anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); + when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); + when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); IdentityProvider provider = new IdentityProvider(); SamlIdentityProviderDefinition definition = SamlIdentityProviderDefinition.Builder.get() .setMetaDataLocation("http://test.saml.com") @@ -184,8 +186,8 @@ public void acceptInvitePage_for_unverifiedSamlUser() throws Exception { @Test public void acceptInvitePage_for_unverifiedLdapUser() throws Exception { Map codeData = getInvitationsCode("ldap"); - when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); - when(expiringCodeStore.generateCode(anyString(), anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); + when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); + when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); IdentityProvider provider = new IdentityProvider(); provider.setType(OriginKeys.LDAP); @@ -217,8 +219,8 @@ private Map getInvitationsCode(String origin) { @Test public void unverifiedLdapUser_acceptsInvite_byLoggingIn() throws Exception { Map codeData = getInvitationsCode("ldap"); - when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); - when(expiringCodeStore.generateCode(anyString(),anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); + when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); + when(expiringCodeStore.generateCode(anyString(),anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); DynamicLdapAuthenticationManager ldapAuthenticationManager = mock(DynamicLdapAuthenticationManager.class); when(zoneAwareAuthenticationManager.getLdapAuthenticationManager(anyObject(), anyObject())).thenReturn(ldapAuthenticationManager); @@ -240,7 +242,7 @@ public void unverifiedLdapUser_acceptsInvite_byLoggingIn() throws Exception { when(scimUserProvisioning.retrieve("user-id-001")).thenReturn(invitedUser); when(invitationsService.acceptInvitation(anyString(), anyString())).thenReturn(new InvitationsService.AcceptedInvitation("blah.test.com", new ScimUser())); - when(expiringCodeStore.generateCode(anyString(), anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); + when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); mockMvc.perform(post("/invitations/accept_enterprise.do") .param("enterprise_username", "test-ldap-user") @@ -261,7 +263,7 @@ public void unverifiedLdapUser_acceptsInvite_byLoggingIn() throws Exception { @Test public void unverifiedLdapUser_acceptsInvite_byLoggingIn_whereEmailDoesNotMatchAuthenticatedEmail() throws Exception { Map codeData = getInvitationsCode("ldap"); - when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); + when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); DynamicLdapAuthenticationManager ldapAuthenticationManager = mock(DynamicLdapAuthenticationManager.class); when(zoneAwareAuthenticationManager.getLdapAuthenticationManager(anyObject(), anyObject())).thenReturn(ldapAuthenticationManager); @@ -277,7 +279,7 @@ public void unverifiedLdapUser_acceptsInvite_byLoggingIn_whereEmailDoesNotMatchA ScimUser invitedUser = new ScimUser("user-id-001", "user@example.com", "g", "f"); invitedUser.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve("user-id-001")).thenReturn(invitedUser); - when(expiringCodeStore.generateCode(anyString(), anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); + when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); mockMvc.perform(post("/invitations/accept_enterprise.do") .param("enterprise_username", "test-ldap-user") @@ -305,8 +307,8 @@ public void acceptInvitePage_for_verifiedUser() throws Exception { codeData.put("user_id", "verified-user"); codeData.put("email", "user@example.com"); - when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); - when(expiringCodeStore.generateCode(anyString(), anyObject())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); + when(expiringCodeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); + when(expiringCodeStore.generateCode(anyString(), anyObject(), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); when(invitationsService.acceptInvitation(anyString(), anyString())).thenReturn(new InvitationsService.AcceptedInvitation("blah.test.com", new ScimUser())); IdentityProvider provider = new IdentityProvider(); provider.setType(OriginKeys.UAA); diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java index dd72f905b63..bee9638a32a 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java @@ -63,7 +63,7 @@ public void authentication_successful() throws Exception { codeData.put("username", "test-username"); codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); codeData.put("action", ExpiringCodeType.AUTOLOGIN.name()); - when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData))); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData), null)); Authentication authenticate = manager.authenticate(authenticationToken); @@ -85,7 +85,7 @@ public void authentication_fails_withInvalidClient() { codeData.put("username", "test-username"); codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); codeData.put("action", ExpiringCodeType.AUTOLOGIN.name()); - when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData))); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData), null)); manager.authenticate(authenticationToken); } @@ -97,7 +97,7 @@ public void authentication_fails_withNoClientId() { codeData.put("username", "test-username"); codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); codeData.put("action", ExpiringCodeType.AUTOLOGIN.name()); - when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData))); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData), null)); manager.authenticate(authenticationToken); } @@ -115,7 +115,7 @@ public void authentication_fails_withCodeIntendedForDifferentPurpose() { codeData.put("client_id", "test-client-id"); codeData.put("username", "test-username"); codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); - when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData))); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData), null)); manager.authenticate(authenticationToken); } @@ -124,7 +124,7 @@ public void authentication_fails_withCodeIntendedForDifferentPurpose() { public void authentication_fails_withInvalidCode() { Map codeData = new HashMap<>(); codeData.put("action", "someotheraction"); - when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData))); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData), null)); manager.authenticate(authenticationToken); } diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java index 4ea5e3a741b..f0b70020c9a 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java @@ -110,7 +110,7 @@ public void testBeginActivation() throws Exception { String redirectUri = ""; String data = setUpForSuccess(redirectUri); when(scimUserProvisioning.createUser(any(ScimUser.class), anyString())).thenReturn(user); - when(codeStore.generateCode(eq(data), any(Timestamp.class))).thenReturn(code); + when(codeStore.generateCode(eq(data), any(Timestamp.class), eq(null))).thenReturn(code); emailAccountCreationService.beginActivation("user@example.com", "password", "login", redirectUri); @@ -136,7 +136,7 @@ public void testBeginActivationInOtherZone() throws Exception { RequestContextHolder.setRequestAttributes(attrs); when(scimUserProvisioning.createUser(any(ScimUser.class), anyString())).thenReturn(user); - when(codeStore.generateCode(eq(data), any(Timestamp.class))).thenReturn(code); + when(codeStore.generateCode(eq(data), any(Timestamp.class), eq(null))).thenReturn(code); emailAccountCreationService.beginActivation("user@example.com", "password", "login", redirectUri); String emailBody = captorEmailBody("Activate your account"); @@ -152,7 +152,7 @@ public void testBeginActivationWithOssBrand() throws Exception { emailAccountCreationService = initEmailAccountCreationService("oss"); String data = setUpForSuccess(null); when(scimUserProvisioning.createUser(any(ScimUser.class), anyString())).thenReturn(user); - when(codeStore.generateCode(eq(data), any(Timestamp.class))).thenReturn(code); + when(codeStore.generateCode(eq(data), any(Timestamp.class), eq(null))).thenReturn(code); emailAccountCreationService.beginActivation("user@example.com", "password", "login", null); @@ -179,7 +179,7 @@ public void testBeginActivationWithUnverifiedExistingUser() throws Exception { user.setVerified(false); when(scimUserProvisioning.createUser(any(ScimUser.class), anyString())).thenThrow(new ScimResourceAlreadyExistsException("duplicate")); when(scimUserProvisioning.query(anyString())).thenReturn(Arrays.asList(new ScimUser[]{user})); - when(codeStore.generateCode(eq(data), any(Timestamp.class))).thenReturn(code); + when(codeStore.generateCode(eq(data), any(Timestamp.class), eq(null))).thenReturn(code); MockHttpServletRequest request = new MockHttpServletRequest(); request.setProtocol("http"); @@ -305,7 +305,7 @@ private String setUpForSuccess(String userId, String redirectUri) throws Excepti data.put("redirect_uri", redirectUri); } - code = new ExpiringCode("the_secret_code", ts, JsonUtils.writeValueAsString(data)); + code = new ExpiringCode("the_secret_code", ts, JsonUtils.writeValueAsString(data), null); when(details.getClientId()).thenReturn("login"); when(details.getRegisteredRedirectUri()).thenReturn(Collections.singleton("http://example.com/*")); diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java index 1eedda57d35..64b72b84910 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java @@ -205,7 +205,7 @@ public void testCompleteActivationWithInvalidClientId() { codeData.put("client_id", "invalid-client"); codeData.put("email", "new@example.com"); - when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); ScimUser user = new ScimUser("user-001", "user@example.com", "", ""); user.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve("user-001")).thenReturn(user); @@ -246,7 +246,7 @@ private Map setUpCompleteActivation(String username, String clie BaseClientDetails clientDetails = new BaseClientDetails("client-id", null, null, "authorization_grant", null, "http://app.com/*"); clientDetails.addAdditionalInformation(CHANGE_EMAIL_REDIRECT_URL, "http://fallback.url/redirect"); - when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData))); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(codeData), null)); ScimUser user = new ScimUser("user-001", username, "", ""); user.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve("user-001")).thenReturn(user); @@ -274,11 +274,11 @@ private void setUpForBeginEmailChange() { when(scimUserProvisioning.retrieve("user-001")).thenReturn(user); when(scimUserProvisioning.query(anyString())).thenReturn(Collections.singletonList(new ScimUser())); String data = JsonUtils.writeValueAsString(codeData); - when(codeStore.generateCode(eq(data), any(Timestamp.class))).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), data)); + when(codeStore.generateCode(eq(data), any(Timestamp.class), eq(null))).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), data, null)); emailChangeEmailService.beginEmailChange("user-001", "user@example.com", "new@example.com", "app", "http://app.com"); - verify(codeStore).generateCode(eq(JsonUtils.writeValueAsString(codeData)), any(Timestamp.class)); + verify(codeStore).generateCode(eq(JsonUtils.writeValueAsString(codeData)), any(Timestamp.class), eq(null)); } } diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java index 5536730ea48..74f859e2209 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java @@ -98,7 +98,7 @@ public void acceptInvitationNoClientId() throws Exception { Map userData = new HashMap<>(); userData.put(USER_ID, "user-id-001"); userData.put(EMAIL, "user@example.com"); - when(expiringCodeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData))); + when(expiringCodeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData), null)); String redirectLocation = emailInvitationsService.acceptInvitation("code", "password").getRedirectUri(); verify(scimUserProvisioning).verifyUser(user.getId(), user.getVersion()); @@ -119,7 +119,7 @@ public void acceptInvitationWithClientNotFound() throws Exception { userData.put(USER_ID, "user-id-001"); userData.put(EMAIL, "user@example.com"); userData.put(CLIENT_ID, "client-not-found"); - when(expiringCodeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData))); + when(expiringCodeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData), null)); String redirectLocation = emailInvitationsService.acceptInvitation("code", "password").getRedirectUri(); @@ -143,7 +143,7 @@ public void acceptInvitationWithValidRedirectUri() throws Exception { userData.put(EMAIL, "user@example.com"); userData.put(CLIENT_ID, "acmeClientId"); userData.put(REDIRECT_URI, "http://example.com/redirect/"); - when(expiringCodeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData))); + when(expiringCodeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData), null)); String redirectLocation = emailInvitationsService.acceptInvitation("code", "password").getRedirectUri(); @@ -166,7 +166,7 @@ public void acceptInvitationWithInvalidRedirectUri() throws Exception { userData.put(EMAIL, "user@example.com"); userData.put(REDIRECT_URI, "http://someother/redirect"); userData.put(CLIENT_ID, "acmeClientId"); - when(expiringCodeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData))); + when(expiringCodeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData), null)); String redirectLocation = emailInvitationsService.acceptInvitation("code", "password").getRedirectUri(); @@ -196,7 +196,7 @@ public void accept_invitation_with_external_user_that_does_not_have_email_as_the userData.put(EMAIL, userBeforeAccept.getPrimaryEmail()); userData.put(REDIRECT_URI, "http://someother/redirect"); userData.put(CLIENT_ID, "acmeClientId"); - when(expiringCodeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData))); + when(expiringCodeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), JsonUtils.writeValueAsString(userData), null)); ScimUser userAfterAccept = new ScimUser(userId, actualUsername, userBeforeAccept.getGivenName(), userBeforeAccept.getFamilyName()); userAfterAccept.setPrimaryEmail(email); diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java index 9e111973c31..c6178a565e7 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java +++ b/login/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java @@ -185,7 +185,7 @@ public void forgotPassword_SuccessfulInOtherZone() throws Exception { } private void forgotPasswordSuccessful(String url, String brand, IdentityZone zone) throws Exception { - when(resetPasswordService.forgotPassword("user@example.com", "example", "redirect.example.com")).thenReturn(new ForgotPasswordInfo("123", new ExpiringCode("code1", new Timestamp(System.currentTimeMillis()), "someData"))); + when(resetPasswordService.forgotPassword("user@example.com", "example", "redirect.example.com")).thenReturn(new ForgotPasswordInfo("123", new ExpiringCode("code1", new Timestamp(System.currentTimeMillis()), "someData", null))); MockHttpServletRequestBuilder post = post("/forgot_password.do") .contentType(APPLICATION_FORM_URLENCODED) .param("email", "user@example.com") @@ -235,8 +235,8 @@ public void testInstructions() throws Exception { @Test public void testResetPasswordPage() throws Exception { - ExpiringCode code = new ExpiringCode("code1", new Timestamp(System.currentTimeMillis()), "someData"); - when(codeStore.generateCode(anyString(), any(Timestamp.class))).thenReturn(code); + ExpiringCode code = new ExpiringCode("code1", new Timestamp(System.currentTimeMillis()), "someData", null); + when(codeStore.generateCode(anyString(), any(Timestamp.class), eq(null))).thenReturn(code); when(codeStore.retrieveCode(anyString())).thenReturn(code); mockMvc.perform(get("/reset_password").param("email", "user@example.com").param("code", "secret_code")) .andExpect(status().isOk()) diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java b/models/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java index e4afea35e03..c43baf47385 100644 --- a/models/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java +++ b/models/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java @@ -28,13 +28,16 @@ public class ExpiringCode { private String data; + private String intent; + public ExpiringCode() { } - public ExpiringCode(String code, Timestamp expiresAt, String data) { + public ExpiringCode(String code, Timestamp expiresAt, String data, String intent) { this.code = code; this.expiresAt = expiresAt; this.data = data; + this.intent = intent; } public String getCode() { @@ -61,6 +64,14 @@ public void setData(String data) { this.data = data; } + public String getIntent() { + return intent; + } + + public void setIntent(String intent) { + this.intent = intent; + } + @JsonIgnore public boolean isExpired() { if (expiresAt == null) @@ -94,7 +105,7 @@ public int hashCode() { @Override public String toString() { - return "ExpiringCode [code=" + code + ", expiresAt=" + expiresAt + ", data=" + trimToLength(data, 1024) + "]"; + return "ExpiringCode [code=" + code + ", expiresAt=" + expiresAt + ", data=" + trimToLength(data, 1024) + ", intent=" + intent + "]"; } private String trimToLength(String s, int length) { @@ -105,5 +116,4 @@ private String trimToLength(String s, int length) { return s.substring(0, min); } } - } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java index 59d242dc68d..20a26f3f89b 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java @@ -143,7 +143,7 @@ public ForgotPasswordInfo forgotPassword(String email, String clientId, String r ScimUser scimUser = results.get(0); PasswordChange change = new PasswordChange(scimUser.getId(), scimUser.getUserName(), scimUser.getPasswordLastModified(), clientId, redirectUri); - ExpiringCode code = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + PASSWORD_RESET_LIFETIME)); + ExpiringCode code = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + PASSWORD_RESET_LIFETIME), null); publish(new ResetPasswordRequestEvent(email, code.getCode(), SecurityContextHolder.getContext().getAuthentication())); return new ForgotPasswordInfo(scimUser.getId(), code); } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java index fdfbefab5a5..a68462d347f 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java @@ -62,7 +62,7 @@ public ResponseEntity generateEmailVerificationCode(@RequestBody EmailCh String code; try { - code = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(emailChange), new Timestamp(System.currentTimeMillis() + EMAIL_CHANGE_LIFETIME)).getCode(); + code = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(emailChange), new Timestamp(System.currentTimeMillis() + EMAIL_CHANGE_LIFETIME), null).getCode(); } catch (JsonUtils.JsonUtilException e) { throw new UaaException("Error while generating change email code", e); } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java index 7e17016c24e..b218b2c4310 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java @@ -138,7 +138,7 @@ private ExpiringCode getCode(String id, String username, String clientId) { codeData.put(OAuth2Utils.CLIENT_ID, clientId); codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); codeData.put("action", ExpiringCodeType.AUTOLOGIN.name()); - return codeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000)); + return codeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000), null); } @ExceptionHandler(InvalidPasswordException.class) diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/util/ScimUtils.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/util/ScimUtils.java index bfeda70a196..b62b13924d0 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/util/ScimUtils.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/util/ScimUtils.java @@ -8,12 +8,10 @@ import org.slf4j.LoggerFactory; import org.springframework.util.Assert; -import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.sql.Timestamp; import java.util.HashMap; -import java.util.IllegalFormatCodePointException; import java.util.Map; /******************************************************************************* @@ -65,7 +63,7 @@ public static ExpiringCode getExpiringCode(ExpiringCodeStore codeStore, String u String codeDataString = JsonUtils.writeValueAsString(codeData); Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + (60 * 60 * 1000)); // 1 hour - return codeStore.generateCode(codeDataString, expiresAt); + return codeStore.generateCode(codeDataString, expiresAt, null); } /** diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java index 0478adaeec2..5fd668fde3d 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java @@ -15,7 +15,6 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.error.InvalidCodeException; -import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.login.ResetPasswordService.ResetPasswordResponse; import org.cloudfoundry.identity.uaa.password.event.ResetPasswordRequestEvent; import org.cloudfoundry.identity.uaa.scim.ScimMeta; @@ -92,7 +91,7 @@ public void forgotPassword_ResetCodeIsReturnedSuccessfully() throws Exception { Timestamp expiresAt = new Timestamp(System.currentTimeMillis()); when(codeStore.generateCode(eq("{\"user_id\":\"user-id-001\",\"username\":\"user@example.com\",\"passwordModifiedTime\":1234,\"client_id\":\"example\",\"redirect_uri\":\"redirect.example.com\"}"), - any(Timestamp.class))).thenReturn(new ExpiringCode("code", expiresAt, "user-id-001")); + any(Timestamp.class), eq(null))).thenReturn(new ExpiringCode("code", expiresAt, "user-id-001", null)); ForgotPasswordInfo forgotPasswordInfo = emailResetPasswordService.forgotPassword("user@example.com", "example", "redirect.example.com"); assertThat(forgotPasswordInfo.getUserId(), equalTo("user-id-001")); @@ -113,7 +112,7 @@ public void forgotPassword_PublishesResetPasswordRequestEvent() throws Exception user.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.query(contains("origin"))).thenReturn(Arrays.asList(user)); Timestamp expiresAt = new Timestamp(System.currentTimeMillis()); - when(codeStore.generateCode(anyString(), any(Timestamp.class))).thenReturn(new ExpiringCode("code", expiresAt, "user-id-001")); + when(codeStore.generateCode(anyString(), any(Timestamp.class), eq(null))).thenReturn(new ExpiringCode("code", expiresAt, "user-id-001", null)); emailResetPasswordService.forgotPassword("user@example.com", "", ""); ArgumentCaptor captor = ArgumentCaptor.forClass(ResetPasswordRequestEvent.class); @@ -130,8 +129,8 @@ public void forgotPassword_ThrowsConflictException() throws Exception { user.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.query(contains("origin"))).thenReturn(Arrays.asList(new ScimUser[]{})); when(scimUserProvisioning.query(eq("userName eq \"user@example.com\""))).thenReturn(Arrays.asList(new ScimUser[]{user})); - when(codeStore.generateCode(anyString(), any(Timestamp.class))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), "user-id-001")); - when(codeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()),"user-id-001")); + when(codeStore.generateCode(anyString(), any(Timestamp.class), eq(null))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), "user-id-001", null)); + when(codeStore.retrieveCode(anyString())).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()),"user-id-001", null)); try { emailResetPasswordService.forgotPassword("user@example.com", "", ""); @@ -183,7 +182,7 @@ public void resetPassword_InvalidPasswordException_NewPasswordSameAsOld() { user.setMeta(new ScimMeta(new Date(), new Date(), 0)); user.setPrimaryEmail("foo@example.com"); ExpiringCode expiringCode = new ExpiringCode("good_code", - new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "user-id"); + new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "user-id", null); when(codeStore.retrieveCode("good_code")).thenReturn(expiringCode); when(scimUserProvisioning.retrieve("user-id")).thenReturn(user); when(scimUserProvisioning.checkPasswordMatches("user-id", "Passwo3dAsOld")) @@ -243,7 +242,7 @@ private void setupResetPassword(String clientId, String redirectUri) { user.setPrimaryEmail("user@example.com"); when(scimUserProvisioning.retrieve(eq("usermans-id"))).thenReturn(user); when(codeStore.retrieveCode(eq("secret_code"))).thenReturn(new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), - "{\"user_id\":\"usermans-id\",\"username\":\"userman\",\"passwordModifiedTime\":null,\"client_id\":\"" + clientId + "\",\"redirect_uri\":\"" + redirectUri + "\"}")); + "{\"user_id\":\"usermans-id\",\"username\":\"userman\",\"passwordModifiedTime\":null,\"client_id\":\"" + clientId + "\",\"redirect_uri\":\"" + redirectUri + "\"}", null)); SecurityContext securityContext = mock(SecurityContext.class); when(securityContext.getAuthentication()).thenReturn(new MockAuthentication()); SecurityContextHolder.setContext(securityContext); diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java index a5ab68e3a16..0abe4a3ce93 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java @@ -59,8 +59,8 @@ public void setUp() throws Exception { @Test public void testGenerateEmailChangeCode() throws Exception { String data = "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\",\"client_id\":null}"; - Mockito.when(expiringCodeStore.generateCode(eq(data), any(Timestamp.class))) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + 1000), data)); + Mockito.when(expiringCodeStore.generateCode(eq(data), any(Timestamp.class), eq(null))) + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + 1000), data, null)); ScimUser userChangingEmail = new ScimUser("user-id-001", "user@example.com", null, null); userChangingEmail.setOrigin("test"); @@ -80,8 +80,8 @@ public void testGenerateEmailChangeCode() throws Exception { @Test public void testGenerateEmailChangeCodeWithExistingUsernameChange() throws Exception { String data = "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\",\"client_id\":null}"; - Mockito.when(expiringCodeStore.generateCode(eq(data), any(Timestamp.class))) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + 1000), data)); + Mockito.when(expiringCodeStore.generateCode(eq(data), any(Timestamp.class), eq(null))) + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + 1000), data, null)); ScimUser userChangingEmail = new ScimUser("id001", "user@example.com", null, null); userChangingEmail.setPrimaryEmail("user@example.com"); @@ -103,7 +103,7 @@ public void testGenerateEmailChangeCodeWithExistingUsernameChange() throws Excep @Test public void testChangeEmail() throws Exception { Mockito.when(expiringCodeStore.retrieveCode("the_secret_code")) - .thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\", \"client_id\":\"app\"}")); + .thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\", \"client_id\":\"app\"}", null)); BaseClientDetails clientDetails = new BaseClientDetails(); Map additionalInformation = new HashMap<>(); @@ -144,7 +144,7 @@ public void testChangeEmail() throws Exception { @Test public void testChangeEmailWhenUsernameNotTheSame() throws Exception { Mockito.when(expiringCodeStore.retrieveCode("the_secret_code")) - .thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\",\"client_id\":null}")); + .thenReturn(new ExpiringCode("the_secret_code", new Timestamp(System.currentTimeMillis()), "{\"userId\":\"user-id-001\",\"email\":\"new@example.com\",\"client_id\":null}", null)); ScimUser scimUser = new ScimUser(); scimUser.setUserName("username"); diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java index b1a75a8ce1e..75b5cf41ec9 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java @@ -80,11 +80,11 @@ public void setUp() throws Exception { PasswordChange change = new PasswordChange("id001", "user@example.com", yesterday, null, null); - when(expiringCodeStore.generateCode(eq("id001"), any(Timestamp.class))) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "id001")); + when(expiringCodeStore.generateCode(eq("id001"), any(Timestamp.class), eq(null))) + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "id001", null)); - when(expiringCodeStore.generateCode(eq(JsonUtils.writeValueAsString(change)), any(Timestamp.class))) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), JsonUtils.writeValueAsString(change))); + when(expiringCodeStore.generateCode(eq(JsonUtils.writeValueAsString(change)), any(Timestamp.class), eq(null))) + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), JsonUtils.writeValueAsString(change), null)); } @Test @@ -99,8 +99,8 @@ public void password_reset_with_client_id_and_redirect_uri() throws Exception { .thenReturn(Arrays.asList(user)); PasswordChange change = new PasswordChange("id001", email, yesterday, clientId, redirectUri); - when(expiringCodeStore.generateCode(anyString(), any(Timestamp.class))) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), JsonUtils.writeValueAsString(change))); + when(expiringCodeStore.generateCode(anyString(), any(Timestamp.class), eq(null))) + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), JsonUtils.writeValueAsString(change), null)); MockHttpServletRequestBuilder post = post("/password_resets") .contentType(APPLICATION_JSON) @@ -112,7 +112,7 @@ public void password_reset_with_client_id_and_redirect_uri() throws Exception { mockMvc.perform(post) .andExpect(status().isCreated()); - verify(expiringCodeStore).generateCode(eq(JsonUtils.writeValueAsString(change)), any(Timestamp.class)); + verify(expiringCodeStore).generateCode(eq(JsonUtils.writeValueAsString(change)), any(Timestamp.class), eq(null)); } @Test @@ -125,8 +125,8 @@ public void password_reset_without_client_id_and_without_redirect_uri() throws E .thenReturn(Arrays.asList(user)); PasswordChange change = new PasswordChange("id001", email, yesterday, null, null); - when(expiringCodeStore.generateCode(anyString(), any(Timestamp.class))) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), JsonUtils.writeValueAsString(change))); + when(expiringCodeStore.generateCode(anyString(), any(Timestamp.class), eq(null))) + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), JsonUtils.writeValueAsString(change), null)); MockHttpServletRequestBuilder post = post("/password_resets") .contentType(APPLICATION_JSON) @@ -136,7 +136,7 @@ public void password_reset_without_client_id_and_without_redirect_uri() throws E mockMvc.perform(post) .andExpect(status().isCreated()); - verify(expiringCodeStore).generateCode(eq(JsonUtils.writeValueAsString(change)), any(Timestamp.class)); + verify(expiringCodeStore).generateCode(eq(JsonUtils.writeValueAsString(change)), any(Timestamp.class), eq(null)); } @Test @@ -205,8 +205,8 @@ public void testCreatingAPasswordResetWithAUsernameContainingSpecialCharacters() .thenReturn(Arrays.asList(user)); PasswordChange change = new PasswordChange("id001", "user\"'@example.com", yesterday, null, null); - when(expiringCodeStore.generateCode(eq(JsonUtils.writeValueAsString(change)), any(Timestamp.class))) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), JsonUtils.writeValueAsString(change))); + when(expiringCodeStore.generateCode(eq(JsonUtils.writeValueAsString(change)), any(Timestamp.class), eq(null))) + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), JsonUtils.writeValueAsString(change), null)); MockHttpServletRequestBuilder post = post("/password_resets") .contentType(APPLICATION_JSON) @@ -236,14 +236,14 @@ public void testCreatingAPasswordResetWithAUsernameContainingSpecialCharacters() @Test public void testChangingAPasswordWithAValidCode() throws Exception { when(expiringCodeStore.retrieveCode("secret_code")) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "eyedee")); + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "eyedee", null)); ScimUser scimUser = new ScimUser("eyedee", "user@example.com", "User", "Man"); scimUser.setMeta(new ScimMeta(new Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24)), new Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24)), 0)); scimUser.addEmail("user@example.com"); when(scimUserProvisioning.retrieve("eyedee")).thenReturn(scimUser); - ExpiringCode autologinCode = new ExpiringCode("autologin-code", new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000), "data"); - when(expiringCodeStore.generateCode(anyString(), any(Timestamp.class))).thenReturn(autologinCode); + ExpiringCode autologinCode = new ExpiringCode("autologin-code", new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000), "data", null); + when(expiringCodeStore.generateCode(anyString(), any(Timestamp.class), eq(null))).thenReturn(autologinCode); MockHttpServletRequestBuilder post = post("/password_change") .contentType(APPLICATION_JSON) @@ -280,7 +280,7 @@ public void changing_password_with_invalid_code() throws Exception { @Test public void testChangingAPasswordForUnverifiedUser() throws Exception { when(expiringCodeStore.retrieveCode("secret_code")) - .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "eyedee")); + .thenReturn(new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "eyedee", null)); ScimUser scimUser = new ScimUser("eyedee", "user@example.com", "User", "Man"); scimUser.setMeta(new ScimMeta(new Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24)), new Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24)), 0)); @@ -288,8 +288,8 @@ public void testChangingAPasswordForUnverifiedUser() throws Exception { scimUser.setVerified(false); when(scimUserProvisioning.retrieve("eyedee")).thenReturn(scimUser); - ExpiringCode autologinCode = new ExpiringCode("autologin-code", new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000), "data"); - when(expiringCodeStore.generateCode(anyString(), any(Timestamp.class))).thenReturn(autologinCode); + ExpiringCode autologinCode = new ExpiringCode("autologin-code", new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000), "data", null); + when(expiringCodeStore.generateCode(anyString(), any(Timestamp.class), eq(null))).thenReturn(autologinCode); MockHttpServletRequestBuilder post = post("/password_change") .contentType(APPLICATION_JSON) @@ -337,7 +337,7 @@ public void changePassword_Returns422UnprocessableEntity_NewPasswordSameAsOld() Mockito.reset(passwordValidator); when(expiringCodeStore.retrieveCode("emailed_code")) - .thenReturn(new ExpiringCode("emailed_code", new Timestamp(System.currentTimeMillis()+ UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "eyedee")); + .thenReturn(new ExpiringCode("emailed_code", new Timestamp(System.currentTimeMillis()+ UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "eyedee", null)); ScimUser scimUser = new ScimUser("eyedee", "user@example.com", "User", "Man"); scimUser.setMeta(new ScimMeta(new Date(System.currentTimeMillis()-(1000*60*60*24)), new Date(System.currentTimeMillis()-(1000*60*60*24)), 0)); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java index 2c6255eec74..606b4372257 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java @@ -219,7 +219,7 @@ public static String createInvitation(String baseUrl, String uaaUrl, String user } Timestamp expiry = new Timestamp(System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(System.currentTimeMillis() + 24 * 3600, TimeUnit.MILLISECONDS)); - ExpiringCode expiringCode = new ExpiringCode(null, expiry, "{\"origin\":\"" + origin + "\", \"client_id\":\"app\", \"redirect_uri\":\"" + redirectUri + "\", \"user_id\":\"" + userId + "\", \"email\":\"" + userEmail + "\"}"); + ExpiringCode expiringCode = new ExpiringCode(null, expiry, "{\"origin\":\"" + origin + "\", \"client_id\":\"app\", \"redirect_uri\":\"" + redirectUri + "\", \"user_id\":\"" + userId + "\", \"email\":\"" + userEmail + "\"}", null); HttpEntity expiringCodeRequest = new HttpEntity<>(expiringCode, headers); ResponseEntity expiringCodeResponse = uaaTemplate.exchange(uaaUrl + "/Codes", HttpMethod.POST, expiringCodeRequest, ExpiringCode.class); expiringCode = expiringCodeResponse.getBody(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java index cd86821fce2..c217b306c98 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/PasscodeMockMvcTests.java @@ -242,6 +242,56 @@ public void testLoginUsingPasscodeWithUnknownToken() throws Exception { .andExpect(status().isForbidden()); } + @Test + public void testLoginUsingOldPasscode() throws Exception { + UaaAuthenticationDetails details = new UaaAuthenticationDetails(new MockHttpServletRequest()); + UaaAuthentication uaaAuthentication = new UaaAuthentication(marissa, new ArrayList(),details); + + final MockSecurityContext mockSecurityContext = new MockSecurityContext(uaaAuthentication); + + SecurityContextHolder.setContext(mockSecurityContext); + MockHttpSession session = new MockHttpSession(); + + session.setAttribute( + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + mockSecurityContext + ); + + MockHttpServletRequestBuilder get = get("/passcode") + .accept(APPLICATION_JSON) + .session(session); + + String passcode = JsonUtils.readValue( + getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(), + String.class); + + // Get another code, which should expire the old. + getMockMvc().perform(get("/passcode") + .accept(APPLICATION_JSON) + .session(session)); + + mockSecurityContext.setAuthentication(null); + session = new MockHttpSession(); + session.setAttribute( + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + mockSecurityContext + ); + + String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64(("cf:").getBytes())); + MockHttpServletRequestBuilder post = post("/oauth/token") + .accept(APPLICATION_JSON) + .contentType(APPLICATION_FORM_URLENCODED) + .header("Authorization", basicDigestHeaderValue) + .param("grant_type", "password") + .param("passcode", passcode) + .param("response_type", "token"); + + getMockMvc().perform(post) + .andExpect(status().isUnauthorized()); + } + public static class MockSecurityContext implements SecurityContext { private static final long serialVersionUID = -1386535243513362694L; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java index 46e6f95ca74..2779a29e75e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java @@ -80,7 +80,7 @@ public void testResettingAPasswordUsingUsernameToEnsureNoModification() throws E assertEquals(1, users.size()); PasswordChange change = new PasswordChange(users.get(0).getId(), users.get(0).getUserName(), users.get(0).getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME)); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null); MvcResult mvcResult = getMockMvc().perform(createChangePasswordRequest(users.get(0), code, true)) .andExpect(status().isFound()) @@ -107,7 +107,7 @@ public void testResettingAPasswordFailsWhenUsernameChanged() throws Exception { ScimUser user = users.get(0); PasswordChange change = new PasswordChange(user.getId(), user.getUserName(), user.getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000)); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), null); String formerUsername = user.getUserName(); user.setUserName("newusername"); @@ -133,7 +133,7 @@ public void testResettingAPasswordChangesCodeInForm() throws Exception { PasswordChange change = new PasswordChange(user.getId(), user.getUserName(), user.getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000)); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), null); MockHttpServletRequestBuilder get = get("/reset_password?code={code}&email={email}", code.getCode(), user.getPrimaryEmail()) .accept(MediaType.TEXT_HTML); @@ -214,7 +214,7 @@ public void testResettingAPasswordFailsWhenPasswordChanged() throws Exception { ScimUserProvisioning userProvisioning = getWebApplicationContext().getBean(ScimUserProvisioning.class); Thread.sleep(1000 - (System.currentTimeMillis() % 1000) + 10); //because password last modified is second only PasswordChange change = new PasswordChange(user.getId(), user.getUserName(), user.getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000)); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), null); userProvisioning.changePassword(user.getId(), "secret", "secr3t"); getMockMvc().perform(createChangePasswordRequest(user, code, true)) @@ -226,7 +226,7 @@ public void testResettingAPasswordNoCsrfParameter() throws Exception { List users = getWebApplicationContext().getBean(ScimUserProvisioning.class).query("username eq \"marissa\""); assertNotNull(users); assertEquals(1, users.size()); - ExpiringCode code = codeStore.generateCode(users.get(0).getId(), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME)); + ExpiringCode code = codeStore.generateCode(users.get(0).getId(), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null); getMockMvc().perform(createChangePasswordRequest(users.get(0), code, false)) .andExpect(status().isFound()) @@ -238,7 +238,7 @@ public void testResettingAPasswordUsingTimestampForUserModification() throws Exc List users = getWebApplicationContext().getBean(ScimUserProvisioning.class).query("username eq \"marissa\""); assertNotNull(users); assertEquals(1, users.size()); - ExpiringCode code = codeStore.generateCode(users.get(0).getId(), new Timestamp(System.currentTimeMillis()+ UaaResetPasswordService.PASSWORD_RESET_LIFETIME)); + ExpiringCode code = codeStore.generateCode(users.get(0).getId(), new Timestamp(System.currentTimeMillis()+ UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null); MockHttpServletRequestBuilder post = createChangePasswordRequest(users.get(0), code, true, "newpassw0rD", "newpassw0rD"); @@ -266,10 +266,10 @@ public void resetPassword_ReturnsUnprocessableEntity_NewPasswordSameAsOld() thro assertEquals(1, users.size()); ScimUser user = users.get(0); - ExpiringCode code = codeStore.generateCode(user.getId(), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME)); + ExpiringCode code = codeStore.generateCode(user.getId(), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null); getMockMvc().perform(createChangePasswordRequest(user, code, true, "d3faultPasswd", "d3faultPasswd")); - code = codeStore.generateCode(user.getId(), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME)); + code = codeStore.generateCode(user.getId(), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null); getMockMvc().perform(createChangePasswordRequest(user, code, true, "d3faultPasswd", "d3faultPasswd")) .andExpect(status().isUnprocessableEntity()) .andExpect(view().name("forgot_password")) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/codestore/ExpiringCodeStoreMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/codestore/ExpiringCodeStoreMockMvcTests.java index 2beeec63856..c26494a7a2c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/codestore/ExpiringCodeStoreMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/codestore/ExpiringCodeStoreMockMvcTests.java @@ -48,7 +48,7 @@ public void setUp() throws Exception { @Test public void testGenerateCode() throws Exception { Timestamp ts = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode code = new ExpiringCode(null, ts, "{}"); + ExpiringCode code = new ExpiringCode(null, ts, "{}", null); String requestBody = JsonUtils.writeValueAsString(code); MockHttpServletRequestBuilder post = post("/Codes") @@ -68,7 +68,7 @@ public void testGenerateCode() throws Exception { @Test public void testGenerateCodeWithInvalidScope() throws Exception { Timestamp ts = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode code = new ExpiringCode(null, ts, "{}"); + ExpiringCode code = new ExpiringCode(null, ts, "{}", null); TestClient testClient = new TestClient(getMockMvc()); String loginToken = testClient.getClientCredentialsOAuthAccessToken("admin", "adminsecret", "scim.read"); @@ -86,7 +86,7 @@ public void testGenerateCodeWithInvalidScope() throws Exception { @Test public void testGenerateCodeAnonymous() throws Exception { Timestamp ts = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode code = new ExpiringCode(null, ts, "{}"); + ExpiringCode code = new ExpiringCode(null, ts, "{}", null); String requestBody = JsonUtils.writeValueAsString(code); MockHttpServletRequestBuilder post = post("/Codes") @@ -101,7 +101,7 @@ public void testGenerateCodeAnonymous() throws Exception { @Test public void testGenerateCodeWithNullData() throws Exception { Timestamp ts = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode code = new ExpiringCode(null, ts, null); + ExpiringCode code = new ExpiringCode(null, ts, null, null); String requestBody = JsonUtils.writeValueAsString(code); MockHttpServletRequestBuilder post = post("/Codes") .header("Authorization", "Bearer " + loginToken) @@ -116,7 +116,7 @@ public void testGenerateCodeWithNullData() throws Exception { @Test public void testGenerateCodeWithNullExpiresAt() throws Exception { - ExpiringCode code = new ExpiringCode(null, null, "{}"); + ExpiringCode code = new ExpiringCode(null, null, "{}", null); String requestBody = JsonUtils.writeValueAsString(code); MockHttpServletRequestBuilder post = post("/Codes") .header("Authorization", "Bearer " + loginToken) @@ -132,7 +132,7 @@ public void testGenerateCodeWithNullExpiresAt() throws Exception { @Test public void testGenerateCodeWithExpiresAtInThePast() throws Exception { Timestamp ts = new Timestamp(System.currentTimeMillis() - 60000); - ExpiringCode code = new ExpiringCode(null, ts, null); + ExpiringCode code = new ExpiringCode(null, ts, null, null); String requestBody = JsonUtils.writeValueAsString(code); MockHttpServletRequestBuilder post = post("/Codes") .header("Authorization", "Bearer " + loginToken) @@ -148,7 +148,7 @@ public void testGenerateCodeWithExpiresAtInThePast() throws Exception { @Test public void testRetrieveCode() throws Exception { Timestamp ts = new Timestamp(System.currentTimeMillis() + 60000); - ExpiringCode code = new ExpiringCode(null, ts, "{}"); + ExpiringCode code = new ExpiringCode(null, ts, "{}", null); String requestBody = JsonUtils.writeValueAsString(code); MockHttpServletRequestBuilder post = post("/Codes") .header("Authorization", "Bearer " + loginToken) @@ -178,7 +178,7 @@ public void testRetrieveCode() throws Exception { @Test public void testRetrieveCodeThatIsExpired() throws Exception { Timestamp ts = new Timestamp(System.currentTimeMillis() + 1000); - ExpiringCode code = new ExpiringCode(null, ts, "{}"); + ExpiringCode code = new ExpiringCode(null, ts, "{}", null); String requestBody = JsonUtils.writeValueAsString(code); MockHttpServletRequestBuilder post = post("/Codes") .header("Authorization", "Bearer " + loginToken) @@ -204,7 +204,7 @@ public void testRetrieveCodeThatIsExpired() throws Exception { @Test public void testCodeThatIsExpiredIsDeletedOnCreateOfNewCode() throws Exception { Timestamp ts = new Timestamp(System.currentTimeMillis() + 1000); - ExpiringCode code = new ExpiringCode(null, ts, "{}"); + ExpiringCode code = new ExpiringCode(null, ts, "{}", null); String requestBody = JsonUtils.writeValueAsString(code); MockHttpServletRequestBuilder post = post("/Codes") .header("Authorization", "Bearer " + loginToken) @@ -221,7 +221,7 @@ public void testCodeThatIsExpiredIsDeletedOnCreateOfNewCode() throws Exception { expireAllCodes(); ts = new Timestamp(System.currentTimeMillis() + 1000); - code = new ExpiringCode(null, ts, "{}"); + code = new ExpiringCode(null, ts, "{}", null); requestBody = JsonUtils.writeValueAsString(code); post = post("/Codes") .header("Authorization", "Bearer " + loginToken) @@ -240,7 +240,7 @@ public void testCodeThatIsExpiredIsDeletedOnCreateOfNewCode() throws Exception { @Test public void testCodeThatIsExpirationIntervalWorks() throws Exception { Timestamp ts = new Timestamp(System.currentTimeMillis() + 1000); - ExpiringCode code = new ExpiringCode(null, ts, "{}"); + ExpiringCode code = new ExpiringCode(null, ts, "{}", null); String requestBody = JsonUtils.writeValueAsString(code); MockHttpServletRequestBuilder post = post("/Codes") .header("Authorization", "Bearer " + loginToken) @@ -259,7 +259,7 @@ public void testCodeThatIsExpirationIntervalWorks() throws Exception { try { getWebApplicationContext().getBean(JdbcExpiringCodeStore.class).setExpirationInterval(10000000); ts = new Timestamp(System.currentTimeMillis() + 1000); - code = new ExpiringCode(null, ts, "{}"); + code = new ExpiringCode(null, ts, "{}", null); requestBody = JsonUtils.writeValueAsString(code); post = post("/Codes") .header("Authorization", "Bearer " + loginToken) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java index 11bdd02f913..ca08f45717e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java @@ -438,7 +438,7 @@ public void resetPasswordControllerResetPasswordNotAllowed() throws Exception { private ExpiringCode getExpiringCode(Object data) { Timestamp fiveMinutes = new Timestamp(System.currentTimeMillis()+(1000*60*5)); - return codeStore.generateCode(JsonUtils.writeValueAsString(data), fiveMinutes); + return codeStore.generateCode(JsonUtils.writeValueAsString(data), fiveMinutes, null); } private CookieCsrfPostProcessor cookieCsrf() { From cb4122130c7efbddf91c4dccd1cda4299ac602dd Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Tue, 1 Dec 2015 10:50:00 -0800 Subject: [PATCH 024/103] Remove /Groups group membership filtering [#108991682] https://www.pivotaltracker.com/story/show/108991682 Signed-off-by: Paul Warren --- .../scim/endpoints/ScimGroupEndpoints.java | 4 +- .../endpoints/ScimGroupEndpointsTests.java | 10 - .../ScimGroupEndpointsMockMvcTests.java | 237 +++++++++++++----- 3 files changed, 170 insertions(+), 81 deletions(-) diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java index c4b69768f60..fd8fd188448 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java @@ -163,9 +163,7 @@ public SearchResults listGroups( throw new ScimException("Invalid filter expression: [" + filter + "]", HttpStatus.BAD_REQUEST); } - List input = securityContextAccessor.isUser() ? - filterForCurrentUser(result, startIndex, count, securityContextAccessor.getUserId()) - : filterForCurrentUser(result, startIndex, count, null); + List input = filterForCurrentUser(result, startIndex, count, null); if (!StringUtils.hasLength(attributesCommaSeparated)) { return new SearchResults<>(Arrays.asList(ScimCore.SCHEMAS), input, startIndex, count, diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java index 75d514bc230..cf5e0765526 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java @@ -683,16 +683,6 @@ public void testExceptionHandler() { HttpStatus.BAD_REQUEST); } - @Test - public void testListGroupsAsUser() { - endpoints.setSecurityContextAccessor(mockSecurityContextAccessor(userIds.get(0))); - try { - validateSearchResults(endpoints.listGroups("id,displayName", "id pr", "created", "ascending", 1, 100), 1); - } finally { - endpoints.setSecurityContextAccessor(null); - } - } - private void validateView(View view, HttpStatus status) { MockHttpServletResponse response = new MockHttpServletResponse(); try { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index f80ae577f41..49c1e245873 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.endpoints; +import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; @@ -29,6 +30,7 @@ import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; @@ -42,6 +44,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -61,6 +64,9 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -82,6 +88,9 @@ public class ScimGroupEndpointsMockMvcTests extends InjectedMockContextTest { private RandomValueStringGenerator generator = new RandomValueStringGenerator(); private List defaultExternalMembers; private List databaseExternalMembers; + private String clientId; + private String clientSecret; + private TestClient testClient; @Before public void setUp() throws Exception { @@ -94,20 +103,20 @@ public void setUp() throws Exception { ScimExternalGroupBootstrap bootstrap = getWebApplicationContext().getBean(ScimExternalGroupBootstrap.class); bootstrap.afterPropertiesSet(); - TestClient testClient = new TestClient(getMockMvc()); + testClient = new TestClient(getMockMvc()); String adminToken = testClient.getClientCredentialsOAuthAccessToken("admin", "adminsecret", "clients.read clients.write clients.secret clients.admin"); - String clientId = generator.generate().toLowerCase(); - String clientSecret = generator.generate().toLowerCase(); + clientId = generator.generate().toLowerCase(); + clientSecret = generator.generate().toLowerCase(); String authorities = "scim.read,scim.write,password.write,oauth.approvals,scim.create"; - utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("foo","bar"), Collections.singletonList("client_credentials"), authorities); + utils().createClient(this.getMockMvc(), adminToken, clientId, clientSecret, Collections.singleton("oauth"), Arrays.asList("foo","bar","scim.read"), Arrays.asList("client_credentials", "password"), authorities); scimReadToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.read password.write"); scimWriteToken = testClient.getClientCredentialsOAuthAccessToken(clientId, clientSecret,"scim.write password.write"); defaultExternalMembers = new LinkedList<>(originalDefaultExternalMembers); databaseExternalMembers = new LinkedList<>(originalDatabaseExternalMembers); - scimUser = createUser(scimWriteToken, new HashSet(Arrays.asList("scim.read", "scim.write", "scim.me"))); + scimUser = createUserAndAddToGroups(IdentityZone.getUaa(), new HashSet(Arrays.asList("scim.read", "scim.write", "scim.me"))); scimReadUserToken = testClient.getUserOAuthAccessToken("cf","", scimUser.getUserName(), "password", "scim.read"); identityClientToken = testClient.getClientCredentialsOAuthAccessToken("identity","identitysecret",""); } @@ -161,7 +170,7 @@ public void testIdentityClientManagesZoneAdmins() throws Exception { //add two users to the same zone for (int i=0; i<2; i++) { - ScimUser user = createUser(scimWriteToken, new HashSet(Arrays.asList("scim.read", "scim.write", "scim.me"))); + ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), new HashSet(Arrays.asList("scim.read", "scim.write", "scim.me"))); member = new ScimGroupMember(user.getId()); group = new ScimGroup(null, "zones."+zone.getId()+".admin", zone.getId()); group.setMembers(Arrays.asList(member)); @@ -313,15 +322,19 @@ public void testDBisDownDuringCreate() throws Exception { } @Test - public void testGetGroups() throws Exception { + public void getGroups_withScimReadTokens_returnsOkWithResults() throws Exception { MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups") .header("Authorization", "Bearer " + scimReadToken) .param("attributes", "displayName") .param("filter", "displayName co \"scim\"") .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON); - getMockMvc().perform(get) - .andExpect(status().isOk()); + MvcResult mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + + SearchResults searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + assertThat(searchResults.getResources().size(), is(5)); get = MockMvcRequestBuilders.get("/Groups") .header("Authorization", "Bearer " + scimReadUserToken) @@ -329,34 +342,63 @@ public void testGetGroups() throws Exception { .param("filter", "displayName co \"scim\"") .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON); - getMockMvc().perform(get) - .andExpect(status().isOk()); + mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + + searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + assertThat(searchResults.getResources().size(), is(5)); get = MockMvcRequestBuilders.get("/Groups") .header("Authorization", "Bearer " + scimReadToken) .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON); - getMockMvc().perform(get) - .andExpect(status().isOk()); + mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + + searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + assertThat(searchResults.getResources().size(), is(greaterThanOrEqualTo(15))); get = MockMvcRequestBuilders.get("/Groups") .header("Authorization", "Bearer " + scimReadUserToken) .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON); - getMockMvc().perform(get) - .andExpect(status().isOk()); - } - + mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + assertThat(searchResults.getResources().size(), is(greaterThanOrEqualTo(15))); + } @Test - public void testGetGroups_Using_ZoneAdmin_Token() throws Exception { + public void getGroupsInOtherZone_withZoneAdminToken_returnsOkWithResults() throws Exception { String subdomain = new RandomValueStringGenerator(8).generate(); BaseClientDetails bootstrapClient = null; MockMvcUtils.IdentityZoneCreationResult result = utils().createOtherIdentityZoneAndReturnResult( subdomain, getMockMvc(), getWebApplicationContext(), bootstrapClient ); + ScimGroup group1 = new ScimGroup(null, "scim.whatever", result.getIdentityZone().getId()); + ScimGroup group2 = new ScimGroup(null, "another.group", result.getIdentityZone().getId()); + + getMockMvc().perform(post("/Groups") + .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) + .header("Authorization", "bearer "+ result.getZoneAdminToken()) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(group1))) + .andExpect(status().isCreated()); + + getMockMvc().perform(post("/Groups") + .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) + .header("Authorization", "bearer "+result.getZoneAdminToken()) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(group2))) + .andExpect(status().isCreated()); + MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups") .header("Authorization", "Bearer " + result.getZoneAdminToken()) .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) @@ -364,55 +406,102 @@ subdomain, getMockMvc(), getWebApplicationContext(), bootstrapClient .param("filter", "displayName co \"scim\"") .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON); - getMockMvc().perform(get) - .andExpect(status().isOk()); + MvcResult mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); - get = MockMvcRequestBuilders.get("/Groups") - .header("Authorization", "Bearer " + result.getZoneAdminToken()) - .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) - .param("attributes", "displayName") - .param("filter", "displayName co \"scim\"") - .contentType(MediaType.APPLICATION_JSON) - .accept(APPLICATION_JSON); - getMockMvc().perform(get) - .andExpect(status().isOk()); + SearchResults searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + assertThat(searchResults.getResources().size(), is(1)); get = MockMvcRequestBuilders.get("/Groups") .header("Authorization", "Bearer " + result.getZoneAdminToken()) .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON); - getMockMvc().perform(get) - .andExpect(status().isOk()); + mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + + searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + assertThat(searchResults.getResources().size(), is(2)); + } + + @Test + public void getGroupsInOtherZone_withZoneUserToken_returnsOkWithResults() throws Exception{ + String subdomain = new RandomValueStringGenerator(8).generate(); + BaseClientDetails bootstrapClient = null; + MockMvcUtils.IdentityZoneCreationResult result = utils().createOtherIdentityZoneAndReturnResult( + subdomain, getMockMvc(), getWebApplicationContext(), bootstrapClient + ); + + String zonedClientId = "zonedClientId"; + String zonedClientSecret = "zonedClientSecret"; + BaseClientDetails zonedClientDetails = (BaseClientDetails) utils().createClient(getMockMvc(), result.getZoneAdminToken(), zonedClientId, zonedClientSecret, Collections.singleton("oauth"), Arrays.asList("scim.read"), Arrays.asList("client_credentials", "password"), "scim.read", null, result.getIdentityZone()); + zonedClientDetails.setClientSecret(zonedClientSecret); + + ScimUser zoneUser = createUserAndAddToGroups(result.getIdentityZone(), new HashSet(Arrays.asList("scim.read"))); + + String basicDigestHeaderValue = "Basic " + new String(Base64.encodeBase64((zonedClientId + ":" + zonedClientSecret).getBytes())); + MockHttpServletRequestBuilder oauthTokenPost = post("/oauth/token") + .with(new SetServerNameRequestPostProcessor(result.getIdentityZone().getSubdomain() + ".localhost")) + .header("Authorization", basicDigestHeaderValue) + .param("grant_type", "password") + .param("client_id", zonedClientId) + .param("username", zoneUser.getUserName()) + .param("password", "password") + .param("scope", "scim.read"); + MvcResult tokenResult = getMockMvc().perform(oauthTokenPost).andExpect(status().isOk()).andReturn(); + TestClient.OAuthToken oauthToken = JsonUtils.readValue(tokenResult.getResponse().getContentAsString(), TestClient.OAuthToken.class); + String zoneUserToken = oauthToken.accessToken; + + MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups") + .with(new SetServerNameRequestPostProcessor(result.getIdentityZone().getSubdomain() + ".localhost")) + .header("Authorization", "Bearer " + zoneUserToken) +// .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) + .param("attributes", "displayName") + .param("filter", "displayName co \"scim\"") + .contentType(MediaType.APPLICATION_JSON) + .accept(APPLICATION_JSON); + MvcResult mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + + SearchResults searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + assertThat(searchResults.getResources().size(), is(1)); get = MockMvcRequestBuilders.get("/Groups") - .header("Authorization", "Bearer " + result.getZoneAdminToken()) - .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) - .contentType(MediaType.APPLICATION_JSON) - .accept(APPLICATION_JSON); - getMockMvc().perform(get) - .andExpect(status().isOk()); + .with(new SetServerNameRequestPostProcessor(result.getIdentityZone().getSubdomain() + ".localhost")) + .header("Authorization", "Bearer " + zoneUserToken) +// .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) + .contentType(MediaType.APPLICATION_JSON) + .accept(APPLICATION_JSON); + mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + + searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + assertThat(searchResults.getResources().size(), is(1)); } @Test public void testGetGroupsInvalidFilter() throws Exception { MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups") - .header("Authorization", "Bearer " + scimReadToken) - .contentType(MediaType.APPLICATION_JSON) - .accept(APPLICATION_JSON) - .param("filter", "blabla eq \"test\""); + .header("Authorization", "Bearer " + scimReadToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(APPLICATION_JSON) + .param("filter", "blabla eq \"test\""); getMockMvc().perform(get) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()); get = MockMvcRequestBuilders.get("/Groups") - .header("Authorization", "Bearer " + scimReadUserToken) - .contentType(MediaType.APPLICATION_JSON) - .accept(APPLICATION_JSON) - .param("filter", "blabla eq \"test\""); + .header("Authorization", "Bearer " + scimReadUserToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(APPLICATION_JSON) + .param("filter", "blabla eq \"test\""); getMockMvc().perform(get) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()); } @Test @@ -873,35 +962,47 @@ protected void validateDbMembers(List expected, ScimGro } } - private ScimUser createUser(String token, Set scopes) throws Exception { + private ScimUser createUserAndAddToGroups(IdentityZone zone, Set groupNames) throws Exception { + if (zone == null) { + zone = IdentityZone.getUaa(); + } ScimUserProvisioning usersRepository = getWebApplicationContext().getBean(ScimUserProvisioning.class); ScimGroupProvisioning groupRepository = getWebApplicationContext().getBean(ScimGroupProvisioning.class); String email = "otheruser@"+generator.generate().toLowerCase()+".com"; ScimUser user = new ScimUser(null, email, "Other", "User"); user.addEmail(email); user.setVerified(true); - user = usersRepository.createUser(user, "password"); - - Collection groups = new LinkedList<>(); - for (String scope : scopes) { - List scimGroups = groupRepository.query("displayName eq \""+scope+"\""); - ScimUser.Group g; - if (scimGroups==null || scimGroups.isEmpty()) { - ScimGroup grp = new ScimGroup(null,scope,IdentityZoneHolder.get().getId()); - grp = groupRepository.create(grp); - scimGroups.add(grp); - g = new ScimUser.Group(grp.getId(), scope); - } else { - g = new ScimUser.Group(scimGroups.get(0).getId(), scope); + IdentityZone originalZone = IdentityZoneHolder.get(); + try { + if (zone != null) { + IdentityZoneHolder.set(zone); + } + user = usersRepository.createUser(user, "password"); + + Collection scimUserGroups = new LinkedList<>(); + for (String groupName : groupNames) { + List scimGroups = groupRepository.query("displayName eq \""+ groupName +"\""); + ScimUser.Group scimUserGroup; + ScimGroup group; + if (scimGroups==null || scimGroups.isEmpty()) { + group = new ScimGroup(null, groupName,IdentityZoneHolder.get().getId()); + group = groupRepository.create(group); + scimUserGroup = new ScimUser.Group(group.getId(), groupName); + } else { + group = scimGroups.get(0); + scimUserGroup = new ScimUser.Group(scimGroups.get(0).getId(), groupName); + } + scimUserGroups.add(scimUserGroup); + ScimGroupMembershipManager scimGroupMembershipManager = getWebApplicationContext().getBean(ScimGroupMembershipManager.class); + ScimGroupMember member = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.READER)); + try { + scimGroupMembershipManager.addMember(group.getId(), member); + } catch (MemberAlreadyExistsException x) {} } - groups.add(g); - ScimGroupMembershipManager scimGroupMembershipManager = getWebApplicationContext().getBean(ScimGroupMembershipManager.class); - ScimGroupMember member = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.READER)); - try { - scimGroupMembershipManager.addMember(scimGroups.get(0).getId(), member); - }catch (MemberAlreadyExistsException x) {} + user.setGroups(scimUserGroups); + } finally { + IdentityZoneHolder.set(originalZone); } - user.setGroups(groups); return user; } From e653d86d36dfd3eef0452bae24e721cc0f41f1ba Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Tue, 1 Dec 2015 10:51:40 -0800 Subject: [PATCH 025/103] [finishes #108973068] https://www.pivotaltracker.com/story/show/108973068 From 1e39fe3d3ab03c7ae6ef10e64ee1d8e45292ca24 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 1 Dec 2015 12:52:11 -0700 Subject: [PATCH 026/103] The UAA zone is a catch all zone if - uaa.url is localhost(default) - login.url is localhost(default) - no zones.internal.hostnames have been configured In other words, if the internal hostnames for the IdentityZoneResolvingFilter is {"localhost"} the UAA will accept any Host header. Zones are still supported for .localhost https://www.pivotaltracker.com/story/show/106892318 [#106892318] --- .../uaa/zone/IdentityZoneResolvingFilter.java | 12 ++- .../IdentityZoneResolvingMockMvcTest.java | 78 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneResolvingMockMvcTest.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java index dac3126d528..16124d176f8 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java @@ -22,7 +22,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -76,6 +75,10 @@ private String getSubdomain(String hostname) { return hostname.substring(0, hostname.length() - internalHostname.length() - 1); } } + //UAA is catch all if we haven't configured anything + if (defaultZoneHostnames.size()==1 && defaultZoneHostnames.contains("localhost")) { + return ""; + } return null; } @@ -93,7 +96,12 @@ public void setDefaultInternalHostnames(Set hostnames) { this.defaultZoneHostnames.addAll(hostnames); } + public synchronized void restoreDefaultHostnames(Set hostnames) { + this.defaultZoneHostnames.clear(); + this.defaultZoneHostnames.addAll(hostnames); + } + public Set getDefaultZoneHostnames() { - return defaultZoneHostnames; + return new HashSet<>(defaultZoneHostnames); } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneResolvingMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneResolvingMockMvcTest.java new file mode 100644 index 00000000000..e38fdc45f2c --- /dev/null +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneResolvingMockMvcTest.java @@ -0,0 +1,78 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.mock.zones; + +import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneResolvingFilter; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class IdentityZoneResolvingMockMvcTest extends InjectedMockContextTest { + + private Set originalHostnames; + @Before + public void storeSettings() { + originalHostnames = getWebApplicationContext().getBean(IdentityZoneResolvingFilter.class).getDefaultZoneHostnames(); + } + + @After + public void restoreSettings() { + getWebApplicationContext().getBean(IdentityZoneResolvingFilter.class).restoreDefaultHostnames(originalHostnames); + } + + @Test + public void testSwitchingZones() throws Exception { + // Authenticate with new Client in new Zone + getMockMvc().perform( + get("/login") + .header("Host", "testsomeother.ip.com") + ) + .andExpect(status().isOk()); + } + + @Test + public void testSwitchingZones_When_HostsConfigured() throws Exception { + Set hosts = new HashSet<>(Arrays.asList("localhost", "testsomeother.ip.com")); + getWebApplicationContext().getBean(IdentityZoneResolvingFilter.class).setDefaultInternalHostnames(hosts); + // Authenticate with new Client in new Zone + getMockMvc().perform( + get("/login") + .header("Host", "testsomeother.ip.com") + ) + .andExpect(status().isOk()); + getMockMvc().perform( + get("/login") + .header("Host", "localhost") + ) + .andExpect(status().isOk()); + + getMockMvc().perform( + get("/login") + .header("Host", "testsomeother2.ip.com") + ) + .andExpect(status().isNotFound()); + } + + + +} From 961ff585a299a5238f5f75981f48decac9e9c88c Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 1 Dec 2015 14:10:11 -0700 Subject: [PATCH 027/103] Fix javadoc for compilation errors in gradle --- .../identity/client/UaaContextFactory.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java index c5373208af5..12966fd517d 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java @@ -114,11 +114,11 @@ public TokenRequest tokenRequest() { /** * Authenticates the client and optionally the user and retrieves an access token + * Token request must be valid, see {@link TokenRequest#isValid()} * @param request - a fully configured token request * @return an authenticated UAA context with * @throws NullPointerException if the request object is null * @throws IllegalArgumentException if the token request is invalid - * @see {@link TokenRequest#isValid()} */ public UaaContext authenticate(TokenRequest request) { if (request == null) { @@ -138,8 +138,8 @@ public UaaContext authenticate(TokenRequest request) { /** * Not yet implemented - * @param tokenRequest - * @return + * @param tokenRequest - a configured TokenRequest + * @return an authenticated {@link UaaContext} */ protected UaaContext authenticateAuthCode(final TokenRequest tokenRequest) { AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); @@ -152,6 +152,11 @@ protected UaaContext authenticateAuthCode(final TokenRequest tokenRequest) { throw new UnsupportedOperationException(AUTHORIZATION_CODE +" is not yet implemented"); } + /** + * Performs and authorization_code grant, but uses a token to assert the user's identity. + * @param tokenRequest - a configured TokenRequest + * @return an authenticated {@link UaaContext} + */ protected UaaContext authenticateAuthCodeWithToken(final TokenRequest tokenRequest) { AuthorizationCodeAccessTokenProvider provider = new AuthorizationCodeAccessTokenProvider() { @Override From 4a792991394228de0dd688b7d79e63e932fb7ee5 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 1 Dec 2015 16:20:19 -0700 Subject: [PATCH 028/103] Since @RunWith(Parameterized.class) doesnt work in gradle for integration tests https://www.pivotaltracker.com/story/show/109308794 We will instead manually run the test twice. [#109308794] --- ...uthorizationCodeGrantIntegrationTests.java | 109 +++--------------- 1 file changed, 16 insertions(+), 93 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/AuthorizationCodeGrantIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/AuthorizationCodeGrantIntegrationTests.java index 47b2801ba9c..805249bca6d 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/AuthorizationCodeGrantIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/AuthorizationCodeGrantIntegrationTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,37 +12,24 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - import org.cloudfoundry.identity.uaa.ServerRunning; +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.junit.Rule; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.security.jwt.Jwt; import org.springframework.security.jwt.JwtHelper; import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; + +import java.util.Map; + +import static org.junit.Assert.assertTrue; /** * @author Dave Syer * @author Luke Taylor */ -@RunWith(Parameterized.class) public class AuthorizationCodeGrantIntegrationTests { @Rule @@ -53,84 +40,20 @@ public class AuthorizationCodeGrantIntegrationTests { @Rule public TestAccountSetup testAccountSetup = TestAccountSetup.standard(serverRunning, testAccounts); - @Parameters - public static List parameters() { - // Make it run twice to test cached approvals - return Arrays.asList(new Object[0], new Object[0]); - } - @Test public void testSuccessfulAuthorizationCodeFlow() throws Exception { - - HttpHeaders headers = new HttpHeaders(); - // TODO: should be able to handle just TEXT_HTML - headers.setAccept(Arrays.asList(MediaType.TEXT_HTML, MediaType.ALL)); - + testSuccessfulAuthorizationCodeFlow_Internal(); + testSuccessfulAuthorizationCodeFlow_Internal(); + } + public void testSuccessfulAuthorizationCodeFlow_Internal() throws Exception { AuthorizationCodeResourceDetails resource = testAccounts.getDefaultAuthorizationCodeResource(); - URI uri = serverRunning.buildUri("/oauth/authorize").queryParam("response_type", "code") - .queryParam("state", "mystateid").queryParam("client_id", resource.getClientId()) - .queryParam("redirect_uri", resource.getPreEstablishedRedirectUri()).build(); - ResponseEntity result = serverRunning.getForResponse(uri.toString(), headers); - assertEquals(HttpStatus.FOUND, result.getStatusCode()); - String location = result.getHeaders().getLocation().toString(); - - if (result.getHeaders().containsKey("Set-Cookie")) { - String cookie = result.getHeaders().getFirst("Set-Cookie"); - headers.set("Cookie", cookie); - } - - ResponseEntity response = serverRunning.getForString(location, headers); - // should be directed to the login screen... - assertTrue(response.getBody().contains("/login.do")); - assertTrue(response.getBody().contains("username")); - assertTrue(response.getBody().contains("password")); - - MultiValueMap formData = new LinkedMultiValueMap(); - formData.add("username", testAccounts.getUserName()); - formData.add("password", testAccounts.getPassword()); - - // Should be redirected to the original URL, but now authenticated - result = serverRunning.postForResponse("/login.do", headers, formData); - assertEquals(HttpStatus.FOUND, result.getStatusCode()); - - if (result.getHeaders().containsKey("Set-Cookie")) { - String cookie = result.getHeaders().getFirst("Set-Cookie"); - headers.set("Cookie", cookie); - } - - response = serverRunning.getForString(result.getHeaders().getLocation().toString(), headers); - if (response.getStatusCode() == HttpStatus.OK) { - // The grant access page should be returned - assertTrue(response.getBody().contains("

Application Authorization

")); - - formData.clear(); - formData.add("user_oauth_approval", "true"); - result = serverRunning.postForResponse("/oauth/authorize", headers, formData); - assertEquals(HttpStatus.FOUND, result.getStatusCode()); - location = result.getHeaders().getLocation().toString(); - } - else { - // Token cached so no need for second approval - assertEquals(HttpStatus.FOUND, response.getStatusCode()); - location = response.getHeaders().getLocation().toString(); - } - assertTrue("Wrong location: " + location, - location.matches(resource.getPreEstablishedRedirectUri() + ".*code=.+")); - - formData.clear(); - formData.add("client_id", resource.getClientId()); - formData.add("redirect_uri", resource.getPreEstablishedRedirectUri()); - formData.add("grant_type", "authorization_code"); - formData.add("code", location.split("code=")[1].split("&")[0]); - HttpHeaders tokenHeaders = new HttpHeaders(); - tokenHeaders.set("Authorization", - testAccounts.getAuthorizationHeader(resource.getClientId(), resource.getClientSecret())); - @SuppressWarnings("rawtypes") - ResponseEntity tokenResponse = serverRunning.postForMap("/oauth/token", formData, tokenHeaders); - assertEquals(HttpStatus.OK, tokenResponse.getStatusCode()); - @SuppressWarnings("unchecked") - Map body = tokenResponse.getBody(); + Map body = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, + testAccounts, + resource.getClientId(), + resource.getClientSecret(), + testAccounts.getUserName(), + testAccounts.getPassword()); Jwt token = JwtHelper.decode(body.get("access_token")); assertTrue("Wrong claims: " + token.getClaims(), token.getClaims().contains("\"aud\"")); assertTrue("Wrong claims: " + token.getClaims(), token.getClaims().contains("\"user_id\"")); From a7ba816c84cfc6e90c62881a12d5d6e976325b6e Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Tue, 1 Dec 2015 17:20:49 -0800 Subject: [PATCH 029/103] Fix listing groups in another zone - postgres and hsqldb are case sensitive. Previously, the identity-zone-id was being lower-cased as part of the sql query and no groups were being returned as a result. Signed-off-by: Madhura Bhave --- .../scim/jdbc/JdbcScimGroupProvisioning.java | 17 +++++++------- .../ScimUserEndpointsMockMvcTests.java | 23 +++++++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java index 9c0b6b51f66..8926234e930 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java @@ -58,10 +58,10 @@ public class JdbcScimGroupProvisioning extends AbstractQueryable impl public static final String UPDATE_GROUP_SQL = String.format( "update %s set version=?, displayName=?, lastModified=? where id=? and version=?", GROUP_TABLE); - public static final String GET_GROUPS_SQL = "select %s from %s where identity_zone_id='%s'"; - public static final String GET_GROUP_SQL = String.format("select %s from %s where id=? and identity_zone_id=?", GROUP_FIELDS, GROUP_TABLE); + public static final String ALL_GROUPS = String.format("select %s from %s", GROUP_FIELDS, GROUP_TABLE); + public static final String DELETE_GROUP_SQL = String.format("delete from %s where id=? and identity_zone_id=?", GROUP_TABLE); private final RowMapper rowMapper = new ScimGroupRowMapper(); @@ -75,15 +75,16 @@ public JdbcScimGroupProvisioning(JdbcTemplate jdbcTemplate, JdbcPagingListFactor @Override protected String getBaseSqlQuery() { - return String.format(GET_GROUPS_SQL, GROUP_FIELDS, GROUP_TABLE, IdentityZoneHolder.get().getId()); + return ALL_GROUPS; } @Override - protected String getQuerySQL(String filter, SearchQueryConverter.ProcessedFilter where) { - boolean containsWhereClause = getBaseSqlQuery().contains(" where "); - return filter == null || filter.trim().length()==0 ? - getBaseSqlQuery() : - getBaseSqlQuery() + (containsWhereClause ? " and " : " where ") + where.getSql(); + public List query(String filter, String sortBy, boolean ascending) { + if (StringUtils.hasText(filter)) { + filter += " and"; + } + filter += " identity_zone_id eq \""+IdentityZoneHolder.get().getId()+"\""; + return super.query(filter, sortBy, ascending); } @Override diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java index 3221f17343b..016d4b243ae 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java @@ -20,6 +20,7 @@ import org.cloudfoundry.identity.uaa.invitations.InvitationConstants; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; +import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.UserAlreadyVerifiedException; @@ -29,6 +30,7 @@ import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; +import org.hamcrest.MatcherAssert; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; @@ -60,6 +62,7 @@ import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.security.oauth2.common.util.OAuth2Utils.CLIENT_ID; import static org.springframework.security.oauth2.common.util.OAuth2Utils.REDIRECT_URI; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -337,6 +340,26 @@ public void verification_link_user_not_found() throws Exception{ .put("error", "scim_resource_not_found")))); } + @Test + public void listUsers_in_anotherZone() throws Exception { + String subdomain = generator.generate(); + MockMvcUtils.IdentityZoneCreationResult result = utils().createOtherIdentityZoneAndReturnResult(subdomain, getMockMvc(), getWebApplicationContext(), null); + String zoneAdminToken = result.getZoneAdminToken(); + createUser(getScimUser(), zoneAdminToken, IdentityZone.getUaa().getSubdomain(), result.getIdentityZone().getId()); + + MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Users") + .header("X-Identity-Zone-Subdomain", subdomain) + .header("Authorization", "Bearer " + zoneAdminToken) + .accept(APPLICATION_JSON); + + MvcResult mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + SearchResults searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + MatcherAssert.assertThat(searchResults.getResources().size(), is(1)); + + } + @Test public void testVerifyUser() throws Exception { verifyUser(scimReadWriteToken); From cdd9cef61c728a13cf40bb414ff1b973e8b296b0 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 1 Dec 2015 21:22:11 -0700 Subject: [PATCH 030/103] jacoco runs both tests and integration tests https://www.pivotaltracker.com/story/show/109313992 [#109313992] --- .travis.yml | 2 +- build.gradle | 122 ++++++++++++++++++++------------------- client-lib/build.gradle | 4 +- common/build.gradle | 5 ++ login/build.gradle | 5 ++ models/build.gradle | 5 ++ samples/api/build.gradle | 4 +- samples/app/build.gradle | 4 +- scim/build.gradle | 6 +- uaa/build.gradle | 4 +- 10 files changed, 89 insertions(+), 72 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22cf051ad8a..7b595a5cb8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ install: script: - ./gradlew -Dspring.profiles.active=$TESTENV jacocoRootReport after_success: -#- ./gradlew coveralls +- ./gradlew coveralls - for i in $(find $HOME/build/cloudfoundry/uaa/ -name reports -type d); do rm -rf $i; done - /bin/df -h - /usr/bin/du -sh * diff --git a/build.gradle b/build.gradle index 871393e7881..9e42aac9352 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,9 @@ buildscript { maven { url 'http://repo.spring.io/plugins-release' } + maven { + url 'https://plugins.gradle.org/m2/' + } } dependencies { @@ -17,9 +20,12 @@ buildscript { classpath group: 'postgresql', name: 'postgresql', version:'9.1-901.jdbc3' classpath group: 'org.flywaydb', name: 'flyway-gradle-plugin', version: flywayVersion classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.7' + classpath "gradle.plugin.com.dorongold.plugins:task-tree:1.2.1" } } +apply plugin: "com.dorongold.task-tree" + ext { databaseType = { List activeProfiles = System.getProperty('spring.profiles.active', 'default').split(',') @@ -56,21 +62,65 @@ allprojects { } } +apply plugin: 'org.flywaydb.flyway' + +flyway { + switch (databaseType()) { + case 'mysql': + driver = 'org.mariadb.jdbc.Driver' + url = 'jdbc:mysql://localhost:3306/uaa' + user = 'root' + password = 'changeme' + schemas = ['uaa'] + break + case 'postgresql': + driver = 'org.postgresql.Driver' + url = 'jdbc:postgresql:uaa' + user = 'root' + password = 'changeme' + break + } +} + +flywayClean.enabled = Boolean.valueOf(System.getProperty("flyway.clean", "true")) + +task prepareDatabase { + dependsOn { databaseType().equals('hsqldb') ? null : flywayClean } +} + +apply plugin: 'cargo' + +task cleanCargoConfDir { + delete file(System.getenv('TMPDIR') + '/cargo/conf') +} + +cargoStartLocal.dependsOn assemble, prepareDatabase +cargoRunLocal.dependsOn cleanCargoConfDir, assemble + +task run(dependsOn: cargoRunLocal) + subprojects { apply plugin: 'java' - [compileJava, compileTestJava]*.options*.compilerArgs = ['-Xlint:none'] + [compileJava, compileTestJava]*.options*.compilerArgs = ['-Xlint:none', '-nowarn'] sourceCompatibility = 1.8 targetCompatibility = 1.8 test { - jvmArgs += [ "-XX:MaxPermSize=512m", "-Xmx2048m" ] + jvmArgs += [ "-Xmx2048m" ] } + task integrationTest(type: Test) { + dependsOn rootProject.cargoStartLocal + } task packageSources(type: Jar) { classifier = 'sources' from sourceSets.main.allSource } + javadoc { + options.addStringOption('XDignore.symbol.file', '-quiet') + } + task javadocJar(type: Jar, dependsOn: javadoc) { classifier 'javadoc' from javadoc.destinationDir @@ -135,20 +185,6 @@ subprojects { } } -// apply plugin: 'artifactory' -// -// artifactoryPublish { -// publish { -// contextUrl = "http://repo.spring.io" -// repository { -// repoKey = version.endsWith('-SNAPSHOT') ? 'libs-snapshot-local' : 'libs-release-local' -// username = project.hasProperty('artifactory_user') ? "${artifactory_user}" : "" -// password = project.hasProperty('artifactory_password') ? "${artifactory_password}" : "" -// maven = true -// } -// } -// } - sourceSets { } @@ -164,43 +200,6 @@ subprojects { } } -apply plugin: 'org.flywaydb.flyway' - -flyway { - switch (databaseType()) { - case 'mysql': - driver = 'org.mariadb.jdbc.Driver' - url = 'jdbc:mysql://localhost:3306/uaa' - user = 'root' - password = 'changeme' - schemas = ['uaa'] - break - case 'postgresql': - driver = 'org.postgresql.Driver' - url = 'jdbc:postgresql:uaa' - user = 'root' - password = 'changeme' - break - } -} - -flywayClean.enabled = Boolean.valueOf(System.getProperty("flyway.clean", "true")) - -task prepareDatabase { - dependsOn { databaseType().equals('hsqldb') ? null : flywayClean } -} - -apply plugin: 'cargo' - -task cleanCargoConfDir { - delete file(System.getenv('TMPDIR') + '/cargo/conf') -} - -cargoStartLocal.dependsOn assemble, prepareDatabase -cargoRunLocal.dependsOn cleanCargoConfDir, assemble - -task run(dependsOn: cargoRunLocal) - cargo { containerId = 'tomcat7x' port = 8080 @@ -262,8 +261,19 @@ Project identityUaa = subprojects.find { it.name.equals('cloudfoundry-identity-u def publishedProjects = subprojects +assemble.dependsOn subprojects.assemble + +task integrationTest { + finalizedBy cargoStopLocal +} +integrationTest.dependsOn subprojects.integrationTest + +test.dependsOn subprojects.test +test.mustRunAfter integrationTest + + task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) { - dependsOn = subprojects.test + dependsOn = [integrationTest, test] additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs) sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs) classDirectories = files(subprojects.sourceSets.main.output) @@ -291,12 +301,8 @@ coveralls { tasks.coveralls { group = 'Coverage reports' description = 'Uploads the aggregated coverage report to Coveralls' - - dependsOn jacocoRootReport } -assemble.dependsOn subprojects.assemble -test.dependsOn subprojects.test // Log timings per task. class TimingsListener implements TaskExecutionListener, BuildListener, org.gradle.api.tasks.testing.TestListener { diff --git a/client-lib/build.gradle b/client-lib/build.gradle index 9b8396b51d1..872d127409e 100644 --- a/client-lib/build.gradle +++ b/client-lib/build.gradle @@ -19,9 +19,7 @@ test { exclude 'org/cloudfoundry/identity/client/integration/*.class' } -task integrationTest(type: Test) { - dependsOn parent.cargoStartLocal - +integrationTest { filter { includeTestsMatching "org.cloudfoundry.identity.client.integration.*" } diff --git a/common/build.gradle b/common/build.gradle index 4ce2be78f5b..55f70a9edc8 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -76,3 +76,8 @@ processResources { //https://www.pivotaltracker.com/story/show/74344574 filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-common') : line } } + +integrationTest {}.onlyIf { //disable since we don't have any + true == false +} + diff --git a/login/build.gradle b/login/build.gradle index d80298a53b9..150f78f559c 100644 --- a/login/build.gradle +++ b/login/build.gradle @@ -30,3 +30,8 @@ dependencies { processResources { filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-login') : line } } + +integrationTest {}.onlyIf { //disable since we don't have any + true == false +} + diff --git a/models/build.gradle b/models/build.gradle index a0429c43526..7822c55e384 100644 --- a/models/build.gradle +++ b/models/build.gradle @@ -27,3 +27,8 @@ processResources { //https://www.pivotaltracker.com/story/show/74344574 filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-models') : line } } + +integrationTest {}.onlyIf { //disable since we don't have any + true == false +} + diff --git a/samples/api/build.gradle b/samples/api/build.gradle index 7927b6aa797..8b7968a2b3b 100644 --- a/samples/api/build.gradle +++ b/samples/api/build.gradle @@ -33,9 +33,7 @@ test { exclude 'org/cloudfoundry/identity/api/web/*IntegrationTests.class' } -task integrationTest(type: Test) { - dependsOn parent.parent.cargoStartLocal - +integrationTest { filter { includeTestsMatching "org.cloudfoundry.identity.api.web.*IntegrationTests" } diff --git a/samples/app/build.gradle b/samples/app/build.gradle index 9afff76c496..8aba61c2110 100644 --- a/samples/app/build.gradle +++ b/samples/app/build.gradle @@ -29,9 +29,7 @@ test { exclude 'org/cloudfoundry/identity/app/integration/*.class' } -task integrationTest(type: Test) { - dependsOn parent.parent.cargoStartLocal - +integrationTest { filter { includeTestsMatching "org.cloudfoundry.identity.app.integration.*" } diff --git a/scim/build.gradle b/scim/build.gradle index 3f5a3c5d8f6..c40389a309b 100644 --- a/scim/build.gradle +++ b/scim/build.gradle @@ -17,4 +17,8 @@ processResources { //https://www.pivotaltracker.com/story/show/74344574 from(new File('../common/src/main/resources/log4j.properties')) filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-scim') : line } -} \ No newline at end of file +} + +integrationTest {}.onlyIf { //disable since we don't have any + true == false +} diff --git a/uaa/build.gradle b/uaa/build.gradle index 4d4baccbf03..32d03b3b1ea 100644 --- a/uaa/build.gradle +++ b/uaa/build.gradle @@ -77,9 +77,7 @@ test { systemProperty "mock.suite.test", "true" } -task integrationTest(type: Test) { - dependsOn parent.cargoStartLocal - +integrationTest { filter { includeTestsMatching "org.cloudfoundry.identity.uaa.integration.*" includeTestsMatching "*IT" From fe477f9b6e9d0d5162ac24f399157f4f7e49883d Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 2 Dec 2015 10:25:55 -0700 Subject: [PATCH 031/103] Suppress javadoc output for a cleaner build --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9e42aac9352..11bc9d8c8e9 100644 --- a/build.gradle +++ b/build.gradle @@ -118,7 +118,8 @@ subprojects { } javadoc { - options.addStringOption('XDignore.symbol.file', '-quiet') + logging.captureStandardError LogLevel.INFO + logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message } task javadocJar(type: Jar, dependsOn: javadoc) { From 00579f96d991bb4c6bf6352122a3ed02c70b2fce Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 1 Dec 2015 19:15:32 -0700 Subject: [PATCH 032/103] Implement password grant using a passcode (uaa specific) https://www.pivotaltracker.com/story/show/109315122 [##109315122] --- .../identity/client/UaaContextFactory.java | 22 +++- .../identity/client/token/GrantType.java | 1 + .../identity/client/token/TokenRequest.java | 31 ++++++ .../ClientAPITokenIntegrationTest.java | 59 ++++++++-- .../ClientIntegrationTestUtilities.java | 105 ++++++++++++++++++ .../client/token/TokenRequestTest.java | 11 ++ .../util/IntegrationTestUtils.java | 2 +- 7 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientIntegrationTestUtilities.java diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java index 12966fd517d..d320037d535 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java @@ -47,6 +47,7 @@ import java.util.Objects; import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE; +import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD_WITH_PASSCODE; import static org.springframework.security.oauth2.common.AuthenticationScheme.header; public class UaaContextFactory { @@ -129,7 +130,8 @@ public UaaContext authenticate(TokenRequest request) { } switch (request.getGrantType()) { case CLIENT_CREDENTIALS: return authenticateClientCredentials(request); - case PASSWORD: return authenticatePassword(request); + case PASSWORD: + case PASSWORD_WITH_PASSCODE: return authenticatePassword(request); case AUTHORIZATION_CODE: return authenticateAuthCode(request); case AUTHORIZATION_CODE_WITH_TOKEN: return authenticateAuthCodeWithToken(request); default: throw new UnsupportedGrantTypeException("Not implemented:"+request.getGrantType()); @@ -166,7 +168,7 @@ protected ResponseExtractor getResponseExtractor() { return new HttpMessageConverterExtractor(CompositeAccessToken.class, Arrays.asList(converter)); } }; - enhanceForIdTokenRetrieval(tokenRequest, provider); + enhanceRequestParameters(tokenRequest, provider); AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); details.setPreEstablishedRedirectUri(tokenRequest.getRedirectUriRedirectUri().toString()); configureResourceDetails(tokenRequest, details); @@ -201,7 +203,7 @@ protected ResponseExtractor getResponseExtractor() { return new HttpMessageConverterExtractor(CompositeAccessToken.class, Arrays.asList(converter)); } }; - enhanceForIdTokenRetrieval(tokenRequest, provider); + enhanceRequestParameters(tokenRequest, provider); ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails(); configureResourceDetails(tokenRequest, details); setUserCredentials(tokenRequest, details); @@ -213,7 +215,16 @@ protected ResponseExtractor getResponseExtractor() { return new UaaContextImpl(tokenRequest, template, (CompositeAccessToken) token); } - protected void enhanceForIdTokenRetrieval(TokenRequest tokenRequest, OAuth2AccessTokenSupport provider) { + /** + * Adds a request enhancer to the provider. + * Currently only two request parameters are being enhanced + * 1. If the {@link TokenRequest} wants an id_token the id_token token values are added as a response_type parameter + * 2. If the {@link TokenRequest} is a {@link org.cloudfoundry.identity.client.token.GrantType#PASSWORD_WITH_PASSCODE} + * the passcode parameter will be added to the request + * @param tokenRequest the token request, expected to be a password grant + * @param provider the provider to enhance + */ + protected void enhanceRequestParameters(TokenRequest tokenRequest, OAuth2AccessTokenSupport provider) { provider.setTokenRequestEnhancer( //add id_token to the response type if requested. (AccessTokenRequest request, OAuth2ProtectedResourceDetails resource, @@ -222,6 +233,9 @@ protected void enhanceForIdTokenRetrieval(TokenRequest tokenRequest, OAuth2Acces if (tokenRequest.wantsIdToken()) { form.put(OAuth2Utils.RESPONSE_TYPE, Arrays.asList("id_token token")); } + if (tokenRequest.getGrantType()==PASSWORD_WITH_PASSCODE) { + form.put("passcode", Arrays.asList(tokenRequest.getPasscode())); + } } ); } diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java index 2bc7fe4cfc7..20a014dd554 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java @@ -20,6 +20,7 @@ public enum GrantType { CLIENT_CREDENTIALS, PASSWORD, + PASSWORD_WITH_PASSCODE, IMPLICIT, AUTHORIZATION_CODE, AUTHORIZATION_CODE_WITH_TOKEN, diff --git a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java index a6bc9b9b5b7..3c0a3d1f9c9 100644 --- a/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java @@ -35,6 +35,7 @@ public class TokenRequest { private String clientSecret; private String username; private String password; + private String passcode; private Set scopes; private URI tokenEndpoint; private URI authorizationEndpoint; @@ -79,6 +80,16 @@ public boolean isValid() { password ) ); + case PASSWORD_WITH_PASSCODE: + return !hasAnyNullValues( + Arrays.asList( + tokenEndpoint, + clientId, + clientSecret, + username, + passcode + ) + ); case AUTHORIZATION_CODE: return !hasAnyNullValues( Arrays.asList( @@ -307,6 +318,26 @@ public TokenRequest setAuthCodeAPIToken(String authCodeAPIToken) { return this; } + /** + * Returns the passcode if set with {@link #setPasscode(String)}, null otherwise. + * Passcode is used with using the {@link GrantType#PASSWORD_WITH_PASSCODE} grant type. + * @return the passcode if set, null otherwise. + */ + public String getPasscode() { + return passcode; + } + + /** + * Sets the passcode to be used with the {@link GrantType#PASSWORD_WITH_PASSCODE} grant type. + * @param passcode a valid passcode retrieved from a logged in session at + * http://uaa.domain/passcode + * @return this mutable object + */ + public TokenRequest setPasscode(String passcode) { + this.passcode = passcode; + return this; + } + /** * Returns true if the list or any item in the list is null * @param objects a list of items to be evaluated for null references diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java index e60c6632a5f..a855abf20e2 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java @@ -25,9 +25,12 @@ import java.net.URI; import java.util.Arrays; +import java.util.Collection; import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE; import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE_WITH_TOKEN; +import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD; +import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD_WITH_PASSCODE; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -67,13 +70,14 @@ public void test_password_token_without_id_token() throws Exception { assertTrue(context.getToken().getScope().contains("openid")); } - protected UaaContext retrievePasswordToken(String scopes) { + protected UaaContext retrievePasswordToken(Collection scopes) { TokenRequest passwordGrant = factory.tokenRequest() .setClientId("cf") .setClientSecret("") - .setGrantType(GrantType.PASSWORD) + .setGrantType(PASSWORD) .setUsername("marissa") - .setPassword("koala"); + .setPassword("koala") + .setScopes(scopes); UaaContext context = factory.authenticate(passwordGrant); assertNotNull(context); assertTrue(context.hasAccessToken()); @@ -85,12 +89,41 @@ protected UaaContext retrievePasswordToken(String scopes) { @Test public void test_password_token_with_id_token() throws Exception { TokenRequest passwordGrant = factory.tokenRequest() - .withIdToken() .setClientId("cf") .setClientSecret("") - .setGrantType(GrantType.PASSWORD) + .setGrantType(PASSWORD) .setUsername("marissa") - .setPassword("koala"); + .setPassword("koala") + .withIdToken() + .setScopes(Arrays.asList("openid")); + UaaContext context = factory.authenticate(passwordGrant); + assertNotNull(context); + assertTrue(context.hasAccessToken()); + assertTrue(context.hasIdToken()); + assertTrue(context.hasRefreshToken()); + } + + protected void performPasswordGrant(String clientId, + String clientSecret, + GrantType grantType, + String username, + String password) { + TokenRequest passwordGrant = factory.tokenRequest() + .withIdToken() + .setClientId(clientId) + .setClientSecret(clientSecret) + .setGrantType(grantType) + .setUsername(username); + switch (grantType) { + case PASSWORD: + passwordGrant.setPassword(password); + break; + case PASSWORD_WITH_PASSCODE: + passwordGrant.setPasscode(password); + break; + default: + throw new IllegalArgumentException("Invalid grant:"+grantType); + } UaaContext context = factory.authenticate(passwordGrant); assertNotNull(context); @@ -100,6 +133,18 @@ public void test_password_token_with_id_token() throws Exception { assertTrue(context.getToken().getScope().contains("openid")); } + @Test + public void test_password_token_with_passcode() throws Exception { + String jsessionIdCookie = ClientIntegrationTestUtilities.performFormLogin(uaaURI, "marissa", "koala"); + String passcode = ClientIntegrationTestUtilities.getPasscode(uaaURI, jsessionIdCookie); + performPasswordGrant("cf", + "", + PASSWORD_WITH_PASSCODE, + "marissa", + passcode); + } + + @Test @Ignore //until we have decided if we want to be able to do this without a UI public void test_auth_code_token_with_id_token() throws Exception { @@ -139,7 +184,7 @@ public void test_auth_code_token_without_id_token() throws Exception { @Test public void test_auth_code_token_using_api() throws Exception { - UaaContext passwordContext = retrievePasswordToken("uaa.user"); + UaaContext passwordContext = retrievePasswordToken(Arrays.asList("uaa.user")); assertTrue(passwordContext.getToken().getScope().contains("uaa.user")); TokenRequest authorizationCode = factory.tokenRequest() .setGrantType(AUTHORIZATION_CODE_WITH_TOKEN) diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientIntegrationTestUtilities.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientIntegrationTestUtilities.java new file mode 100644 index 00000000000..4c94e45fca0 --- /dev/null +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientIntegrationTestUtilities.java @@ -0,0 +1,105 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.client.integration; + +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; + +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.TEXT_HTML; + +public class ClientIntegrationTestUtilities { + public static final String DEFAULT_CSRF_COOKIE_NAME = "X-Uaa-Csrf"; + + + public static String extractCookieCsrf(String body) { + String pattern = "\\ passcode = template.exchange(baseUrl+"/passcode", HttpMethod.GET, new HttpEntity<>(headers), String.class); + return passcode.getBody().replace('"',' ').trim(); + } + + + public static String performFormLogin(String baseUrl, String username, String password) throws Exception { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(TEXT_HTML)); + RestTemplate template = new RestTemplate(); + ResponseEntity loginPage = template.exchange(baseUrl+"/login", HttpMethod.GET, new HttpEntity<>(headers), String.class); + String csrfValue = extractCookieCsrf(loginPage.getBody()); + String jsessionId = extractAndSetCookies(loginPage, headers,"JSESSIONID"); + + assertTrue(loginPage.getBody().contains("/login.do")); + assertTrue(loginPage.getBody().contains("username")); + assertTrue(loginPage.getBody().contains("password")); + + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("username", username); + formData.add("password", password); + formData.add(DEFAULT_CSRF_COOKIE_NAME, csrfValue); + headers.setContentType(APPLICATION_FORM_URLENCODED); + // Should be redirected to the original URL, but now authenticated + ResponseEntity loggedInPage = template.exchange(baseUrl+"/login.do", + POST, + new HttpEntity<>(formData, headers), + String.class); + + assertEquals(HttpStatus.FOUND, loggedInPage.getStatusCode()); + String newJsessionId = extractAndSetCookies(loggedInPage, headers,"JSESSIONID"); + + return newJsessionId==null ? jsessionId : newJsessionId; + } + + protected static String extractAndSetCookies(ResponseEntity response, HttpHeaders headers, String findCookie) { + String result = null; + if (response.getHeaders().containsKey("Set-Cookie")) { + for (String cookie : response.getHeaders().get("Set-Cookie")) { + if (StringUtils.hasText(cookie)) { + headers.add("Cookie", cookie); + if (cookie.toLowerCase().contains(findCookie.toLowerCase())) { + result = cookie; + } + } + } + } + return result; + } +} diff --git a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java index ade80840bf4..d1cfdec0089 100644 --- a/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java @@ -25,6 +25,7 @@ import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE_WITH_TOKEN; import static org.cloudfoundry.identity.client.token.GrantType.CLIENT_CREDENTIALS; import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD; +import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD_WITH_PASSCODE; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -58,6 +59,16 @@ public void test_is_password_grant_valid() throws Exception { assertTrue(request.setPassword("password").isValid()); } + @Test + public void test_is_password_with_code_grant_valid() throws Exception { + assertFalse(request.isValid()); + assertFalse(request.setGrantType(PASSWORD_WITH_PASSCODE).isValid()); + assertFalse(request.setClientId("client_id").isValid()); + assertFalse(request.setClientSecret("client_secret").isValid()); + assertFalse(request.setUsername("username").isValid()); + assertTrue(request.setPasscode("passcode").isValid()); + } + @Test public void test_is_auth_code_grant_valid() throws Exception { assertFalse(request.isValid()); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index 9ac48303585..a92a46f18d8 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -20,6 +20,7 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.cloudfoundry.identity.uaa.ServerRunning; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.resources.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimGroup; @@ -29,7 +30,6 @@ import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.junit.Assert; From ee7140ce77ccce9fbff912be6c2c34d28bae871b Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Thu, 29 Oct 2015 15:00:20 -0700 Subject: [PATCH 033/103] Create line-aware log layout so that mutli-line events can get tags. [#102530470] https://www.pivotaltracker.com/story/show/102530470 Signed-off-by: Jonathan Lo --- common/build.gradle | 2 +- .../identity/uaa/audit/LineAwareLayout.java | 98 ++++++++++++++ common/src/main/resources/log4j.properties | 12 +- .../uaa/audit/LineAwareLayoutTest.java | 128 ++++++++++++++++++ 4 files changed, 234 insertions(+), 6 deletions(-) create mode 100644 common/src/main/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayout.java create mode 100644 common/src/test/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayoutTest.java diff --git a/common/build.gradle b/common/build.gradle index 55f70a9edc8..ec95134d31e 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -34,7 +34,7 @@ dependencies { compile group: 'org.springframework', name: 'spring-tx', version:parent.springVersion compile group: 'org.springframework.security', name: 'spring-security-core', version:parent.springSecurityVersion compile group: 'org.springframework.security', name: 'spring-security-web', version:parent.springSecurityVersion - compile group: 'log4j', name: 'log4j', version:'1.2.14' + compile group: 'log4j', name: 'log4j', version:'1.2.17' compile(group: 'org.apache.httpcomponents', name: 'httpclient', version:'4.3.3') { exclude(module: 'commons-logging') } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayout.java b/common/src/main/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayout.java new file mode 100644 index 00000000000..4461068e304 --- /dev/null +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayout.java @@ -0,0 +1,98 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.audit; + +import org.apache.log4j.Layout; +import org.apache.log4j.spi.LoggingEvent; + +/** + * Created by pivotal on 10/28/15. + */ +public class LineAwareLayout extends Layout { + private Layout messageLayout; + private Layout lineLayout; + + public LineAwareLayout() { + } + + public LineAwareLayout(Layout lineLayout) { + this(lineLayout, null); + } + + public LineAwareLayout(Layout lineLayout, Layout messageLayout) { + this.messageLayout = messageLayout; + this.lineLayout = lineLayout; + } + + @Override + public String format(LoggingEvent event) { + if(lineLayout == null) { return messageLayout == null ? event.getRenderedMessage() : messageLayout.format(event); } + + String message = event.getRenderedMessage(); + + String[] lines; + String[] throwable; + if(messageLayout == null && (throwable = event.getThrowableStrRep()) != null) { + lines = throwable; + } else { + lines = message.split("\r?\n"); + } + + StringBuffer strBuf = new StringBuffer(); + for (String line : lines) { + String formattedLine = lineLayout.format(replaceEventMessageWithoutThrowable(event, line)); + strBuf.append(formattedLine); + } + + String formattedLines = strBuf.toString(); + if (messageLayout == null) return formattedLines; + return messageLayout.format(replaceEventMessage(event, formattedLines)); + } + + @Override + public boolean ignoresThrowable() { + return messageLayout != null && messageLayout.ignoresThrowable(); + } + + @Override + public void activateOptions() { + if(lineLayout != null) { lineLayout.activateOptions(); } + if (messageLayout != null) { messageLayout.activateOptions(); } + } + + public Layout getLineLayout() { + return lineLayout; + } + + public void setLineLayout(Layout lineLayout) { + this.lineLayout = lineLayout; + } + + public Layout getMessageLayout() { + return messageLayout; + } + + public void setMessageLayout(Layout messageLayout) { + this.messageLayout = messageLayout; + } + + private static LoggingEvent replaceEventMessageWithoutThrowable(LoggingEvent event, String message) { + return new LoggingEvent(event.getFQNOfLoggerClass(), event.getLogger(), event.getTimeStamp(), event.getLevel(), message, event.getThreadName(), null, event.getNDC(), event.getLocationInformation(), event.getProperties()); + } + + private static LoggingEvent replaceEventMessage(LoggingEvent event, String message) { + return new LoggingEvent(event.getFQNOfLoggerClass(), event.getLogger(), event.getTimeStamp(), event.getLevel(), message, event.getThreadName(), event.getThrowableInformation(), event.getNDC(), event.getLocationInformation(), event.getProperties()); + } +} diff --git a/common/src/main/resources/log4j.properties b/common/src/main/resources/log4j.properties index 76cc90e2931..e05fa78a28b 100644 --- a/common/src/main/resources/log4j.properties +++ b/common/src/main/resources/log4j.properties @@ -21,14 +21,16 @@ LOG_FILE=${LOG_PATH}/uaa.log LOG_PATTERN=[%d{yyyy-MM-dd HH:mm:ss.SSS}] ${project.artifactId}%X{context} - ${PID} [%t] .... %5p --- %c{1}: %m%n log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender -log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout -log4j.appender.CONSOLE.layout.ConversionPattern=${LOG_PATTERN} +log4j.appender.CONSOLE.layout=org.cloudfoundry.identity.uaa.audit.LineAwareLayout +log4j.appender.CONSOLE.layout.lineLayout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.lineLayout.ConversionPattern=${LOG_PATTERN} log4j.appender.FILE=org.apache.log4j.RollingFileAppender log4j.appender.FILE.File=${LOG_FILE} log4j.appender.FILE.MaxFileSize=10MB -log4j.appender.FILE.layout = org.apache.log4j.PatternLayout -log4j.appender.FILE.layout.ConversionPattern=${LOG_PATTERN} +log4j.appender.FILE.layout = org.cloudfoundry.identity.uaa.audit.LineAwareLayout +log4j.appender.FILE.layout.lineLayout=org.apache.log4j.PatternLayout +log4j.appender.FILE.layout.lineLayout.ConversionPattern=${LOG_PATTERN} log4j.category.org.springframework.retry=INFO log4j.category.org.springframework.security=INFO @@ -52,4 +54,4 @@ log4j.category.org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPos log4j.category.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping=WARN log4j.category.org.springframework.beans.factory.support.DefaultListableBeanFactory=WARN log4j.category.org.springframework.jmx.exportMBeanExporter=WARN -log4j.category.org.springframework.security.oauth2.client.test.OAuth2ContextSetup=WARN \ No newline at end of file +log4j.category.org.springframework.security.oauth2.client.test.OAuth2ContextSetup=WARN diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayoutTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayoutTest.java new file mode 100644 index 00000000000..c99688a152a --- /dev/null +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayoutTest.java @@ -0,0 +1,128 @@ +/* + * ****************************************************************************** + * Cloud Foundry Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.audit; + +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.PatternLayout; +import org.apache.log4j.WriterAppender; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class LineAwareLayoutTest { + private final static String delimiter = " NEWLINE "; + + @Test + public void messages_over_multiple_lines_are_formatted_per_line() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + LineAwareLayout lineAwareLayout = new LineAwareLayout(); + lineAwareLayout.setLineLayout(new PatternLayout("%m" + delimiter)); + Appender appender = new WriterAppender(lineAwareLayout, output); + + appender.setName("TestLog"); + appender.setLayout(lineAwareLayout); + + Logger testLogger = LogManager.getLogger("test-logger"); + testLogger.addAppender((appender)); + testLogger.setLevel(Level.INFO); + String eventMessage = "test message\nwith\nmultiple lines"; + testLogger.info(eventMessage); + + String expectedLog = String.join(delimiter, eventMessage.split("\n")) + delimiter; + + assertEquals(expectedLog, output.toString()); + } + + @Test + public void logged_exceptions_are_formatted_per_line_when_treating_throwable_as_lines() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + LineAwareLayout lineAwareLayout = new LineAwareLayout(); + lineAwareLayout.setLineLayout(new PatternLayout("%m" + delimiter)); + Appender appender = new WriterAppender(lineAwareLayout, output); + + appender.setName("TestLog"); + appender.setLayout(lineAwareLayout); + Logger testLogger = LogManager.getLogger("test-logger"); + testLogger.addAppender(appender); + testLogger.setLevel(Level.INFO); + + Exception ex = new Exception("SOMETHING BAD HAPPEN\nNO REALLY IT'S VERY BAD\n\ntrust me"); + ex.setStackTrace(new StackTraceElement[]{new StackTraceElement("CLAZZ", "MEETOD", "FEEL", 123)}); + testLogger.info(ex, ex); + String expectedLog = String.join(delimiter, ex.toString().split("\n")) + delimiter + "\tat CLAZZ.MEETOD(FEEL:123)" + delimiter; + + assertEquals(expectedLog, output.toString()); + } + + @Test + public void messages_get_the_message_format_applied_after_the_line_format() throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + + String extraLine = "\nTHESE NEWLINES SHOULD NOT GET AFFECTED BY THE LINE FORMAT\n"; + LineAwareLayout lineAwareLayout = new LineAwareLayout(); + lineAwareLayout.setLineLayout(new PatternLayout("%m" + delimiter)); + lineAwareLayout.setMessageLayout(new PatternLayout("%m" + extraLine)); + Appender appender = new WriterAppender(lineAwareLayout, output); + + appender.setName("TestLog"); + appender.setLayout(lineAwareLayout); + + Logger testLogger = LogManager.getLogger("test-logger"); + testLogger.addAppender((appender)); + testLogger.setLevel(Level.INFO); + String eventMessage = "test message\nwith\nmultiple lines"; + testLogger.info(eventMessage); + + String expectedLog = String.join(delimiter, eventMessage.split("\n")) + delimiter + + extraLine; + + assertEquals(expectedLog, output.toString()); + } + + @Test + public void ignores_throwable_only_if_message_layout_ignores_throwable() throws Exception { + LineAwareLayout lineAwareLayout = new LineAwareLayout(); + + Layout lineLayout = mock(Layout.class); + lineAwareLayout.setLineLayout(lineLayout); + + when(lineLayout.ignoresThrowable()).thenReturn(false); + assertFalse(lineAwareLayout.ignoresThrowable()); + + when(lineLayout.ignoresThrowable()).thenReturn(true); + assertFalse(lineAwareLayout.ignoresThrowable()); + + Layout messageLayout = mock(Layout.class); + lineAwareLayout.setMessageLayout(messageLayout); + + when(messageLayout.ignoresThrowable()).thenReturn(false); + assertFalse(lineAwareLayout.ignoresThrowable()); + + when(messageLayout.ignoresThrowable()).thenReturn(true); + assertTrue(lineAwareLayout.ignoresThrowable()); + } +} From 94a1fdfad8a129b19d8bf24aec84fee4694fe73a Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Wed, 2 Dec 2015 14:20:23 -0800 Subject: [PATCH 034/103] Get client credentials from OAuth Token request body if present. [#108973498] https://www.pivotaltracker.com/story/show/108973498 Signed-off-by: Jeremy Coffield --- ...tClientParametersAuthenticationFilter.java | 154 ++++++++++++++++++ .../ClientParametersAuthenticationFilter.java | 135 +-------------- ...nClientParametersAuthenticationFilter.java | 58 +++++++ .../WEB-INF/spring/login-server-security.xml | 4 +- .../webapp/WEB-INF/spring/oauth-endpoints.xml | 5 + .../uaa/mock/token/TokenMvcMockTests.java | 46 ++++++ 6 files changed, 272 insertions(+), 130 deletions(-) create mode 100644 common/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java create mode 100644 common/src/main/java/org/cloudfoundry/identity/uaa/authentication/LoginClientParametersAuthenticationFilter.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java new file mode 100644 index 00000000000..9b4ad9f3113 --- /dev/null +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java @@ -0,0 +1,154 @@ +/* + * ****************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ +package org.cloudfoundry.identity.uaa.authentication; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; +import org.springframework.security.web.AuthenticationEntryPoint; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +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 java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * Filter which processes and authenticates a client based on + * parameters client_id and client_secret + * It sets the authentication to a client only + * Oauth2Authentication object as that is expected by + * the LoginAuthenticationManager. + */ +public abstract class AbstractClientParametersAuthenticationFilter implements Filter { + + public static final String CLIENT_ID = "client_id"; + public static final String CLIENT_SECRET = "client_secret"; + protected final Log logger = LogFactory.getLog(getClass()); + + protected AuthenticationManager clientAuthenticationManager; + + protected AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); + + public AuthenticationManager getClientAuthenticationManager() { + return clientAuthenticationManager; + } + + public void setClientAuthenticationManager(AuthenticationManager clientAuthenticationManager) { + this.clientAuthenticationManager = clientAuthenticationManager; + } + + /** + * @param authenticationEntryPoint the authenticationEntryPoint to set + */ + public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { + this.authenticationEntryPoint = authenticationEntryPoint; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, + ServletException { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse res = (HttpServletResponse) response; + + Map loginInfo = getCredentials(req); + String clientId = loginInfo.get(CLIENT_ID); + + wrapClientCredentialLogin(req, res, loginInfo, clientId); + + chain.doFilter(request, response); + } + + public abstract void wrapClientCredentialLogin(HttpServletRequest req, HttpServletResponse res, Map loginInfo, String clientId) throws IOException, ServletException; + + protected void doClientCredentialLogin(HttpServletRequest req, Map loginInfo, String clientId) { + Authentication clientAuth = performClientAuthentication(req, loginInfo, clientId); + SecurityContextHolder.getContext().setAuthentication(clientAuth); + } + + private Map getSingleValueMap(HttpServletRequest request) { + Map map = new HashMap(); + @SuppressWarnings("unchecked") + Map parameters = request.getParameterMap(); + for (String key : parameters.keySet()) { + String[] values = parameters.get(key); + map.put(key, values != null && values.length > 0 ? values[0] : null); + } + return map; + } + + private Collection getScope(HttpServletRequest request) { + return OAuth2Utils.parseParameterList(request.getParameter("scope")); + } + + private Authentication performClientAuthentication(HttpServletRequest req, Map loginInfo, String clientId) { + + String clientSecret = loginInfo.get(CLIENT_SECRET); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(clientId, clientSecret); + authentication.setDetails(new UaaAuthenticationDetails(req, clientId)); + try { + Authentication auth = clientAuthenticationManager.authenticate(authentication); + if (auth == null || !auth.isAuthenticated()) { + throw new BadCredentialsException("Client Authentication failed."); + } + loginInfo.remove(CLIENT_SECRET); + AuthorizationRequest authorizationRequest = new AuthorizationRequest(clientId, getScope(req)); + authorizationRequest.setRequestParameters(getSingleValueMap(req)); + authorizationRequest.setApproved(true); + //must set this to true in order for + //Authentication.isAuthenticated to return true + OAuth2Authentication result = new OAuth2Authentication(authorizationRequest.createOAuth2Request(), null); + result.setAuthenticated(true); + return result; + } catch (AuthenticationException e) { + throw new BadCredentialsException(e.getMessage(), e); + } catch (Exception e) { + logger.debug("Unable to authenticate client: " + clientId, e); + throw new BadCredentialsException(e.getMessage(), e); + } + } + + private Map getCredentials(HttpServletRequest request) { + Map credentials = new HashMap(); + credentials.put(CLIENT_ID, request.getParameter(CLIENT_ID)); + credentials.put(CLIENT_SECRET, request.getParameter(CLIENT_SECRET)); + return credentials; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void destroy() { + } + +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java index 607cc9760b8..0d1cbdc1a48 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java @@ -14,31 +14,14 @@ */ package org.cloudfoundry.identity.uaa.authentication; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.security.authentication.AuthenticationManager; +import org.flywaydb.core.internal.util.StringUtils; import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint; -import org.springframework.security.web.AuthenticationEntryPoint; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -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 java.io.IOException; -import java.util.Collection; -import java.util.HashMap; import java.util.Map; /** @@ -49,118 +32,16 @@ * the LoginAuthenticationManager. * */ -public class ClientParametersAuthenticationFilter implements Filter { - - public static final String CLIENT_ID = "client_id"; - public static final String CLIENT_SECRET = "client_secret"; - private final Log logger = LogFactory.getLog(getClass()); - - private AuthenticationManager clientAuthenticationManager; - - private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); - - public AuthenticationManager getClientAuthenticationManager() { - return clientAuthenticationManager; - } - - public void setClientAuthenticationManager(AuthenticationManager clientAuthenticationManager) { - this.clientAuthenticationManager = clientAuthenticationManager; - } - - /** - * @param authenticationEntryPoint the authenticationEntryPoint to set - */ - public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { - this.authenticationEntryPoint = authenticationEntryPoint; - } +public class ClientParametersAuthenticationFilter extends AbstractClientParametersAuthenticationFilter { @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, - ServletException { - - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse res = (HttpServletResponse) response; - - Map loginInfo = getCredentials(req); - String clientId = loginInfo.get(CLIENT_ID); - - try { - if (loginInfo.isEmpty()) { - throw new BadCredentialsException("Request does not contain credentials."); - } else if (clientAuthenticationManager==null || loginInfo.get(CLIENT_ID)==null) { - logger.debug("Insufficient resources to perform client authentication. AuthMgr:"+ - clientAuthenticationManager + "; clientId:"+clientId); - throw new BadCredentialsException("Request does not contain client credentials."); - } else { - logger.debug("Located credentials in request, with keys: " + loginInfo.keySet()); - - Authentication clientAuth = performClientAuthentication(req, loginInfo, clientId); - SecurityContextHolder.getContext().setAuthentication(clientAuth); + public void wrapClientCredentialLogin(HttpServletRequest req, HttpServletResponse res, Map loginInfo, String clientId) throws IOException, ServletException { + if (!StringUtils.hasText(req.getHeader("Authorization")) && !"password".equals(req.getParameter("grant_type"))) { + try { + doClientCredentialLogin(req, loginInfo, clientId); + } catch(AuthenticationException e) { + logger.debug("Could not authenticate with client credentials."); } - } catch (AuthenticationException e) { - logger.debug("Client Parameter Authentication failed"); - authenticationEntryPoint.commence(req, res, e); - return; - } - - chain.doFilter(request, response); - } - - private Map getSingleValueMap(HttpServletRequest request) { - Map map = new HashMap(); - @SuppressWarnings("unchecked") - Map parameters = request.getParameterMap(); - for (String key : parameters.keySet()) { - String[] values = parameters.get(key); - map.put(key, values != null && values.length > 0 ? values[0] : null); } - return map; } - - private Collection getScope(HttpServletRequest request) { - return OAuth2Utils.parseParameterList(request.getParameter("scope")); - } - - private Authentication performClientAuthentication(HttpServletRequest req, Map loginInfo, String clientId) { - - String clientSecret = loginInfo.get(CLIENT_SECRET); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(clientId, clientSecret); - authentication.setDetails(new UaaAuthenticationDetails(req, clientId)); - try { - Authentication auth = clientAuthenticationManager.authenticate(authentication); - if (auth==null || !auth.isAuthenticated()) { - throw new BadCredentialsException("Client Authentication failed."); - } - loginInfo.remove(CLIENT_SECRET); - AuthorizationRequest authorizationRequest = new AuthorizationRequest(clientId, getScope(req)); - authorizationRequest.setRequestParameters(getSingleValueMap(req)); - authorizationRequest.setApproved(true); - //must set this to true in order for - //Authentication.isAuthenticated to return true - OAuth2Authentication result = new OAuth2Authentication(authorizationRequest.createOAuth2Request(),null); - result.setAuthenticated(true); - return result; - } catch (AuthenticationException e) { - throw new BadCredentialsException(e.getMessage(), e); - } catch (Exception e) { - logger.debug("Unable to authenticate client: " + clientId, e); - throw new BadCredentialsException(e.getMessage(), e); - } - } - - private Map getCredentials(HttpServletRequest request) { - Map credentials = new HashMap(); - credentials.put(CLIENT_ID, request.getParameter(CLIENT_ID)); - credentials.put(CLIENT_SECRET, request.getParameter(CLIENT_SECRET)); - return credentials; - } - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - } - - @Override - public void destroy() { - } - } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/LoginClientParametersAuthenticationFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/LoginClientParametersAuthenticationFilter.java new file mode 100644 index 00000000000..85efb0ffab8 --- /dev/null +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/LoginClientParametersAuthenticationFilter.java @@ -0,0 +1,58 @@ +/* + * ****************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ****************************************************************************** + */ +package org.cloudfoundry.identity.uaa.authentication; + +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; + +/** + * Filter which processes and authenticates a client based on + * parameters client_id and client_secret + * It sets the authentication to a client only + * Oauth2Authentication object as that is expected by + * the LoginAuthenticationManager. + * + */ +public class LoginClientParametersAuthenticationFilter extends AbstractClientParametersAuthenticationFilter { + + @Override + public void wrapClientCredentialLogin(HttpServletRequest req, HttpServletResponse res, Map loginInfo, String clientId) throws IOException, ServletException { + try { + if (loginInfo.isEmpty()) { + throw new BadCredentialsException("Request does not contain credentials."); + } else if (clientAuthenticationManager==null || loginInfo.get(CLIENT_ID)==null) { + logger.debug("Insufficient resources to perform client authentication. AuthMgr:"+ + clientAuthenticationManager + "; clientId:"+clientId); + throw new BadCredentialsException("Request does not contain client credentials."); + } else { + logger.debug("Located credentials in request, with keys: " + loginInfo.keySet()); + + doClientCredentialLogin(req, loginInfo, clientId); + } + } catch (AuthenticationException e) { + logger.debug("Client Parameter Authentication failed"); + authenticationEntryPoint.commence(req, res, e); + return; + } + } +} diff --git a/uaa/src/main/webapp/WEB-INF/spring/login-server-security.xml b/uaa/src/main/webapp/WEB-INF/spring/login-server-security.xml index 9644504cb4e..413c522253f 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/login-server-security.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/login-server-security.xml @@ -133,12 +133,10 @@ - + - - diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml index ae8487cc753..6bc16a4bc64 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml @@ -111,6 +111,7 @@ + @@ -151,6 +152,10 @@ + + + + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java index fbc040f7624..f3500916758 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java @@ -240,6 +240,52 @@ protected ScimGroup createIfNotExist(String scope, String zoneId) { } } + @Test + public void getOauthToken_withClientIdAndSecretInRequestBody_shouldBeOk() throws Exception { + String clientId = "testclient"+new RandomValueStringGenerator().generate(); + setUpClients(clientId, "uaa.user", "uaa.user", "authorization_code", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); + + String username = "testuser"+new RandomValueStringGenerator().generate(); + String userScopes = "uaa.user"; + ScimUser developer = setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); + + UaaPrincipal p = new UaaPrincipal(developer.getId(),developer.getUserName(),developer.getPrimaryEmail(), OriginKeys.UAA,"", IdentityZoneHolder.get().getId()); + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(p, "", UaaAuthority.USER_AUTHORITIES); + Assert.assertTrue(auth.isAuthenticated()); + SecurityContextHolder.getContext().setAuthentication(auth); + MockHttpSession session = new MockHttpSession(); + session.setAttribute( + HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, + new MockSecurityContext(auth) + ); + + String state = new RandomValueStringGenerator().generate(); + + MvcResult result = getMockMvc().perform(get("/oauth/authorize") + .session(session) + .param(OAuth2Utils.RESPONSE_TYPE, "code") + .param(OAuth2Utils.STATE, state) + .param(OAuth2Utils.CLIENT_ID, clientId)) + .andExpect(status().isFound()) + .andReturn(); + + URL url = new URL(result.getResponse().getHeader("Location").replace("redirect#","redirect?")); + Map query = splitQuery(url); + String code = ((List) query.get("code")).get(0); + state = ((List) query.get("state")).get(0); + + getMockMvc().perform(post("/oauth/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .accept(MediaType.APPLICATION_JSON_VALUE) + .param(OAuth2Utils.RESPONSE_TYPE, "token") + .param(OAuth2Utils.GRANT_TYPE, "authorization_code") + .param(OAuth2Utils.CLIENT_ID, clientId) + .param("client_secret", "secret") + .param("code", code) + .param("state", state)) + .andExpect(status().isOk()); + } + @Test public void testClientIdentityProviderWithoutAllowedProvidersForPasswordGrantWorksInOtherZone() throws Exception { String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; From f55f2b4d55c0b14dc76a4aa4d096595203770f37 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 2 Dec 2015 17:27:31 -0700 Subject: [PATCH 035/103] Add support for complete configuration via environment variable https://www.pivotaltracker.com/story/show/108500942 [#108500942] --- README.md | 23 +++++++- .../uaa/SystemEnvironmentAccessor.java | 26 ++++++++ .../config/YamlServletProfileInitializer.java | 41 ++++++++++++- .../YamlServletProfileInitializerTests.java | 59 +++++++++++++++---- docs/Sysadmin-Guide.rst | 41 +++++++++---- 5 files changed, 167 insertions(+), 23 deletions(-) create mode 100644 common/src/main/java/org/cloudfoundry/identity/uaa/SystemEnvironmentAccessor.java diff --git a/README.md b/README.md index 50d6b63a148..1c10b12295b 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ In the steps above, replace: * `myuaa` with a unique application name * `2.3.2-SNAPSHOT` with the appropriate version label from your build * `` this is your app domain. We will be parsing this from the system environment in the future +* You may also provide a configuration manifest where the environment variable UAA_CONFIG_YAML contains full configuration yaml. * We have not tested our system on Apache Tomcat 8 and Java 8, so we pick a build pack that produces lower versions ### Demo of command line usage on local server @@ -194,13 +195,33 @@ then from `uaa/uaa` $ CLOUD_FOUNDRY_CONFIG_PATH=/tmp/config ./gradlew test -The webapp looks for a Yaml file in the following locations +The webapp looks for Yaml content in the following locations (later entries override earlier ones) when it starts up. classpath:uaa.yml file:${CLOUD_FOUNDRY_CONFIG_PATH}/uaa.yml file:${UAA_CONFIG_FILE} ${UAA_CONFIG_URL} + System.getEnv('UAA_CONFIG_YAML') -> environment variable, if set must contain valid Yaml + +For example, to deploy the UAA as a Cloud Foundry application, you can provide an application manifest like + + --- + applications: + - name: sample-uaa-cf-war + memory: 256M + instances: 1 + host: uaa.myapp.com + path: cloudfoundry-identity-uaa.war + env: + UAA_CONFIG_YAML: | + uaa.url: http://uaa.myapp.com + login.url: http://uaa.myapp.com + smtp: + host: mail.server.host + port: 3535 + +In addition, any simple type property that is read by the UAA can also be fully expanded and read as a system environment variable itself. ### Using Gradle to test with postgresql or mysql diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/SystemEnvironmentAccessor.java b/common/src/main/java/org/cloudfoundry/identity/uaa/SystemEnvironmentAccessor.java new file mode 100644 index 00000000000..ddd736a0da5 --- /dev/null +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/SystemEnvironmentAccessor.java @@ -0,0 +1,26 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa; + +/** + * Interface that by default reads environments directly from the + * {@link System#getenv(String)} method. + * Can be overridden when you wish to access these values from elsewhere. + */ +public interface SystemEnvironmentAccessor { + default String getEnvironmentVariable(String name) { + return System.getenv(name); + } +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializer.java b/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializer.java index 636bcf121f9..bdc71814c1e 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializer.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializer.java @@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.config; import org.apache.log4j.MDC; +import org.cloudfoundry.identity.uaa.SystemEnvironmentAccessor; import org.cloudfoundry.identity.uaa.config.YamlProcessor.ResolutionMethod; import org.springframework.context.ApplicationContextInitializer; import org.springframework.core.env.ConfigurableEnvironment; @@ -20,6 +21,7 @@ import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.security.util.InMemoryResource; import org.springframework.util.Log4jConfigurer; import org.springframework.util.StringUtils; import org.springframework.web.context.ConfigurableWebApplicationContext; @@ -35,6 +37,8 @@ import java.util.Map; import java.util.Set; +import static org.springframework.util.StringUtils.hasText; + /** * An {@link ApplicationContextInitializer} for a web application to enable it * to externalize the environment and @@ -48,7 +52,7 @@ * location (if it exists) * * - * @author Dave Syer + * * */ public class YamlServletProfileInitializer implements ApplicationContextInitializer { @@ -64,6 +68,10 @@ public class YamlServletProfileInitializer implements ApplicationContextInitiali private String rawYamlKey = DEFAULT_YAML_KEY; + private String yamlEnvironmentVariableName = "UAA_CONFIG_YAML"; + + private SystemEnvironmentAccessor environmentAccessor = new SystemEnvironmentAccessor(){}; + @Override public void initialize(ConfigurableWebApplicationContext applicationContext) { @@ -89,6 +97,11 @@ public void initialize(ConfigurableWebApplicationContext applicationContext) { resources.addAll(getResource(servletContext, applicationContext, locations)); + Resource yamlFromEnv = getYamlFromEnvironmentVariable(); + if (yamlFromEnv!=null) { + resources.add(yamlFromEnv); + } + if (resources.isEmpty()) { servletContext.log("No YAML environment properties from servlet. Defaulting to servlet context."); locations = servletContext.getInitParameter(PROFILE_CONFIG_FILE_LOCATIONS); @@ -116,6 +129,17 @@ public void initialize(ConfigurableWebApplicationContext applicationContext) { } + protected Resource getYamlFromEnvironmentVariable() { + if (getEnvironmentAccessor()!=null){ + String data = getEnvironmentAccessor().getEnvironmentVariable(getYamlEnvironmentVariableName()); + if (hasText(data)) { + //validate the Yaml? We don't do that for the others + return new InMemoryResource(data); + } + } + return null; + } + private List getResource(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext, String locations) { List resources = new LinkedList<>(); @@ -179,4 +203,19 @@ private void applySpringProfiles(ConfigurableEnvironment environment, ServletCon } } + public String getYamlEnvironmentVariableName() { + return yamlEnvironmentVariableName; + } + + public void setYamlEnvironmentVariableName(String yamlEnvironmentVariableName) { + this.yamlEnvironmentVariableName = yamlEnvironmentVariableName; + } + + public SystemEnvironmentAccessor getEnvironmentAccessor() { + return environmentAccessor; + } + + public void setEnvironmentAccessor(SystemEnvironmentAccessor environmentAccessor) { + this.environmentAccessor = environmentAccessor; + } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializerTests.java index be17e2b49b2..f0368a7cb82 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializerTests.java @@ -12,17 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.config; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.times; - -import java.util.Enumeration; -import java.util.List; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; - +import org.cloudfoundry.identity.uaa.SystemEnvironmentAccessor; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -40,6 +30,16 @@ import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.support.StandardServletEnvironment; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import java.util.Enumeration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.atLeastOnce; +import static org.springframework.util.StringUtils.hasText; + /** * @author Dave Syer * @@ -232,8 +232,45 @@ public void testLoggingConfigVariableWorks() throws Exception { new ByteArrayResource("logging:\n config: /some/path".getBytes())); initializer.initialize(context); assertEquals("/some/path", environment.getProperty("logging.config")); + assertNull(environment.getProperty("smtp.host")); + assertNull(environment.getProperty("smtp.port")); + } + + @Test + public void testReadingYamlFromEnvironment() throws Exception { + testReadingYamlFromEnvironment(null); + } + + @Test + public void testReadingYamlFromEnvironment_Rename_Env_Variable() throws Exception { + testReadingYamlFromEnvironment("Renaming environment variable"); } + public void testReadingYamlFromEnvironment(String variableName) throws Exception { + if (hasText(variableName)) { + initializer.setYamlEnvironmentVariableName(variableName); + } + SystemEnvironmentAccessor env = new SystemEnvironmentAccessor() { + @Override + public String getEnvironmentVariable(String name) { + return name.equals(initializer.getYamlEnvironmentVariableName()) ? + "uaa.url: http://uaa.test.url/\n" + + "login.url: http://login.test.url/\n" + + "smtp:\n" + + " host: mail.server.host\n" + + " port: 3535\n" : + null; + } + }; + initializer.setEnvironmentAccessor(env); + initializer.initialize(context); + assertEquals("mail.server.host", environment.getProperty("smtp.host")); + assertEquals("3535", environment.getProperty("smtp.port")); + assertEquals("http://uaa.test.url/", environment.getProperty("uaa.url")); + assertEquals("http://login.test.url/", environment.getProperty("login.url")); + } + + @Test public void testIgnoreDashDTomcatLoggingConfigVariable() throws Exception { final String tomcatLogConfig = "-Djava.util.logging.config=/some/path/logging.properties";; diff --git a/docs/Sysadmin-Guide.rst b/docs/Sysadmin-Guide.rst index b94f3bbb4ac..9311e9b3b4f 100644 --- a/docs/Sysadmin-Guide.rst +++ b/docs/Sysadmin-Guide.rst @@ -21,7 +21,7 @@ source Date: Thu, 3 Dec 2015 08:35:01 -0700 Subject: [PATCH 036/103] Upgrade to Gradle 2.9 --- gradle/wrapper/gradle-wrapper.jar | Bin 51017 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 10 +++------- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index b7612167031001b7b84baf2a959e8ea8ad03c011..941144813d241db74e1bf25b6804c679fbe7f0a3 100644 GIT binary patch delta 27722 zcmZ5{V{oQn(`;SYczVBC0)jdDEu6t&> zdmZm4g>@S2E_G;i&PR4CG3B2=vER}I7vVg>s;r-_1+Z}{->>s z1meFqN&SEEnfbp6qygQB{m+My-LO>*AS#$WATA1)RiR2jUf96|+^WvY+$rgy{wcDn#Gq_-_uTrey=B)A>zHdkYO90r$~_Ua8G zx?YDJ8PeFVwx50o6WE6Pl?LG*Y69lc%Y$Xc+Eso(y_W=eu^t$OUuaF&hM-=NJD;8c z0T}WXZP=_e#82duiZ2%HsH#3>WVbCw*HvvWDQo+5DF~~VBq&WMk2Gk*K03%2Qw}n_ zPhnfY9x-(<<}1j+@$BqJcukx%J<)5mPD7e(vkYt~1boHD@1KC*jwC7kf|aKGLg3PU z>}qnIMbPN{J0>*KGUhDEgLU?iT6siDfD7*tDBSX~$s#t%QxA0V*Um9k*0OV^H zj4VX}F2hf?i{!dRxpHZS7+7s{C?E2Q2s*mJ5Rx$q-C7ecdwufkA4u=0ow6!%$Ni+= zyNWqx1B#oYysAdd=Ljd|QLd&n`hnWPVZxw6A^oR)8qrWP2@C|}9SWGwfD2Sa!Uq5yl+Z;`#{O=du7(H&2Ne~OU6bY% z?6bonYp`k{U`qRo)j6k;AIEPSaNeugkg?u}5&uHA$m!K9sM#9kW^ZPH%>TO9|M-}j zGXgoeqlu8qm@wvwA&5n|aS-@_If10#^!Jn)e%Z-K0F3HS{^PuxGCDolY{NAK9 zAvXqz3$D2;ESty?=D(^$60a8-<}b_Vp`L7F2?AR&L}x9O%p{3hN#-rMDhdfuHn9ew zsQ{w07;0jYBthn}1X)@oNS;0@;DtfS8I8R)ri5?bL-tW@FVu&BhamQ)|LDcN;$HiZ~*f1?3m zcq7BapWvUxv8Qc_Xxhz*d3kQPy!nrGfbZ`=i2Eo?KkLB&zhAgb21AGS6i$cSLgnkcN@Dd9bw-2p z&Ulk^#w2CDhY9hOUIcn1_52_m@bFPn{||K!Uw?t)v{Aj*uowV#UOSB<#VVCqxT!XQ z7<zy+ipX(z7L=phLL%Tpk~uFx&9D>bE_TxI7*%L0OWht|)t) zUkCGn2p+PEbaAAVr~uX>>@jRE+tBFWPp3at`o&=Fv-DB6b z<35B8op*k+{G`ut>wxfewhjnbi^Y1Tpk_DcG{rVSXH)<`=$i(}?E8KSa(sV!KSLpX zXrChK2mIMKLGBQy>`n0$sBSTn+0*RDS?rH=FDq@mFfCX^a_OCrslauDJmh`Rtz16c3W=^F{O@U*uaueqmeqM16Wjp0skF7?s z5o=7upCUeC5@2<&ia;E5c;w;_mLGed=Hic+pEP16(M33NYX0p{Fm~63U=(>6qvB5+ z&OEjdRzG~Fq4J%DV1%8$hjO0H>MwWVGhZL(F z1JHFDch=w1rs)i~pi_1$tK15am~nBJ8c31@eu8H2wUXGR^+`j>C@aHKd7k2@(GA?cKKTCjqToxgpST$EbMeR z{Mo}(0|sd>I8c4ISrA`~f=AU~Yl7zs0IB7h->5^&W3p55U4pfY7#{b``&OJ=i5?BO zuIUIxHw2AWqN`r1y%u)#^j*2R^V_9?lCAOz+3`pvo?Tc@AMjn>?8J|izHNE;R+4@@ zG%5B9?sH!KwQJ~3G9AC|UAi$LJ!Y0YDoEN2c>iQjMa3T5tYT-|PnqJh^+!(<1N0#m zG0h|fyf123{IFeCEB#x{v6<~=(I`*m(~0ViN^cH&r@D@;Ap^!~&f=>JhO}ItMv@&m z>^2sz0cyfM4JUB?_;pA$$~Z@b4w?~K*w3jX!l+|z1B07?nmp8q4Lv-T?iUHzdar1v z!;{TsgC{?DFA);6yRvgLr?1hP03~jtOpZz3YT2V&+MbXuc!b7bJy%{gd2w_fVyenS zIZd)JMNHY|e~^%#CtxwLomEDB1c*iw)De!x=<ytXmCe*9IM|d&pr`^tt6Z10!gdk-%6v6qMqz}iuR|wtvc4<6fpWV0IWaDb-I9x zFBEcX`Vy>t&SFOH#c#RMdq>DUZ^dE>lJ`=WfWdr?nj;NNzwTITODzhHF_h zHpH`CkuzvL{9#jYh(|_2L2Vbm4x53Q&fbgFM}Y?239#&#DqVWJ#H21|yF@8hsL{Yg zl4g5oPknnVJi1>_@+PJmaIy(id5G8};|h_RfZaT7xt7j-MMG5a7J+dF_ATQG$39Cb z(_3;tA$I~_<`H9@?38W_S-yLqK-s14#mI@g+R_1<;LKRrg;B4OcV|tAs%2>?a?9Vf z587X1(7CGaOxkVR!5>Fa48TYGHi-KyRKs5OTqXVZ|cF=mIV z-TmZCG9;`#cK#B&pZ{xP8F)0|lW;<2CeZ6A>2)2>dd?OohEUp7N+~Hz?=w@{>3ne! zB#t)$y-QY^)9*%;S`E)-aBER*!)C5RKYC2E%#H6O=9H=yR8E(y3dr#b^^-&^!C8|hVhNL`-nWYQ5a$%Zon-j7IZ(E$J`@ze z6$@PK=TGVi+AbH?#Mz$s@T$~+P)G`!W1g3SrDpf_H6(^807V1DFpI-G*Izj$mD%>C zOg21vn7A@OI+%n~KXy#<*)T4YTwuQ`=GDdz|GHqRx@EQ(suve)L>C$}0OZW+V0IU@ z=NXmfnXjtSv$8oj^yy;l!KySQMq8uUVlhNYHh_f^O)+%*@I zOM?D@+;}4v08zjLT}jpUw+>Kg(@cJb0<&>JK!B*SmQd;HuE-lAh!kRQya>OK}*VGrw%kpH^a9*Geo z=%U>+R#B@t8gjbJh3JPtDEnpR=nI?bN)JVkb)IRQq_s~JZja%9^?A-OUkeyRF3?GT?FlK6`-!c9@WP=9669ex1e z?W6WsL~5%)#^m4swea%nw(a2m+hwk3fi8s903CRLjN?{e>h6sVh9P>C!AU!DGM@-; zJQPHk#C4R!I~I})bFbvsEApG!Wbsngmlu4HcPrnswWNwwJ=z*47nG zJEzO*-rm=j-K`J0SAeA_ceXjoA#?wi<%wU;>EE1NeondW^|$)uYirH{tg29K6?(5<`=q!eKH3STz_1xBol9+EDx)Ize79g91a?s zBh_mBGZSv^;qeM&&P3s03EUlT4)G3GK*?9Ogjen09W-m`2wZ>C0<`ML^}QysOf{>@ zMSq49j`{9UGz@zpb_6tQPObz{nV&uZaO;fpON-4VEHJHmeuVR>W7v1|)(DzAegyNb zR`d18 z=^iWuaX9r9j`*^F>lFKa&9KkJB~d-~)uWb&ib_@G%dASy;#}rzXGgNAY=dZ67QnNM9y9#RvSi;LrGR+uap_| z`aF3tVd3(Klecr-LNBA|`MzO*qj4h^m$A~iJ88YdOj!U0`8&O3=xKyii*_w<@jQ=7W3p3_$fS}Ie-6(P39A?LdK`D8 zYNf>jzl}sH9%PYIw0-g^j{Jcl0b#>}u7evPU$EI^&)$8EUj}I(>!_2a{>FZX1|?Pr zQncYEzsqM=Gcmz>7Zy0;x$;fGX z!azkg31pe>(89YT3P!7hYxrMR#v_RY?!K(R^t}iLiL|?yfw6c{7-tE|R;|3y&1Y=b zXnQo?VUe47J{@n`nh79DGvXBPSwBmXR`RT^AWE{Ouyhk5`wm%xUC6s?8Vjf{*>CzP z;uC=e>}1l+OHLaAcyqSsbh@XK1PNoG4J56tRQ;P<+*?|xSD;*~#0i;yh5L@no}+Fp zK~8wnp|aU~C$Rf=$0b^XUZVnre`2UFZ6737S7|9xoRC_!CA4I7n+~%iS{2b+bMl*Y zrOM(I8?Ypo#mJOVBUU$J7c>n=uQWnt7hjGFmpEvgP zk|2Nf7STebMX7Vg=JQkTQv|o()11Y630)j5R1k@!RBEFt)lB)W$UAbHt(O(FmbcjB zW5iPHx$EcvP`Jp#OO`@oN?BB7%C4@#9}cI8FM>^B!RPaub~x{bL}`REyB9+b-So|9 z*HGUjr)8fk0!+4W+ltb%ib((o1T+J9T zIh$nkJK2D5(NbCfmhM(K7IW$dcHZD&Q2U{9_A^k;w2d#I-)Yp51ai$xKkU_8NPtNbI$=AEU z)Z1exX}A2z*BCV9icgX+1pI;d!??-}3&}Y9LCdZye?4l(cFp;dbc$2EgN~$SQx`cg z5!-W1`6aV}jI>{Y_~`_iwVY+#Ml;cNgwnsW7GeVU7Rwo@XD_QIGg*fL0UyU_n(x^F z{~ri1iIaPWU-JBlG~!uaXbgwCPe7T1hoMi0)Em-_GZKK*v6M09O%pvMyLq_*<8EQ+ z#C+k9$ib4FDP<$quVoKK_1mScCxh@+M;z9f#p0bND?7om6%7NlX1OPE_2w>j__)Fu zFUDV|b~{l#Sh@jt3_H|41PvOc1Dd%2qKM6`<=U*wP#3f!asu=;%B;K~>+?{k*!8Q$ zsn&RLfvWSYRB6f)Lq$Xl#%P<)k+SfnDouKsv|+YP6=1Y-c+O4Dh}ii<-Krv<{itWSA8sFh9nq;~vUJ_NEw#0iEX#&su3=go?zlDTTqBd2+2-M~+sy#>51CxxCoE51bxsT8}*zbaeP&s z%O*OtP0JsY2?mV#Ou4v8QEBeYJrIs_Cxw5nVSXn~3ZLXxe5wV4 zn+YmXRy<@Qe{-yrceqSFmAySUP|03A~)^YZJOp{OR9TJBcF_W;&&OFjUg<$ z?|zfY=y=zbmHtnR90C!_Aw|WNS(DAE@2(eff#fEHsPcnsb7DI1w}lW!vR61`@Dg0U zSw)xgaNe$CdNM75oMgR?-}*gI-_^1fi55GU^~XuE{c`RU5Bo+U?K(*?Q8@b?&wDDi zwtTSSvo8aY7%jF-N|xq@E3EOUT!D~@9@|=?m=`fn%42f0Bpa*E@Wb3q_HJUk5kkq^ zHI>{;-`FC;R1pF5)V%sYD|TsT?zLzorLgcxA?fK4B|$de`m!L^O!TBej&dS>gCBo_ z2!FAZZYi_eJQ))=hZmHhQje*UtKIVNX%c(G;4J9OJrRW>2A-IJ>uJY>va$$H?B9fC-0WEDakw2s@MIlkWLk622vuC1%D# z=lO$#7hgM|ae$ONzN@BkqIIAlrUL!Iq6(j}GKk!T)iw~Bty`pAjW`umMF{d-wktZL zZd8jXAPer;JMLAK*KSKGC;6H>e%f1cU>pCOs>SclgOJAiK1WOF2jp=GhBZ9_ccvi` z$umnFb3Ko|tk1E@L#h^G8Ek|-QofiU-uwLQh-@67Y!NrnglMJ=ttVH8+pJ`}P^Nvp zR{SMI@PK_N}uQ{Ft0C&DCq$>ATs@gG zldVPmItu9%$=cK;dV+kUr>}ylsK_r9lp0BhOKe8qOAB$OG_NhfRY#n_UU#kY=&(4} zmoNg9FpCuV^JVwR`-xEgkUQgAUkhnHcIV%Low58e7vPR?mpkLe7n$C@ebQ5#p|^Hv%$aeb4Zym?u8>X7?D=(Q2!#)4Sf<^{aO;i9)IwHO)u~!1lVn<4OPFiypEE(D-fjm@G{p)$l`|+SN8fnpM zzf2!oSTvyw^btW4!j|#4!7fm4Ma`XRhPef!E$8OFIg|rYH9OX zXzXZZ?5}E_!G4OSYkf~xLK|38y+zgK!f1ALDAz{}Uz_LQF{LA8jno!^FUtR?{Mmy% zIJKGmiV#YLA_)|-%!{k&nbd4%t+pf=v^XX3LH|76a8lZGNv>WdA^1)6j4nXAdI4p_ z+u)xD@w)M0%}UhDs3Dc^BRKUCgM#>)q^Xh8U$Kh)^t==uP@)MHptLY4u-U~=EFIYH zI?mnf5Zo?OyLFJwUy;%m-0%&cgXjyH4b#p9VTRniZ4Ys$)A-dT)fmX<95^kxgYfpm z+jAV_tOI$u82A*D<;~1|DOi3g^W?wAb^UthmuKj_NZSw21bOxsC6dpI*asO=)kRO@ zpbC`Fi?AJG(Z`|Bs$0BicP+K7k z`)R3sc)|Ng%}uzL|A!q^WvN5rYWu_|BDnhjh(n6}LNsKyEhg*UNt^E97eF=b$|TL}tmkhUDW2~T?rx53s^kJWSj339|3f?|B_rxj?O zb9#5Cc`hQKMcg%+r>>n#o$=fEf31hVMCi-`U@D?d%jfZ*)bKX}5@$X`;huT=mZjXY z4sU4d`I_G6s9jesiyxJ}okc9mN(VjAG<$yYZY=n|VO$Btzvfu8pjs55ZN)=ukis~e zQmN+kJhhJ}!hEvkLK0OQI(r7=BWwj?J@Pb~=^9~k3>}B6U02EW?Sgumej4jN5Nuy3 zX%8%iiHY*u|0P2JJfUg##u|G=Qu$%hUU5=k3*ct1W)+^it;3@#dd}dAd%b8>kD1@E^wv|%Q#o~ zn#j5bUAh;tN=&2p7ep5D!|{vUIv|wql33)NTpa~4OI7HzZo5~!H`vDCF!PO6O*wKq zv+2SdkhseOcnPlh<%o7sj4ZMOj{6ccL`>>O)!t_EskFcUdsT0q8ih9VNtv z7>(woueW`s(aRp$SXKV7`RP(kB=GZTuHYteJ(8CQAdvb1XK(#os5)$go@2tkoJ0G> zY!eRmWw%KMC!R9H(cK>OW^}%{M5-E6K0TQ}{994igNmo5@lCkk@@%9}P4AN%XIw`= zTM% z_FZH`abxDIp}9HxlCfEO3>?QFN}Hf#-xTV6KndhQEstcVYTz~g!h z-;W1JqMZSW3V7$5rqhML;Qv#kYEETk>_gOazF%JWxw{m_TIh)jQM^1c0(41GwwxuIARRk zM*XE1LNkyXxQ!`}iv$HI+;`OJ97|}2bgn0MLOvw)wnD+|V2+d}cseo=Q9m+<+Wx6NiTlWMX;N=!!RPM8+7T3xam1uRQmD5x-MF4?D} z;$hWdsGs8l$|R|p&B`CI+kF0=h$&++kXNo)prC+hxDGT+XSsmn_ZBKgLdG zzAR6}Q|OahOj4>xSUl8=R@0KiY6gFAxDI=QApLRgHoCXm+QGPp!wH-|5b%vrEd96k z9`SQR)ddGB*rb(a^OoCFw9PWBiAea1XlpFaABq9;#2s`ovV!$Ul1x!AHq{l;=bp+k zif13FgaZdEl+Mzyk%I?Pdl=N`7e7T+SL?SD(YxicnFYHjF@VeTjAWS%U27INSl3Hs zADs8n?2wc$^W~Z7;w5E~NO(!k;&j$Ld7_Z5M-sn@izQ>S24kJq+i=yMI+O1@lZ<=T zxQGD`+g+wFRgU?*ECo?1ZqYeHDR0Nc*ywv^aF%LBgs! z+iLX&y1H8T)B?qa9vw0x`4R?1V~LguRc|rQpBh}3J=qH~ap~3^?4^fJ@v+$&0`vg; zi`1xdn&Lu!ylhRRMNn$v`KqyEm7m=v(lZ zI7-6!Q>M6vydpYR&Q3}0*6?h&I7WRpMh7^%O7HL4;8um}gCrTj+K5-m5hbB(%m^vV8`a)8Td;jJ8#v@)G-GNVR~>>qP1p|;6j)@g8Mlf9j)^?*drn={qxJ@>q; zs+d5t+jkkggQISg%hXem&UFe9RtT*w+viI^v=Fmrs;Z(gi0xDkO?hAFnZ1Yx+tuek z(EO93Cz7|Bn&G(&xnwxF;S$=-kA%wm&B?`PgZJU8Gzu3THA>}Cc|_?-{J4?m1rpuQ(@Q7# zoz6pwZ=qIU!k?HPbj{h5<&?@K3d8*?+LR$4}*`t(#pYW?GwQ7?V-+gZ9s?U`>!Y zV{1+qnlfNK3H}?O7Nws=Dw?$)GS1>KG_=B0<7+V%C2{x!Y_|aVYvv~vVLhD$t&^U4 zG)I&6=2#jS1_}$*1fz@6mnNZPj1em6HvF-hSa=@Pj&?QyC>}}B9fC0yIt=aQg_sW$ zMS)oK%{&V_cFwDs8+O#ZcGUcKCZ?XUSk<{BYPlnl*)DO-ZL2zVO>0@mg(k&w;pB(E z7Ra2JKP(p>*1rJmW{5X~g4(BGjhJ%0jzI~RD?Eg)JRvH==+p-cbvYq0vy`o1sqGqG)PdDa~9J(Skq(x<)H7C)!hg zT-TI}Q6I$@JXmczVikKouxHN`fuJmg&CbOEgvrB}%rrolak#yMUgZ~AMT+AX zLTgd%yI>r>b(qZLm{Zn=rsAuoA0o=SSmCpdJx_3{8&9rIcOaJ4^x-tFu7{=x5~9kn zXW3a~t*VE32VHM88?VG1+s5&4MA+qsheH%t2hYk!`-@M33$XOA zL@}&fj0J!M>dsefA)|%j3wqUA26Ky0mc*Q*YHO{IeE(Z*|YDDPa*1|*j4)O(G zA3^rc_NqO591Kt7U24DRUe=WKK#wzGmle$41pt7*?hWnl6N5n-Z;$iQ6D_V%mKx<7 zZDqm^O@&bF5`kRNR56L%P#<#wKfnSX;##hDmFS(E6+WkI_2#W>GVq%2$`bCo-u`a- zX#{KWtFrccP%-sBlz+dgYCh9$`;qYGosHMX`;BhyplSQS&LuZSCRtK09k!Kk6PoMK zrY_(j^CjdJ+kB66_(3=A&Z847#WjPu8^uuK3DeJ0kX+St&M;){!9LM)Pg>cZ7lh*b zllzdbVS{nxM~Q^VEl=ru#HAo5*0>GAKB8xh#G*o7No|Vyg|sar)w!fnL3|oTGqJr~vV^>+VIh?&`J+Gq}ATpc5uAG#8JJUy++y5!|5fv?!j^RhAbVIf;P zYQp=~g?oKD7{Sd8$!6lx&i;j_b@AQaxA6a*hC}g6s+<1Jz@j+dARu)AwQMO1-~yn% zF~%^zp%pimY&CdbONLUw;g=IvkaXnBsts=vJBwtF>4E}h$nU*GP1A)S;}s2V5l zBz?T=qqz@e;C73O7D4+pMv!_G9{juYcS+N|sydKwRqq@jZ|{!+VSVePkG=DQU#qpl zkSnyqo8gF*RmeNHBarFd5{Z$kDn=qSN?1wzsg55K^@(20CKD1W?v7A90RZ&@UQFT< zGdiNNF?D}}eC2x?fx>+?IH6io_B{1_T>sUCIhm}vk9$Z+qm+E3iGdKR?7OD1>&?inj1Jj`cH{ zi#@O9se`j%Of~!mO+rs^aeZrZb+O=3`Jlwc-rmMS`cI~8$Qg3FZM-<86d>tNT9h~*_W%eR z;J+;$>b1EgVzpK)b;L2rJ!kxy?ArU zdBx-w>xnfworGF>C1I#?)`Tphbt1~Ah?|sRf!03?4{+v6bN3sojAV6C3=d{=x>PHP;&1Xswr}evF?R3;Nsal~e_MuCFL&R{3(0IFH5by=f=chEr_@ypsa`nZpJ<#?<^m-9kyz{3cy#L@9?_Y+%R{9VLvHad1 z5$i_%jv4XqC;%eOx1(Nd^;kO=d-kYdM*ue=^AC$%5WvkR#C!h1viB?x4OR&}B0vouF4#S@WWSQ(a3HQH0js*JKQ?flioB}l|wJKrGU z70Ze$nv{_zAHURGSZqo5y9(d*6oybC!>kY{dmB~ICe(I~!Yt`rNSBex()u-v6rb{U5g@dz8bU*QB!aedvAV>rNU~;OSXYI< zAxdCd7d?7wgerxAn{1(2#f%3dEonu#Y z39$0&I(Ki3geAD-;U3?#e8k2~v;wS+N?Oi9ZD%Uc4X@9EjhB>Cg*B|<^s(UH!`XO% zyQ(znTlJ2I)G_y5Fr&v~t+}ex7TjrkfQB5&HG`${^7vGBo$cH{3>tM&9#L}@#(1q>%3Px&l$XCEsuG+)o0`@T2c5lLVlBhiK$ za8sMrYqIVj2`E`<1nLEuu4~SdM)Wj|s)a7a3C#6$g)yEUK6dj@K_U_{xOLw$Q7OwK zNnlP-!z+cq`&d}D zby*=Ggi>#Mp)lc55~eQUhtaTp{%G)Bc+`yz*)dMH7;xcK%Ik;le-@RYLi&QG_K9_I zXf6mJAeVPJqL=0^e^LJX|I!VFsL_yH_{fPg{8(gy`-8U#(}JN`R3rgQ7mB4=m`oG1 zq@66Okx0G5_+8L%gC^3cv#Hv`q|Ff(qq|(N$r5HEHc&*%aEhnR8kLF;qhPY4JTLrn z1--)?j`MdbRa`53+O1^JB_5wIqeE@!(1l7E6oV8w(+J;{DErHj13=46u(uf%s<6BP zw%wH7Q3~ELxRxh`{vJoy;Wmz+h-_Dd-#_7jmsd6c>wr}Jol@+9ggC8OTblRv2x>U8 zm3WLboToQXu-7qJj+9ql&KeBPqSb|lFf4zO>wY1Ptet)N5<-!UtlXX#Z0GZ&dKS?= zvk&evK}QZRY$Ovg5*0(CTvrYBhc(flKu6~XNg}r ziQPnh$Hv_pJyEyt?!`sj&)57=z5W6}et3cqqlsNVsmT-P@o5Lg0X=A=&-U}V^Qx

(#Rf0Ew9#fRfaW}F@T71Q7NGAE&!!8ex47<$Tt&&^ zTzZRYkFUEv&mVP-?!C&ZmAMM5@3ESup}41iW7`zW7GyXG1#>M%`6rVvM82rn2Y_tZ zqcxGZ6MdT4mm~A2FOcBe@6XuXB{YMAb>|h-1+}?oy|;7+A!{GNM?mSx;^F1VsR}fR z`#HmQ?sN0IYVPeh6FeOcplq8TSe$TpVQ$Es*qgcC zd8s#>gZhL1Nmq12$pES$Uy>?6f>-879)Mq#UT?hI&f?(3Zt}{aQyPbuGA}LXRDRtg z+j2-$bb&>Wd$p)5EzCf5e!EyRf^}-dk#}@6f^nc%d zH)c`23!p$iT9E!F!e{|nc3$`^m^*8DI?)0$1fZZQ5fCgS^y{(+uoQ}-==?z0wRJ0jS5kqHfC3BRIPUK{k4W)#RUK)gOP>MW~k<7k!09E zam5hK*u0f}-)TlmTgBN)4ZJa6%H2t;;r@F~)}XI}nI% zhmh}vxISTovF@EQ5+eUB-~3x!(e_6|R4J=}vzUx@% zws~~6Q6(jUXhx*YCo201JPp+ZY}0u~KvreiFhZP^Yu$XM|c7 zZ|XAps>G;x5vtGs*&%4|V>9DDQMPfNetN+-Q;qPJoOGSPW5Cr)&8krah5J)*#1ia) z5ktkf#?Y8C8>incX~m%K*Hqou#|mg-(fU$WjNUf&pwlP9Vf*m(o#*a^TSI@88R9qf z{rT=X#^DAS0e!77>y}g$i9^f0#bP^2u>mrRuNt0G4F$tt&3Ocbt-xz}_bBTfcZJh# zVTtaN(s&Y2WkF8Zu?Azqw6)dA2HxUlvoxdGY&4{LA@u)VxSSn<@^JYl5==bGk14&f ztAUm1tedp|wwN)#_vP#tG#y2|@oZ|-GdFq7`0fBqx$T$p1%_jL*bitD;y0*B)ko?% zSH~-?*Bo(e*A*i(FK7kO5Hg#!52nV2ZfG=J<(!|t@ft4N!}m=3K6gmD%TIM6*PnY6 zI-Pl|4eJ&8;os7P`oV>tUcQx90K$yeb!$!CyA%b!sDiybdLq>LcE_MmKQ+g0@B9df z50L?U-;qnG{v}6BZ*CC8ml>$S7FVYxEXJE<%!<@DWe3^dzw-vu%Ah5d82hZ9!jhZD(oX>jfO^sfutRYkA~vcS$<0|}%fqdMzzan# zUQVpas-FGfid!hUuuI^KV(sWr`3xP>vaHt6Da37COz<4~D7RDXg;u$~&b7O-OdRrT z&KLnJ?z@s1{Rh}h^M405+Zou*>30%*O6kX|wP223RW?T+ zS-1QJ=FEkQGm&fkk6q96L})wwrdu$6l$P(hU5*XbU$jB;7;6h$!Q4-TUR1AoMNA-graq%ZE&5k1YMwoiBhrmdJoP1b1E zv&C?EV-_6chW6rLOtLtGpqE`8)fb>&?Z}jc z^SBA#m@>yQPh@tp!YFj89;>J-91sO+@YHx77@FFc^Z-nd!%>P(H98WEIDL_DaQwUT z17B5&RoJ)tfPt^j`oE#R?Sv^-Q-9Cb#veery0p&qy`z zv~evAvXa8nG{Iaz&yxz(n*--~f_gs7-dS`$nWj!pJ=%NVb``zC-Gdot#T5z{Ugpg( zA>(FLEWie^a4Sa$Kz+a8H74 zQ>g5Rg+d#YTt%BGoS*!CRAd*6hUHK{`_gKQSOLz)jG+D7O1zL z^!}H!J5SZ)@#H32U`&q_m)fYg&~f6DA_rxIs{;feApnD7EclDS@}Cg;+$iGh1xUCW z)s+$7j1uPVZc0Um(`cT2;NYrx%s;pnR{LQs;QY@j_M_6AVH3#-4f#Fi^opOL!=G77 zk6if)U-8K|RP@Ob;x+aX35y{jQ)PPDVi}o%R3wVt!qV*jl=T&0aV$&Qi%W24akmiM z-3c0kYaqD0%LWfFn-Cm=ySuvwch}%{a7+FT$-U>x{b!$jrl;nuw`+QOmZqzw`X%07 zU{qv+F;%u*4XKf73GRiWkJOmtE`90`sv8>>-L7>nLj_q6AG-NgB;iJwqxCQ$%?0M$ zsbO3y0WV!RnIZN)s0E0v!Nf~R96$! zDb>(@#DBz}-c4CK!qgaHxr@GeSl#l@*!A8W{tU_h!F9Mjs+t-HSHSR1EVBT0O}FSB z)Ir;h3Vm6~aQ*739Wac-LFf_RsoswTGLm-5nEEL6^{aDkVKXM5y~ah~Qhk3ng-ogf znH$sK%uqe|^bzaDKc|@x1ZExgyqZS`Z3i$x^m8O4~n z_OlTqk(^^kTfaQ2FHJRSChXqWyA9iD5wK6Rqb+PsVE*d(b9$RZ^PX4*tt}nrIk_e?>RXvooE4rZa|L6{c2?_ogv*5z{MgUa)f`P}m>2Qmr=#YH z{fbw_r?q(BN^_)K!AmJ6Dl_SzXofj102#Sj_O|z>|(kwGtewJ|Mxc z2`Y<%wsNjb_t(?D#ZpNMQ(n8imkV|!GoLe01kyR3-cQ7xVYN0@o;+XNle{F}Wx9$u_jt_BdC&|iP%#ATpuGz@EH!;f)Gj5Z)D<9|=Vor0^h*oO%iI$gJxTs&w zoKYFh_KJ9AGC~6)-&x$!_~mZQd8qf9qPBP1n?5Ra<31X6lS#6Yb&w@JfFjq!)5aR5 z<@z!>TSDe&N*KC@v}wzN2AuE~@m=#<&xJJgtH&GEr!grL4Je#4}m`@ zuueLcK_#$F8p)qKY#5dB8L<|e?iJBpK4$|fPdeKwrwI347PHevV{ew!xwcV@-#9p3 z)9=6BVn(&!V#FcVWEfuHT!cYsU73VKn87y=U$K z9R7*CsZBpqX=noKC%K%U57w1+Y781ydh(ZxIz(Z!<=UUef^3S~^&N`;%yP09IXH2I;VfT}_vJZW^;1Z^?iJT@pRUrPqqy z*|4plhCitu4tn@vQBL>Mh` z_JtaxX>-R7vL#8JRF0HBoE!7=y}`;kNv}S0y-Amna9`8DWk3d{o|mk2y1@k4@HpO> z1y#UBS@Z*?YtTpQt_fTbYL(Y& zozz4BcIC9Zn`8}QDf)yy9`yR?Mvrr&H8IO9)mVfEfj8a2jrJ*mLI2z5;>jRRf<1;z zM^+Cg#jqC^O5@hUSY6n0hEUect1-V1)=?$mgY?OlPUw(aho3aQ5d1~K&|uM%FS--1_`&u)6K{bkA15v2ppgNWm{kaBs_EzqtjDQq`)&!TsS z3Qy07N5{`w6~v&K1y{XDU0Tu(Q^&b80xap{lU5vvTf$=QJ2lwISI<3>|vMM$836l-}(N3YPTPw^(0@`X^3 zdlW`&(JB%zF*v?<&_Twwrc{nK|1>cWrYk4^bv!sX7cM!$mm|VIhZBibB!TDle3Zz( z1?RXGzo1ck#NBNA_0CBc!wzW~%+ z(UVQtt(4ijQb=1FH2hkxv&sW9?9wL8kk7=GhF&_?!^-G4B^;wWhi=2@k6>?0T(4xI z8IS&A;=|`}N8soq)$%TS3-{bl-J*q7$&V|ne4|RsPaohT(Rb+B5+{n@meiBtA;0G% zFWAORYCm`z9dvBAFsqpDWa2qn=K{(eNGpsui09X;y_#IF(IJf+XD`M_ohj2v4gyEL zlUXr+%@rQ_s$<}jB`!ilf)2f9;02BEnd3~6MKF@HqY4+YYSY&=rTt>qB;zDXW9DIRLPmFB`<_pT&{<`kjD5jb>%0Mg}Iu>*MOyI@lG+p?1Z94 z+RJLaZ26NLyn{JJ5Wncn$<9wuP;rAqtyy`#qPq}zn%6$6}eWY%$W+ICT>fhyQz<6 zCnU)VoJa>cjmgP(9*#OL4IIcMXu>%*caQXm>7!cb*qJjDLaQTh>VDxiM-+Zs^PGon zSSx&o>Ml!FEbhqqrvTi?0L?L4)j)SC;q3J7#Tf9b5i- z{Vj=%=SrkuKF-YBZ^hY~-??H!)5?^SR@3F|`&f?^#F{(yUOY(ky&7nMM2%_SwuI*6 zJdzfF+vaAYqe#Vdjk8NvN$qZOudP%ACg+_D$@82#X*u1G#OflXm6EWfN^P;ahfzxg zq#cf>*TGAij78dlP>E2jHE5!Eryd;Y2wObq#slZ!rgYT8*l@<{l>Jx-?!RRv3@58- zk@-Xg+FTUV2-^eeiX!&hVSC6CZP1f*P+I8s=J8$V=||MI=C?eRUT%1NWi?<_agZlN zKhw0L14+un;*p>u1?mN#QIsw0{kSKe3*|2PRiL2069z?640V6t{J?5*(h}bc$KX!% zwNV#wO*H8Jr}szj+pcU%mBH^f;p3mI-%u;;&Q&OOsi)?|#-C~0WHd%;9Mqu7z}r!_ zhImt>$TVlA8%WDWzkhIec_U1zUs*N)bPIBH+Zk=wIB9WD8x3OX+J&O`apTw~c`RKM zD+G&tb7u!_d$WZ-utw-tR1&dl`n3e17|_1X7IaL7T9Dl^ilhk1pNNn-WBbvcNVCZs zjbR^ui|z5rjkK#jt(-y(DteP(T{Z?&staiddF^C0M|tzLTq?0oytq$*MvxMyiRzlp zWi;41Qob!Xt=burf|i7hv8Ms!=Z7+Dv`EBBp~Wo_?@98(%`#$`<99QppBn;wH~UL_ zwDmViGJSeKLa73TLKh^&|4EEG@j?jmxo=CEcjeAlJ#J~raxFfaA4+TAwcq?7J{rt-?ZP>OWHv}8hsSkj857r}UBjr`N>(_!iI|qtwa4^2GOR94eE465r8H1G^ zq%GkVCa#eC&|U0@%rBc&kZ6qseo;kR;qcXGL*T5@yW zge#D7ZTW;kW8h}+h?^A@9Bm=N-^?SqtCN2L>K8e1ZNBEiQT%X^e6q*Bnw|owu%$` zAtW7zRL1DOsl+r2btT9TK^og^1e{^9R(i>o*`B%1mn)vpEcG21sShl-C&!!gzVi%uu-@$&3KDI@64BX| z5;O?T=l?UPsJ{3x{%4TTA5aASJeCU1t-&|P0s#8Je!(oD7anRewFXQVykIa4bsQj2 zkP>~+I36JW;f!5U?Gr33Vo0cmtWv!Hph992K&2pSn(<1dILpviv#?Y{>h(vJlG4-n zw#B|H!ZxSJSF5^9-uB*0Ue>z%?bk_3BMNA=*IQdhLf+-Avx|r4r?u{@kGGB3lAICB z-zU-TL3fWJDPgj~-E!a)^Oqk=WQW^dRr!w%Ckw;0w}{hREcN*bk7gIT4U%R#uPswk z`uTVF)jC3y?gYiR;`njT3j{}Z0|Z}fb8K_yY*D76oe_}5%vcJ>OwE^1QIno-aqQf{ z-YMXOzvkEP2K(y0q%t2g506y1I_I<)obvUl>*H_%A+ttoV@1ik7QMfAOsd)W2WcQ2 z`A!Wvka0smRk&JGxz^5&G^5*jTQ&1sFAdMRw(r74XXt{)7H)ePjjlj7Bmr7Q>K4%|2It@N$vxEnWd|AGC!uWeW6y>q2QowhqGKU_y zF^61spx6^e%*MrjYspi2pJn>?IV+z+B~8RFS3UC9tIhBam(aj>-?wNN=HSzGJhLyk z?>wrtcAe7d&v5xuT4wscWg$IIWL5W;3fqAP-J${&dQ3_XLrX362O@h*2&NL%)6Na4 zmkDCvyKPe&>_IE-h*9Y&D*K>_FMN=zvq8P*Ka+Ux|T8sxi|y5sbX zyq#(ZqqHab8VKt-(Zn`;^OFt@8661FIB;hWjM+ZcnOkSuS|NKE+D7TBcys1C23ghs z`9uh!dWo9r#LLk*mZ9N?OJ-%Khk$bp9Ht)@_06V-Vj~0xh6y_SqFk)m(kiX47IBM# zpgPX!S(de`Cb#3ooiCiMcbaw_?#b%<=Y|E0R<+vA#TqG&7PtljoQ&_8no9%*jn#mz ztqVxW%YBBr^O5dSmEAi@)e%P-6dcPMrW{%g>{zzNRuRd}6vah+I3;pNwx+B$3q!B; zpwr>Z=y4X*$(C(vsYiNyh$>*X(l=N}Ksa?;X)47FYHc5I=+TK5s{-Q~xoU)gJ*-+8 zY>MIxMG5Knkx9A|crQ=%aZnJpPjl1~r04AkmjpD3LOH}2$1qKXI!edm>L zIGW2=jjD8+@10jyjg4t%j*GSS(S-bRPqwBvoEAJlidze?FCD@ijC~c(uEvc}Oc8LVow|(wDwc@@CpvnP1jDpDJmefw z?eu0M-lS4gYn#Sz54`$!k56qRnI=orGDthXQn0vG%h+i|g` zsZ5q}Tlf_*clIdde+*`DPmjX8^AZ+}BF1^`#?c#wGrL=`$-LeO-vBTWxIMlSTKCgB zZVK$tzi_u~Sz5Pio6HQ|XUp6C8XYKh6xRoQXL?4u%7h|9l~J5j6e@U4e5j-C8`bxK z(w0y@<10U`@5tGVV zE}@|rH1uA+KoO>}+z-CbBvhap1*b;nvjwg;QBtE0&2m0LX~oAg{M5QPp_@?xR6;`7LKIdTw$WThRO5iEtl zr$nSIZB&{CX`!4mRH;>)O7GCYmmK>RWT7C%**xWb~R8Zf?jJSQf&=qP8pU% z^#fzJsD?y>X!OuVN#ZRAb_3xgs(4rAyBs!G?$Z1%uk+hwP!YnFFQnPrJskM=2H!W_ zr9L@y;@on6$=z7@{hlpzx4%#FmgT8sKf1RxS_~cO+OfqUvt*3FKwVqJYn&Mr*!11} zE8dV$IU)8@JhH{sdQ*jqLy`z3OjCy_?&CUVSfyOww^TeNaf7 z9GWkFdTtIb+AQ>y8mprzLXDXX%Q53SUJsmxWxGCL$%^AuW?<6P`-q# zI)KG_5mMry+oB)LfD((3#3R}D8sR(B&^C8M1jw(9yKrRGEnFzSLkZ@E`edU+I#uFg zR$STWl~|l^A{y#H+Z2vG#(N{mk`{PX)MIj7yTKKUZ}za)5%#l;B??0W10sp*$qc2< zC^K!xFG{qWITL-34?odG-l1_5=6!BiDz!6BkAneUQqIyUTHSI}&vgL?givaDBVdpKxZyCyLCjNNI^rPNG2$xp{P z&8NU3=1<gA$7TO}7$B=E&^XSOJhalvxk+#i*bNe zLRS=LBe|qwvG$q8W01Et?@@f5mqDNU!e(xrmR;a9JQOoEKnlE43oq5&uV;)>w+mN) zB_tm6{s-cH)p7ay&+bct#-PGO^2;$8B6pD;o-0)`)rIQqgKY6U1ODtU(sMVeDyrHa zye$R@8H6}%`wnle$2l>MpgLWo-t6COvz&abE^g&E#iTbwEd4(B zc)QakUdr5=^`fFcjhSD5qDctpZN-&$3q#YOOyLnLu74BpbSOi42K1R)Z-f>!C<|C_ zvvhi#Jwz-yLa?!;D66(O(Tudpm7Fz=_344}z7RRm3~%({>w>sobLeQCn^n}<>ETzt z(1P((c{}`~aVWy{rS-y5<{@0Lx`^aOCDUWP@3(>p4($V^1h4R&<&dXFpY7>k84CuN z!PlBRY>>cZ^xQ8P`$MJ|z-S*qP<2-5cLeHmJ;wCtw}7=M&S>rlwH^; z*f7wNC6lWvhrS}S3%0gwrG}(7k&yiHB^KO%b4`>Ve9;ZPB-vYpv@F(`Oh~#)rF2<= zhQusAImdJv{bgAJPpach+v#s)X}W;C)9g=vJ9n8}>Xgj_&l}SKMjv zma2P)m(=^j5~Cu)xQ*jh#mQGk!C z5#JlY>QAcUv_1V`3d6<+H%m*NgkF~f+dLwvi^@{sPd3{th2&`HEhR$@ zf;h`M{=vG(vshLWo!ZEttLWy+^hh%3oPRG`b~}>GOI_)V-C9&J6*>SyT^)PvIYjCy zL3plsUuTOs$9M*l{0~U?sivl|<;y6X&n&dY7cMBcL$CdokZ_!~>j|gCtc=mn{Z|E! zn)KGwfNrd7UWV2nu@>%>7s20isb18@o|bmrUSJi~85Mw7^Mh8@RaSBJhX zVe)(Cl;qo)7N3M7+zgQ5b62SMX=pbgqc$XBKC zpn;YbrNu`&l0p6g5^r9RU7taGfo(}7oqQsmESIo?UaCZX`2!B>+Bs3iJk}~&)_YzC zL#a7HB8`bN#RHqyixw`~_Ovz$dn&=Td`mzx9ovII(DltUD12u8ioR<_p36ky6G9a# zoF7p6t)(N%_&j{WPvO>xO0M;SKI#InrT;1<4F}?#o?numq=+=u?Va{L5~st2O&h}E zsq!h@=JODHGz70Yng{q7fwuL%0n%9xjolN|6Vdf|84;*^UPe%q zYmV1xcO5kT@SaEq6~c1xSlWJO`5q{6Hsl^1P$jcp4?>Ey4K+2HqZ>Mep-X5eT9G;l zL!T%Ujq`SklA$Q3Q;E2JD6B24V2%T%_iatD%8RM-#&0jhUo2Jc#=Mn%IW-}%(T$WW z%l*xd2k_A6r?X6*YtG_2i-2>RvvrdbuEyB>+2BMJ3u^TR3NgzJC-j=ggvC7v`IAJl zGRWUn7?d@$7Gq+d@&Gsd zBcI#=Hy*SgOvbVmUex^D%qL0YMP=&DC%jutCrY}tP=&iw$b0?4kkngvZX)UG=9>9w z5v2EIMjv~B%D1~~9SyyjOvC@#sp2$xM*WlQX#!-#hAzQ=bd^6{TA=d{BT6LD?eYzd z)`_sV-5iIS27!RmxjFr1O%Vlrc#zfhfZ*ki5^CR%C3umef-f{#qRd{GpEH-(_OJEp>d{zm`0J5J0lb|K4E#>;V7_ zz>`=q;Qh1xV%`50 zl*RPFoE8BlX4w$b4CG7oA2?)R<4+96Y#;-#)nk+Ys{46~8VIoi#8k<@Z~$mD9R`E|H@=DCxuU;Tg7~YV zmw3N$aL)@vfK?iP+x34^_5*1Dg0~a=fiF2CqV_bH=+Eo zs|g70$nk7fGwrkfzsXhr0Q$f5Kkz>5E1@BPN7>N84$Uaf3je;D3VAX0FNGwDXN5O{ z_~3y*;D3Yr0RYOsz+k0kaFaATn4sl1^Irj!e}SFVpTP!Azv3yWpUwCyF7Gd}uO1h&)Cq(>`T&tJfE_wXo|XS~qW`b*egL?%lk*uAA;x9n18MOWkf>Syzf9{42Satq z{$^L~0z!X|d^Rnzi|twQzZ3e9PWbl|A`t@tQi6YWVLb!ip$kuDLt2^v;?L*!tA>-W zV76|tXOPT<*-<@C5)M@E2QeKIDcIf>C-{ zf7>kD1BCV~fm|ph*steLD*)Uh Dt49Vb delta 25274 zcmZ5{V~{4%vTfVuv~AnAZQC}#Y1_7K+nTm*+wN(+x#zwg@4R1G5tR{D5qm{u?zM6S z7lDq)f+8r&fP%pQ0YO0lJ;?2gCLoYO|4(d;QAguT;8jNpXnt@82KxV!$_c^#lRQQ7 zzwwpfKQW0Kcn|r1Uc~7<#bST}0Xc#J0f{ChfMF!nYCt974N@f;gWv&1hlA6}m&rpB z&0%pkU3y4-qLKk62DU{J0{sML+2EC6QcU;!S5q@Avr|n?J_7#!UkLfpW8@2z>Gg_? z^{In$sF!P!Ml2y_*E`G&A)Dj}j&Qa(?L+atC*ZjoX5l;|prN94w$Py^otWXPJw+l! zyLTda#O{2y&Og6p6m9`y1Hl)b$U`6Bgvtk&3ZZ&LDHOyHv5?ET7vX|la<#(ahCMJK zL^k|!#6sDp&x)71Q)C^Kl#KzBxPqj$BSYXYDn@a& z;G*zKI!kK#A2uZ=L0@T;_ZILzfoIyW*O?Ss9SIO!!@?l&nXbNf9kHDI)Z~GyPUU|S z@sNJ-T6Puj!&&($6xoA@(v|Ti6c%GP(~R_qz$uuMev`2A;E^CxSG%=WOOH#54pOmO z!@XnCVYB&d)0_fuQEi^mwB`(Onf$7r!c#3z6H72dLatGV`IAsZQdf=`*ZRe@Ym`82 zb&0YB5#E_Pq?KZh`-r}02A*vWsO(_{}`1oFR6 z=ujvS|3Q&ST|lSdKNKNB00Ghc2SxG-IDo`W`$a*dP>Uo^c|4Ap0y?@=!Gc(w3y5|g zCADOB++sl`VV#Jq390By>a~RH-@_^aqQS%9i2Wi&=Bx5qv^Wv4vr{uO0*_u-y8!=h zkl)!2+>^WLC?6hih#IGIcr(#R@3rH$GqPgE<==#JVSq;`9MNWGM>ko`2al3 zh&pw-NR;wB0!_kMf?T!dI=LpT_cJbUoo(u_(t_Uy2*%(s2fFT?&s&yZg4%U>$bYWh z`MOs#Yc2Y){IAvqc&ZjHSUzi)1sXxpa+6HSx!puST1p8xOHnm@dhPrXst5WJrViI` z-*+Hau^=!;1Zk%mioPm1XfN2l@c=FH4Akd~Y{@NyEs+LxMqBpYcqxji-Q_EF|%o*=7x&G2@u?^!tQ@Mi3<6 z^hTFv(du`=xHH&B?pS;`aP$-+vQJO)%)KHvmtIY@GWt14GN%VL>2V`GnmL8bqaRSt zn3OP+JEC$aZkmx*i3qF{O@Lakgj!g^1>QfENJkK{NWY7{knq@Aun3=m<_6S=!iR%w zp@i%sgh=sCVcDLs2(>|+b)XWDh45j7)JF)l@Q;wK@n?ak$O%1y37HWa>A*T@AQHUd z%gWbtfR&{MSJK6QVE?oDW$SLW2J`uc06d$7%KeJU_V0kktA3u(j7RU|Hp$mtf!AFS zN9>Qzz@q3aJ9zdGw)>5E);{VDMWBy%|Cj#WJ`Hg%^?{92 zH>D8}gV?Ih=t8&O_2$wBRJ(4^5w zWkBv6?dDP{o(_vEu(SNC8jFfLtxO9TZU-+9J5z>UCWEO(TCMD9lT1j*`qq*eaKkvx zgdCZk`jc{}Ai~pv7_bjZsQr4RJp-kg1bV%Lx%)?it$D4#wZXV*q?y}}@-&Tj2pum+ zW`e&iP1ON9nUkA0eVxe00^oK?z2x3$B|o)M^@@veqE6KL{k^?zsrwsgH5mZ| zZcJCnD`|L#v$OJ9TRCm~f*q@XnieU1MGdHy%oWjf(OvN?L!#njJwVT=mi^jD| zM=6>|yQ4gl*?)pvU}!cXp*h;%&Kj*R`JN{C3%d@6^KnOPhTos$ppDu54nUf6Q=Gyt zRdQ*YpRV6qP`gI#PjUGA2D<(AIn0F&NWQ1|)EXt%PrAqaR1-||DHh&eIMk*p+q8~Q z>YITm@(e=O0LO(>4E&utdQ)ArsV%mBZ{aZNI5E<0zjm*NVz0Znipz5&WfAPR8jhT~ zHMDEGHI^FP$3pVTrE6gm_wNJbJH$?J({VSYB475!v1@NLhX4{P^2Q36mL*m9lB@b(qGmHO|xI`?{e z+fCNR2m0bc>EJp0=L0(bltZw7us;@!<4!CuNdL0ql#Sbva~FvVS8& zn0nhastpbJ9Tfa5PIZqax$I`qBp;q{V{NzKh<8kS!ZRiRI!gWJv=SL9cQY7abC_*a zd?pb^>bx^kAWDy9%Y1e`x0a#97Nhgo=>~n3&j1c$pt@RC*E9)qHGGLbskL)66o%Td z*G^2fUnS!=qcou&1?aO2tYcrb8fsjQlsJgCiU+%j(fdui$LZ&cS2Ek>?s}+{4XKu* zZYMeaF&O*I_*LGtz$@*uWNGz@RD)koCOGTZ2(iz$`ZUBU{RH-bRH)F*lsTS@Jep8I z!IOT6$19&|OVbc!dI8_e)_p~yDVW`jJBI(%m5boB(o2!c1fYdTAT!9F{ZS@O$Zboi zpx&^7I!+Q{3oc6>s=y}36xKn)e6O>F_h}`hTbUE0>4@xU@MCoivn)3Tz~^8NZni>Z z+M39<-q@BqQ8tDIMUCG$dK8D&PJ46nh0mqut3osoNZ}Htviut4ZkVjlyBAzl>57?b zme3w}{G&v-0XSQd;}vRh=dN0$x#m?$BKj*;NGR+7LafjPrL}N5LvaZ4g<5#pKuTm= zcc2I#R%R?Q=x}gm>{D6)3!8pR9HZ&$kEQzgmj8PTyGtrRml?h@KQ(|Lcmv+f;Cuj@t< zZt4V20B|{$%OmoqO{!j3K)h#4Con$F?p%@p4r34d5S~>{uodN%%?O|-ARm1PEls+> zE$U>iTu_PI2qNPZVH)HF8(EH&T@5>QQ9HvKi~9k|F%c(MW)fy}g`7XSwA_>(3Efts znB-%3Mm)#f=^Hn@XCvT|dysEfnYF$AOn{`5wzL1g*U|8`HcZYr3K8(2g*F|#(lNbpp0GiOgXs2!f1)nKvHYisjx)V(k zXjhpnO+#C~Uu2!pNS*_~B{mX%b$5}>&IF2HaXL9V+EQ3qdha#MU2vD!Z-V=(Q zD-^$%0$6twPZVV4KQp4gc_rPRN`-e{kN}6zgy3fkrq>K5@AC0Gg|`srK2I7#-tphs znSDxU4ne9y01BNS{es)Hd4V|r>L&%@4~vj*GRc^yIw5VjAM(c^{QRHzZvpg=I>MSV zjv!UJAG+xu$wyGFzXk~hKOj#c86B03R8zq80O)#1)GPP}^B1W@#Kyq@W%}d%K{KI? zVh+&i!c+9d23>KQpzcT-v|816=~fL$D~?q#E6h6ec6Lc0u|Pdt%snK2&lpx3^j<1S zzHaq>H=&Ee7GQmXJ$PUQ$brif<()rmf;6>=jzLdZ!JFn**y#2!3zQr(i^`)lTjX+G zfI&PV>@tGhT0fZTf#P?8}Ei7rGu!%AGwL!XJ7Fq1viQ$Z(rh4 z9a)?xykFD1K_WI!>*Jc#R@w8fI7~f0&dLhpVa>tpbZFd>N@sm1!10f_Y$%~UaTRU% z*CRc>wsK(^-96^27TR2vq)+>*zQSz?R+?ykGm^Bg>dmnpa&n(G%A!dZHwv>y0F4d5 zMB(JMJIycs#23svTguZVw(xJ#nM+kt&2h|)6_PHQhpv!DMs||kFJtB)Uc9=WQy&yXU2Js zEpL};tMKW0TUEUsWEBhpAk(Gc;H1Z*&x6g&X#F~vQWF`7b@i0n{#rbb1Cr@JqA%39 zB*{lkfCiMU!$Qa~E*c;CDk`Y97m6U==q^Id7h*H1-b%>hp8XdiZT|h>-6)b-J_}1g zbNp$w(quB>trQkh8p>?{@g&Ee$Xqf+WC-y_&h;d>Fc)J&;8b<%(Uqcioj`hX#B`@Q zt$qKe%=`9K!EY6(!RF9@08|IWC|jaLG}r=3^9w6GUPz$1vu09Ki4tF_Ot6j^ZAgqX z^KmiVpC|_Q-$COliZctq&6Or2%1sp1!@d>6T%Z#Ul#y4y7H?0%cGG5G`)k+|JV?S{ zjl@jg7E{Hx^>Z!l{hT7Eqz?L;JhVMZdJieYuJF(g8@v-=5vsbgfD?>kU2=JwG#3RC zx8q_i7LNEohr79pR1W?MP8GV{WH8w|lt>#wnSeoHq8I$j0bSWEoF&}NRS$P}W_XM` zlyhCbq4OfsR+UV;1>5O{4dUvTUb?En4xYYtb4$A({+i2^1h^VI;i_wKOT1~k9s01> zt@^JAp-iHd!_eb+z>tagMCOuSe#N=pb)#HKUVfhHIqXxnbf4&K<&)A!x->+N`uUKn z5LwbKP!~?8)}I3Vl!Ii&bN)!Gts-ZJSE?M)2Eyt1=|@5k}(j4Aao?YVNwvadVma|ovzm3JqhfTI(~rKP0(V5=}EhI%#< z<9}ahmE`>m+wu`fjo=hgv)_`Z0j%0c)um{=O3O zCw&Uy!>`4A8<>QX957fGT!05kxo`Mb0<=s2bt|o%{9M(8k+=9UiErBkG8W#k3H4W? zCLN~{=|U(bKJkon;DD)_#WWHk?9+x`yU4Rj&Fw+AqTAJjJ)>iPw9*Km8*z6!Z^b z&e>h1&e4a&7Q)y~{pRZnm4*7bf)Eap?4-jnc(em%5BdF}!@T zx+3bEwjw4y84yZyP5QCGa5Q2gdRy6$(<#+AT78_r5VV)IvL{DujYH7i1;*r?sFdZ@ z6zl4oB@bBqRS&d9wNw_{8&wZn{&^FKcR|#B28xO*%?JJ5i6|KFL?HtC50Kzoh>u~+ z>;~%i05yJw8!u7HTD^b>`O>@An<+>6FuWhk98-%lXdI}W` zdVhBb22ROeX4LNb-yzAzA!@kDhNOQFS=fyS1dc^JAJcW+CAeC%HB}m5&E*oBW?(t3 zvKR5%_tf?SDoig0m5{$as$mzEy_UmEXJiVj)_F>%Bwx%D`NFN|&rrUxgq*h9k3Zr# z0g@dsfzQ^^EJi?(An>IZqlMnJ_O#A@?!v(It+>|@v&Xp@=#%7RuvVclygHf+CyA~2 zvwfMdfl@ct7nri+L4`Hx<7sloxqK+z+o)13sf}(LW54apD>c-R&5<0%dyT^voN?*f z9ac}og!i{QYTx8^qg7LMr28&S*?&Ku0rDR`H+@8qbYH-dHK%&(C+>h%NPxhPvvv|% zUQBiO#UqQsy_93)%m5D)I!KfoC;46&Sde1WV>F)k+PuGfXJTog^9e3W@t;P77Q^RG>qJoGXi1 zMQnIC8gWi(xD~bmnQJ8Vv3#6UiSBp~{;SIqO~Y6*R`cs2jXeKD{_b42p!6d#IB`k| z`qMEuOs}BXG#x{HUB@%Havi5AfUdd9={F@@=w$`=l}Iykkq+hg6nF8TQnUmX6y!Cm z2qdy!c?C^pAPX5BMOxc=-FA#h9Ff`&L}1AKq;19emp zeL`Dqb$=cTu7SLyrNe| zd1{a87tevzc)?e33`FJ0Gt}vIUU@~Vq$p~W#1+kBOI#o~7dfuzsVbN($%mDvA@FFk zb!|0i!}B;dv3R2)Q4WlQ0-lh);hyj*yp)~1PoSrg|M)Om2kc7J<=^TTKPP~1FoHn_ zGt9HQav-+#bS_@5pI6;4d*&57>clwMqzN45mBpk<+QGjY6zWKy{qZ6&=>51}$-cY9 z3zXR3FhTmp=RD?gTY&CFKisq$RN4c2VO0kA!!`bE^oH?)`~fzz3Ya(VloWt@whvs$ z8_P&`yO@C*C{3Q_k<9XbPaOe;yLv^wXGJ467@H#=PCZSD@ zgJHRdgvhXP68?j39qmb-HA=iQancZ8=a#EIic#J&iSsa{qLapOlvVaZVRh)T)F7Et zznnTz^Vkgkx3ws%41o98{K$N5P^_0Awt<&$-A1pt3UYn^z_6f?+g>Ase0d$^3ci!S z5wGmk1skGhXq$V(>HL1-Au_@1hG|<6_maeiZ&NAJQq})4;aw3sQ*ip{>k*~%L1uN zVKFG5U1FBdm_O{^B|PgCnT28T@44F5Cr05Zk7E?4d+3B?FW$f%sq8lr+g#aMS^qsq zO;H}iEl~S5Vr5=vR9F8G%Ttr%JrVXapp5xd_*c#g|Xoae07rh~#5ACQK5VTN&!^&{XH)A<>IHhDAFqO;66fgsyIFL|e41k{ z>^YhQW7&em1%omMaByw;nN|roI|@o(Hd$p5l?uAOAAY}$P=tJ$HFUP8`hLHdQFck% z3=qRLJ`-jGgM^!l+2$LR7`TYq<}(NZkALEvt)6ob8gm_d!Bb?Ii|L~0?-nzZDQ(5^ zDFbR!PB4-il&Dl&N9|d4v9pgvs1XO#r|(7V4g-Z78yxlOJ0yt zeNB1gu-0>}&NWQa9L^l(Gf5pwO*%U%%sFtpb9>7O_;x{=&AEl1SNd(AIk|T~4~z3+ z-G#fb-LBUi4#2Lbg8$VxBtD2^Eeg!x3Zp*yKvhxsfX!e#U8FW{wD17H#_ZJ1sGC2K zBlv!l(tZq2X=#6k5@XNsmqVm6GQvv2!!&cZSZLeC9s7Q-OlVig(KT|n$ce|qOxMw+ zBG_%}J0Ej5N8s!Dr#}`y(g38{cRXC!P&-8+V$@6=)NPNF>p{3C8=V2mgPQ@^Yt#s0 zFcS?c;-B%Hhv#`l{OALKFK+Z6Q3gTmo(ZxqZ=eM6nS4_1rPB`e_#;x`D1-0mMt2ulqDdCEjLkA)(J%3Hq!%-5ELyGL zbn$?*7HoCeq~*CJZF4tE13P84H2qeVn1^~r?YO8|!P~+UOiIo-RHab8KAfkNJbe6q z?V2x9xNgP)AP4s3#W1KXZTz^Ya8~gCL~N_oXyEC*3MGCNBbqh^Sm>2nE%MU|hsc43pT{X{i{ah^n7R0w^H@<`p@n6IE-m zRjSbd zw~_;fH<~U#3v?8FK1wO?qurfK9q_RtvhVGi>d~hitMENV$O5H%4qkppakDbDyFbv6 zA#E+w%nZXUB^datE~;Xc7_(=oFP<_j4a+{cC3H@7X%)OwI-3i~1UXBv-o9}O)1za6 z=k^kXjIN4$!_1D@H%E1gwZoA>i_9l>eP%q1V^V`WsKeX99RbxX;x~q8%)+{T8P1=I z1A{k@Uq4Fs1{KwQYv`j%)Zb%*l#!K7l#gh(*sO>0tNC0nrk~b+c~!0PtBc0=p%7r^ zGq~=wwJKjy@f#A9uOAEb1+E0}@9whz-tQ{&+Wm&>S(_B6UX>AWi;kYcR6S+oc8!|A zC{XW;pBcv}f%Xh`@Mnig1J&|gcFZ<)ezf4qf(z%A1G6$4c-3V^DB@ZSA~S#5nl2o& zYj=`cW^vf>5TAntbl<4q2!_mgsmU!B@5p5(S!%ut88P}~pV+vH?CY1m3pka4PiA}S zgtDzEsiXkliWWKIVJNE^83mz-Vtc7NGeyT?_S3@deJk+S^fq;qxh9nXF8_+{q|B6s zI;x9~W_=^|kxPFkwFhnnwDSgEvNnj`zHB&XPC~)+YAPcgr_VIlp>*rq!y(~Q#qE*@X0hG zYt%*q)|5k*&|5u;2jtlM5wP~1Z>)?iy=5%II|^MjNOYPh4it~-_%JJg*u!w0_F~j{ zelAFpk)0u-6Y&BLgP+zLHQ?fcDb55Zq222{CDXssUyZ;w99hw{8&pQC~6&{lN5Ecvt=S zz&Qx9BTK6AnEV7WMtXm7(AqYFd*mZogz}*;bmZgPGzXRhw0Xg+! zZ~oKlf$zyUc*GhYWQj4xVwnCQKD_2x9h0ZZ4ZZ4l#~L)uN>>;ehw5M<;AM@)W}B2k zwW2|hHY0Pi)J~VJRiWt8-*we+lZf`00qMr4UAXVxu=r z;GRs-k3N3Irv9SRzZfP9P21;w5Vv}H1|w4i5j>d9gUtoK%vk^%j9?FuE7=IS^hL*@ zr-D0#pjcv)2RMO4kD?e_Su$Zbju}2sS|^2VanxP+?M(fQz#9i@QgOGD)*Vl8qoj^&%Ls%{9wM;g`SyHx@K_6HqsEULbp?+czj^T zkC`Qdoy8XeQ-kuM!4Ll5gJ|qv@*VNN@iPP?sX>Gp0PTr7hW-sXsqfVTL4so|RFBTM z2TCg`jAAQnYzt`{Y-d}48J}8PGNK;e9NT;#nN+IbUFNZPVwuTqwb(^!XU1VKJXjLtAdgVwDq*0NPMT6OeLrPd8mr{n&CA>n7o5M{##QsK}2b>=o|%l82YoHy>aL zQ0&L-lou$4H1Rg3=Cmd&im0FuxWH#gXnYE0D;--170f^Kbl45J`BpS=!2hRtvLd&* z!z8yf$3lC0PeCEl!-}Df8glbnks_WYF^N2|40+qiDu^HRcAYhp7EuMj6R~iiXG>os zq$Bg@E?JmIn>x1f;dz+nX@1*R7c82u>3to=F9iAc!r& z>Ep#n0nKtH=1rm*Zz!aRY?$!OhB`?(6T=sWIUx` z)H{h<(Gok*xi36avxe|mYfJwDYgplof*yjw$2}+D%cy@s^$L(WjA6n*zV)Rwl`4s8 zv>lB$ohU7zyr+~-GeHPIJwqb{SQSsXGTXYANt&EF1)zJaf1t8hO=_Je*)ikAh>1tn zBj+?cn6mp8BxAN@Z~l;7R28)%$^WXO9WO$Vsx^W;#CFA9j1Mg#0*OTC)bO==WA7Ca zi;5A-dNhb}adpW|&MG@shTk1|Me{WnLc9|Ri28}Mox3CX)ErW{QvnwMEZ(_%1{&2P zZTdVdJ@NaA4>8;ahZ^j!VjpR z@Z(QWFMtvbCyS(943uw5 zQC6Krwl0q_D-<`hh|85sS0^-{Lvb1vB*a}*sldW!a~zh1Xgq)bX!e0hB%1RgnO9{j zsfZgMek_f~iYalidgUOuvNq_xk&bE0ElIh5=jXU*CCTrgXG&Bf(VQ;}u^1+SHIfGC zN~;9B5)0G2*@vj1J{Jn1`Zq}LDI1%@iVzxUgbG@ZLP^Al#z+!oa)SDY5v$uAfJ8T? zb}oT<*N(|PZW+S@j%^cmmAW(J`E2n z1A+OIY4)aRolfVssi&$r(>h-EngrpFtv9qHrxY=|t+oFton;G7Bt|(h&Ahd?-4cZ= z=cFp;ep1`CYT~VV&Rs2eb?mi0ZF}i@Giw;7a8>mpY)72{2`diu#x(E%y;G%gSVhgp zPVK0ZhtkB>PBXs87PLj>b!HYjboXK->yEjEmE5j#6?F0gPbWn8%B9~nb#3sAav!3i z`tKNHYnE;u@sI)~H5e#aqI8!nVPirOHR{bg+*-O^C{y6stuZBFYMC#nT-| zg6Eyrsa666GdZ9B3Z-se)~8xQ6z2Fg?j5Im5m5x7*MX3m(=???{?`<`q}zraP5ZXr zb%FQhXnf5$-hT|MXx~btGk>t1zh=y`4n1Tnr*?JO8y-5n+$m>g{pKJ?gx+Fnt7-7K zItR;LQ*$x4OI(`1r^fzt#u532Q=>W*W7TiG#=0Oo_eke{YAq_H&TXKGVvtD%lB{EOJtmG5yu>t7 zKauXF16gilSwdu(Sg;}W>P^;AqT*lNi5Dlp9o$TjuQ3LC4t^g6sM*#n72wxS1hc{M zS@2j{thr)J06Tx2wKCl`?Q5D$rt+Z5 zJN9x_-{iBLsgU$+U6T+2OP(q^OlT=Oc$<` z>zEO5jsfD$zKv~SYvphb)Z;1&@$Tqi5+%uZ4Ws9v$P?9fMk$|2vk}_r{L0_+{GxE7 z{q~%+ZmErDh-M<-Buf_d&VK!e!zN0=WjiBs>RngDOXk=|1p98N7ME2)MWf6$xD4zC zLkVYpO8JP|+HZTEloSLsWmEXS8pG;hDYFx_Lw4(u6@^*aIelf32PJKU`jxgezmd*) zMLkk$cQ81E*uQOF?H=rA@=Y)XjNAFZ9iP28`MsJPJr)g>ZvwP!#|>>DFwGnRP7rBV zJp{`IUb@`#{85wgNn^*o*I2GQ0T3N$mk+$rc&KJ}MK0~e7(5CVAsx;b8aa(#%X93T zjft#@kc@v?zI5?~@j|gEN#IhHu1w93t2HDf@W!BX>x3xe7yR;3-?yeFKEU)eXKsI) zJ-b=d+c<2Rl(!zAI|ZMa<-ddgHmJszuY0$#uGZVAB<2~Y*x{XR3?D1xGblj)XU}_H z^ur^%ZdvEE=EOi{NM6?r&JTehVNDonZulbn#pJyeEkk{&`gH}1JZDJ(TZNEoqatwo|Lp+X_;_|= z|6BB)hf9hD!%n&xhDe%}hX$yhYoKbN{qQjpF-1pGYFAU%G>1|Sx6}kuf@oWdf`hlT zx=H35xu!=XL5FX9bggaltogOLi_suIXXoY~w)x+i{6v1k+TBi*3JMpGB@XG_^xVGs zdwAVW695eU!uMnA=7w-z9hwVp@NGRoAPoW&AV4TaitzK|{w9F*bOxkL#J$~9zQ#2i zL2+Pn_-%=To(WGOjwcm0AxZQRaO)7j?s3AYBPI_7k3c}@=28a5m}21;w_`n-gpW{s z{dze>LnJ`lgWjouRd}c@0~x-zBFMep*CNP8r6gF2)*gzq9wp*y(BmlX&;G+?rwDtg zo_-nwVrGlFm?WQN4-1%@own^^I6h15*kvcj$XI~By}ig6E0OQZt=6NfCpBKbcidVr z4uwNp*Cr;qb_~mKWgRl?BHv!hF%O8{lMQ{(j+}PZS$&40tWl5V&A^$LThG8gOM2n2 ziSw%pudm|Eta;?6QBAJ|0$V~@Om?p-AWKRlm9a2X?F#)v0|pSENrSwEF~M+zKtV-* z9_Dl;^RDR!S&av=2Sq@N6w+LT)h1xP@Ini2j9;`#9EL*(Rx~)DDE4}K)hO-O7UWC?~r8Fdc zVeXse`cadP$WZQy%uwO_y;7+e;*Ac!72Kdb!rd(krNW`;gq;PBA=h|SI>>gosoaU2 z)EcRAR~fl_$8A z2)MvP6$f;ar^%v)_aEroiue|_F5#|=yOGy7oQ>bO{Z*gjtsGNvMS^N^+TeNs`==(w z%2>pWHg6H%&v&cf#D|bwoTNISkFuX%EkTx5mDT-G2c>)BNu@#2#HV$iJ_1H=Vdh&; zom3B{$6Hb6JAcTQ;nn#_Oige;!k<1b>$jXQmjx`0v}e|i{;@Og65C3TRj(Ikdg*E7fTfca$e$ZX&flUTrhYEq#@?6`RrC0!%lZN?(@W#gw*uio>6jgP`W z*avXAZt?r(#!&D6w7|GCd2qXqjOSP?P;p8&t*9f`YGoH;nu@>nbR_2IFWre{rAiTB zME8EH)%~~!L!vy_F!9pdsZ2QjMT+9YfL(;`2Y^|{p?bLH@4 zcglU7&CYPd&DL;a1s_bF!fF%0lIPGp84>}lV6RF#(l&x;uPRmMqfz%j*aLY`u49Sl zM5$M#-O4EN1`_83b7slK`&|e>12pN!Gay#UYUNACOn+hgh!U2PMz@ev>tu_3!C<8b zHP5I|Vv3BH^tAjD_>XzfW>3gSIJrr_KcPQoK3FlH^o7k<%8iLC{wPbCAZrynLEZv5 zVyRP?4MvO+FZA+xd|YHl8;X-umqD_UsgJ2sVWJI5;!>eMV;AIhsNUFu4UWkU=tHg` z)OMV?+SqRkVh zpf82UTHv9Sp>2za!xaw@RT5+4UswSp_i!b*AXc?TsP>8=4ux0%qbMlDD$U;*4N#k+ zJlZ4t%~Afgm=yIQaD^l)KzdKW{+WrpvSKF%YBcpB;`*NvHBUAHgJkh^artXecE#wN zw3YLjRGnE*gKZ`}yurMPJd#y=1GYvTod|<|;iNE9?x}5x5n@{?Bo3mbpWT85tbs)= zOJy7~h9IFEdRTwuuV$Wn>N$!og*m{1q2kr>m;pckV=DT8JVjTf>KskLKtSrCKtP=T z*OvjG)SyNNSZ?pJyrh%z3*VenKhIJ|Dp7=@rsRJ!*>p7r9;GN*VJB#-R7-C`OsmVSc$))aB{kI(Dv-JY=v{EA7Mn7_v zSl#PWzr@*MiCZyq4_$A0*HfgN!(}^yN4r@C3ou^@SWV*Q#}MMUASk>xr9;ezs-5Ah>Kdao7#C2FU&CWgo-A-(i;v|3A z*aJxh+!G4^*8Ytp3wg(MT_nHu4T!rM)s_-liHJ*-S*M073Dq)lL&35BwDA0#O-8~- z#0?omQmptD)jE_lOq=JDt=Atg`C4=~f`s<<8?^_w5UmG$@N3i!@-cO2%Gb11AsWRq z91OiyH6x}$a}SZ7Svxi+x^vz;nu4Sz-QmDTl=>rfVO8KLm(HEe$dQMUn3Q%0*8Mi{ z(Wf6F_3Ax&J8)NiOY1Og&f&igbTcH3g6p5r#v2|8i0D5DTC0HwP`A>-`L_%uA(=VP zh(XBM@NdRC5W6C?H<6If#*RiE0aHPfG;4E%JO^nk!7?*HD#_|9v08MM%5p7{T5Y%M zT&tkd6-ng(+C%(e_=2_D<$;niWVOl{lHDXR~Q)xKtLD6!?498>n4SixN&mxq^xQ$K4^?(*PTZmQNzm{IxYEt#qKRX zlxZgqW4QdsaBSlgT-I5=b;hRM!{gQMWnqgS9q2gkRw+MJhC0}I$_;t2`f3cl zvi`f~UsC($ABK5~jAT*!7axql? z*B{T9)}-eUit3FvyB&4^sT6rr zI$pQ!fg%xrzjiliPqQ>o@@rh9mRS=z#+|ylNHee}!UJ*E7Sn^>=SRn#)NtODUWN4@ z6Yi-QST*8GLDQw0O)v~EPAYK<9D_1J&MPAWIrjy`{ zs4iImE$TDrCypxg9I$0;(KMWskd&m9jn0PhO74qFZ>jYgaddIpHYLx&ar#BA4SF6e zq5|^}2lf8&ClkU5Cr}B;rG8XrPT)Wcy_fC9=0&Xlj zu@MHi?vOUM&xQWFI~&k{EDn73s1$l1%N+uswuzNvANREbtwUT}Qo`h!wbJzW?&Llq z^|$4o-sgC~RyGOP17D~gi0bEhzk%aazk!p;h#ctcz5|4QKb`t;c;qGowWgZWSpTlm zYCpC*e6@_GFTOavnif-K74?Q4&ZMzk;l@q)aZ{m7tC;Oa_YIzjzGX2WZ9rNvFdlVYS$i~f%EnbozJ?yoO_xJgR=ompq;5FgIv z%e0B+t?>TAi0+Ql*N%t#qEJg16_gi%9T5;@)C28CdgBm!FZx)jtWaY;`zVf7WI&|nAwv07@+Md0p4pysZ+1l&_e7NcH{tZrJetkm*5K;F^)@GAKwjMU}D*SkAljP&6CwSUVog6T*2?YnYEe@+OkkuPh#_2Ay6Y;{hrZ0NO$e{T5tQK>O>Ux`T zZ1JrfV0ZB}MD;BT0e3g%uLlqCi0mqoMgCkP4sdUBqN zGqGJ$JETnSm%2&iKW*2!alok*tjm8M2^c6)`?b0KytoZ?8U%0Ew!C0JM4F`Xxz6I5 zno2bjl*~K!em-qDApkvsV7-!JMTUi4Z79Lq9cpO~Z-*srXCOA_%Bu?yqQxQ&&5>fm zk9HebhH)PaLU*MQ!3*11Z7Ig!DjXAWstF8Zj*|d!IBc-@vp6z)4Bx5oFw$Up7}YuR zMeH)+L%+HgI|%2;UDb9Mr(W~<>U%A|*@t-<5qZrY5kY@K*cJJ41dr2|Fj%mxR4?JS z_w`J=MHytPzT;DWOVI&T5SbN(9a3tTY&rd^sV>7`Q6adb6fz!@JX2cuV7;`+_MDo# zNE&9585biySN4vP>=mXpAc?I*9pQ-BgE6~Re4#hvpzIP`$IgAtpk$J!xjj?S4Z}JX zlVu6LU~vgWX0=8*M6EuD>JbRhDlU+Lu->X!ZP%%D#_ZSgr!fNAPQD&z_E&QiNf z->v_l73f}G_~c;A8~R=ob`z1EEIlUwwP z1oSNeW~Me~b`3{&cmyRzW=7WiBnd+*_>F%)f3S;HvZ;UhSV?|{6o6ZeNrzN_ckk`dC5jA_0U z|EI670E(m8+QuCMB)CH$xVwbl4hilAcL+g3fUvmx;%-5Lv$(s<;t<>+xFk6FH<0_j zm-|)yQ#D0z@ALF?x~F%}RG*&1%n%4F#2wxDtMb*6Jx1*|&HFEAspT zj)8vQ3FBG(?u9(X1U`p?5_;gh7$626*^D7RQo5W-VINCdpZH-h{zy@}^=rq#LqR2> z1EGC!fFB}JL5l7L@Q{KPmcE^Ee@uEZ>bu39;BuQ>PH zU#WS>ilGcTcxV@A1h0dPzDQU!(K8Cq1dn!N&9fjO8mOLV6PH=zwL+Yx&ZdrI9)8$Wz&G&dk@$*Tr(9oN@lqwl6GGD-(xp{}1VRR2g+1FgA9O--+W z^5o)fbd+FVWTm!nX4q&_m2LeGcPu^u=VH}x(h+KiZP)3!&D0n_>9)7X13L(2oB(0| zm0I|(%g8^DGbdAOWhWJuFeZ+0tfl#M#Wq{SjuJ&s|GGB}lwuP@@~=rm5uNt(S<2ft zVV!ZgR5XY?{|rahw;!vwR{kYLOvl!zis~>%4IONIrldDjnoz_OL;>HgPB^Wxr%L<` zOxEb`md)s&X|>;{uF81${pbDRFd6_6M+DGf*0?49_0rE4Iomgs%!HumI$cev0Oip7 z&}vvM%8G;c9cVy8C04!qTRv01EYoIofemI;8wSaIh28rW5;@80B_(%`1H#TB$@~#< z)8^9n0Z!*_nMPf1JnDMU>PDM{ydf^oLS0w?dsFziHoW;vK|*2<{OxY0;z>Z({%yl~ zto+4fJVi`Ft?U^---1gWx|g101+7pz&j6PrQR!UD7}wDk#GiY9!bF+w7y7@2SAIm) z8Pi9kFd#W4U(oH%=kb@MoTplUcbh1)YoFEg3qTATarTw=5ot-0UY z*?iUOzHq*U5?1{HCUei(ZMR}xyl%%bXX@F;jh`#yA2E{cjXXB?RIEvYOZ{D66_K%$ zPYfrMLe>`Oe2Ydz?^(L;%UiaNcS+srIMKZAPfM`rhLB&%nEV>Bbw`?a4u0=U zt5>n;G}a`a+l}=u5gw;?+yR}4-!mMQ-_sZMK)j|Rmpm0~Q>ki{WJG=Nw{8WM_Mzm2 zIpdh;0CXP(hZyPCPfIJ(5_Z~%RZ=hYESfUIu_h+4`)|S8@mJF|8Y+Z) zgpM4He`9i%K3i{&T}Gt6JI_4b*xXm3otsA*UKYRPr+ks~!gwZ284#wjUx#tf$K*tx zII`$dx=poxN?Q+2CiFY z;(XoNXt+SDy9OoS$TkxHl{|${^Q~68IN2}5sL_jcQu#G~O-P$3XH=C0CR1EdqxePT}FB~MVTvcw}@ zt~Q5l&S}jZCEAxgcmfhf_VX7nY^OH zijS4VjqgFo5!24gG`cE7IkvM+mmZj+iK7idSI@}mLf!+p1-#0p7n-Bu`S3yUt?c={@m!C_E96-}i(uNR= zjqg2a0RJ+sI$7+JozmA^ESo8));5%3 ze+SZ@IIgdkjmv571fh1A96e*JKipkdF8|Gqu=U&*YYt#7C2qdhwc5JDJi*=-A1EE7LtPo4&sI-AG~(&5H;v3OcmzA_tGNksi|T^ zK^4;f_0R+WWDnJ;`rj=>AI(v=hGJ!0+jQttq8NW(%oyE}O3Te&Ag3rEK&<-*(ykzC#{(kvCM$f50bx>2J!^>1J2TL?<# z6V=hS89OJpt#gu=O_*T7yZYR|R(t*2-zY{=<75L$DLUHdDwT#x1y&GU`ScuW6nyEF zMxSW94M-wP*ir2!UQxCj+)!a=px;oTLo-8OroS4(<(q2YyAn(ojZ*UNG5=BJw{59g zGQpl+b^;TQ_oFJ++UeV-glLuKIyHY9mAdA)iO=jHzXVHU73>%ISBipB+$8`!%uCA)mS&)wD!}q??vExL%vcj5S;|FUF)BExTtMKj;$lc1=5_a;C zeO-OJB~AWH3sxG9YC>B+zgQn+xA`N02^~=VlcA=#P_Ndm+aFy%OKpBA%}h;$owzVW z$9DSg8J&y^9acEWTZ6a(^i=r*5=Rnz}jcif_qOtMpQ}YO4jNsErnMt`Xc+(X*x3$96^CYc0>nkiVnl0(Xudz`Ca7cAtaj&C{pz`l+Sl&8S1ow!WBXN<8`ArA z@T@DuwSuO=Qx#JoWzR)zs_Ca~B&SnO%5Pl|n)`pTb|qdTH_W9+AIOb=6*2D%0<7yZHD_l{5N|d<~vN)_xPi|NZPfei8KoM-g+jM|N zUB1qe#}Qc7b1pIm%k0VU-6zbuQG>F|2oeV$Yz$(tESnQ|Ea%?ZKVb70(|6@(XQZNx zd);>}vAb%+h4m?|KBT^GO5w8}lhdikH29l2gAljCw*~=~=*=zkgxr`|EiN0tcf-$) z18)#uyi=uRCnXV29o+iKCGDWg7$l3Lmr6R(&3L%=IYJn9yE{8snpIf0v;F1}UInlVsm#v>Bh`XXg6+F1G2P@DmyEhq zWK1GMG`(uoH$x_+MFKR7I*_9Qlc2>P2sZniRC=?M<`O`MtL6^7(8}n~4TSM(6{Wdw znMHnmZ&Jlbe(?60IVf7;b5eo)0zR}L6?;!l7#^A>$6Jfc9H*t{fMxn{>kC(1hU!Yw zdCpHZ8b%tKa3%einEN#i@Iu9l%{Cosqg9b-G`zBidH!4)?i~D8DwzID$pPO9(a^T;Pi7H>Cer$ibf$(lEDRLybML zJ)AzwnPXysNh<*zRqOt%Z2-RVG;Fn)ShXrwlko-a)kq|tY^;B$Id4H75+g#hp7c(5 zMX4)?6>s}Vg|FriHUh#TK;{#Hlh+~Z4ysd;h(WGEB2BJq*`ch93$>xj;C^{6nAZ?| z!Dk7kIP3@{jT&8q>Vk$J<#>MZ7W-98Xc*xUrz_qO_YQ?yY@urb@I9uvNqNsAg29MJ z5T^SED521;@A+tTZroJH*rlv*+1eLE1KVS+rsb8<(`v7}g>v>wfTIVAXlCX3%H9=1 z-T(#9Fk_u*+2}f4LGgm?Zp`o|!gAjXfyeV@n`5S1cwhU?c`sAyLF}&>-7#9G2+Jb| zFR}&DUblUMDVu7NA7mtX0oW8Uj!Q9*qoCr5;#9LJ4eB*^@IAuL>P1(a{g5m1F)3?C zPv9 zp1%5|ZjLG6zekCZl|BUh8vbs4CBMr{=?u#^WAzoM=X7SqG2orA`^WhFXPCb3)N4%~ z*c{nf-sYX4_TEcv-T%IC3kvXsXX-A=|Y|Y@D z?KBWacAd~8DhJh@UAMxzhSol0&xVm-=`@7ue7Kh?iPxg+4mH{;Bf5<-5mQJb2=9sM zHj|TcU^FfXt_F-vN`n-D(#a<#C#4@Mn_bN|hqMqDWUIiNrK4IoUcEp{F_xCqLCs8m%fVwaQ=15g9x*dA`ANg!j<0c_nvc#zY+KAAwUD{S3rl0^72RNNfl|--#TmB7 zm{g6HOe_!V+MbK$iwd}!TP%j}@irQUD6kv7+2|D(1K#i<>|c9OqDLoasAqTWDzafd)2`4a;!@$iIfsXqPfAn1Bp;w z-kJQ%@PQQ?03J+Um1gjV1uGeYIymSC)+{b6-6{0j=P^s0EM!iE2bnaKrxS36F=K;3 z01-diV%~M_x-j$JH^7@}|Bnf;=8Gv!1(;Vy=z8v-Z3I0;7^{q*Xzdaj9l&{e6A;*+*CDR(8+?^`;ix+F&x zf&J{5365**qX(Xk4oRAd zn>*R2DS~1jOLY;qMAG(K!*hwfvX$Rr=d_>w3b&!IXvu7v?o`2(N;xS5>C{SOIP5>i z>6$t7S~5bC2k7e(dbndR#&~rdnkeka!p-^n>}6QG&YhFrt{0(Qx$R8)-n#Z_@B%6V zX-IN?Jd+n*%H?wsZ@IJTE{NrmpCN`)Ly;k@z_W=IN0LqXHo^BAbMrz4 zo>=XQGTy78hs7%J_6sM&GiVNV*DF98Nu1MpdS|1gN^G)NeOYD3w>Arx)l|}ChAv0< z9W=}QDL7GYQvNe?oB$X)X9|=>^3F3tkE!X1WDvG{gz#&7Zxzq5kC_^?tE0plp2RxY;)kY<$k^_Ka%I zMBdjVKnPXKh9A9A;p?j%nQ=bvSO5xxPn10T%b7p?o&b1ki8R zt}k7R42NQOKHhPC8ZfXvrLR<7)~B)Y1(Qx|BSmMuOnOsYfpk(YR*LA?4Q8uw*-lQk zJbD~=BDhW#Q7#^-L-iaC81H=bnwBW2zp$Jex*w)|*ksm}`@5B^A=7O+v}X&PXQKx6 zN6a(-2n{FkQ=akxSX!1Wd!HV;97}}TGsyEdYDmH^P8d^`%z!y*H-&#%%8r_U8npPm z^|7b^ca>j#L<2$g;z*5NHdLlCyj3v9~uQ6;u|D0y`X5Lb#E>kFs4;)HbA zu?8>c>I=_gLXaur)ZtgT6|8&WC%{()9Q5U|nsdrAgEGoRBu_JZMf0wunlklR6 z$#gKQ6jz>~> z2X37%M*UJh6$Rn#yrLg|X*nWpJ$N-yl`0Hd@ZJ56*qA$#XIyYeO8dvD&;F!v>l;+Q zomr zC}z-0TM1HWO81Da(wtolD99_aU=OIwD zP9pR|Xan5!(ejr&^z6*VL<|j`kb5DhMbuk~##5c9_h~{DLMMM)f0cSPTKF zYCEe{L!=SWrA1ey5f8Bg31>z>HDF=_>?w$T&`V;`N^l>S+Q)!Iu5v%mM^Xe1G4JYF z>izg*|3{ePnAC47w2Di>?kDC@p?Zmio3-V}!&IK8wBM2UE0u|X6&(9Nd(XcXXb)p5 z@cO%)1KtvA9tz3pk8-H0C-O@6o70|FeWO4K@w3|K^EkiHb-6~;V#4POY51V`ynRHh zkw~M)t|}o%FX9(Ul{@$lK5{g(S^3HV@!;%-6W4cZ*4G3Fo>dtV?o3;uU{8z#XN$6n ztub^9;b+%GV@q%iKZ$cc-L(V$P0$Sw^{{|-n|1s*0Ck+Z{I@ibUk&#dI|?g?fqe){ z)wGp6CPJ3CzCm{0{s~Ia0|`fQLCa;?e<}Wzy3mi5P0*XaWV*W1zje3uDvvQS$r_{! zA;@;C0c0pBfrnMH$YFRO+87Qfy-5ld`;SG*lkYO5AR#je$VmnTR2ZWG;DkpD1H|KcIZr6G)_zYOz_h9)+q|6d{`cn9^D{D-;yXFC$(|0-Gdn3&~2 zfK!l@3UdDEsPZO+26}hnJY+*3YJWlge{!Y&2MPIG<$eDzn52X2aWK?7#+LNTkPtH+xg_HZaD$|t~J$-kx`0_A!hhxEw~z$f5!`3LYZJ~}X~=jB7oPj;6*0q-k5fJ-$n zfJnWMBl1sx90GiLWlhw9CkE6&EeP<>bpIzZ4%vu^|D@Gl>OlWqnui}1u&kF5=28PV z*h}*Gc^~rml_qdCp8z;wNC*_@Lwl^3>m!7z)CN|BQ2|5xoK z_7lQnLUQYl&;j4}Q$857Lxp}S1u;SnGWFS>7!qa(Ark{j`>7s)PvX;0fRx6-lYY5J zg*O9)Fyht^3O7!e!2O}e?rs=Oy;_AR*g@jZ!cWL%cEI)lg$IRy!`YC8*-t>Z4iBa% zx}yQ>dmdX%wb32ThuED5xvlU%0lR&AkS`h}d1&?D@b~|=`sp(;xtJ1&GKBqTJNXbH z%%&G`F+}js$UcytEbl*2srP}HB>hhZ|6S<+UlntI2#^xUKm1qa?JyzC?*yP>6D=@y znBY;l0#bLD{P(&b0B?t}AJkoG)>Pj^ZeE0tJBq*)_31RoBoO*{0=SP5!bE06y2Jd> z06n>NKOx6`g(&^!dW{mo1Qk4tZdv8CfBG#phP(9xGO`{JWx*%pok9qh3OGIbcS=En z31JvZfh1t52a}m_EPN0l7rGH*Hv1Fe@3IFX6J%RG>%imaK5+|A!0MHd0TO!nS^Zz< d@Gr8;d%qguD(Em+A(_`ZF(Lm@qUO(+{|^9G&p!YF diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 88a068a11d7..c5b4885b8ef 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Aug 18 08:04:57 MDT 2014 +#Thu Dec 03 08:34:35 MST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip diff --git a/gradlew b/gradlew index 91a7e269e19..9d82f789151 100755 --- a/gradlew +++ b/gradlew @@ -42,11 +42,6 @@ case "`uname`" in ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" @@ -61,9 +56,9 @@ while [ -h "$PRG" ] ; do fi done SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- +cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" -cd "$SAVED" >&- +cd "$SAVED" >/dev/null CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -114,6 +109,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` From d371f0b34db34b50ad4bf915681909ca41a6ece1 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 3 Dec 2015 11:00:04 -0700 Subject: [PATCH 037/103] Add integration test to verify that we are able to read email correctly https://www.pivotaltracker.com/story/show/108824610 [#108824610] --- .travis.yml | 1 + .../ExternalIdentityProviderDefinition.java | 6 + .../uaa/integration/feature/SamlLoginIT.java | 147 ++++++++++++++++-- 3 files changed, 138 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7b595a5cb8b..44b4ac2f666 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,6 +67,7 @@ addons: - testzone1.localhost - testzone2.localhost - testzone3.localhost + - testzone4.localhost artifacts: key: secure: yRJd/NtH3uwSCtHLiJKt+X3ZPb57euSZA+gMG4/HkOTdkB0NuZnZaYb0GjKaLRbTAelqottjqPf5LVJXebBvjIAVH5R9C6yC1ghRYBPtHR3AJaod8ZTSUs+mLijvvhwfksKId4aZaF/GgNfPgFnC4IPybh21vTcAfrX4qS9FmN4= diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java index f66df6cdfd9..fe6ab8947d8 100644 --- a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java +++ b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java @@ -55,6 +55,12 @@ public Map getAttributeMappings() { return Collections.unmodifiableMap(attributeMappings); } + /** + * adds an attribute mapping, where the key is known to the UAA and the value represents + * the attribute name on the IDP + * @param key - known to the UAA, such as {@link #EMAIL_ATTRIBUTE_NAME}, {@link #GROUP_ATTRIBUTE_NAME}, {@link #PHONE_NUMBER_ATTRIBUTE_NAME} + * @param value - the name of the attribute on the IDP side, for example emailAddress + */ @JsonIgnore public void addAttributeMapping(String key, Object value) { attributeMappings.put(key, value); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java index b2d3868e0cd..27d61d2174a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/SamlLoginIT.java @@ -17,24 +17,24 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; -import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; -import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.test.LoginServerClassRunner; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.flywaydb.core.internal.util.StringUtils; import org.hamcrest.Matchers; import org.junit.Assert; @@ -73,9 +73,10 @@ import java.util.Map; import java.util.UUID; +import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.createSimplePHPSamlIDP; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; -import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.createSimplePHPSamlIDP; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; @@ -174,7 +175,7 @@ public void testSimpleSamlLoginWithAddShadowUserOnLoginFalse() throws Exception BaseClientDetails client = createClientAndSpecifyProvider(clientId, provider, redirectUri); //tells us that we are on travis - assumeTrue("Expected testzone1/2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + assumeTrue("Expected testzone1/2/3/4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); String firstUrl = "/oauth/authorize?" + "client_id=" + clientId @@ -204,7 +205,7 @@ public void testSimpleSamlLoginWithAddShadowUserOnLoginFalse() throws Exception @Test public void failureResponseFromSamlIDP_showErrorFromSaml() throws Exception { - assumeTrue("Expected testzone1/2/3.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + assumeTrue("Expected testzone1/2/3/4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); String zoneId = "testzone3"; String zoneUrl = baseUrl.replace("localhost",zoneId+".localhost"); @@ -283,7 +284,7 @@ private void testSimpleSamlLogin(String firstUrl, String lookfor, String usernam IdentityProvider provider = createIdentityProvider("simplesamlphp"); //tells us that we are on travis - assumeTrue("Expected testzone1/2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + assumeTrue("Expected testzone1/2/3/4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); webDriver.get(baseUrl + firstUrl); Assert.assertEquals("Cloud Foundry", webDriver.getTitle()); @@ -361,7 +362,7 @@ public void test_SamlInvitation_Automatic_Redirect_In_Zone2() throws Exception { public void perform_SamlInvitation_Automatic_Redirect_In_Zone2(String username, String password, boolean emptyList) throws Exception { //ensure we are able to resolve DNS for hostname testzone1.localhost - assumeTrue("Expected testzone1/2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + assumeTrue("Expected testzone1/2/3/4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); String zoneId = "testzone2"; String zoneUrl = baseUrl.replace("localhost",zoneId+".localhost"); @@ -461,7 +462,7 @@ protected boolean isMember(String userId, ScimGroup group) { @Test public void testSamlLoginClientIDPAuthorizationAutomaticRedirectInZone1() throws Exception { //ensure we are able to resolve DNS for hostname testzone1.localhost - assumeTrue("Expected testzone1/2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + assumeTrue("Expected testzone1/2/3/4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); String zoneId = "testzone1"; //identity client token @@ -531,7 +532,7 @@ public void testSamlLoginClientIDPAuthorizationAutomaticRedirectInZone1() throws @Test public void testSamlLogin_Map_Groups_In_Zone1() throws Exception { //ensure we are able to resolve DNS for hostname testzone1.localhost - assumeTrue("Expected testzone1/2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + assumeTrue("Expected testzone1/2/3/4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); String zoneId = "testzone1"; String zoneUrl = baseUrl.replace("localhost", "testzone1.localhost"); @@ -638,7 +639,7 @@ public void testSamlLogin_Custom_User_Attributes_In_ID_Token() throws Exception //ensure we are able to resolve DNS for hostname testzone1.localhost - assumeTrue("Expected testzone1/2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + assumeTrue("Expected testzone1/2/3/4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); String zoneId = "testzone1"; String zoneUrl = baseUrl.replace("localhost", "testzone1.localhost"); @@ -743,12 +744,117 @@ public void testSamlLogin_Custom_User_Attributes_In_ID_Token() throws Exception } + @Test + public void testSamlLogin_Email_In_ID_Token_When_UserID_IsNotEmail() throws Exception { + + //ensure we are able to resolve DNS for hostname testzone1.localhost + assumeTrue("Expected testzone1/2/3/4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + String zoneId = "testzone4"; + String zoneUrl = baseUrl.replace("localhost", zoneId+".localhost"); + + //identity client token + RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[]{"zones.write", "zones.read", "scim.zones"}, "identity", "identitysecret") + ); + //admin client token - to create users + RestTemplate adminClient = IntegrationTestUtils.getClientCredentialsTemplate( + IntegrationTestUtils.getClientCredentialsResource(baseUrl, new String[0], "admin", "adminsecret") + ); + //create the zone + IntegrationTestUtils.createZoneOrUpdateSubdomain(identityClient, baseUrl, zoneId, zoneId); + + //create a zone admin user + String email = new RandomValueStringGenerator().generate() +"@samltesting.org"; + ScimUser user = IntegrationTestUtils.createUser(adminClient, baseUrl,email ,"firstname", "lastname", email, true); + IntegrationTestUtils.makeZoneAdmin(identityClient, baseUrl, user.getId(), zoneId); + + //get the zone admin token + String zoneAdminToken = + IntegrationTestUtils.getAuthorizationCodeToken(serverRunning, + UaaTestAccounts.standard(serverRunning), + "identity", + "identitysecret", + email, + "secr3T"); + + SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZoneIDP("simplesamlphp", zoneId); + samlIdentityProviderDefinition.addAttributeMapping(EMAIL_ATTRIBUTE_NAME, "emailAddress"); + + IdentityProvider provider = new IdentityProvider(); + provider.setIdentityZoneId(zoneId); + provider.setType(OriginKeys.SAML); + provider.setActive(true); + provider.setConfig(samlIdentityProviderDefinition); + provider.setOriginKey(samlIdentityProviderDefinition.getIdpEntityAlias()); + provider.setName("simplesamlphp for "+zoneId); + + provider = IntegrationTestUtils.createOrUpdateProvider(zoneAdminToken,baseUrl,provider); + assertEquals(provider.getOriginKey(), provider.getConfig().getIdpEntityAlias()); + + List idps = Arrays.asList(provider.getOriginKey()); + + String adminClientInZone = new RandomValueStringGenerator().generate(); + BaseClientDetails clientDetails = new BaseClientDetails(adminClientInZone, null, "openid,user_attributes", "authorization_code,client_credentials", "uaa.admin,scim.read,scim.write,uaa.resource", zoneUrl); + clientDetails.setClientSecret("secret"); + clientDetails.addAdditionalInformation(ClientConstants.AUTO_APPROVE, true); + clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, idps); + + clientDetails = IntegrationTestUtils.createClientAsZoneAdmin(zoneAdminToken, baseUrl, zoneId, clientDetails); + clientDetails.setClientSecret("secret"); + + String adminTokenInZone = IntegrationTestUtils.getClientCredentialsToken(zoneUrl,clientDetails.getClientId(), "secret"); + + webDriver.get(zoneUrl + "/logout.do"); + + String authUrl = zoneUrl + "/oauth/authorize?client_id=" + clientDetails.getClientId() + "&redirect_uri=" + URLEncoder.encode(zoneUrl) + "&response_type=code&state=8tp0tR"; + webDriver.get(authUrl); + //we should now be in the Simple SAML PHP site + webDriver.findElement(By.xpath("//h2[contains(text(), 'Enter your username and password')]")); + webDriver.findElement(By.name("username")).clear(); + webDriver.findElement(By.name("username")).sendKeys("marissa6"); + webDriver.findElement(By.name("password")).sendKeys("saml6"); + webDriver.findElement(By.xpath("//input[@value='Login']")).click(); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); + + Cookie cookie= webDriver.manage().getCookieNamed("JSESSIONID"); + + //do an auth code grant + //pass up the jsessionid + System.out.println("cookie = " + String.format("%s=%s",cookie.getName(), cookie.getValue())); + + serverRunning.setHostName(zoneId+".localhost"); + Map authCodeTokenResponse = IntegrationTestUtils.getAuthorizationCodeTokenMap(serverRunning, + UaaTestAccounts.standard(serverRunning), + clientDetails.getClientId(), + clientDetails.getClientSecret(), + null, + null, + "token id_token", + cookie.getValue(), + zoneUrl, + false); + + webDriver.get(baseUrl + "/logout.do"); + webDriver.get(zoneUrl + "/logout.do"); + + //validate that we have an ID token, and that it contains costCenter and manager values + + String idToken = authCodeTokenResponse.get("id_token"); + assertNotNull(idToken); + + Jwt idTokenClaims = JwtHelper.decode(idToken); + Map claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() {}); + + assertNotNull(claims.get(ClaimConstants.USER_ATTRIBUTES)); + assertEquals("marissa6", claims.get(ClaimConstants.USER_NAME)); + assertEquals("marissa6@test.org", claims.get(ClaimConstants.EMAIL)); + } @Test public void testSimpleSamlPhpLoginInTestZone1Works() throws Exception { - assumeTrue("Expected testzone1/2.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); + assumeTrue("Expected testzone1/2/3/4.localhost to resolve to 127.0.0.1", doesSupportZoneDNS()); String zoneId = "testzone1"; RestTemplate identityClient = IntegrationTestUtils.getClientCredentialsTemplate( @@ -950,7 +1056,8 @@ protected boolean doesSupportZoneDNS() { try { return Arrays.equals(Inet4Address.getByName("testzone1.localhost").getAddress(), new byte[] {127,0,0,1}) && Arrays.equals(Inet4Address.getByName("testzone2.localhost").getAddress(), new byte[] {127,0,0,1}) && - Arrays.equals(Inet4Address.getByName("testzone3.localhost").getAddress(), new byte[] {127,0,0,1}); + Arrays.equals(Inet4Address.getByName("testzone3.localhost").getAddress(), new byte[] {127,0,0,1}) && + Arrays.equals(Inet4Address.getByName("testzone4.localhost").getAddress(), new byte[] {127,0,0,1}); } catch (UnknownHostException e) { return false; } @@ -964,4 +1071,12 @@ public SamlIdentityProviderDefinition createTestZone1IDP(String alias) { return createSimplePHPSamlIDP(alias, "testzone1"); } + public SamlIdentityProviderDefinition createTestZone3IDP(String alias) { + return createSimplePHPSamlIDP(alias, "testzone3"); + } + + public SamlIdentityProviderDefinition createTestZoneIDP(String alias, String zoneSubdomain) { + return createSimplePHPSamlIDP(alias, zoneSubdomain); + } + } From 1e6e3a4facf266c8bcd15daf902a4aa8e78eb547 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 3 Dec 2015 11:24:55 -0700 Subject: [PATCH 038/103] Add an example of how to set Yaml string for an environment variable [skip ci] --- README.md | 4 ++++ docs/Sysadmin-Guide.rst | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 1c10b12295b..b65578015b2 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,10 @@ For example, to deploy the UAA as a Cloud Foundry application, you can provide a host: mail.server.host port: 3535 +Or as an alternative, set the yaml configuration as a string for an environment variable using the set-env command + + cf set-env sample-uaa-cf-war UAA_CONFIG_YAML '{ uaa.url: http://uaa.myapp.com, login.url: http://uaa.myapp.com, smtp: { host: mail.server.host, port: 3535 } }' + In addition, any simple type property that is read by the UAA can also be fully expanded and read as a system environment variable itself. ### Using Gradle to test with postgresql or mysql diff --git a/docs/Sysadmin-Guide.rst b/docs/Sysadmin-Guide.rst index 9311e9b3b4f..614c7bd3989 100644 --- a/docs/Sysadmin-Guide.rst +++ b/docs/Sysadmin-Guide.rst @@ -116,6 +116,12 @@ written as an environment variable. For a Cloud Foundry application this could l port: 3535 +Or as an alternative, set the yaml configuration as a string for an environment variable using the set-env command + +:: + + cf set-env sample-uaa-cf-war UAA_CONFIG_YAML '{ uaa.url: http://uaa.myapp.com, login.url: http://uaa.myapp.com, smtp: { host: mail.server.host, port: 3535 } }' + Database -------- From df653f84c13e19f6a4b15ecd20d2991d9f8ca56c Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 3 Dec 2015 11:48:56 -0700 Subject: [PATCH 039/103] For now, run less permutations of the LDAP tests Move a test into the test block --- .../uaa/mock/ldap/LdapMockMvcTests.java | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java index 3a35fe8889f..295385c00a5 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java @@ -18,9 +18,10 @@ import org.cloudfoundry.identity.uaa.authentication.manager.DynamicZoneAwareAuthenticationManager; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserMapper; -import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.ldap.ProcessLdapProperties; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.ZoneScimInviteData; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.rest.jdbc.LimitSqlAdapter; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -33,7 +34,6 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityProviderValidationRequest; import org.cloudfoundry.identity.uaa.zone.IdentityProviderValidationRequest.UsernamePasswordAuthentication; @@ -82,10 +82,11 @@ import java.util.List; import java.util.Set; -import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; -import static org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition.LDAP_ATTRIBUTE_MAPPINGS; +import static java.util.Collections.EMPTY_LIST; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; +import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; +import static org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition.LDAP_ATTRIBUTE_MAPPINGS; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; @@ -117,18 +118,19 @@ public class LdapMockMvcTests extends TestClassNullifier { @Parameters(name = "{index}: auth[{0}]; group[{1}]; url[{2}]") public static Collection data() { return Arrays.asList(new Object[][]{ - {"ldap-simple-bind.xml", "ldap-groups-null.xml", "ldap://localhost:33389"}, - {"ldap-simple-bind.xml", "ldap-groups-as-scopes.xml", "ldap://localhost:33389"}, - {"ldap-simple-bind.xml", "ldap-groups-map-to-scopes.xml", "ldap://localhost:33389"}, - {"ldap-simple-bind.xml", "ldap-groups-map-to-scopes.xml", "ldaps://localhost:33636"}, - {"ldap-search-and-bind.xml", "ldap-groups-null.xml", "ldap://localhost:33389"}, - {"ldap-search-and-bind.xml", "ldap-groups-as-scopes.xml", "ldap://localhost:33389"}, +// {"ldap-simple-bind.xml", "ldap-groups-null.xml", "ldap://localhost:33389"}, +// {"ldap-simple-bind.xml", "ldap-groups-as-scopes.xml", "ldap://localhost:33389"}, +// {"ldap-simple-bind.xml", "ldap-groups-map-to-scopes.xml", "ldap://localhost:33389"}, +// {"ldap-simple-bind.xml", "ldap-groups-map-to-scopes.xml", "ldaps://localhost:33636"}, +// {"ldap-search-and-bind.xml", "ldap-groups-null.xml", "ldap://localhost:33389"}, +// {"ldap-search-and-bind.xml", "ldap-groups-as-scopes.xml", "ldap://localhost:33389"}, {"ldap-search-and-bind.xml", "ldap-groups-map-to-scopes.xml", "ldap://localhost:33389"}, - {"ldap-search-and-bind.xml", "ldap-groups-map-to-scopes.xml", "ldaps://localhost:33636"}, - {"ldap-search-and-compare.xml", "ldap-groups-null.xml", "ldap://localhost:33389"}, - {"ldap-search-and-compare.xml", "ldap-groups-as-scopes.xml", "ldap://localhost:33389"}, - {"ldap-search-and-compare.xml", "ldap-groups-map-to-scopes.xml", "ldap://localhost:33389"}, - {"ldap-search-and-compare.xml", "ldap-groups-map-to-scopes.xml", "ldaps://localhost:33636"} +// {"ldap-search-and-bind.xml", "ldap-groups-map-to-scopes.xml", "ldaps://localhost:33636"}, +// {"ldap-search-and-compare.xml", "ldap-groups-null.xml", "ldap://localhost:33389"}, +// {"ldap-search-and-compare.xml", "ldap-groups-as-scopes.xml", "ldap://localhost:33389"}, +// {"ldap-search-and-compare.xml", "ldap-groups-map-to-scopes.xml", "ldap://localhost:33389"}, + {"ldap-search-and-compare.xml", "ldap-groups-as-scopes.xml", "ldaps://localhost:33636"}, +// {"ldap-search-and-compare.xml", "ldap-groups-map-to-scopes.xml", "ldaps://localhost:33636"} }); } @@ -331,7 +333,7 @@ public void acceptInvitation_for_ldap_user_whose_username_is_not_email() throws } @Test - public void test_whitelisted_external_groups() throws Exception { + public void test_external_groups_whitelist() throws Exception { Assume.assumeThat("ldap-groups-map-to-scopes.xml, ldap-groups-as-scopes.xml", StringContains.containsString(ldapGroup)); setUp(); IdentityProviderProvisioning idpProvisioning = mainContext.getBean(IdentityProviderProvisioning.class); @@ -351,23 +353,20 @@ public void test_whitelisted_external_groups() throws Exception { assertNotNull(externalGroups); assertEquals(2, externalGroups.size()); assertThat(externalGroups, containsInAnyOrder("admins", "thirdmarissa")); - } - @Test - public void test_external_groups_with_default_whitelist() throws Exception { - Assume.assumeThat("ldap-groups-map-to-scopes.xml, ldap-groups-as-scopes.xml", StringContains.containsString(ldapGroup)); - setUp(); - AuthenticationManager manager = mainContext.getBean(DynamicZoneAwareAuthenticationManager.class); - UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa3", "ldap3"); - Authentication auth = manager.authenticate(token); + //default whitelist + def.setExternalGroupsWhitelist(EMPTY_LIST); + idp.setConfig(def); + idpProvisioning.update(idp); + auth = manager.authenticate(token); assertNotNull(auth); assertTrue(auth instanceof UaaAuthentication); - UaaAuthentication uaaAuth = (UaaAuthentication) auth; - Set externalGroups = uaaAuth.getExternalGroups(); + uaaAuth = (UaaAuthentication) auth; + externalGroups = uaaAuth.getExternalGroups(); assertNotNull(externalGroups); assertEquals(0, externalGroups.size()); - } + } @Test public void testCustomUserAttributes() throws Exception { @@ -649,10 +648,11 @@ public void testLdapConfigurationBeforeSave() throws Exception { } } - @Test public void testLoginInNonDefaultZone() throws Exception { - Assume.assumeThat("ldap-search-and-bind.xml", StringContains.containsString(ldapProfile)); - Assume.assumeThat("ldap-groups-map-to-scopes.xml", StringContains.containsString(ldapGroup)); + if (!(ldapProfile.contains("ldap-search-and-bind.xml") && + ldapGroup.contains("ldap-groups-map-to-scopes.xml"))) { + return; + } setUp(); String identityAccessToken = utils().getClientOAuthAccessToken(mockMvc, "identity", "identitysecret", ""); @@ -861,6 +861,8 @@ public void runLdapTestblock() throws Exception { deleteLdapUsers(); acceptInvitation_for_ldap_user_whose_username_is_not_email(); deleteLdapUsers(); + testLoginInNonDefaultZone(); + deleteLdapUsers(); } public Object getBean(String name) { @@ -983,7 +985,7 @@ public void validateEmailMissingForLdapUser() throws Exception { @Test public void validateCustomEmailForLdapUser() throws Exception { - Assume.assumeTrue(ldapGroup.equals("ldap-groups-null.xml")); //this only pertains to auth + Assume.assumeThat("ldap-groups-map-to-scopes.xml", StringContains.containsString(ldapGroup)); mockEnvironment.setProperty("ldap.base.mailSubstitute", "{0}@ldaptest.org"); setUp(); String username = "marissa7"; From bac30e2cf2d6b49927c15f4f9e393fda003ed601 Mon Sep 17 00:00:00 2001 From: Paul Warren Date: Wed, 2 Dec 2015 17:07:03 -0800 Subject: [PATCH 040/103] When authing an LDAP user, try to find shadow user by email. [#108824986] https://www.pivotaltracker.com/story/show/108824986 Signed-off-by: Jeremy Coffield --- .../ExternalLoginAuthenticationManager.java | 5 ++ .../uaa/user/InMemoryUaaUserDatabase.java | 5 ++ .../uaa/user/JdbcUaaUserDatabase.java | 23 ++++++- .../identity/uaa/user/UaaUserDatabase.java | 2 + ...xternalLoginAuthenticationManagerTest.java | 69 +++++++++++++++++-- .../uaa/user/MockUaaUserDatabase.java | 10 +++ 6 files changed, 106 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java index e5721ab18d2..872a223c034 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java @@ -96,6 +96,11 @@ public Authentication authenticate(Authentication request) throws Authentication boolean addnew = false; try { UaaUser temp = userDatabase.retrieveUserByName(user.getUsername(), getOrigin()); + + if(temp == null) { + temp = userDatabase.retrieveUserByEmail(user.getEmail(), getOrigin()); + } + if (temp != null) { user = temp; } else { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java b/common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java index 460134670f9..525c5a2f64a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java @@ -57,6 +57,11 @@ public UaaUser retrieveUserById(String id) throws UsernameNotFoundException { return u; } + @Override + public UaaUser retrieveUserByEmail(String email, String origin) throws UsernameNotFoundException { + return users.values().stream().filter(u -> origin.equalsIgnoreCase(u.getOrigin()) && email.equalsIgnoreCase(u.getEmail())).findAny().orElse(null); + } + public UaaUser updateUser(String userId, UaaUser user) throws UsernameNotFoundException { if (!ids.containsKey(userId)) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java b/common/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java index 2601ff44d8f..7b6b83d1da1 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java @@ -14,7 +14,6 @@ import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -24,6 +23,7 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.security.core.GrantedAuthority; @@ -47,6 +47,9 @@ public class JdbcUaaUserDatabase implements UaaUserDatabase { public static final String DEFAULT_USER_BY_ID_QUERY = "select " + USER_FIELDS + "from users " + "where id = ? and active=?"; + public static final String DEFAULT_USER_BY_EMAIL_AND_ORIGIN_QUERY = "select " + USER_FIELDS + "from users " + + "where lower(email)=? and active=? and origin=?"; + private String userAuthoritiesQuery = null; private String userByUserNameQuery = DEFAULT_USER_BY_USERNAME_QUERY; @@ -92,6 +95,24 @@ public UaaUser retrieveUserById(String id) throws UsernameNotFoundException { } } + @Override + public UaaUser retrieveUserByEmail(String email, String origin) throws UsernameNotFoundException { + try { + List results = jdbcTemplate.query(DEFAULT_USER_BY_EMAIL_AND_ORIGIN_QUERY, mapper, email.toLowerCase(Locale.US), true, origin); + if(results.size() == 0) { + return null; + } + else if(results.size() == 1) { + return results.get(0); + } + else { + throw new IncorrectResultSizeDataAccessException(String.format("Multiple users match email=%s origin=%s", email, origin), 1, results.size()); + } + } catch (EmptyResultDataAccessException e) { + throw new UsernameNotFoundException(email); + } + } + private final class UaaUserRowMapper implements RowMapper { @Override public UaaUser mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserDatabase.java b/common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserDatabase.java index 09a972724f6..79f09458e8d 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserDatabase.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserDatabase.java @@ -21,4 +21,6 @@ public interface UaaUserDatabase { UaaUser retrieveUserByName(String username, String origin) throws UsernameNotFoundException; UaaUser retrieveUserById(String id) throws UsernameNotFoundException; + + UaaUser retrieveUserByEmail(String email, String origin) throws UsernameNotFoundException; } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java index 56117b9a0d1..343a6b6845a 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java @@ -3,6 +3,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserDetails; import org.cloudfoundry.identity.uaa.ldap.extension.ExtendedLdapUserImpl; import org.cloudfoundry.identity.uaa.user.Mailable; import org.cloudfoundry.identity.uaa.user.UaaUser; @@ -22,11 +23,16 @@ import java.util.HashMap; +import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -68,14 +74,9 @@ public void setUp() throws Exception { private void mockUaaWithUser() { applicationEventPublisher = mock(ApplicationEventPublisher.class); - user = mock(UaaUser.class); - when(user.getUsername()).thenReturn(userName); - when(user.getId()).thenReturn(userId); - when(user.getOrigin()).thenReturn(origin); - uaaUserDatabase = mock(UaaUserDatabase.class); - when(uaaUserDatabase.retrieveUserById(eq(userId))).thenReturn(user); - when(uaaUserDatabase.retrieveUserByName(eq(userName),eq(origin))).thenReturn(user); + + user = addUserToDb(userName, userId, origin, "test@email.org"); inputAuth = mock(Authentication.class); when(inputAuth.getPrincipal()).thenReturn(userDetails); @@ -84,6 +85,18 @@ private void mockUaaWithUser() { setupManager(); } + private UaaUser addUserToDb(String userName, String userId, String origin, String email) { + UaaUser user = mock(UaaUser.class); + when(user.getUsername()).thenReturn(userName); + when(user.getId()).thenReturn(userId); + when(user.getOrigin()).thenReturn(origin); + when(user.getEmail()).thenReturn(email); + + when(this.uaaUserDatabase.retrieveUserById(eq(userId))).thenReturn(user); + when(this.uaaUserDatabase.retrieveUserByName(eq(userName),eq(origin))).thenReturn(user); + return user; + } + private void setupManager() { manager.setOrigin(origin); manager.setBeanName(beanName); @@ -335,6 +348,48 @@ public void testAuthenticateCreateUserWithUserDetailsPrincipal() throws Exceptio assertEquals(userName, event.getUser().getExternalId()); } + @Test + public void testAuthenticateInvitedUserWithoutAcceptance() throws Exception { + String username = "guyWhoDoesNotAcceptInvites"; + String origin = "ldap"; + String email = "guy@ldap.org"; + + UserDetails ldapUserDetails = mock(ExtendedLdapUserDetails.class, withSettings().extraInterfaces(Mailable.class)); + when(ldapUserDetails.getUsername()).thenReturn(username); + when(ldapUserDetails.getPassword()).thenReturn(password); + when(ldapUserDetails.getAuthorities()).thenReturn(null); + when(ldapUserDetails.isAccountNonExpired()).thenReturn(true); + when(ldapUserDetails.isAccountNonLocked()).thenReturn(true); + when(ldapUserDetails.isCredentialsNonExpired()).thenReturn(true); + when(ldapUserDetails.isEnabled()).thenReturn(true); + when(((Mailable) ldapUserDetails).getEmailAddress()).thenReturn(email); + + // Invited users are created with their email as their username. + UaaUser invitedUser = addUserToDb(email, userId, origin, email); + when(invitedUser.modifyAttributes(anyString(), anyString(), anyString(), anyString())).thenReturn(invitedUser); + + manager = new LdapLoginAuthenticationManager(); + setupManager(); + manager.setOrigin(origin); + + when(uaaUserDatabase.retrieveUserByName(eq(this.userName),eq(origin))) + .thenReturn(null) + .thenReturn(invitedUser); // This is only required to failure comprehensible. Otherwise get null source error. + when(uaaUserDatabase.retrieveUserByEmail(eq(email), eq(origin))) + .thenReturn(invitedUser); + + Authentication ldapAuth = mock(Authentication.class); + when(ldapAuth.getPrincipal()).thenReturn(ldapUserDetails); + + manager.authenticate(ldapAuth); + + userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); + verify(applicationEventPublisher, atLeastOnce()).publishEvent(userArgumentCaptor.capture()); + + for(ApplicationEvent event : userArgumentCaptor.getAllValues()) { + assertNotEquals(event.getClass(), NewUserAuthenticatedEvent.class); + } + } @Test public void testAuthenticateUserExists() throws Exception { diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java b/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java index e146f828c8c..20ac8916923 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java @@ -55,6 +55,16 @@ public UaaUser retrieveUserById(String id) throws UsernameNotFoundException { } } + @Override + public UaaUser retrieveUserByEmail(String email, String origin) throws UsernameNotFoundException { + if (email.equals(user.getEmail()) && origin.equals(user.getOrigin())) { + return user; + } + else { + throw new UsernameNotFoundException(email); + } + } + public UaaUser updateUser(String userId, UaaUser user) throws UsernameNotFoundException { if (user.getId().equals(userId)) { this.user = user; From 9661309f3da86ab36b9d28fcaa947266a4119f6c Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 3 Dec 2015 13:49:58 -0700 Subject: [PATCH 041/103] Update documentation examples [skip ci] --- README.md | 46 ++++++++++++++++++++++++++++++++--------- docs/Sysadmin-Guide.rst | 18 +++++++++------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b65578015b2..feca45e78ab 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,15 @@ The apps all work together with the apps running on the same port ### Deploy to Cloud Foundry You can also build the app and push it to Cloud Foundry, e.g. +Our recommended way is to use a manifest file, but you can do everything on the command line. $ ./gradlew :cloudfoundry-identity-uaa:war - $ cf push myuaa --no-start -m 512M -b https://github.com/cloudfoundry/java-buildpack#v3.3.1 -p uaa/build/libs/cloudfoundry-identity-uaa-2.3.2-SNAPSHOT.war - $ cf set-env myuaa SPRING_PROFILES_ACTIVE default + $ cf push myuaa --no-start -m 512M -p uaa/build/libs/cloudfoundry-identity-uaa-2.3.2-SNAPSHOT.war + $ cf set-env myuaa SPRING_PROFILES_ACTIVE default,hsqldb $ cf set-env myuaa UAA_URL http://myuaa. $ cf set-env myuaa LOGIN_URL http://myuaa. + $ cf set-env myuaa JBP_CONFIG_SPRING_AUTO_RECONFIGURATION '[enabled: false]' + $ cf set-env myuaa JBP_CONFIG_TOMCAT '{tomcat: { version: 7.0.+ }}' $ cf start myuaa In the steps above, replace: @@ -206,26 +209,49 @@ The webapp looks for Yaml content in the following locations For example, to deploy the UAA as a Cloud Foundry application, you can provide an application manifest like - --- + --- applications: - - name: sample-uaa-cf-war - memory: 256M + - name: standalone-uaa-cf-war + memory: 512M instances: 1 - host: uaa.myapp.com - path: cloudfoundry-identity-uaa.war + host: standalone-uaa + path: cloudfoundry-identity-uaa-3.0.0-SNAPSHOT.war env: + JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '[enabled: false]' + JBP_CONFIG_TOMCAT: '{tomcat: { version: 7.0.+ }}' + SPRING_PROFILES_ACTIVE: hsqldb,default UAA_CONFIG_YAML: | - uaa.url: http://uaa.myapp.com - login.url: http://uaa.myapp.com + uaa.url: http://standalone-uaa.cfapps.io + login.url: http://standalone-uaa.cfapps.io smtp: host: mail.server.host port: 3535 + Or as an alternative, set the yaml configuration as a string for an environment variable using the set-env command - cf set-env sample-uaa-cf-war UAA_CONFIG_YAML '{ uaa.url: http://uaa.myapp.com, login.url: http://uaa.myapp.com, smtp: { host: mail.server.host, port: 3535 } }' + cf set-env sample-uaa-cf-war UAA_CONFIG_YAML '{ uaa.url: http://standalone-uaa.myapp.com, login.url: http://standalone-uaa.myapp.com, smtp: { host: mail.server.host, port: 3535 } }' In addition, any simple type property that is read by the UAA can also be fully expanded and read as a system environment variable itself. +Notice how uaa.url can be converted into an environment variable called UAA_URL + + --- + applications: + - name: standalone-uaa-cf-war + memory: 512M + instances: 1 + host: standalone-uaa + path: cloudfoundry-identity-uaa-3.0.0-SNAPSHOT.war + env: + JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '[enabled: false]' + JBP_CONFIG_TOMCAT: '{tomcat: { version: 7.0.+ }}' + SPRING_PROFILES_ACTIVE: hsqldb,default + UAA_URL: http://standalone-uaa.cfapps.io + LOGIN_URL: http://standalone-uaa.cfapps.io + UAA_CONFIG_YAML: | + smtp: + host: mail.server.host + port: 3535 ### Using Gradle to test with postgresql or mysql diff --git a/docs/Sysadmin-Guide.rst b/docs/Sysadmin-Guide.rst index 614c7bd3989..8301d894f66 100644 --- a/docs/Sysadmin-Guide.rst +++ b/docs/Sysadmin-Guide.rst @@ -100,22 +100,26 @@ written as an environment variable. For a Cloud Foundry application this could l :: - --- + --- applications: - - name: sample-uaa-cf-war - memory: 256M + - name: standalone-uaa-cf-war + memory: 512M instances: 1 - host: uaa.myapp.com - path: cloudfoundry-identity-uaa.war + host: standalone-uaa + path: cloudfoundry-identity-uaa-3.0.0-SNAPSHOT.war env: + JBP_CONFIG_SPRING_AUTO_RECONFIGURATION: '[enabled: false]' + JBP_CONFIG_TOMCAT: '{tomcat: { version: 7.0.+ }}' + SPRING_PROFILES_ACTIVE: hsqldb,default UAA_CONFIG_YAML: | - uaa.url: http://uaa.myapp.com - login.url: http://uaa.myapp.com + uaa.url: http://standalone-uaa.cfapps.io + login.url: http://standalone-uaa.cfapps.io smtp: host: mail.server.host port: 3535 + Or as an alternative, set the yaml configuration as a string for an environment variable using the set-env command :: From 377ede3294f1dc29bdacf4ab2ca72714f0aa541e Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Thu, 3 Dec 2015 11:56:03 -0800 Subject: [PATCH 042/103] SAML authentication checks for existing shadow users by email. [#108824630] https://www.pivotaltracker.com/story/show/108824630 Signed-off-by: Madhura Bhave --- .../uaa/user/InMemoryUaaUserDatabase.java | 4 +- .../uaa/user/JdbcUaaUserDatabase.java | 43 +++++---- .../user/InMemoryUaaUserDatabaseTests.java | 11 +++ .../uaa/user/JdbcUaaUserDatabaseTests.java | 37 ++++++-- .../uaa/user/MockUaaUserDatabase.java | 4 +- .../saml/LoginSamlAuthenticationProvider.java | 27 +++--- .../LoginSamlAuthenticationProviderTests.java | 87 ++++++++++++++----- 7 files changed, 147 insertions(+), 66 deletions(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java b/common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java index 525c5a2f64a..653132190f1 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java @@ -12,11 +12,11 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.user; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + import java.util.HashMap; import java.util.Map; -import org.springframework.security.core.userdetails.UsernameNotFoundException; - /** * In-memory user account information storage. * diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java b/common/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java index 7b6b83d1da1..a602185bed2 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java @@ -12,15 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.user; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - +import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; @@ -32,6 +24,15 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + /** * @author Luke Taylor * @author Dave Syer @@ -48,7 +49,7 @@ public class JdbcUaaUserDatabase implements UaaUserDatabase { + "where id = ? and active=?"; public static final String DEFAULT_USER_BY_EMAIL_AND_ORIGIN_QUERY = "select " + USER_FIELDS + "from users " - + "where lower(email)=? and active=? and origin=?"; + + "where lower(email)=? and active=? and origin=? and identity_zone_id=?"; private String userAuthoritiesQuery = null; @@ -97,19 +98,15 @@ public UaaUser retrieveUserById(String id) throws UsernameNotFoundException { @Override public UaaUser retrieveUserByEmail(String email, String origin) throws UsernameNotFoundException { - try { - List results = jdbcTemplate.query(DEFAULT_USER_BY_EMAIL_AND_ORIGIN_QUERY, mapper, email.toLowerCase(Locale.US), true, origin); - if(results.size() == 0) { - return null; - } - else if(results.size() == 1) { - return results.get(0); - } - else { - throw new IncorrectResultSizeDataAccessException(String.format("Multiple users match email=%s origin=%s", email, origin), 1, results.size()); - } - } catch (EmptyResultDataAccessException e) { - throw new UsernameNotFoundException(email); + List results = jdbcTemplate.query(DEFAULT_USER_BY_EMAIL_AND_ORIGIN_QUERY, mapper, email.toLowerCase(Locale.US), true, origin, IdentityZoneHolder.get().getId()); + if(results.size() == 0) { + return null; + } + else if(results.size() == 1) { + return results.get(0); + } + else { + throw new IncorrectResultSizeDataAccessException(String.format("Multiple users match email=%s origin=%s", email, origin), 1, results.size()); } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java index af312b58083..94d5f317b10 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.Map; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; public class InMemoryUaaUserDatabaseTests { @@ -49,6 +50,16 @@ public void testRetrieveUserByInvalidId() throws Exception { db.retrieveUserById(user.getId() + "1"); } + @Test + public void retrieveUserByEmail() throws Exception { + assertSame(user, db.retrieveUserByEmail(user.getEmail(), OriginKeys.UAA)); + } + + @Test + public void retrieveUserByEmail_with_invalidEmail() throws Exception { + assertNull(db.retrieveUserByEmail("invalid.email@wrong.no", OriginKeys.UAA)); + } + @Test public void testUpdateUser() throws Exception { assertSame(user, db.retrieveUserById(user.getId())); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java index 8cabe35dfd1..d6e04d7ce09 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java @@ -28,7 +28,11 @@ import java.util.Collections; import java.util.UUID; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class JdbcUaaUserDatabaseTests extends JdbcTestBase { @@ -162,14 +166,31 @@ public void getValidUserInOtherZoneFromOtherZone() { @Test(expected = UsernameNotFoundException.class) public void getValidUserInOtherZoneFromDefaultZoneFails() { - UaaUser alice = db.retrieveUserByName("alice", OriginKeys.UAA); - assertNotNull(alice); - assertEquals(ALICE_ID, alice.getId()); - assertEquals("alice", alice.getUsername()); - assertEquals("alice@test.org", alice.getEmail()); - assertEquals("alicespassword", alice.getPassword()); + db.retrieveUserByName("alice", OriginKeys.UAA); + } + + @Test + public void retrieveUserByEmail_also_isCaseInsensitive() { + UaaUser joe = db.retrieveUserByEmail("JOE@test.org", OriginKeys.UAA); + assertNotNull(joe); + assertEquals(JOE_ID, joe.getId()); + assertEquals("Joe", joe.getUsername()); + assertEquals("joe@test.org", joe.getEmail()); + assertEquals("joespassword", joe.getPassword()); assertTrue("authorities does not contain uaa.user", - alice.getAuthorities().contains(new SimpleGrantedAuthority("uaa.user"))); + joe.getAuthorities().contains(new SimpleGrantedAuthority("uaa.user"))); + assertNull(joe.getSalt()); + assertNotNull(joe.getPasswordLastModified()); + assertEquals(joe.getCreated(), joe.getPasswordLastModified()); + } + + @Test + public void null_if_noUserWithEmail() { + assertNull(db.retrieveUserByEmail("email@doesnot.exist", OriginKeys.UAA)); } + @Test + public void null_if_userWithEmail_in_differentZone(){ + assertNull(db.retrieveUserByEmail("alice@test.org", OriginKeys.UAA)); + } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java b/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java index 20ac8916923..24608ae777e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java @@ -12,12 +12,12 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.user; -import java.util.Date; - import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import java.util.Date; + /** * @author Luke Taylor */ diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java index 32513a02959..19dbdf0a6fe 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java @@ -25,13 +25,13 @@ import org.cloudfoundry.identity.uaa.authentication.manager.NewUserAuthenticatedEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.SamlUserAuthority; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -250,16 +250,21 @@ protected UaaUser createIfMissing(UaaPrincipal samlPrincipal, boolean addNew, Co user = userDatabase.retrieveUserByName(samlPrincipal.getName(), samlPrincipal.getOrigin()); } } catch (UsernameNotFoundException e) { - if (!addNew) { - throw new LoginSAMLException("SAML user does not exist. " - + "You can correct this by creating a shadow user for the SAML user.", e); - } - // Register new users automatically - publish(new NewUserAuthenticatedEvent(userWithSamlAttributes)); - try { - user = userDatabase.retrieveUserByName(samlPrincipal.getName(), samlPrincipal.getOrigin()); - } catch (UsernameNotFoundException ex) { - throw new BadCredentialsException("Unable to establish shadow user for SAML user:"+ samlPrincipal.getName()); + UaaUser uaaUser = userDatabase.retrieveUserByEmail(userWithSamlAttributes.getEmail(), samlPrincipal.getOrigin()); + if (uaaUser != null) { + user = uaaUser.modifyUsername(samlPrincipal.getName()); + } else { + if (!addNew) { + throw new LoginSAMLException("SAML user does not exist. " + + "You can correct this by creating a shadow user for the SAML user.", e); + } + // Register new users automatically + publish(new NewUserAuthenticatedEvent(userWithSamlAttributes)); + try { + user = userDatabase.retrieveUserByName(samlPrincipal.getName(), samlPrincipal.getOrigin()); + } catch (UsernameNotFoundException ex) { + throw new BadCredentialsException("Unable to establish shadow user for SAML user:"+ samlPrincipal.getName()); + } } } if (haveUserAttributesChanged(user, userWithSamlAttributes)) { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java index 0941cbc333c..03fbf963721 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java @@ -17,6 +17,7 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; @@ -32,7 +33,6 @@ import org.cloudfoundry.identity.uaa.user.JdbcUaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityProviderProvisioning; @@ -45,6 +45,7 @@ import org.opensaml.xml.XMLObject; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; @@ -71,6 +72,7 @@ import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -108,6 +110,10 @@ public class LoginSamlAuthenticationProviderTests extends JdbcTestBase { SamlIdentityProviderDefinition providerDefinition = new SamlIdentityProviderDefinition(); private IdentityProvider provider; private ScimUserProvisioning userProvisioning; + private JdbcScimGroupExternalMembershipManager externalManager; + private ScimGroup uaaSamlUser; + private ScimGroup uaaSamlAdmin; + private ScimGroup uaaSamlTest; public List getAttributes(Map values) { List result = new LinkedList<>(); @@ -142,16 +148,16 @@ public void configureProvider() throws Exception { userProvisioning = new JdbcScimUserProvisioning(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter)); ScimGroupProvisioning groupProvisioning = new JdbcScimGroupProvisioning(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter)); - ScimGroup uaaSamlUser = groupProvisioning.create(new ScimGroup(null,UAA_SAML_USER,IdentityZone.getUaa().getId())); - ScimGroup uaaSamlAdmin = groupProvisioning.create(new ScimGroup(null,UAA_SAML_ADMIN,IdentityZone.getUaa().getId())); - ScimGroup uaaSamlTest = groupProvisioning.create(new ScimGroup(null,UAA_SAML_TEST,IdentityZone.getUaa().getId())); + uaaSamlUser = groupProvisioning.create(new ScimGroup(null,UAA_SAML_USER, IdentityZone.getUaa().getId())); + uaaSamlAdmin = groupProvisioning.create(new ScimGroup(null,UAA_SAML_ADMIN, IdentityZone.getUaa().getId())); + uaaSamlTest = groupProvisioning.create(new ScimGroup(null,UAA_SAML_TEST, IdentityZone.getUaa().getId())); JdbcScimGroupMembershipManager membershipManager = new JdbcScimGroupMembershipManager(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter)); membershipManager.setScimGroupProvisioning(groupProvisioning); membershipManager.setScimUserProvisioning(userProvisioning); ScimUserBootstrap bootstrap = new ScimUserBootstrap(userProvisioning, groupProvisioning, membershipManager, Collections.EMPTY_LIST); - JdbcScimGroupExternalMembershipManager externalManager = new JdbcScimGroupExternalMembershipManager(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter)); + externalManager = new JdbcScimGroupExternalMembershipManager(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter)); externalManager.setScimGroupProvisioning(groupProvisioning); externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML); externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML); @@ -256,18 +262,24 @@ public void test_group_mapping() throws Exception { @Test public void externalGroup_NotMapped_ToScope() throws Exception { - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); - provider.setConfig(providerDefinition); - providerProvisioning.update(provider); - UaaAuthentication authentication = getAuthentication(); - assertEquals("Three authorities should have been granted!", 3, authentication.getAuthorities().size()); - assertThat(authentication.getAuthorities(), - containsInAnyOrder( - new SimpleGrantedAuthority(UAA_SAML_ADMIN), - new SimpleGrantedAuthority(UAA_SAML_USER), - new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority()) - ) - ); + try { + externalManager.unmapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML); + externalManager.unmapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML); + providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider); + UaaAuthentication authentication = getAuthentication(); + assertEquals("Three authorities should have been granted!", 1, authentication.getAuthorities().size()); + assertThat(authentication.getAuthorities(), + not(containsInAnyOrder( + new SimpleGrantedAuthority(UAA_SAML_ADMIN), + new SimpleGrantedAuthority(UAA_SAML_USER) + )) + ); + } finally { + externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML); + externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML); + } } @Test @@ -400,6 +412,44 @@ public void shadowAccount_createdWith_MappedUserAttributes() throws Exception { assertEquals("1234567890", user.getPhoneNumber()); } + @Test + public void should_NotCreateShadowAccount_AndInstead_UpdateExistingUserUsername_if_userWithEmailExists() throws Exception { + Map attributeMappings = new HashMap<>(); + attributeMappings.put("email", "emailAddress"); + providerDefinition.setAttributeMappings(attributeMappings); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider); + + ScimUser createdUser = createSamlUser("marissa.bloggs@test.com", "marissa.bloggs@test.com", "Marissa", "Bloggs"); + + getAuthentication(); + + UaaUser uaaUser = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML); + assertEquals(createdUser.getId(), uaaUser.getId()); + assertEquals("marissa-saml", uaaUser.getUsername()); + } + + @Test(expected = IncorrectResultSizeDataAccessException.class) + public void error_when_multipleUsers_with_sameEmail() throws Exception { + Map attributeMappings = new HashMap<>(); + attributeMappings.put("email", "emailAddress"); + providerDefinition.setAttributeMappings(attributeMappings); + provider.setConfig(providerDefinition); + providerProvisioning.update(provider); + + createSamlUser("marissa.bloggs@test.com", "marissa.bloggs@test.com", "Marissa", "Bloggs"); + createSamlUser("marissa.bloggs", "marissa.bloggs@test.com", "Marissa", "Bloggs"); + + getAuthentication(); + } + + private ScimUser createSamlUser(String username, String email, String givenName, String familyName) { + ScimUser user = new ScimUser("", username, givenName, familyName); + user.setPrimaryEmail(email); + user.setOrigin(OriginKeys.SAML); + return userProvisioning.createUser(user, ""); + } + @Test public void shadowUser_GetsCreatedWithDefaultValues_IfAttributeNotMapped() throws Exception { Map attributeMappings = new HashMap<>(); @@ -449,7 +499,6 @@ protected UaaAuthentication getAuthentication() { return (UaaAuthentication)authentication; } - protected SAMLAuthenticationToken mockSamlAuthentication(String originKey) { ExtendedMetadata metadata = mock(ExtendedMetadata.class); when(metadata.getAlias()).thenReturn(originKey); @@ -476,7 +525,6 @@ public void publishEvent(ApplicationEvent event) { } } - public static final String IDP_META_DATA = "\n" + "\n" + @@ -510,4 +558,3 @@ public void publishEvent(ApplicationEvent event) { " \n" + ""; } - From c5cbd1f71fc87b521e65e1d90ac6589f9bcce51b Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Fri, 4 Dec 2015 14:28:56 -0800 Subject: [PATCH 043/103] Run integration tests with Jacoco coverage [finishes #109313992] https://www.pivotaltracker.com/story/show/109313992 Signed-off-by: Madhura Bhave --- build.gradle | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 11bc9d8c8e9..db1ed91cfb3 100644 --- a/build.gradle +++ b/build.gradle @@ -201,6 +201,9 @@ subprojects { } } +def jacocoJarPath = project.zipTree(configurations.jacocoAgent.singleFile).filter({ it.name == 'jacocoagent.jar' }).asPath +def integrationTestCoverageExecutionData = "${buildDir}/integrationTestCoverageReport.exec" + cargo { containerId = 'tomcat7x' port = 8080 @@ -216,6 +219,8 @@ cargo { } local { + jvmArgs = "-javaagent:${jacocoJarPath}=output=file,dumponexit=true,append=false,destfile=${integrationTestCoverageExecutionData}" + systemProperties { property 'spring.profiles.active', System.getProperty('spring.profiles.active', 'default') } @@ -278,7 +283,7 @@ task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) { additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs) sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs) classDirectories = files(subprojects.sourceSets.main.output) - executionData = files(subprojects.jacocoTestReport.executionData) + executionData = files(integrationTestCoverageExecutionData, subprojects.jacocoTestReport.executionData) reports { html.enabled = true xml.enabled = true From 6f5a86920d36a7462aa475b3d43282d88d7b376d Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Fri, 4 Dec 2015 17:06:45 -0800 Subject: [PATCH 044/103] Update docs for /oauth/token client credentials in body - Added tests [#108973498] https://www.pivotaltracker.com/story/show/108973498 Signed-off-by: Paul Warren --- .../ClientParametersAuthenticationFilter.java | 12 +- docs/UAA-APIs.rst | 156 ++++++++++++------ .../uaa/mock/token/TokenMvcMockTests.java | 48 +++++- 3 files changed, 156 insertions(+), 60 deletions(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java index 0d1cbdc1a48..511cfa86327 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java @@ -15,7 +15,7 @@ package org.cloudfoundry.identity.uaa.authentication; import org.flywaydb.core.internal.util.StringUtils; -import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import javax.servlet.ServletException; @@ -36,7 +36,7 @@ public class ClientParametersAuthenticationFilter extends AbstractClientParamete @Override public void wrapClientCredentialLogin(HttpServletRequest req, HttpServletResponse res, Map loginInfo, String clientId) throws IOException, ServletException { - if (!StringUtils.hasText(req.getHeader("Authorization")) && !"password".equals(req.getParameter("grant_type"))) { + if (!StringUtils.hasText(req.getHeader("Authorization")) && isUrlEncodedForm(req)) { try { doClientCredentialLogin(req, loginInfo, clientId); } catch(AuthenticationException e) { @@ -44,4 +44,12 @@ public void wrapClientCredentialLogin(HttpServletRequest req, HttpServletRespons } } } + + private boolean isUrlEncodedForm(HttpServletRequest req) { + boolean isUrlEncodedForm = false; + if (req.getHeader("Content-Type") != null) { + isUrlEncodedForm = req.getHeader("Content-Type").startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE); + } + return isUrlEncodedForm; + } } diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index f762874721c..3b1b56f6b13 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -144,6 +144,73 @@ Several modes of operation and other optional features can be set in configurati * Keystone - Keystone authentication is experimental and disabled in the Travis CI tests +OAuth2 Token Endpoint: ``POST /oauth/token`` +============================================ +An OAuth2 defined endpoint which accepts authorization code or refresh tokens and provides access_tokens, in the case of authorization code grant. This endpoint also supports client credentials and password grant, which takes the client id and client secret for the former, in addition to username and password in case of the latter. The access_tokens can then be used to gain access to resources within a resource server. + +* Request: ``POST /oauth/token`` + +=============== ================================================= +Request ``POST /oauth/token`` +Authorization Basic authentication, client ID and client secret + (or ``client_id`` and ``client_secret`` can + be provided as url encoded form parameters) +Request Body the authorization code (form encoded) in the case + of authorization code grant, e.g.:: + + grant_type=authorization_code + code=F45jH + response_type=token + + OR the client credentials (form encoded) in the + case of client credentials grant, e.g.:: + + grant_type=client_credentials + client_id=client + client_secret=clientsecret + response_type=token + + OR the client and user credentials (form encoded) + in the case of password grant, e.g.:: + + grant_type=password + client_id=client + client_secret=clientsecret + username=user + password=pass + response_type=token + +Response Codes ``200 OK`` +Response Body :: + + { + "access_token":"2YotnFZFEjr1zCsicMWpAA", + "token_type":"bearer", + "expires_in":3600 + } +=============== ================================================= + +Support for additional authorization attributes +----------------------------------------------- + +Additional user defined claims can be added to the token by sending them in the token request. The format of the request is as follows:: + + authorities={"additionalAuthorizationAttributes":{"external_group":"domain\\group1","external_id":"abcd1234"}} + +A sample password grant request is as follows:: + + POST /uaa/oauth/token HTTP/1.1 + Host: localhost:8080 + Accept: application/json + Authorization: Basic YXBwOmFwcGNsaWVudHNlY3JldA== + "grant_type=password&username=marissa&password=koala&authorities=%7B%22additionalAuthorizationAttributes%22%3A%7B%22external_group%22%3A%22domain%5C%5Cgroup1%22%2C%20%22external_id%22%3A%22abcd1234%22%7D%7D%0A" + +The access token will contain an az_attr claim like:: + + "az_attr":{"external_group":"domain\\group1","external_id":"abcd1234"}} + +These attributes can be requested in an authorization code flow as well. + Authentication and Delegated Authorization APIs =============================================== @@ -289,17 +356,23 @@ See `oauth2 token endpoint`_ below for a more detailed description. =============== ================================================= Request ``POST /oauth/token`` Authorization Basic authentication, client ID and client secret + (or ``client_id`` and ``client_secret`` can + be provided as url encoded form parameters) Request Body the authorization code (form encoded), e.g.:: + [client_id=client] + [client_secret=clientsecret] + grant_type=authorization_code code=F45jH + response_type=token Response Codes ``200 OK`` Response Body :: { - "access_token":"2YotnFZFEjr1zCsicMWpAA", - "token_type":"bearer", - "expires_in":3600, + "access_token":"2YotnFZFEjr1zCsicMWpAA", + "token_type":"bearer", + "expires_in":3600 } =============== ================================================= @@ -352,19 +425,48 @@ This works similarly to the previous section, but does not require the credentia Password Grant with Client and User Credentials: ``POST /oauth/token`` ---------------------------------------------------------------------- + +=============== ================================================= +Request ``POST /oauth/token`` +Authorization Basic authentication, client ID and client secret + (or ``client_id`` and ``client_secret`` can + be provided as url encoded form parameters) +Request Body the ``username`` and ``password`` (form encoded), e.g. + :: + + [client_id=client] + [client_secret=clientsecret] + grant_type=password + username=user + password=pass + response_type=token + +Response Codes ``200 OK`` +Response Body :: + + { + "access_token":"2YotnFZFEjr1zCsicMWpAA", + "token_type":"bearer", + "expires_in":3600 + } + +=============== ================================================= + * Request: ``POST /oauth/token`` * Authorization: Basic auth with client_id and client_secret + (optionally ``client_id`` and ``client_secret`` can instead + be provided as url encoded form parameters) * Request query component: some parameters specified by the spec, appended to the query component using the "application/x-www-form-urlencoded" format, * ``grant_type=password`` * ``response_type=token`` * ``client_id=cf`` + * ``client_secret=cfsecret`` * ``username=marissa`` * ``password=koala`` * ``scope=read write`` - optional. Omit to receive the all claims. * ``redirect_uri`` - optional because it can be pre-registered, but a dummy is still needed where cf is concerned (it doesn't redirect) and must be pre-registered, see `Client Registration Administration APIs`_. - Trusted Authentication from Login Server ---------------------------------------- @@ -550,52 +652,6 @@ Notes: .. _oauth2 token endpoint: -OAuth2 Token Endpoint: ``POST /oauth/token`` --------------------------------------------- - -An OAuth2 defined endpoint which accepts authorization code or refresh tokens and provides access_tokens. The access_tokens can then be used to gain access to resources within a resource server. - -* Request: ``POST /oauth/token`` - -=============== ================================================= -Request ``POST /oauth/token`` -Request Body the authorization code (form encoded), e.g.:: - - code=F45jH - -Response Codes ``200 OK`` -Response Body :: - - { - "access_token":"2YotnFZFEjr1zCsicMWpAA", - "token_type":"bearer", - "expires_in":3600, - } - -=============== ================================================= - - -Support for additional authorization attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Additional user defined claims can be added to the token by sending them in the token request. The format of the request is as follows:: - - authorities={"additionalAuthorizationAttributes":{"external_group":"domain\\group1","external_id":"abcd1234"}} - -A sample password grant request is as follows:: - - POST /uaa/oauth/token HTTP/1.1 - Host: localhost:8080 - Accept: application/json - Authorization: Basic YXBwOmFwcGNsaWVudHNlY3JldA== - "grant_type=password&username=marissa&password=koala&authorities=%7B%22additionalAuthorizationAttributes%22%3A%7B%22external_group%22%3A%22domain%5C%5Cgroup1%22%2C%20%22external_id%22%3A%22abcd1234%22%7D%7D%0A" - -The access token will contain an az_attr claim like:: - - "az_attr":{"external_group":"domain\\group1","external_id":"abcd1234"}} - -These attributes can be requested in an authorization code flow as well. - OpenID User Info Endpoint: ``GET /userinfo`` -------------------------------------------- diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java index f3500916758..8c08cb3bdbc 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenMvcMockTests.java @@ -17,15 +17,17 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authorization.UaaAuthorizationEndpoint; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; -import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.oauth.DisableIdTokenResponseTypeFilter; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.oauth.token.SignerProvider; import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -40,12 +42,10 @@ import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; -import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -241,7 +241,7 @@ protected ScimGroup createIfNotExist(String scope, String zoneId) { } @Test - public void getOauthToken_withClientIdAndSecretInRequestBody_shouldBeOk() throws Exception { + public void getOauthToken_usingAuthCode_withClientIdAndSecretInRequestBody_shouldBeOk() throws Exception { String clientId = "testclient"+new RandomValueStringGenerator().generate(); setUpClients(clientId, "uaa.user", "uaa.user", "authorization_code", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); @@ -286,6 +286,40 @@ public void getOauthToken_withClientIdAndSecretInRequestBody_shouldBeOk() throws .andExpect(status().isOk()); } + @Test + public void getOauthToken_usingPassword_withClientIdAndSecretInRequestBody_shouldBeOk() throws Exception { + String clientId = "testclient"+new RandomValueStringGenerator().generate(); + setUpClients(clientId, "uaa.user", "uaa.user", "password", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); + + String username = "testuser"+new RandomValueStringGenerator().generate(); + String userScopes = "uaa.user"; + setUpUser(username, userScopes, OriginKeys.UAA, IdentityZone.getUaa().getId()); + + getMockMvc().perform(post("/oauth/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .param(OAuth2Utils.RESPONSE_TYPE, "token") + .param(OAuth2Utils.GRANT_TYPE, "password") + .param(OAuth2Utils.CLIENT_ID, clientId) + .param("client_secret", SECRET) + .param("username", username) + .param("password", SECRET)) + .andExpect(status().isOk()); + } + + @Test + public void getOauthToken_usingClientCredentials_withClientIdAndSecretInRequestBody_shouldBeOk() throws Exception { + String clientId = "testclient"+new RandomValueStringGenerator().generate(); + setUpClients(clientId, "uaa.user", "uaa.user", "client_credentials", true, TEST_REDIRECT_URI, Arrays.asList("uaa")); + + getMockMvc().perform(post("/oauth/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .param(OAuth2Utils.RESPONSE_TYPE, "token") + .param(OAuth2Utils.GRANT_TYPE, "client_credentials") + .param(OAuth2Utils.CLIENT_ID, clientId) + .param("client_secret", SECRET)) + .andExpect(status().isOk()); + } + @Test public void testClientIdentityProviderWithoutAllowedProvidersForPasswordGrantWorksInOtherZone() throws Exception { String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,openid"; @@ -1747,12 +1781,10 @@ public void testOtherClientAuthenticationMethods() throws Exception { String scopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*"; setUpClients(clientId, scopes, scopes, GRANT_TYPES, true); - String oauthClientId = "testclient" + new RandomValueStringGenerator().generate(); String oauthScopes = "space.*.developer,space.*.admin,org.*.reader,org.123*.admin,*.*,*,oauth.something"; setUpClients(oauthClientId, oauthScopes, oauthScopes, GRANT_TYPES, true); - String userId = "testuser" + new RandomValueStringGenerator().generate(); String userScopes = "space.1.developer,space.2.developer,org.1.reader,org.2.reader,org.12345.admin,scope.one,scope.two,scope.three"; ScimUser developer = setUpUser(userId, userScopes, OriginKeys.UAA, IdentityZoneHolder.get().getId()); From 3f3f95b191559bff837992e2e88dd821c3166c8f Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 7 Dec 2015 09:10:40 -0700 Subject: [PATCH 045/103] Add debug statements to user attribute retrieval --- .../uaa/login/saml/LoginSamlAuthenticationProvider.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java index 19dbdf0a6fe..8182c3b5910 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java @@ -196,6 +196,7 @@ public Collection retrieveSamlAuthorities(SamlIdenti } public MultiValueMap retrieveUserAttributes(SamlIdentityProviderDefinition definition, SAMLCredential credential) { + logger.debug(String.format("Retrieving SAML user attributes [zone:%s, origin:%s]", definition.getZoneId(), definition.getIdpEntityAlias())); MultiValueMap userAttributes = new LinkedMultiValueMap<>(); if (definition != null && definition.getAttributeMappings() != null) { for (Entry attributeMapping : definition.getAttributeMappings().entrySet()) { @@ -207,6 +208,7 @@ public MultiValueMap retrieveUserAttributes(SamlIdentityProvider if (xmlObject instanceof XSString) { String value = ((XSString) xmlObject).getValue(); userAttributes.add(key, value); + logger.debug(String.format("Found SAML user attribute %s at index %s of value %s [zone:%s, origin:%s]", key, count, value, definition.getZoneId(), definition.getIdpEntityAlias())); } else { logger.debug(String.format("SAML user attribute %s at index %s is not of type XSString [zone:%s, origin:%s]", key, count, definition.getZoneId(), definition.getIdpEntityAlias())); } From 867516d3a433d3c10d99071233aef720032a1116 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Fri, 4 Dec 2015 16:08:14 -0800 Subject: [PATCH 046/103] Two shadow users no longer being created if authed email matches user in db [#108824986] https://www.pivotaltracker.com/story/show/108824986 Signed-off-by: Madhura Bhave --- .../ExternalLoginAuthenticationManager.java | 22 +++++++------------ .../LdapLoginAuthenticationManager.java | 2 +- ...xternalLoginAuthenticationManagerTest.java | 8 ++++--- .../uaa/mock/ldap/LdapMockMvcTests.java | 19 ++++++++++++++++ 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java index 872a223c034..4ab64babfa1 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java @@ -93,23 +93,17 @@ public Authentication authenticate(Authentication request) throws Authentication return null; } - boolean addnew = false; - try { - UaaUser temp = userDatabase.retrieveUserByName(user.getUsername(), getOrigin()); - - if(temp == null) { - temp = userDatabase.retrieveUserByEmail(user.getEmail(), getOrigin()); - } + UaaUser scimUser; - if (temp != null) { - user = temp; - } else { - addnew = true; - } + try { + scimUser = userDatabase.retrieveUserByName(user.getUsername(), getOrigin()); } catch (UsernameNotFoundException e) { - addnew = true; + scimUser = userDatabase.retrieveUserByEmail(user.getEmail(), getOrigin()); } - if (addnew) { + + if (scimUser != null) { + user = scimUser; + } else { // Register new users automatically publish(new NewUserAuthenticatedEvent(user)); try { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java index 17765671b49..34faae6aa9f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java @@ -106,7 +106,7 @@ protected UaaUser userAuthenticated(Authentication request, UaaUser user) { if (request.getPrincipal() !=null && request.getPrincipal() instanceof ExtendedLdapUserDetails) { UaaUser fromRequest = getUser(request); if (haveUserAttributesChanged(user, fromRequest)) { - user = user.modifyAttributes(fromRequest.getEmail(), fromRequest.getGivenName(), fromRequest.getFamilyName(), fromRequest.getPhoneNumber()); + user = user.modifyAttributes(fromRequest.getEmail(), fromRequest.getGivenName(), fromRequest.getFamilyName(), fromRequest.getPhoneNumber()).modifyUsername(fromRequest.getUsername()); userModified = true; } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java index 343a6b6845a..28cadd05ec3 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java @@ -8,6 +8,7 @@ import org.cloudfoundry.identity.uaa.user.Mailable; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; +import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -367,14 +368,15 @@ public void testAuthenticateInvitedUserWithoutAcceptance() throws Exception { // Invited users are created with their email as their username. UaaUser invitedUser = addUserToDb(email, userId, origin, email); when(invitedUser.modifyAttributes(anyString(), anyString(), anyString(), anyString())).thenReturn(invitedUser); + UaaUser updatedUser = new UaaUser(new UaaUserPrototype().withUsername(username).withId(userId).withOrigin(origin).withEmail(email)); + when(invitedUser.modifyUsername(username)).thenReturn(updatedUser); manager = new LdapLoginAuthenticationManager(); setupManager(); manager.setOrigin(origin); - when(uaaUserDatabase.retrieveUserByName(eq(this.userName),eq(origin))) - .thenReturn(null) - .thenReturn(invitedUser); // This is only required to failure comprehensible. Otherwise get null source error. + when(uaaUserDatabase.retrieveUserByName(eq(username),eq(origin))) + .thenThrow(new UsernameNotFoundException("")); when(uaaUserDatabase.retrieveUserByEmail(eq(email), eq(origin))) .thenReturn(invitedUser); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java index 295385c00a5..980ee69c043 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java @@ -983,6 +983,25 @@ public void validateEmailMissingForLdapUser() throws Exception { assertEquals("marissa7@user.from.ldap.cf", getEmail(username)); } + @Test + public void validateLoginAsInvitedUserWithoutClickingInviteLink() throws Exception { + setUp(); + assertNull(userDatabase.retrieveUserByEmail("marissa7@user.from.ldap.cf", OriginKeys.LDAP)); + + ScimUser user = new ScimUser(null, "marissa7@user.from.ldap.cf", "Marissa", "Seven"); + user.setPrimaryEmail("marissa7@user.from.ldap.cf"); + user.setOrigin(OriginKeys.LDAP); + ScimUser createdUser = uDB.createUser(user, ""); + + performUiAuthentication("marissa7", "ldap7", HttpStatus.FOUND); + + UaaUser authedUser = userDatabase.retrieveUserByEmail("marissa7@user.from.ldap.cf", OriginKeys.LDAP); + assertEquals(createdUser.getId(), authedUser.getId()); + List scimUserList = uDB.query(String.format("origin eq '%s'", OriginKeys.LDAP)); + assertEquals(1, scimUserList.size()); + assertEquals("marissa7", authedUser.getUsername()); + } + @Test public void validateCustomEmailForLdapUser() throws Exception { Assume.assumeThat("ldap-groups-map-to-scopes.xml", StringContains.containsString(ldapGroup)); From 6941d5663aaf0e16d7fdd597d00438d5ef288964 Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Mon, 7 Dec 2015 14:09:08 -0800 Subject: [PATCH 047/103] Remove a trivially true check from ScimGroupEndpoints [#109107468] https://www.pivotaltracker.com/story/show/109107468 Signed-off-by: Jeremy Coffield --- .../uaa/scim/endpoints/ScimGroupEndpoints.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java index fd8fd188448..a0fa11596f1 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java @@ -122,21 +122,15 @@ private boolean isMember(ScimGroup group, String userId, ScimGroupMember.Role ro return false; } - private boolean isReaderMember(ScimGroup group, String userId) { - return isMember(group, userId, ScimGroupMember.Role.READER); - } - - private List filterForCurrentUser(List input, int startIndex, int count, String userId) { + private List filterForCurrentUser(List input, int startIndex, int count) { List response = new ArrayList(); int expectedResponseSize = Math.min(count, input.size()); boolean needMore = response.size() < expectedResponseSize; while (needMore && startIndex <= input.size()) { for (ScimGroup group : UaaPagingUtils.subList(input, startIndex, count)) { group.setMembers(membershipManager.getMembers(group.getId())); - if (isReaderMember(group, userId)) { - response.add(group); - needMore = response.size() < expectedResponseSize; - } + response.add(group); + needMore = response.size() < expectedResponseSize; if (!needMore) { break; } @@ -163,7 +157,7 @@ public SearchResults listGroups( throw new ScimException("Invalid filter expression: [" + filter + "]", HttpStatus.BAD_REQUEST); } - List input = filterForCurrentUser(result, startIndex, count, null); + List input = filterForCurrentUser(result, startIndex, count); if (!StringUtils.hasLength(attributesCommaSeparated)) { return new SearchResults<>(Arrays.asList(ScimCore.SCHEMAS), input, startIndex, count, From f2c9dac8f7371ced736592c043293ebc20cc3b24 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Mon, 7 Dec 2015 15:47:46 -0800 Subject: [PATCH 048/103] Skip context path when getting path token by position [finishes #109397782] https://www.pivotaltracker.com/story/show/109397782 Signed-off-by: Jonathan Lo --- .../identity/uaa/util/UaaUrlUtils.java | 18 +-- .../uaa/scim/security/GroupRoleCheck.java | 6 +- .../scim/security/GroupRoleCheckTests.java | 119 ++++++++++++++++++ 3 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 scim/src/test/java/org/cloudfoundry/identity/uaa/scim/security/GroupRoleCheckTests.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java b/common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java index 9d38ead42b3..3846197274b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java @@ -85,11 +85,11 @@ public static String getSubdomain() { return subdomain.trim(); } - public static String extractPathVariableFromUrl(int pathParameterIndex, String pathInfo) { - if (pathInfo.startsWith("/")) { - pathInfo = pathInfo.substring(1); + public static String extractPathVariableFromUrl(int pathParameterIndex, String path) { + if (path.startsWith("/")) { + path = path.substring(1); } - String[] paths = StringUtils.delimitedListToStringArray(pathInfo, "/"); + String[] paths = StringUtils.delimitedListToStringArray(path, "/"); if (paths.length!=0 && pathParameterIndex getAuthorities() { + return null; + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public Object getPrincipal() { + return new UaaPrincipal(userId, "test-username", "test@email.com", OriginKeys.UAA, userId, "uaa"); + } + + @Override + public boolean isAuthenticated() { + return false; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + + } + + @Override + public String getName() { + return null; + } + }; + } + + @Override + public void setAuthentication(Authentication authentication) { + + } + }; + } +} From 8554c94172edc9a1bea4dad6fa09442b2b2e7f51 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 7 Dec 2015 14:11:30 -0700 Subject: [PATCH 049/103] Add better debug to SAML attribute mapping https://www.pivotaltracker.com/story/show/108824610 [#108824610] --- .../uaa/login/saml/LoginSamlAuthenticationProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java index 8182c3b5910..f4be111c17b 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java @@ -209,8 +209,8 @@ public MultiValueMap retrieveUserAttributes(SamlIdentityProvider String value = ((XSString) xmlObject).getValue(); userAttributes.add(key, value); logger.debug(String.format("Found SAML user attribute %s at index %s of value %s [zone:%s, origin:%s]", key, count, value, definition.getZoneId(), definition.getIdpEntityAlias())); - } else { - logger.debug(String.format("SAML user attribute %s at index %s is not of type XSString [zone:%s, origin:%s]", key, count, definition.getZoneId(), definition.getIdpEntityAlias())); + } else if (xmlObject !=null){ + logger.debug(String.format("SAML user attribute %s at index %s is not of type XSString, %s [zone:%s, origin:%s]", key, count, xmlObject.getClass().getName(),definition.getZoneId(), definition.getIdpEntityAlias())); } count++; } From 7b84e3bc0caea31a93464a19d91d78b725fc7e57 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 7 Dec 2015 19:23:47 -0700 Subject: [PATCH 050/103] Support other attribute types than XSString https://www.pivotaltracker.com/story/show/108824610 [#108824610] --- .../saml/LoginSamlAuthenticationProvider.java | 52 +++++++++++-- .../LoginSamlAuthenticationProviderTests.java | 74 ++++++++++++++++++- 2 files changed, 118 insertions(+), 8 deletions(-) diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java index f4be111c17b..d9c46dc5aac 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java @@ -35,9 +35,18 @@ import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.joda.time.DateTime; import org.opensaml.saml2.core.Attribute; import org.opensaml.xml.XMLObject; +import org.opensaml.xml.schema.XSAny; +import org.opensaml.xml.schema.XSBase64Binary; +import org.opensaml.xml.schema.XSBoolean; +import org.opensaml.xml.schema.XSBooleanValue; +import org.opensaml.xml.schema.XSDateTime; +import org.opensaml.xml.schema.XSInteger; +import org.opensaml.xml.schema.XSQName; import org.opensaml.xml.schema.XSString; +import org.opensaml.xml.schema.XSURI; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -60,6 +69,7 @@ import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; +import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -203,16 +213,11 @@ public MultiValueMap retrieveUserAttributes(SamlIdentityProvider if (attributeMapping.getValue() instanceof String) { if (credential.getAttribute((String)attributeMapping.getValue()) != null) { String key = attributeMapping.getKey(); - int count = 0; for (XMLObject xmlObject : credential.getAttribute((String) attributeMapping.getValue()).getAttributeValues()) { - if (xmlObject instanceof XSString) { - String value = ((XSString) xmlObject).getValue(); + String value = getStringValue(key, definition, xmlObject); + if (value!=null) { userAttributes.add(key, value); - logger.debug(String.format("Found SAML user attribute %s at index %s of value %s [zone:%s, origin:%s]", key, count, value, definition.getZoneId(), definition.getIdpEntityAlias())); - } else if (xmlObject !=null){ - logger.debug(String.format("SAML user attribute %s at index %s is not of type XSString, %s [zone:%s, origin:%s]", key, count, xmlObject.getClass().getName(),definition.getZoneId(), definition.getIdpEntityAlias())); } - count++; } } } @@ -221,6 +226,39 @@ public MultiValueMap retrieveUserAttributes(SamlIdentityProvider return userAttributes; } + protected String getStringValue(String key, SamlIdentityProviderDefinition definition, XMLObject xmlObject) { + String value = null; + if (xmlObject instanceof XSString) { + value = ((XSString) xmlObject).getValue(); + } else if (xmlObject instanceof XSAny) { + value = ((XSAny)xmlObject).getTextContent(); + } else if (xmlObject instanceof XSInteger) { + Integer i = ((XSInteger)xmlObject).getValue(); + value = i!=null ? i.toString() : null; + } else if (xmlObject instanceof XSBoolean) { + XSBooleanValue b = ((XSBoolean)xmlObject).getValue(); + value = b!=null && b.getValue()!=null ? b.getValue().toString() : null; + } else if (xmlObject instanceof XSDateTime) { + DateTime d = ((XSDateTime)xmlObject).getValue(); + value = d!=null ? d.toString() : null; + } else if (xmlObject instanceof XSQName) { + QName name = ((XSQName) xmlObject).getValue(); + value = name!=null ? name.toString() : null; + } else if (xmlObject instanceof XSURI) { + value = ((XSURI) xmlObject).getValue(); + } else if (xmlObject instanceof XSBase64Binary) { + value = ((XSBase64Binary) xmlObject).getValue(); + } + + if (value!=null) { + logger.debug(String.format("Found SAML user attribute %s of value %s [zone:%s, origin:%s]", key, value, definition.getZoneId(), definition.getIdpEntityAlias())); + return value; + } else if (xmlObject !=null){ + logger.debug(String.format("SAML user attribute %s at is not of type XSString, %s [zone:%s, origin:%s]", key, xmlObject.getClass().getName(),definition.getZoneId(), definition.getIdpEntityAlias())); + } + return null; + } + protected UaaUser createIfMissing(UaaPrincipal samlPrincipal, boolean addNew, Collection authorities, MultiValueMap userAttributes) { UaaUser user = null; String invitedUserId = null; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java index 03fbf963721..6d4bd9c2934 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java @@ -36,13 +36,23 @@ import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityProviderProvisioning; +import org.joda.time.DateTime; import org.junit.Before; import org.junit.Test; import org.opensaml.saml2.core.Assertion; import org.opensaml.saml2.core.Attribute; import org.opensaml.saml2.core.NameID; +import org.opensaml.ws.wsaddressing.impl.AttributedURIImpl; import org.opensaml.ws.wssecurity.impl.AttributedStringImpl; import org.opensaml.xml.XMLObject; +import org.opensaml.xml.schema.XSBooleanValue; +import org.opensaml.xml.schema.impl.XSAnyImpl; +import org.opensaml.xml.schema.impl.XSBase64BinaryImpl; +import org.opensaml.xml.schema.impl.XSBooleanImpl; +import org.opensaml.xml.schema.impl.XSDateTimeImpl; +import org.opensaml.xml.schema.impl.XSIntegerImpl; +import org.opensaml.xml.schema.impl.XSQNameImpl; +import org.opensaml.xml.schema.impl.XSURIImpl; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.dao.IncorrectResultSizeDataAccessException; @@ -61,6 +71,7 @@ import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import javax.xml.namespace.QName; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -128,7 +139,35 @@ public List getAttributes(final String name, Object value) { when(attribute.getFriendlyName()).thenReturn(name); List xmlObjects = new LinkedList<>(); - if (value instanceof List) { + if ("XSURI".equals(name)) { + XSURIImpl impl = new AttributedURIImpl("", "", ""); + impl.setValue((String)value); + xmlObjects.add(impl); + } else if ("XSAny".equals(name)) { + XSAnyImpl impl = new XSAnyImpl("","","") {}; + impl.setTextContent((String)value); + xmlObjects.add(impl); + } else if ("XSQName".equals(name)) { + XSQNameImpl impl = new XSQNameImpl("","","") {}; + impl.setValue(new QName("", (String)value)); + xmlObjects.add(impl); + } else if ("XSInteger".equals(name)) { + XSIntegerImpl impl = new XSIntegerImpl("","",""){}; + impl.setValue((Integer)value); + xmlObjects.add(impl); + } else if ("XSBoolean".equals(name)) { + XSBooleanImpl impl = new XSBooleanImpl("","",""){}; + impl.setValue(new XSBooleanValue((Boolean)value, false)); + xmlObjects.add(impl); + } else if ("XSDateTime".equals(name)) { + XSDateTimeImpl impl = new XSDateTimeImpl("","",""){}; + impl.setValue((DateTime)value); + xmlObjects.add(impl); + } else if ("XSBase64Binary".equals(name)) { + XSBase64BinaryImpl impl = new XSBase64BinaryImpl("","",""){}; + impl.setValue((String)value); + xmlObjects.add(impl); + } else if (value instanceof List) { for (String s : (List)value) { AttributedStringImpl impl = new AttributedStringImpl("", "", ""); impl.setValue(s); @@ -214,6 +253,16 @@ private SAMLCredential getUserCredential(String username, String firstName, Stri attributes.put("2ndgroups", Arrays.asList(SAML_TEST)); attributes.put(COST_CENTER, Arrays.asList(DENVER_CO)); attributes.put(MANAGER, Arrays.asList(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); + + //test different types + attributes.put("XSURI", "http://localhost:8080/someuri"); + attributes.put("XSAny", "XSAnyValue"); + attributes.put("XSQName", "XSQNameValue"); + attributes.put("XSInteger", new Integer(3)); + attributes.put("XSBoolean", Boolean.TRUE); + attributes.put("XSDateTime", new DateTime(0)); + attributes.put("XSBase64Binary", "00001111"); + return new SAMLCredential( usernameID, mock(Assertion.class), @@ -260,6 +309,29 @@ public void test_group_mapping() throws Exception { ); } + @Test + public void test_non_string_attributes() throws Exception { + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSURI", "XSURI"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSAny", "XSAny"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSQName", "XSQName"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSInteger", "XSInteger"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSBoolean", "XSBoolean"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSDateTime", "XSDateTime"); + providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSBase64Binary", "XSBase64Binary"); + + provider.setConfig(providerDefinition); + providerProvisioning.update(provider); + UaaAuthentication authentication = getAuthentication(); + assertEquals("http://localhost:8080/someuri", authentication.getUserAttributes().getFirst("XSURI")); + assertEquals("XSAnyValue", authentication.getUserAttributes().getFirst("XSAny")); + assertEquals("XSQNameValue", authentication.getUserAttributes().getFirst("XSQName")); + assertEquals("3", authentication.getUserAttributes().getFirst("XSInteger")); + assertEquals("true", authentication.getUserAttributes().getFirst("XSBoolean")); + assertEquals(new DateTime(0).toString(), authentication.getUserAttributes().getFirst("XSDateTime")); + assertEquals("00001111", authentication.getUserAttributes().getFirst("XSBase64Binary")); + } + + @Test public void externalGroup_NotMapped_ToScope() throws Exception { try { From 4924bc6736e7fdbc8db23125416dbfee136dc8fd Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 7 Dec 2015 20:35:48 -0700 Subject: [PATCH 051/103] Implement test to go red for zone delete https://www.pivotaltracker.com/story/show/108254860 [#108254860] --- .../identity/uaa/audit/JdbcAuditService.java | 19 +- .../identity/uaa/scim/ScimCore.java | 9 +- .../identity/uaa/scim/ScimGroup.java | 9 +- .../identity/uaa/scim/ScimUser.java | 19 +- .../IdentityZoneEndpointsMockMvcTests.java | 168 ++++++++++++++++-- 5 files changed, 185 insertions(+), 39 deletions(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditService.java b/common/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditService.java index cac19ba221f..434bfd4ab0c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditService.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,18 +12,17 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.audit; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +import javax.sql.DataSource; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.List; -import javax.sql.DataSource; - -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; - /** - * + * * @author Luke Taylor */ public class JdbcAuditService implements UaaAuditService { @@ -54,8 +53,8 @@ public void log(AuditEvent auditEvent) { data = data == null ? "" : data; data = data.length() > 255 ? data.substring(0, 255) : data; template.update("insert into sec_audit (principal_id, event_type, origin, event_data, identity_zone_id) values (?,?,?,?,?)", - auditEvent.getPrincipalId(), auditEvent.getType().getCode(), auditEvent.getOrigin(), - auditEvent.getData(), auditEvent.getIdentityZoneId()); + auditEvent.getPrincipalId(), auditEvent.getType().getCode(), origin, + data, auditEvent.getIdentityZoneId()); } private class AuditEventRowMapper implements RowMapper { @@ -71,7 +70,7 @@ public AuditEvent mapRow(ResultSet rs, int rowNum) throws SQLException { data, time, identityZoneId); } } - + private static String nullSafeTrim(String s) { return s == null ? null : s.trim(); } diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java index 1fdd6a8bd5e..5d87fd06f23 100644 --- a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java +++ b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -12,11 +12,11 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim; -import java.util.Arrays; - import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.util.Assert; +import java.util.Arrays; + public abstract class ScimCore { public static final String[] SCHEMAS = new String[] { "urn:scim:schemas:core:1.0" }; @@ -50,8 +50,9 @@ public String getExternalId() { return externalId; } - public void setExternalId(String externalId) { + public ScimCore setExternalId(String externalId) { this.externalId = externalId; + return this; } public ScimMeta getMeta() { diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java index 71a20f5d687..7a7fc5eba92 100644 --- a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java +++ b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java @@ -33,24 +33,27 @@ public String getDisplayName() { return displayName; } - public void setDisplayName(String displayName) { + public ScimGroup setDisplayName(String displayName) { this.displayName = displayName; + return this; } public String getZoneId() { return zoneId; } - public void setZoneId(String zoneId) { + public ScimGroup setZoneId(String zoneId) { this.zoneId = zoneId; + return this; } public List getMembers() { return members; } - public void setMembers(List members) { + public ScimGroup setMembers(List members) { this.members = members; + return this; } public ScimGroup() { diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java index f0191218f8b..54288b3ba9e 100644 --- a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java +++ b/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java @@ -12,24 +12,24 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.impl.JsonDateSerializer; +import org.cloudfoundry.identity.uaa.oauth.approval.Approval; import org.cloudfoundry.identity.uaa.scim.impl.ScimUserJsonDeserializer; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + /** * Object to hold SCIM data for Jackson to map to and from JSON * @@ -501,8 +501,9 @@ public String getExternalId() { return externalId; } - public void setExternalId(String externalId) { + public ScimUser setExternalId(String externalId) { this.externalId = externalId; + return this; } public String getZoneId() { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index d337ea6aa89..4a7a70660a8 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -6,36 +6,43 @@ import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; import org.cloudfoundry.identity.uaa.audit.event.GroupModifiedEvent; import org.cloudfoundry.identity.uaa.audit.event.UserModifiedEvent; -import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; -import org.cloudfoundry.identity.uaa.zone.SamlConfig; -import org.cloudfoundry.identity.uaa.zone.TokenPolicy; -import org.cloudfoundry.identity.uaa.zone.KeyPair; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.IdentityZoneCreationResult; +import org.cloudfoundry.identity.uaa.oauth.approval.Approval; +import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.oauth.event.ClientCreateEvent; import org.cloudfoundry.identity.uaa.oauth.event.ClientDeleteEvent; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.scim.ScimGroup; +import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; +import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; +import org.cloudfoundry.identity.uaa.scim.ScimGroupMembershipManager; +import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; +import org.cloudfoundry.identity.uaa.zone.KeyPair; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; +import org.cloudfoundry.identity.uaa.zone.SamlConfig; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.zone.event.IdentityZoneModifiedEvent; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpStatus; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.test.web.servlet.MvcResult; @@ -49,16 +56,21 @@ import java.util.Map; import java.util.UUID; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LOGIN_SERVER; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.TEXT_HTML_VALUE; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -439,8 +451,8 @@ public void testCreateZoneAndIdentityProvider() throws Exception { checkZoneAuditEventInUaa(1, AuditEventType.IdentityZoneCreatedEvent); IdentityProviderProvisioning idpp = (IdentityProviderProvisioning) getWebApplicationContext().getBean("identityProviderProvisioning"); - IdentityProvider idp1 = idpp.retrieveByOrigin(OriginKeys.UAA, identityZone.getId()); - IdentityProvider idp2 = idpp.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId()); + IdentityProvider idp1 = idpp.retrieveByOrigin(UAA, identityZone.getId()); + IdentityProvider idp2 = idpp.retrieveByOrigin(UAA, IdentityZone.getUaa().getId()); assertNotEquals(idp1, idp2); IdentityZoneProvisioning identityZoneProvisioning = (IdentityZoneProvisioning) getWebApplicationContext().getBean("identityZoneProvisioning"); @@ -451,6 +463,136 @@ public void testCreateZoneAndIdentityProvider() throws Exception { assertEquals("saml-private-key", createdZone.getConfig().getSamlConfig().getPrivateKey()); } + @Test + public void test_delete_zone_cleans_db() throws Exception { + IdentityProviderProvisioning idpp = getWebApplicationContext().getBean(IdentityProviderProvisioning.class); + ScimGroupProvisioning groupProvisioning = getWebApplicationContext().getBean(ScimGroupProvisioning.class); + ScimUserProvisioning userProvisioning = getWebApplicationContext().getBean(ScimUserProvisioning.class); + ScimGroupMembershipManager membershipManager = getWebApplicationContext().getBean(ScimGroupMembershipManager.class); + ScimGroupExternalMembershipManager externalMembershipManager = getWebApplicationContext().getBean(ScimGroupExternalMembershipManager.class); + ApprovalStore approvalStore = getWebApplicationContext().getBean(ApprovalStore.class); + JdbcTemplate template = getWebApplicationContext().getBean(JdbcTemplate.class); + + String id = generator.generate(); + IdentityZone zone = createZone(id, HttpStatus.CREATED, identityClientToken); + + //create zone and clients + BaseClientDetails client = new BaseClientDetails("limited-client", null, "openid", "authorization_code", + "uaa.resource"); + client.setClientSecret("secret"); + client.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(UAA)); + client.addAdditionalInformation("foo", "bar"); + for (String url : Arrays.asList("","/")) { + getMockMvc().perform( + post("/identity-zones/" + zone.getId() + "/clients"+url) + .header("Authorization", "Bearer " + identityClientZonesReadToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(client))) + .andExpect(status().isForbidden()); + } + + MvcResult result = getMockMvc().perform( + post("/identity-zones/" + zone.getId() + "/clients") + .header("Authorization", "Bearer " + identityClientToken) + .contentType(APPLICATION_JSON) + .accept(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(client))) + .andExpect(status().isCreated()).andReturn(); + BaseClientDetails created = JsonUtils.readValue(result.getResponse().getContentAsString(), BaseClientDetails.class); + assertNull(created.getClientSecret()); + assertEquals("zones.write", created.getAdditionalInformation().get(ClientConstants.CREATED_WITH)); + assertEquals(Collections.singletonList(UAA), created.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS)); + assertEquals("bar", created.getAdditionalInformation().get("foo")); + + //ensure that UAA provider is there + assertNotNull(idpp.retrieveByOrigin(UAA, zone.getId())); + assertEquals(UAA, idpp.retrieveByOrigin(UAA, zone.getId()).getOriginKey()); + + //create login-server provider + IdentityProvider provider = new IdentityProvider() + .setOriginKey(LOGIN_SERVER) + .setActive(true) + .setIdentityZoneId(zone.getId()) + .setName("Delete Test") + .setType(LOGIN_SERVER); + provider = idpp.create(provider); + assertNotNull(idpp.retrieveByOrigin(LOGIN_SERVER, zone.getId())); + assertEquals(provider.getId(), idpp.retrieveByOrigin(LOGIN_SERVER, zone.getId()).getId()); + + IdentityZoneHolder.set(zone); + //create user and add user to group + ScimUser user = getScimUser(); + user.setOrigin(LOGIN_SERVER); + user = userProvisioning.createUser(user, ""); + assertNotNull(userProvisioning.retrieve(user.getId())); + assertEquals(zone.getId(), user.getZoneId()); + + //create group + ScimGroup group = new ScimGroup("Delete Test Group"); + group.setZoneId(zone.getId()); + group = groupProvisioning.create(group); + membershipManager.addMember(group.getId(), new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER))); + assertEquals(zone.getId(), group.getZoneId()); + assertNotNull(groupProvisioning.retrieve(group.getId())); + assertEquals("Delete Test Group", groupProvisioning.retrieve(group.getId()).getDisplayName()); + assertEquals(1, membershipManager.getMembers(group.getId()).size()); + + //failed authenticated user + getMockMvc().perform( + post("/login.do") + .header("Host", zone.getSubdomain()+".localhost") + .with(cookieCsrf()) + .accept(TEXT_HTML_VALUE) + .param("username", user.getUserName()) + .param("password", "adasda") + ) + .andDo(print()) + .andExpect(status().isFound()); + + //ensure we have some audit records + //this doesn't work yet + //assertThat(template.queryForInt("select count(*) from sec_audit where identity_zone_id=?", user.getZoneId()), greaterThan(0)); + + //create an external group map + IdentityZoneHolder.set(zone); + ScimGroupExternalMember externalMember = externalMembershipManager.mapExternalGroup(group.getId(), "externalDeleteGroup", LOGIN_SERVER); + assertEquals(1, externalMembershipManager.getExternalGroupMapsByGroupId(group.getId(), LOGIN_SERVER).size()); + + //add user approvals + approvalStore.addApproval( + new Approval() + .setClientId(client.getClientId()) + .setScope("openid") + .setStatus(Approval.ApprovalStatus.APPROVED) + .setUserId(user.getId()) + ); + assertEquals(1, approvalStore.getApprovals(user.getId(), client.getClientId()).size()); + + //perform zone delete + + assertTrue("IMPLEMENT ZONE DELETE API HERE", false); + + assertEquals(0, template.queryForInt("select count(*) from identity_zone where id=?", zone.getId())); + + assertEquals(0, template.queryForInt("select count(*) from oauth_client_details where identity_zone_id=?", zone.getId())); + + assertEquals(0, template.queryForInt("select count(*) from groups where identity_zone_id=?", zone.getId())); + + assertEquals(0, template.queryForInt("select count(*) from sec_audit where identity_zone_id=?", zone.getId())); + + assertEquals(0, template.queryForInt("select count(*) from users where identity_zone_id=?", zone.getId())); + + assertEquals(0, template.queryForInt("select count(*) from external_group_mapping where origin=?", LOGIN_SERVER)); + assertEquals(0, externalMembershipManager.getExternalGroupMapsByGroupId(group.getId(), LOGIN_SERVER).size()); + + assertEquals(0, template.queryForInt("select count(*) from authz_approvals where user_id=?", user.getId())); + assertEquals(0, approvalStore.getApprovals(user.getId(), client.getClientId()).size()); + + + + } + @Test public void testCreateAndDeleteLimitedClientInNewZoneUsingZoneEndpoint() throws Exception { String id = generator.generate(); @@ -458,7 +600,7 @@ public void testCreateAndDeleteLimitedClientInNewZoneUsingZoneEndpoint() throws BaseClientDetails client = new BaseClientDetails("limited-client", null, "openid", "authorization_code", "uaa.resource"); client.setClientSecret("secret"); - client.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA)); + client.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(UAA)); client.addAdditionalInformation("foo", "bar"); for (String url : Arrays.asList("","/")) { getMockMvc().perform( @@ -480,7 +622,7 @@ public void testCreateAndDeleteLimitedClientInNewZoneUsingZoneEndpoint() throws BaseClientDetails created = JsonUtils.readValue(result.getResponse().getContentAsString(), BaseClientDetails.class); assertNull(created.getClientSecret()); assertEquals("zones.write", created.getAdditionalInformation().get(ClientConstants.CREATED_WITH)); - assertEquals(Collections.singletonList(OriginKeys.UAA), created.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS)); + assertEquals(Collections.singletonList(UAA), created.getAdditionalInformation().get(ClientConstants.ALLOWED_PROVIDERS)); assertEquals("bar", created.getAdditionalInformation().get("foo")); checkAuditEventListener(1, AuditEventType.ClientCreateSuccess, clientCreateEventListener, id, "http://localhost:8080/uaa/oauth/token", "identity"); @@ -505,7 +647,7 @@ public void testCreateAndDeleteLimitedClientInUAAZoneReturns403() throws Excepti BaseClientDetails client = new BaseClientDetails("limited-client", null, "openid", "authorization_code", "uaa.resource"); client.setClientSecret("secret"); - client.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(OriginKeys.UAA)); + client.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singletonList(UAA)); getMockMvc().perform( post("/identity-zones/uaa/clients") .header("Authorization", "Bearer " + identityClientToken) From e5c4f95cbb61c04c291038d12c797f29ba083cc0 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 8 Dec 2015 13:27:29 -0700 Subject: [PATCH 052/103] Delete users when a zone or provider is deleted https://www.pivotaltracker.com/story/show/108254860 [#108254860] --- .../uaa/event/EntityDeletedEvent.java | 32 ++++ .../uaa/oauth/approval/JdbcApprovalStore.java | 9 +- .../scim/jdbc/JdbcScimUserProvisioning.java | 57 +++++- .../jdbc/JdbcScimUserProvisioningTests.java | 165 +++++++++++++++++- 4 files changed, 255 insertions(+), 8 deletions(-) create mode 100644 common/src/main/java/org/cloudfoundry/identity/uaa/event/EntityDeletedEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/event/EntityDeletedEvent.java b/common/src/main/java/org/cloudfoundry/identity/uaa/event/EntityDeletedEvent.java new file mode 100644 index 00000000000..2f04d8bcfe6 --- /dev/null +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/event/EntityDeletedEvent.java @@ -0,0 +1,32 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.event; + +import org.springframework.context.ApplicationEvent; + +public class EntityDeletedEvent extends ApplicationEvent { + + private final T deleted; + + public EntityDeletedEvent(T deleted) { + super(deleted); + this.deleted = deleted; + } + + public T getDeleted() { + return deleted; + } +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java index 1f2853b1850..7afee881c1c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java @@ -169,12 +169,9 @@ public boolean purgeExpiredApprovals() { logger.debug("Purging expired approvals from database"); try { int deleted = jdbcTemplate.update(DELETE_AUTHZ_SQL + " where expiresAt <= ?", - new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setTimestamp(1, new Timestamp(new Date().getTime())); - } - }); + ps -> { //PreparedStatementSetter + ps.setTimestamp(1, new Timestamp(new Date().getTime())); + }); logger.debug(deleted + " expired approvals deleted"); } catch (DataAccessException ex) { logger.error("Error purging expired approvals", ex); diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java index 1f2aa31a601..bb4c93d468e 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java @@ -15,6 +15,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.rest.ResourceMonitor; import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryable; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; @@ -27,7 +29,11 @@ import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceAlreadyExistsException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceConstraintFailedException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.ApplicationListener; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; @@ -59,7 +65,9 @@ * @author Luke Taylor * @author Dave Syer */ -public class JdbcScimUserProvisioning extends AbstractQueryable implements ScimUserProvisioning, ResourceMonitor { +public class JdbcScimUserProvisioning extends AbstractQueryable + implements ScimUserProvisioning, ResourceMonitor, + ApplicationEventPublisherAware, ApplicationListener> { private final Log logger = LogFactory.getLog(getClass()); @@ -84,6 +92,10 @@ public class JdbcScimUserProvisioning extends AbstractQueryable implem public static final String ALL_USERS = "select " + USER_FIELDS + " from users"; + public static final String HARD_DELETE_BY_ZONE = "delete from users where identity_zone_id = ?"; + + public static final String HARD_DELETE_BY_PROVIDER = "delete from users where identity_zone_id = ? and origin = ?"; + protected final JdbcTemplate jdbcTemplate; private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); @@ -94,6 +106,8 @@ public class JdbcScimUserProvisioning extends AbstractQueryable implem private Pattern usernamePattern = Pattern.compile("[a-zA-Z0-9+\\-_.@'!]+"); + private ApplicationEventPublisher applicationEventPublisher = null; + public JdbcScimUserProvisioning(JdbcTemplate jdbcTemplate, JdbcPagingListFactory pagingListFactory) { super(jdbcTemplate, pagingListFactory, new ScimUserRowMapper()); Assert.notNull(jdbcTemplate); @@ -389,6 +403,47 @@ public void setUsernamePattern(String usernamePattern) { this.usernamePattern = Pattern.compile(usernamePattern); } + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + + protected int deleteByIdentityZone(String zoneId) { + return jdbcTemplate.update(HARD_DELETE_BY_ZONE, zoneId); + } + + protected int deleteByOrigin(String origin, String zoneId) { + return jdbcTemplate.update(HARD_DELETE_BY_PROVIDER, zoneId, origin); + } + + @Override + public void onApplicationEvent(EntityDeletedEvent event) { + if (event==null || event.getDeleted()==null) { + return; + } else if (event.getDeleted() instanceof IdentityZone) { + String zoneId = ((IdentityZone)event.getDeleted()).getId(); + if (isUaaZone(zoneId)) { + logger.debug("Attempt to delete default zone ignored:"+event.getDeleted()); + return; + } + deleteByIdentityZone(zoneId); + } else if (event.getDeleted() instanceof IdentityProvider) { + String zoneId = ((IdentityProvider)event.getDeleted()).getIdentityZoneId(); + String origin = ((IdentityProvider)event.getDeleted()).getOriginKey(); + if (OriginKeys.UAA.equals(origin)) { + logger.debug("Attempt to delete default UAA provider ignored:"+event.getDeleted()); + return; + } + deleteByOrigin(origin, zoneId); + } else { + logger.debug("Unsupported deleted event for user deletion:"+event.getDeleted()); + } + } + + protected boolean isUaaZone(String zoneId) { + return IdentityZone.getUaa().getId().equals(zoneId); + } + private static final class ScimUserRowMapper implements RowMapper { @Override public ScimUser mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java index 8e66e9da9d4..c22db56e91b 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java @@ -13,6 +13,8 @@ package org.cloudfoundry.identity.uaa.scim.jdbc; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -25,7 +27,6 @@ import org.cloudfoundry.identity.uaa.scim.test.TestUtils; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.user.UaaAuthority; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityProviderProvisioning; @@ -51,6 +52,8 @@ import java.util.Map; import java.util.UUID; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LOGIN_SERVER; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -147,6 +150,166 @@ public void canCreateUserWithExclamationMarkInUsername() { assertEquals(userName, created.getUserName()); } + @Test + public void test_can_delete_provider_users_in_default_zone() throws Exception { + ScimUser user = new ScimUser(null, "jo@foo.com", "Jo", "User"); + user.addEmail("jo@blah.com"); + user.setOrigin(LOGIN_SERVER); + ScimUser created = db.createUser(user, "j7hyqpassX"); + assertEquals("jo@foo.com", created.getUserName()); + assertNotNull(created.getId()); + assertEquals(LOGIN_SERVER, created.getOrigin()); + assertEquals(1, + jdbcTemplate.queryForInt( + "select count(*) from users where origin=? and identity_zone_id=?", + LOGIN_SERVER, + IdentityZone.getUaa().getId() + ) + ); + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(LOGIN_SERVER) + .setIdentityZoneId(IdentityZone.getUaa().getId()); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertEquals(0, + jdbcTemplate.queryForInt( + "select count(*) from users where origin=? and identity_zone_id=?", + LOGIN_SERVER, + IdentityZone.getUaa().getId() + ) + ); + } + + @Test + public void test_can_delete_provider_users_in_other_zone() throws Exception { + String id = generator.generate(); + IdentityZone zone = MultitenancyFixture.identityZone(id, id); + IdentityZoneHolder.set(zone); + ScimUser user = new ScimUser(null, "jo@foo.com", "Jo", "User"); + user.addEmail("jo@blah.com"); + user.setOrigin(LOGIN_SERVER); + ScimUser created = db.createUser(user, "j7hyqpassX"); + assertEquals("jo@foo.com", created.getUserName()); + assertNotNull(created.getId()); + assertEquals(LOGIN_SERVER, created.getOrigin()); + assertEquals(zone.getId(), created.getZoneId()); + assertEquals(1, + jdbcTemplate.queryForInt( + "select count(*) from users where origin=? and identity_zone_id=?", + LOGIN_SERVER, + zone.getId() + ) + ); + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(LOGIN_SERVER) + .setIdentityZoneId(zone.getId()); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertEquals(0, + jdbcTemplate.queryForInt( + "select count(*) from users where origin=? and identity_zone_id=?", + LOGIN_SERVER, + zone.getId() + ) + ); + } + + @Test + public void test_can_delete_zone_users() throws Exception { + String id = generator.generate(); + IdentityZone zone = MultitenancyFixture.identityZone(id, id); + IdentityZoneHolder.set(zone); + ScimUser user = new ScimUser(null, "jo@foo.com", "Jo", "User"); + user.addEmail("jo@blah.com"); + user.setOrigin(UAA); + ScimUser created = db.createUser(user, "j7hyqpassX"); + assertEquals("jo@foo.com", created.getUserName()); + assertNotNull(created.getId()); + assertEquals(UAA, created.getOrigin()); + assertEquals(zone.getId(), created.getZoneId()); + assertEquals(1, + jdbcTemplate.queryForInt( + "select count(*) from users where origin=? and identity_zone_id=?", + UAA, + zone.getId() + ) + ); + db.onApplicationEvent(new EntityDeletedEvent<>(zone)); + assertEquals(0, + jdbcTemplate.queryForInt( + "select count(*) from users where origin=? and identity_zone_id=?", + UAA, + zone.getId() + ) + ); + } + + @Test + public void test_cannot_delete_uaa_zone_users() throws Exception { + ScimUser user = new ScimUser(null, "jo@foo.com", "Jo", "User"); + user.addEmail("jo@blah.com"); + user.setOrigin(UAA); + ScimUser created = db.createUser(user, "j7hyqpassX"); + assertEquals("jo@foo.com", created.getUserName()); + assertNotNull(created.getId()); + assertEquals(UAA, created.getOrigin()); + assertEquals(3, + jdbcTemplate.queryForInt( + "select count(*) from users where origin=? and identity_zone_id=?", + UAA, + IdentityZone.getUaa().getId() + ) + ); + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(UAA) + .setIdentityZoneId(IdentityZone.getUaa().getId()); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertEquals(3, + jdbcTemplate.queryForInt( + "select count(*) from users where origin=? and identity_zone_id=?", + UAA, + IdentityZone.getUaa().getId() + ) + ); + } + + @Test + public void test_cannot_delete_uaa_provider_users_in_other_zone() throws Exception { + String id = generator.generate(); + IdentityZone zone = MultitenancyFixture.identityZone(id, id); + IdentityZoneHolder.set(zone); + ScimUser user = new ScimUser(null, "jo@foo.com", "Jo", "User"); + user.addEmail("jo@blah.com"); + user.setOrigin(UAA); + ScimUser created = db.createUser(user, "j7hyqpassX"); + assertEquals("jo@foo.com", created.getUserName()); + assertNotNull(created.getId()); + assertEquals(UAA, created.getOrigin()); + assertEquals(zone.getId(), created.getZoneId()); + assertEquals(1, + jdbcTemplate.queryForInt( + "select count(*) from users where origin=? and identity_zone_id=?", + UAA, + zone.getId() + ) + ); + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(UAA) + .setIdentityZoneId(zone.getId()); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertEquals(1, + jdbcTemplate.queryForInt( + "select count(*) from users where origin=? and identity_zone_id=?", + UAA, + zone.getId() + ) + ); + } + + + @Test public void canCreateUserInDefaultIdentityZone() { ScimUser user = new ScimUser(null, "jo@foo.com", "Jo", "User"); From 3d44cc21ff1af4e854c5ce159494e060c847c431 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Tue, 8 Dec 2015 13:34:23 -0800 Subject: [PATCH 053/103] In case of Create Account Code Expiry, display page with the configured create account link [#108722168] https://www.pivotaltracker.com/story/show/108722168 Signed-off-by: Jonathan Lo --- .../uaa/login/AccountsController.java | 3 ++- .../login/EmailAccountCreationService.java | 1 - .../templates/web/accounts/link_prompt.html | 25 +++++++++++++++++++ .../login/AccountsControllerMockMvcTests.java | 25 +++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 login/src/main/resources/templates/web/accounts/link_prompt.html diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java index 1d56fb713a5..9acc26378cb 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java @@ -97,9 +97,10 @@ public String verifyUser(Model model, try { accountCreation = accountCreationService.completeActivation(code); } catch (HttpClientErrorException e) { + model.addAttribute("error_message_code", "code_expired"); response.setStatus(HttpStatus.UNPROCESSABLE_ENTITY.value()); - return "accounts/new_activation_email"; + return "accounts/link_prompt"; } UaaPrincipal uaaPrincipal = new UaaPrincipal(accountCreation.getUserId(), accountCreation.getUsername(), accountCreation.getEmail(), OriginKeys.UAA, null, IdentityZoneHolder.get().getId()); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java index 8ad21bbeac9..c70c593fc19 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java @@ -104,7 +104,6 @@ public AccountCreationResponse completeActivation(String code) throws IOExceptio ExpiringCode expiringCode = codeStore.retrieveCode(code); if (expiringCode==null) { - //just to satisfy unit tests throw new HttpClientErrorException(HttpStatus.BAD_REQUEST); } diff --git a/login/src/main/resources/templates/web/accounts/link_prompt.html b/login/src/main/resources/templates/web/accounts/link_prompt.html new file mode 100644 index 00000000000..a2b811d234b --- /dev/null +++ b/login/src/main/resources/templates/web/accounts/link_prompt.html @@ -0,0 +1,25 @@ + + +

+
+

Create your account

+

+ A Pivotal ID lets you sign in to Pivotal products + using a single username and password. +

+
+
+
+

Error Message

+

Error Message

+

Please continue here.

+
+
+ +
+ diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java index 005ba019dc7..d9486b5a5cc 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java @@ -47,8 +47,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath; import static org.springframework.util.StringUtils.isEmpty; @@ -448,6 +450,29 @@ public void redirectToSavedRequest_ifPresent() throws Exception { .andReturn(); } + @Test + public void ifInvalidOrExpiredCode_goTo_createAccountDefaultPage() throws Exception { + getMockMvc().perform(get("/verify_user") + .param("code", "expired-code")) + .andExpect(status().isUnprocessableEntity()) + .andExpect(model().attribute("error_message_code", "code_expired")) + .andExpect(view().name("accounts/link_prompt")) + .andExpect(xpath("//a[text()='continue here']/@href").string("/create_account")); + } + + @Test + public void ifInvalidOrExpiredCode_withNonDefaultSignupLinkProperty_goToNonDefaultSignupPage() throws Exception { + String signUpLink = "http://mypage.com/signup"; + ((MockEnvironment) getWebApplicationContext().getEnvironment()).setProperty("links.signup", signUpLink); + + getMockMvc().perform(get("/verify_user") + .param("code", "expired-code")) + .andExpect(status().isUnprocessableEntity()) + .andExpect(model().attribute("error_message_code", "code_expired")) + .andExpect(view().name("accounts/link_prompt")) + .andExpect(xpath("//a[text()='continue here']/@href").string(signUpLink)); + } + private BaseClientDetails createTestClient() throws Exception { BaseClientDetails clientDetails = new BaseClientDetails(); clientDetails.setClientId("test-client-" + RandomStringUtils.randomAlphanumeric(2)); From 034d0befd6821d4c167db45de93bf88f482e09a3 Mon Sep 17 00:00:00 2001 From: Madhura Date: Tue, 8 Dec 2015 13:54:19 -0800 Subject: [PATCH 054/103] Update java version requirement [#107068672] https://www.pivotaltracker.com/story/show/107068672 Signed-off-by: Jonathan Lo --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index feca45e78ab..b36ac11eb0f 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,9 @@ clients, as well as various other management functions. ## Quick Start +Requirements: +* Java 8 + If this works you are in business: $ git clone git://github.com/cloudfoundry/uaa.git @@ -52,7 +55,6 @@ In the steps above, replace: * `2.3.2-SNAPSHOT` with the appropriate version label from your build * `` this is your app domain. We will be parsing this from the system environment in the future * You may also provide a configuration manifest where the environment variable UAA_CONFIG_YAML contains full configuration yaml. -* We have not tested our system on Apache Tomcat 8 and Java 8, so we pick a build pack that produces lower versions ### Demo of command line usage on local server From c4c6223b7ddf1620ec49b48a82bf4ef30a34959a Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Tue, 8 Dec 2015 14:46:26 -0800 Subject: [PATCH 055/103] Update servlet-api to 3.1.0 [#109725544] https://www.pivotaltracker.com/story/show/109725544 Signed-off-by: Paul Warren --- common/build.gradle | 2 +- login/build.gradle | 2 +- samples/api/build.gradle | 2 +- samples/app/build.gradle | 2 +- scim/build.gradle | 2 +- shared_versions.gradle | 1 + uaa/build.gradle | 12 ++++++++---- .../password/PasswordChangeEndpointMockMvcTests.java | 2 +- ...sableUserManagementSecurityFilterMockMvcTest.java | 3 ++- 9 files changed, 17 insertions(+), 11 deletions(-) diff --git a/common/build.gradle b/common/build.gradle index ec95134d31e..7efa297094a 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -58,7 +58,7 @@ dependencies { } - provided group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' + provided group: 'javax.servlet', name: 'javax.servlet-api', version:parent.servletVersion testCompile group: 'org.springframework', name: 'spring-test', version:parent.springVersion testCompile group: 'junit', name: 'junit', version:parent.junitVersion diff --git a/login/build.gradle b/login/build.gradle index 150f78f559c..20fd849d9f9 100644 --- a/login/build.gradle +++ b/login/build.gradle @@ -14,7 +14,7 @@ dependencies { compile(identityModels) compile group: 'org.springframework', name: 'spring-context-support', version:springVersion - provided group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' + provided group: 'javax.servlet', name: 'javax.servlet-api', version:parent.servletVersion testCompile identityCommon.configurations.testCompile.dependencies testCompile identityCommon.sourceSets.test.output diff --git a/samples/api/build.gradle b/samples/api/build.gradle index 8b7968a2b3b..6a00d30940c 100644 --- a/samples/api/build.gradle +++ b/samples/api/build.gradle @@ -26,7 +26,7 @@ dependencies { testCompile(group: 'org.cloudfoundry', name: 'cloudfoundry-client-lib', version:'1.0.2') { exclude(module: 'jackson-core-asl') } - providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' + providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version:parent.servletVersion } test { diff --git a/samples/app/build.gradle b/samples/app/build.gradle index 8aba61c2110..84a26a4e674 100644 --- a/samples/app/build.gradle +++ b/samples/app/build.gradle @@ -22,7 +22,7 @@ dependencies { testCompile identityCommon.configurations.testCompile.dependencies testCompile identityCommon.sourceSets.test.output testCompile identityScim - providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' + providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version:parent.servletVersion } test { diff --git a/scim/build.gradle b/scim/build.gradle index c40389a309b..2b9080a9c72 100644 --- a/scim/build.gradle +++ b/scim/build.gradle @@ -7,7 +7,7 @@ dependencies { compile identityCommon compile identityModels compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version:'2.1.2.RELEASE' - provided group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' + provided group: 'javax.servlet', name: 'javax.servlet-api', version:parent.servletVersion testCompile identityCommon.configurations.testCompile.dependencies testCompile identityCommon.sourceSets.test.output } diff --git a/shared_versions.gradle b/shared_versions.gradle index f092f6e064c..561e1305618 100644 --- a/shared_versions.gradle +++ b/shared_versions.gradle @@ -1,4 +1,5 @@ ext { + servletVersion = '3.1.0' springVersion = '4.1.6.RELEASE' springSecurityVersion = '4.0.1.RELEASE' springSecurityOAuthVersion = '2.0.7.RELEASE' diff --git a/uaa/build.gradle b/uaa/build.gradle index 32d03b3b1ea..1058db30627 100644 --- a/uaa/build.gradle +++ b/uaa/build.gradle @@ -1,3 +1,7 @@ +plugins { + id "org.asciidoctor.convert" version "1.5.2" +} + Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } Project identityScim = parent.subprojects.find { it.name.equals('cloudfoundry-identity-scim') } @@ -60,15 +64,15 @@ dependencies { exclude(module: 'slf4j-api') exclude(module: 'slf4j-log4j12') } - providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' + providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version:parent.servletVersion providedCompile group: 'javax.servlet.jsp', name: 'jsp-api', version:'2.1' testCompile group: 'org.seleniumhq.selenium', name: 'selenium-java', version:'2.42.2' - testCompile group: 'com.github.detro.ghostdriver', name: 'phantomjsdriver', version:'1.1.0' + testCompile(group: 'com.github.detro.ghostdriver', name: 'phantomjsdriver', version:'1.1.0') { + exclude(module: 'servlet-api-2.5') + } testCompile group: 'dumbster', name: 'dumbster', version:'1.6' testCompile group: 'org.reflections', name: 'reflections', version: '0.9.10' testCompile group: 'org.skyscreamer', name:'jsonassert', version: '0.9.0' - - } test { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/password/PasswordChangeEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/password/PasswordChangeEndpointMockMvcTests.java index 3ea1ac82369..92cf40d9041 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/password/PasswordChangeEndpointMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/password/PasswordChangeEndpointMockMvcTests.java @@ -117,6 +117,7 @@ public void changePassword_Resets_Session() throws Exception { ScimUser user = createUser(); MockHttpSession session = new MockHttpSession(); + session.invalidate(); MockHttpSession afterLoginSession = (MockHttpSession) getMockMvc().perform(post("/login.do") .with(cookieCsrf()) .session(session) @@ -127,7 +128,6 @@ public void changePassword_Resets_Session() throws Exception { .andExpect(redirectedUrl("/")) .andReturn().getRequest().getSession(false); - assertTrue(session.isInvalid()); assertNotNull(afterLoginSession); assertNotNull(afterLoginSession.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java index ca08f45717e..1ddd171c2af 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java @@ -447,6 +447,8 @@ private CookieCsrfPostProcessor cookieCsrf() { private MockHttpSession getUserSession(String username, String password) throws Exception { MockHttpSession session = new MockHttpSession(); + session.invalidate(); + MockHttpSession afterLoginSession = (MockHttpSession) getMockMvc().perform(post("/login.do") .with(cookieCsrf()) .session(session) @@ -455,7 +457,6 @@ private MockHttpSession getUserSession(String username, String password) throws .param("password", password)) .andReturn().getRequest().getSession(false); - assertTrue(session.isInvalid()); assertNotNull(afterLoginSession); assertNotNull(afterLoginSession.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)); return afterLoginSession; From 85a423cd65ce675307fdaa6e6ec495566946e67b Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 8 Dec 2015 18:11:12 -0700 Subject: [PATCH 056/103] Deleting a user trickles to approvals and group memberships https://www.pivotaltracker.com/story/show/108254860 [#108254860] --- .../scim/jdbc/JdbcScimUserProvisioning.java | 24 ++++++++-------- .../jdbc/JdbcScimUserProvisioningTests.java | 28 +++++++++++++++++++ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java index bb4c93d468e..6239a38d077 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java @@ -31,8 +31,6 @@ import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.EmptyResultDataAccessException; @@ -66,8 +64,7 @@ * @author Dave Syer */ public class JdbcScimUserProvisioning extends AbstractQueryable - implements ScimUserProvisioning, ResourceMonitor, - ApplicationEventPublisherAware, ApplicationListener> { + implements ScimUserProvisioning, ResourceMonitor, ApplicationListener> { private final Log logger = LogFactory.getLog(getClass()); @@ -92,6 +89,14 @@ public class JdbcScimUserProvisioning extends AbstractQueryable public static final String ALL_USERS = "select " + USER_FIELDS + " from users"; + public static final String HARD_DELETE_OF_GROUP_MEMBERS_BY_ZONE = "delete from group_membership where member_type='USER' and member_id in (select id from users where identity_zone_id = ?)"; + + public static final String HARD_DELETE_OF_GROUP_MEMBERS_BY_PROVIDER = "delete from group_membership where member_type='USER' and member_id in (select id from users where identity_zone_id = ? and origin = ?)"; + + public static final String HARD_DELETE_OF_USER_APPROVALS_BY_ZONE = "delete from authz_approvals where user_id in (select id from users where identity_zone_id = ?)"; + + public static final String HARD_DELETE_OF_USER_APPROVALS_BY_PROVIDER = "delete from authz_approvals where user_id in (select id from users where identity_zone_id = ? and origin = ?)"; + public static final String HARD_DELETE_BY_ZONE = "delete from users where identity_zone_id = ?"; public static final String HARD_DELETE_BY_PROVIDER = "delete from users where identity_zone_id = ? and origin = ?"; @@ -106,8 +111,6 @@ public class JdbcScimUserProvisioning extends AbstractQueryable private Pattern usernamePattern = Pattern.compile("[a-zA-Z0-9+\\-_.@'!]+"); - private ApplicationEventPublisher applicationEventPublisher = null; - public JdbcScimUserProvisioning(JdbcTemplate jdbcTemplate, JdbcPagingListFactory pagingListFactory) { super(jdbcTemplate, pagingListFactory, new ScimUserRowMapper()); Assert.notNull(jdbcTemplate); @@ -403,16 +406,15 @@ public void setUsernamePattern(String usernamePattern) { this.usernamePattern = Pattern.compile(usernamePattern); } - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - protected int deleteByIdentityZone(String zoneId) { + jdbcTemplate.update(HARD_DELETE_OF_GROUP_MEMBERS_BY_ZONE, zoneId); + jdbcTemplate.update(HARD_DELETE_OF_USER_APPROVALS_BY_ZONE, zoneId); return jdbcTemplate.update(HARD_DELETE_BY_ZONE, zoneId); } protected int deleteByOrigin(String origin, String zoneId) { + jdbcTemplate.update(HARD_DELETE_OF_GROUP_MEMBERS_BY_PROVIDER, zoneId, origin); + jdbcTemplate.update(HARD_DELETE_OF_USER_APPROVALS_BY_PROVIDER, zoneId, origin); return jdbcTemplate.update(HARD_DELETE_BY_PROVIDER, zoneId, origin); } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java index c22db56e91b..9c5dea4f021 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java @@ -43,6 +43,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -83,6 +84,9 @@ public class JdbcScimUserProvisioningTests extends JdbcTestBase { private static final String VERIFY_USER_SQL_FORMAT = "select verified from users where id=?"; + private static final String INSERT_APPROVAL = "insert into authz_approvals (client_id, user_id, scope, status, expiresat, lastmodifiedat) values (?,?,?,?,?,?)"; + private static final String INSERT_MEMBERSHIP = "insert into group_membership (group_id, member_id, member_type,authorities,added, origin) values (?,?,?,?,?,?)"; + private int existingUserCount = 0; private String defaultIdentityProviderId; @@ -150,6 +154,12 @@ public void canCreateUserWithExclamationMarkInUsername() { assertEquals(userName, created.getUserName()); } + protected void addApprovalAndMembership(String userId, String origin) { + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + jdbcTemplate.update(INSERT_APPROVAL, userId, userId, "uaa.user", "APPROVED", timestamp, timestamp); + jdbcTemplate.update(INSERT_MEMBERSHIP, userId, userId, "USER", "authorities", timestamp, origin); + } + @Test public void test_can_delete_provider_users_in_default_zone() throws Exception { ScimUser user = new ScimUser(null, "jo@foo.com", "Jo", "User"); @@ -166,6 +176,10 @@ public void test_can_delete_provider_users_in_default_zone() throws Exception { IdentityZone.getUaa().getId() ) ); + addApprovalAndMembership(created.getId(), created.getOrigin()); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from authz_approvals where user_id=?", created.getId())); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from group_membership where member_id=?", created.getId())); + IdentityProvider loginServer = new IdentityProvider() .setOriginKey(LOGIN_SERVER) @@ -178,6 +192,8 @@ public void test_can_delete_provider_users_in_default_zone() throws Exception { IdentityZone.getUaa().getId() ) ); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from authz_approvals where user_id=?", created.getId())); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from group_membership where member_id=?", created.getId())); } @Test @@ -200,6 +216,10 @@ public void test_can_delete_provider_users_in_other_zone() throws Exception { zone.getId() ) ); + addApprovalAndMembership(created.getId(), created.getOrigin()); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from authz_approvals where user_id=?", created.getId())); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from group_membership where member_id=?", created.getId())); + IdentityProvider loginServer = new IdentityProvider() .setOriginKey(LOGIN_SERVER) @@ -212,6 +232,8 @@ public void test_can_delete_provider_users_in_other_zone() throws Exception { zone.getId() ) ); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from authz_approvals where user_id=?", created.getId())); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from group_membership where member_id=?", created.getId())); } @Test @@ -234,6 +256,10 @@ public void test_can_delete_zone_users() throws Exception { zone.getId() ) ); + addApprovalAndMembership(created.getId(), created.getOrigin()); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from authz_approvals where user_id=?", created.getId())); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from group_membership where member_id=?", created.getId())); + db.onApplicationEvent(new EntityDeletedEvent<>(zone)); assertEquals(0, jdbcTemplate.queryForInt( @@ -242,6 +268,8 @@ public void test_can_delete_zone_users() throws Exception { zone.getId() ) ); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from authz_approvals where user_id=?", created.getId())); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from group_membership where member_id=?", created.getId())); } @Test From 72a1ef1d001c8d5474f9927bf8df8089c11de12f Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 8 Dec 2015 19:26:05 -0700 Subject: [PATCH 057/103] Deleting groups and membership based on provider and zone https://www.pivotaltracker.com/story/show/108254860 [#108254860] --- .../jdbc/AbstractQueryableWithDelete.java | 65 +++++++++++ .../scim/jdbc/JdbcScimGroupProvisioning.java | 43 ++++++-- .../scim/jdbc/JdbcScimUserProvisioning.java | 43 ++------ .../JdbcScimGroupMembershipManagerTests.java | 103 +++++++++++++++--- 4 files changed, 192 insertions(+), 62 deletions(-) create mode 100644 common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/AbstractQueryableWithDelete.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/AbstractQueryableWithDelete.java b/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/AbstractQueryableWithDelete.java new file mode 100644 index 00000000000..45cf89de58b --- /dev/null +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/AbstractQueryableWithDelete.java @@ -0,0 +1,65 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.rest.jdbc; + +import org.apache.commons.logging.Log; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.springframework.context.ApplicationListener; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +public abstract class AbstractQueryableWithDelete extends AbstractQueryable implements ApplicationListener> { + + protected AbstractQueryableWithDelete(JdbcTemplate jdbcTemplate, JdbcPagingListFactory pagingListFactory, RowMapper rowMapper) { + super(jdbcTemplate, pagingListFactory, rowMapper); + } + + @Override + public void onApplicationEvent(EntityDeletedEvent event) { + if (event==null || event.getDeleted()==null) { + return; + } else if (event.getDeleted() instanceof IdentityZone) { + String zoneId = ((IdentityZone)event.getDeleted()).getId(); + if (isUaaZone(zoneId)) { + getLogger().debug("Attempt to delete default zone ignored:"+event.getDeleted()); + return; + } + deleteByIdentityZone(zoneId); + } else if (event.getDeleted() instanceof IdentityProvider) { + String zoneId = ((IdentityProvider)event.getDeleted()).getIdentityZoneId(); + String origin = ((IdentityProvider)event.getDeleted()).getOriginKey(); + if (OriginKeys.UAA.equals(origin)) { + getLogger().debug("Attempt to delete default UAA provider ignored:"+event.getDeleted()); + return; + } + deleteByOrigin(origin, zoneId); + } else { + getLogger().debug("Unsupported deleted event for deletion of object:"+event.getDeleted()); + } + } + + protected boolean isUaaZone(String zoneId) { + return IdentityZone.getUaa().getId().equals(zoneId); + } + + protected abstract int deleteByIdentityZone(String zoneId); + + protected abstract int deleteByOrigin(String origin, String zoneId); + + protected abstract Log getLogger(); +} diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java index 8926234e930..9e5b4a26243 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java @@ -12,19 +12,11 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.jdbc; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.util.Date; -import java.util.List; -import java.util.UUID; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryable; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryableWithDelete; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; -import org.cloudfoundry.identity.uaa.rest.jdbc.SearchQueryConverter; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.scim.ScimMeta; @@ -33,6 +25,7 @@ import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceConstraintFailedException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.springframework.context.ApplicationListener; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; @@ -42,15 +35,30 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; -public class JdbcScimGroupProvisioning extends AbstractQueryable implements ScimGroupProvisioning { +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +public class JdbcScimGroupProvisioning extends AbstractQueryableWithDelete + implements ScimGroupProvisioning, ApplicationListener> { private JdbcTemplate jdbcTemplate; private final Log logger = LogFactory.getLog(getClass()); + @Override + protected Log getLogger() { + return logger; + } + public static final String GROUP_FIELDS = "id,displayName,created,lastModified,version,identity_zone_id"; public static final String GROUP_TABLE = "groups"; + public static final String GROUP_MEMBERSHIP_TABLE = "group_membership"; public static final String ADD_GROUP_SQL = String.format("insert into %s ( %s ) values (?,?,?,?,?,?)", GROUP_TABLE, GROUP_FIELDS); @@ -64,6 +72,10 @@ public class JdbcScimGroupProvisioning extends AbstractQueryable impl public static final String DELETE_GROUP_SQL = String.format("delete from %s where id=? and identity_zone_id=?", GROUP_TABLE); + public static final String DELETE_GROUP_BY_ZONE = String.format("delete from %s where identity_zone_id=?", GROUP_TABLE); + public static final String DELETE_GROUP_MEMBERSHIP_BY_ZONE = String.format("delete from %s where group_id in (select id from %s where identity_zone_id = ?)", GROUP_MEMBERSHIP_TABLE, GROUP_TABLE); + public static final String DELETE_GROUP_MEMBERSHIP_BY_PROVIDER = String.format("delete from %s where group_id in (select id from %s where identity_zone_id = ?) and origin = ?", GROUP_MEMBERSHIP_TABLE, GROUP_TABLE); + private final RowMapper rowMapper = new ScimGroupRowMapper(); public JdbcScimGroupProvisioning(JdbcTemplate jdbcTemplate, JdbcPagingListFactory pagingListFactory) { @@ -172,6 +184,15 @@ public ScimGroup delete(String id, int version) throws ScimResourceNotFoundExcep return group; } + protected int deleteByIdentityZone(String zoneId) { + jdbcTemplate.update(DELETE_GROUP_MEMBERSHIP_BY_ZONE, zoneId); + return jdbcTemplate.update(DELETE_GROUP_BY_ZONE, zoneId); + } + + protected int deleteByOrigin(String origin, String zoneId) { + return jdbcTemplate.update(DELETE_GROUP_MEMBERSHIP_BY_PROVIDER, zoneId, origin); + } + protected void validateGroup(ScimGroup group) throws ScimResourceConstraintFailedException { if (!StringUtils.hasText(group.getZoneId())) { throw new ScimResourceConstraintFailedException("zoneId is a required field"); diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java index 6239a38d077..6fa02ad0b66 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java @@ -15,10 +15,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.rest.ResourceMonitor; -import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryable; +import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryableWithDelete; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimMeta; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -29,9 +27,7 @@ import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceAlreadyExistsException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceConstraintFailedException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.springframework.context.ApplicationListener; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; @@ -63,11 +59,16 @@ * @author Luke Taylor * @author Dave Syer */ -public class JdbcScimUserProvisioning extends AbstractQueryable - implements ScimUserProvisioning, ResourceMonitor, ApplicationListener> { +public class JdbcScimUserProvisioning extends AbstractQueryableWithDelete + implements ScimUserProvisioning, ResourceMonitor { private final Log logger = LogFactory.getLog(getClass()); + @Override + protected Log getLogger() { + return logger; + } + public static final String USER_FIELDS = "id,version,created,lastModified,username,email,givenName,familyName,active,phoneNumber,verified,origin,external_id,identity_zone_id,salt,passwd_lastmodified "; public static final String CREATE_USER_SQL = "insert into users (" + USER_FIELDS @@ -418,34 +419,6 @@ protected int deleteByOrigin(String origin, String zoneId) { return jdbcTemplate.update(HARD_DELETE_BY_PROVIDER, zoneId, origin); } - @Override - public void onApplicationEvent(EntityDeletedEvent event) { - if (event==null || event.getDeleted()==null) { - return; - } else if (event.getDeleted() instanceof IdentityZone) { - String zoneId = ((IdentityZone)event.getDeleted()).getId(); - if (isUaaZone(zoneId)) { - logger.debug("Attempt to delete default zone ignored:"+event.getDeleted()); - return; - } - deleteByIdentityZone(zoneId); - } else if (event.getDeleted() instanceof IdentityProvider) { - String zoneId = ((IdentityProvider)event.getDeleted()).getIdentityZoneId(); - String origin = ((IdentityProvider)event.getDeleted()).getOriginKey(); - if (OriginKeys.UAA.equals(origin)) { - logger.debug("Attempt to delete default UAA provider ignored:"+event.getDeleted()); - return; - } - deleteByOrigin(origin, zoneId); - } else { - logger.debug("Unsupported deleted event for user deletion:"+event.getDeleted()); - } - } - - protected boolean isUaaZone(String zoneId) { - return IdentityZone.getUaa().getId().equals(zoneId); - } - private static final class ScimUserRowMapper implements RowMapper { @Override public ScimUser mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java index b63d124b8c3..db4020a6707 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java @@ -13,6 +13,8 @@ package org.cloudfoundry.identity.uaa.scim.jdbc; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; @@ -37,6 +39,8 @@ import java.util.List; import java.util.Set; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LOGIN_SERVER; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -57,6 +61,9 @@ public class JdbcScimGroupMembershipManagerTests extends JdbcTestBase { private static final String addGroupSqlFormat = "insert into groups (id, displayName, identity_zone_id) values ('%s','%s','%s')"; private static final String addMemberSqlFormat = "insert into group_membership (group_id, member_id, member_type, authorities, origin) values ('%s', '%s', '%s', '%s', '%s')"; + private RandomValueStringGenerator generator = new RandomValueStringGenerator(); + + private IdentityZone zone = MultitenancyFixture.identityZone(generator.generate(), generator.generate()); @Before public void initJdbcScimGroupMembershipManagerTests() { @@ -72,13 +79,20 @@ public void initJdbcScimGroupMembershipManagerTests() { dao.setScimUserProvisioning(udao); dao.setDefaultUserGroups(Collections.singleton("uaa.user")); - addGroup("g1", "test1", IdentityZone.getUaa().getId()); - addGroup("g2", "test2", IdentityZone.getUaa().getId()); - addGroup("g3", "test3", IdentityZone.getUaa().getId()); - addUser("m1", "test", IdentityZone.getUaa().getId()); - addUser("m2", "test", IdentityZone.getUaa().getId()); - addUser("m3", "test", IdentityZone.getUaa().getId()); - + for (String id : Arrays.asList(zone.getId(), IdentityZone.getUaa().getId())) { + String g1 = id.equals(zone.getId()) ? zone.getId()+"-"+"g1" : "g1"; + String g2 = id.equals(zone.getId()) ? zone.getId()+"-"+"g2" : "g2"; + String g3 = id.equals(zone.getId()) ? zone.getId()+"-"+"g3" : "g3"; + String m1 = id.equals(zone.getId()) ? zone.getId()+"-"+"m1" : "m1"; + String m2 = id.equals(zone.getId()) ? zone.getId()+"-"+"m2" : "m2"; + String m3 = id.equals(zone.getId()) ? zone.getId()+"-"+"m3" : "m3"; + addGroup(g1, "test1", id); + addGroup(g2, "test2", id); + addGroup(g3, "test3", id); + addUser(m1, "test", id); + addUser(m2, "test", id); + addUser(m3, "test", id); + } validateCount(0); } @@ -86,6 +100,8 @@ private void addMember(String gId, String mId, String mType, String authorities) addMember(gId,mId,mType,authorities, OriginKeys.UAA); } private void addMember(String gId, String mId, String mType, String authorities, String origin) { + gId = IdentityZoneHolder.isUaa() ? gId : IdentityZoneHolder.get().getId()+"-"+gId; + mId = IdentityZoneHolder.isUaa() ? mId : IdentityZoneHolder.get().getId()+"-"+mId; jdbcTemplate.execute(String.format(addMemberSqlFormat, gId, mId, mType, authorities, origin)); } @@ -139,7 +155,7 @@ public void cleanupDataSource() throws Exception { public void canQuery_Filter_Has_ZoneIn_Effect() throws Exception { addMembers(); validateCount(4); - String id = new RandomValueStringGenerator().generate(); + String id = generator.generate(); IdentityZone zone = MultitenancyFixture.identityZone(id,id); IdentityZoneHolder.set(zone); assertEquals(0,dao.query("origin eq \"" + OriginKeys.UAA + "\"").size()); @@ -188,7 +204,7 @@ public void canDeleteWithFilter5() throws Exception { @Test public void cannot_Delete_With_Filter_Outside_Zone() throws Exception { - String id = new RandomValueStringGenerator().generate(); + String id = generator.generate(); addMembers(); validateCount(4); IdentityZone zone = MultitenancyFixture.identityZone(id,id); @@ -212,11 +228,66 @@ public void canGetGroupsForMember() { assertEquals(3, groups.size()); } + private void addMembers(String origin) { + addMember("g1", "m3", "USER", "READER", origin); + addMember("g1", "g2", "GROUP", "READER", origin); + addMember("g3", "m2", "USER", "READER,WRITER", origin); + addMember("g2", "m3", "USER", "READER", origin); + } private void addMembers() { - addMember("g1", "m3", "USER", "READER"); - addMember("g1", "g2", "GROUP", "READER"); - addMember("g3", "m2", "USER", "READER,WRITER"); - addMember("g2", "m3", "USER", "READER"); + addMembers(OriginKeys.UAA); + } + + @Test + public void test_zone_deleted() { + IdentityZoneHolder.set(zone); + addMembers(); + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); + assertEquals(3, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + gdao.onApplicationEvent(new EntityDeletedEvent<>(zone)); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + } + + @Test + public void test_provider_deleted() { + IdentityZoneHolder.set(zone); + addMembers(LOGIN_SERVER); + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); + assertEquals(3, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(LOGIN_SERVER) + .setIdentityZoneId(zone.getId()); + gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); + assertEquals(3, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + } + + @Test + public void test_cannot_delete_uaa_zone() { + addMembers(); + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + gdao.onApplicationEvent(new EntityDeletedEvent<>(IdentityZone.getUaa())); + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + } + + @Test + public void test_cannot_delete_uaa_provider() { + IdentityZoneHolder.set(zone); + addMembers(LOGIN_SERVER); + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); + assertEquals(3, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(UAA) + .setIdentityZoneId(zone.getId()); + gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); + assertEquals(3, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + } @Test @@ -245,7 +316,7 @@ public void canAddMember() throws Exception { @Test(expected = ScimResourceNotFoundException.class) public void addMember_In_Different_Zone_Causes_Issues() throws Exception { - String subdomain = new RandomValueStringGenerator().generate(); + String subdomain = generator.generate(); IdentityZone otherZone = MultitenancyFixture.identityZone(subdomain, subdomain); IdentityZoneHolder.set(otherZone); ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, null); @@ -255,7 +326,7 @@ public void addMember_In_Different_Zone_Causes_Issues() throws Exception { @Test(expected = ScimResourceNotFoundException.class) public void canAddMember_Validate_Origin_and_ZoneId() throws Exception { - String subdomain = new RandomValueStringGenerator().generate(); + String subdomain = generator.generate(); IdentityZone otherZone = MultitenancyFixture.identityZone(subdomain, subdomain); IdentityZoneHolder.set(otherZone); validateCount(0); @@ -303,7 +374,7 @@ public void canGetMembers_Fails_In_Other_Zone() throws Exception { addMember("g1", "m1", "USER", "READER"); addMember("g1", "g2", "GROUP", "READER"); addMember("g3", "m2", "USER", "READER,WRITER"); - IdentityZoneHolder.set(MultitenancyFixture.identityZone(new RandomValueStringGenerator().generate(), new RandomValueStringGenerator().generate())); + IdentityZoneHolder.set(MultitenancyFixture.identityZone(generator.generate(), generator.generate())); assertEquals(0, dao.getMembers("g1").size()); } From ccabd0bad06dd0d8b78f9afd89f51fafaf1899e0 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 9 Dec 2015 05:50:58 -0700 Subject: [PATCH 058/103] Allow external group mappings to be deleted when a group is deleted by zone or provider https://www.pivotaltracker.com/story/show/108254860 [#108254860] --- .../scim/jdbc/JdbcScimGroupProvisioning.java | 5 +++++ .../JdbcScimGroupMembershipManagerTests.java | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java index 9e5b4a26243..efb057ee5f1 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java @@ -59,6 +59,7 @@ protected Log getLogger() { public static final String GROUP_TABLE = "groups"; public static final String GROUP_MEMBERSHIP_TABLE = "group_membership"; + public static final String EXTERNAL_GROUP_TABLE = "external_group_mapping"; public static final String ADD_GROUP_SQL = String.format("insert into %s ( %s ) values (?,?,?,?,?,?)", GROUP_TABLE, GROUP_FIELDS); @@ -74,7 +75,9 @@ protected Log getLogger() { public static final String DELETE_GROUP_BY_ZONE = String.format("delete from %s where identity_zone_id=?", GROUP_TABLE); public static final String DELETE_GROUP_MEMBERSHIP_BY_ZONE = String.format("delete from %s where group_id in (select id from %s where identity_zone_id = ?)", GROUP_MEMBERSHIP_TABLE, GROUP_TABLE); + public static final String DELETE_EXTERNAL_GROUP_BY_ZONE = String.format("delete from %s where group_id in (select id from %s where identity_zone_id = ?)", EXTERNAL_GROUP_TABLE, GROUP_TABLE); public static final String DELETE_GROUP_MEMBERSHIP_BY_PROVIDER = String.format("delete from %s where group_id in (select id from %s where identity_zone_id = ?) and origin = ?", GROUP_MEMBERSHIP_TABLE, GROUP_TABLE); + public static final String DELETE_EXTERNAL_GROUP_BY_PROVIDER = String.format("delete from %s where group_id in (select id from %s where identity_zone_id = ?) and origin = ?", EXTERNAL_GROUP_TABLE, GROUP_TABLE); private final RowMapper rowMapper = new ScimGroupRowMapper(); @@ -185,11 +188,13 @@ public ScimGroup delete(String id, int version) throws ScimResourceNotFoundExcep } protected int deleteByIdentityZone(String zoneId) { + jdbcTemplate.update(DELETE_EXTERNAL_GROUP_BY_ZONE, zoneId); jdbcTemplate.update(DELETE_GROUP_MEMBERSHIP_BY_ZONE, zoneId); return jdbcTemplate.update(DELETE_GROUP_BY_ZONE, zoneId); } protected int deleteByOrigin(String origin, String zoneId) { + jdbcTemplate.update(DELETE_EXTERNAL_GROUP_BY_PROVIDER, zoneId, origin); return jdbcTemplate.update(DELETE_GROUP_MEMBERSHIP_BY_PROVIDER, zoneId, origin); } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java index db4020a6707..7d4c549c780 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java +++ b/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.jdbc; +import org.cloudfoundry.identity.uaa.SystemEnvironmentAccessor; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; @@ -32,6 +33,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -61,6 +63,9 @@ public class JdbcScimGroupMembershipManagerTests extends JdbcTestBase { private static final String addGroupSqlFormat = "insert into groups (id, displayName, identity_zone_id) values ('%s','%s','%s')"; private static final String addMemberSqlFormat = "insert into group_membership (group_id, member_id, member_type, authorities, origin) values ('%s', '%s', '%s', '%s', '%s')"; + + private static final String addExternalMapSql = "insert into external_group_mapping (group_id, external_group, added, origin) values (?, ?, ?, ?)"; + private RandomValueStringGenerator generator = new RandomValueStringGenerator(); private IdentityZone zone = MultitenancyFixture.identityZone(generator.generate(), generator.generate()); @@ -92,10 +97,18 @@ public void initJdbcScimGroupMembershipManagerTests() { addUser(m1, "test", id); addUser(m2, "test", id); addUser(m3, "test", id); + mapExternalGroup(g1, g1+"-external", UAA); + mapExternalGroup(g2, g2+"-external", LOGIN_SERVER); + mapExternalGroup(g3, g3+"-external", UAA); } validateCount(0); } + private void mapExternalGroup(String gId, String external, String origin) { + Timestamp now = new Timestamp(System.currentTimeMillis()); + jdbcTemplate.update(addExternalMapSql, gId, external, now, origin); + } + private void addMember(String gId, String mId, String mType, String authorities) { addMember(gId,mId,mType,authorities, OriginKeys.UAA); } @@ -244,9 +257,11 @@ public void test_zone_deleted() { addMembers(); assertEquals(4, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); assertEquals(3, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + assertEquals(3, jdbcTemplate.queryForInt("select count(*) from external_group_mapping where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); gdao.onApplicationEvent(new EntityDeletedEvent<>(zone)); assertEquals(0, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); assertEquals(0, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from external_group_mapping where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); } @Test @@ -255,6 +270,8 @@ public void test_provider_deleted() { addMembers(LOGIN_SERVER); assertEquals(4, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); assertEquals(3, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from external_group_mapping where origin = ? and group_id in (select id from groups where identity_zone_id=?)", LOGIN_SERVER, IdentityZoneHolder.get().getId())); + IdentityProvider loginServer = new IdentityProvider() .setOriginKey(LOGIN_SERVER) @@ -262,6 +279,7 @@ public void test_provider_deleted() { gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); assertEquals(0, jdbcTemplate.queryForInt("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", IdentityZoneHolder.get().getId())); assertEquals(3, jdbcTemplate.queryForInt("select count(*) from groups where identity_zone_id=?", IdentityZoneHolder.get().getId())); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from external_group_mapping where origin = ? and group_id in (select id from groups where identity_zone_id=?)", LOGIN_SERVER, IdentityZoneHolder.get().getId())); } @Test From d472c92b41418fad292568fbfae25d2cd9283aa8 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 9 Dec 2015 06:01:18 -0700 Subject: [PATCH 059/103] Remove abstract class since we can have an interface with default methods --- .../SystemDeletable.java} | 24 +++++++------------ .../scim/jdbc/JdbcScimGroupProvisioning.java | 13 +++++----- .../scim/jdbc/JdbcScimUserProvisioning.java | 13 +++++----- 3 files changed, 22 insertions(+), 28 deletions(-) rename common/src/main/java/org/cloudfoundry/identity/uaa/{rest/jdbc/AbstractQueryableWithDelete.java => event/SystemDeletable.java} (70%) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/AbstractQueryableWithDelete.java b/common/src/main/java/org/cloudfoundry/identity/uaa/event/SystemDeletable.java similarity index 70% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/AbstractQueryableWithDelete.java rename to common/src/main/java/org/cloudfoundry/identity/uaa/event/SystemDeletable.java index 45cf89de58b..7cd34643f39 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/AbstractQueryableWithDelete.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/event/SystemDeletable.java @@ -2,6 +2,7 @@ * ***************************************************************************** * Cloud Foundry * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * @@ -12,25 +13,16 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.rest.jdbc; +package org.cloudfoundry.identity.uaa.event; import org.apache.commons.logging.Log; import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.springframework.context.ApplicationListener; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; - -public abstract class AbstractQueryableWithDelete extends AbstractQueryable implements ApplicationListener> { - - protected AbstractQueryableWithDelete(JdbcTemplate jdbcTemplate, JdbcPagingListFactory pagingListFactory, RowMapper rowMapper) { - super(jdbcTemplate, pagingListFactory, rowMapper); - } - @Override - public void onApplicationEvent(EntityDeletedEvent event) { +public interface SystemDeletable extends ApplicationListener> { + default void onApplicationEvent(EntityDeletedEvent event) { if (event==null || event.getDeleted()==null) { return; } else if (event.getDeleted() instanceof IdentityZone) { @@ -53,13 +45,13 @@ public void onApplicationEvent(EntityDeletedEvent event) { } } - protected boolean isUaaZone(String zoneId) { + default boolean isUaaZone(String zoneId) { return IdentityZone.getUaa().getId().equals(zoneId); } - protected abstract int deleteByIdentityZone(String zoneId); + int deleteByIdentityZone(String zoneId); - protected abstract int deleteByOrigin(String origin, String zoneId); + int deleteByOrigin(String origin, String zoneId); - protected abstract Log getLogger(); + Log getLogger(); } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java index efb057ee5f1..16ca8c82da0 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java @@ -15,7 +15,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; -import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryableWithDelete; +import org.cloudfoundry.identity.uaa.event.SystemDeletable; +import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryable; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; @@ -43,15 +44,15 @@ import java.util.List; import java.util.UUID; -public class JdbcScimGroupProvisioning extends AbstractQueryableWithDelete - implements ScimGroupProvisioning, ApplicationListener> { +public class JdbcScimGroupProvisioning extends AbstractQueryable + implements ScimGroupProvisioning, ApplicationListener>, SystemDeletable { private JdbcTemplate jdbcTemplate; private final Log logger = LogFactory.getLog(getClass()); @Override - protected Log getLogger() { + public Log getLogger() { return logger; } @@ -187,13 +188,13 @@ public ScimGroup delete(String id, int version) throws ScimResourceNotFoundExcep return group; } - protected int deleteByIdentityZone(String zoneId) { + public int deleteByIdentityZone(String zoneId) { jdbcTemplate.update(DELETE_EXTERNAL_GROUP_BY_ZONE, zoneId); jdbcTemplate.update(DELETE_GROUP_MEMBERSHIP_BY_ZONE, zoneId); return jdbcTemplate.update(DELETE_GROUP_BY_ZONE, zoneId); } - protected int deleteByOrigin(String origin, String zoneId) { + public int deleteByOrigin(String origin, String zoneId) { jdbcTemplate.update(DELETE_EXTERNAL_GROUP_BY_PROVIDER, zoneId, origin); return jdbcTemplate.update(DELETE_GROUP_MEMBERSHIP_BY_PROVIDER, zoneId, origin); } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java index 6fa02ad0b66..1d8f7c07551 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java @@ -15,8 +15,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.event.SystemDeletable; import org.cloudfoundry.identity.uaa.rest.ResourceMonitor; -import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryableWithDelete; +import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryable; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimMeta; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -59,13 +60,13 @@ * @author Luke Taylor * @author Dave Syer */ -public class JdbcScimUserProvisioning extends AbstractQueryableWithDelete - implements ScimUserProvisioning, ResourceMonitor { +public class JdbcScimUserProvisioning extends AbstractQueryable + implements ScimUserProvisioning, ResourceMonitor, SystemDeletable { private final Log logger = LogFactory.getLog(getClass()); @Override - protected Log getLogger() { + public Log getLogger() { return logger; } @@ -407,13 +408,13 @@ public void setUsernamePattern(String usernamePattern) { this.usernamePattern = Pattern.compile(usernamePattern); } - protected int deleteByIdentityZone(String zoneId) { + public int deleteByIdentityZone(String zoneId) { jdbcTemplate.update(HARD_DELETE_OF_GROUP_MEMBERS_BY_ZONE, zoneId); jdbcTemplate.update(HARD_DELETE_OF_USER_APPROVALS_BY_ZONE, zoneId); return jdbcTemplate.update(HARD_DELETE_BY_ZONE, zoneId); } - protected int deleteByOrigin(String origin, String zoneId) { + public int deleteByOrigin(String origin, String zoneId) { jdbcTemplate.update(HARD_DELETE_OF_GROUP_MEMBERS_BY_PROVIDER, zoneId, origin); jdbcTemplate.update(HARD_DELETE_OF_USER_APPROVALS_BY_PROVIDER, zoneId, origin); return jdbcTemplate.update(HARD_DELETE_BY_PROVIDER, zoneId, origin); From 4ece11e2a4758bd6b1fe4f018d5ab2c14fb336de Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 9 Dec 2015 06:18:58 -0700 Subject: [PATCH 060/103] When a zone is deleted, delete the clients and the approvals for that client https://www.pivotaltracker.com/story/show/108254860 [#108254860] --- .../MultitenantJdbcClientDetailsService.java | 24 +++++- ...titenantJdbcClientDetailsServiceTests.java | 74 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java index 712fed9b262..58e61e575a1 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java @@ -14,6 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.event.SystemDeletable; import org.cloudfoundry.identity.uaa.rest.ResourceMonitor; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.dao.DuplicateKeyException; @@ -50,10 +51,12 @@ * A copy of JdbcClientDetailsService but with IdentityZone awareness */ public class MultitenantJdbcClientDetailsService extends JdbcClientDetailsService implements ClientDetailsService, - ClientRegistrationService, ResourceMonitor { + ClientRegistrationService, ResourceMonitor, SystemDeletable { private static final Log logger = LogFactory.getLog(MultitenantJdbcClientDetailsService.class); + + private JsonMapper mapper = createJsonMapper(); private static final String CLIENT_FIELDS_FOR_UPDATE = "resource_ids, scope, " @@ -80,6 +83,9 @@ public class MultitenantJdbcClientDetailsService extends JdbcClientDetailsServic private static final String DEFAULT_DELETE_STATEMENT = "delete from oauth_client_details where client_id = ? and identity_zone_id = ?"; + private static final String DELETE_CLIENTS_BY_ZONE = "delete from oauth_client_details where identity_zone_id = ?"; + private static final String DELETE_CLIENT_APPROVALS_BY_ZONE = "delete from authz_approvals where client_id in (select client_id from oauth_client_details where identity_zone_id = ?)"; + private RowMapper rowMapper = new ClientDetailsRowMapper(); private String deleteClientDetailsSql = DEFAULT_DELETE_STATEMENT; @@ -244,6 +250,22 @@ public void setRowMapper(RowMapper rowMapper) { this.rowMapper = rowMapper; } + @Override + public int deleteByIdentityZone(String zoneId) { + jdbcTemplate.update(DELETE_CLIENT_APPROVALS_BY_ZONE, zoneId); + return jdbcTemplate.update(DELETE_CLIENTS_BY_ZONE, zoneId); + } + + @Override + public int deleteByOrigin(String origin, String zoneId) { + return 0; + } + + @Override + public Log getLogger() { + return logger; + } + /** * Row mapper for ClientDetails. * diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java index 2f44920a7df..fa2f898a511 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java @@ -1,6 +1,9 @@ package org.cloudfoundry.identity.uaa.zone; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.flywaydb.core.Flyway; import org.junit.After; import org.junit.Before; @@ -10,10 +13,12 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.ClientAlreadyExistsException; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices; import java.sql.Timestamp; import java.util.Arrays; @@ -22,6 +27,8 @@ import java.util.Iterator; import java.util.Map; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LOGIN_SERVER; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -40,8 +47,12 @@ public class MultitenantJdbcClientDetailsServiceTests { private static final String INSERT_SQL = "insert into oauth_client_details (client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, autoapprove, identity_zone_id, lastmodified) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + private static final String INSERT_APPROVAL = "insert into authz_approvals (client_id, user_id, scope, status, expiresat, lastmodifiedat) values (?,?,?,?,?,?)"; + private IdentityZone otherIdentityZone; + private RandomValueStringGenerator generate = new RandomValueStringGenerator(); + @Before public void setUp() throws Exception { // creates a HSQL in-memory db populated from default scripts @@ -68,6 +79,69 @@ public void tearDown() throws Exception { IdentityZoneHolder.clear(); } + protected void addApproval(String clientId) { + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + jdbcTemplate.update(INSERT_APPROVAL, clientId, clientId, "uaa.user", "APPROVED", timestamp, timestamp); + } + + @Test + public void test_can_delete_zone_clients() throws Exception { + String id = generate.generate(); + IdentityZone zone = MultitenancyFixture.identityZone(id,id); + IdentityZoneHolder.set(zone); + BaseClientDetails clientDetails = new BaseClientDetails(); + clientDetails.setClientId(id); + clientDetails.setClientSecret("secret"); + service.addClientDetails(clientDetails); + clientDetails = (BaseClientDetails)service.loadClientByClientId(id); + assertEquals(1, + jdbcTemplate.queryForInt( + "select count(*) from oauth_client_details where identity_zone_id=?", + IdentityZoneHolder.get().getId() + ) + ); + addApproval(id); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from authz_approvals where client_id=?", id)); + + service.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get())); + assertEquals(0, + jdbcTemplate.queryForInt( + "select count(*) from oauth_client_details where identity_zone_id=?", + IdentityZoneHolder.get().getId() + ) + ); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from authz_approvals where client_id=?", id)); + } + + @Test + public void test_cannot_delete_uaa_zone_clients() throws Exception { + String id = generate.generate(); + BaseClientDetails clientDetails = new BaseClientDetails(); + clientDetails.setClientId(id); + clientDetails.setClientSecret("secret"); + service.addClientDetails(clientDetails); + clientDetails = (BaseClientDetails)service.loadClientByClientId(id); + assertEquals(1, + jdbcTemplate.queryForInt( + "select count(*) from oauth_client_details where identity_zone_id=?", + IdentityZoneHolder.get().getId() + ) + ); + addApproval(id); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from authz_approvals where client_id=?", id)); + + service.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get())); + assertEquals(1, + jdbcTemplate.queryForInt( + "select count(*) from oauth_client_details where identity_zone_id=?", + IdentityZoneHolder.get().getId() + ) + ); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from authz_approvals where client_id=?", id)); + } + + + @Test(expected = NoSuchClientException.class) public void testLoadingClientForNonExistingClientId() { service.loadClientByClientId("nonExistingClientId"); From 82e9776e86929625bc2acff478d739591169917d Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 9 Dec 2015 07:19:16 -0700 Subject: [PATCH 061/103] Implement deletion of a provider by zone or provider https://www.pivotaltracker.com/story/show/108254860 [#108254860] --- .../JdbcIdentityProviderProvisioning.java | 26 ++++++++++- ...JdbcIdentityProviderProvisioningTests.java | 43 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java index 958d6c96ade..64f0e10a7ab 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java @@ -12,7 +12,10 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.zone; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.event.SystemDeletable; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.KeystoneIdentityProviderDefinition; @@ -37,7 +40,9 @@ import java.util.List; import java.util.UUID; -public class JdbcIdentityProviderProvisioning implements IdentityProviderProvisioning { +public class JdbcIdentityProviderProvisioning implements IdentityProviderProvisioning, SystemDeletable { + + private static Log logger = LogFactory.getLog(JdbcIdentityProviderProvisioning.class); public static final String ID_PROVIDER_FIELDS = "id,version,created,lastmodified,name,origin_key,type,config,identity_zone_id,active"; @@ -51,6 +56,10 @@ public class JdbcIdentityProviderProvisioning implements IdentityProviderProvisi public static final String UPDATE_IDENTITY_PROVIDER_SQL = "update identity_provider set " + ID_PROVIDER_UPDATE_FIELDS + " where id=?"; + public static final String DELETE_IDENTITY_PROVIDER_BY_ORIGIN_SQL = "delete from identity_provider where identity_zone_id=? and origin_key = ?"; + + public static final String DELETE_IDENTITY_PROVIDER_BY_ZONE_SQL = "delete from identity_provider where identity_zone_id=?"; + public static final String IDENTITY_PROVIDER_BY_ID_QUERY = "select " + ID_PROVIDER_FIELDS + " from identity_provider " + "where id=?"; public static final String IDENTITY_PROVIDER_BY_ORIGIN_QUERY = "select " + ID_PROVIDER_FIELDS + " from identity_provider " + "where origin_key=? and identity_zone_id=? "; @@ -152,6 +161,21 @@ protected void validate(IdentityProvider provider) { } } + @Override + public int deleteByIdentityZone(String zoneId) { + return jdbcTemplate.update(DELETE_IDENTITY_PROVIDER_BY_ZONE_SQL, zoneId); + } + + @Override + public int deleteByOrigin(String origin, String zoneId) { + return jdbcTemplate.update(DELETE_IDENTITY_PROVIDER_BY_ORIGIN_SQL, zoneId, origin); + } + + @Override + public Log getLogger() { + return logger; + } + private static final class IdentityProviderRowMapper implements RowMapper { @Override public IdentityProvider mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java index 005875efbc3..f962ae748ca 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java @@ -1,6 +1,7 @@ package org.cloudfoundry.identity.uaa.zone; import org.apache.commons.lang.RandomStringUtils; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; @@ -11,6 +12,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import java.sql.Timestamp; import java.util.List; @@ -18,10 +20,13 @@ import java.util.UUID; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; public class JdbcIdentityProviderProvisioningTests extends JdbcTestBase { private JdbcIdentityProviderProvisioning db; + private RandomValueStringGenerator generator = new RandomValueStringGenerator(); @Before public void createDatasource() throws Exception { @@ -34,6 +39,44 @@ public void cleanUp() { IdentityZoneHolder.clear(); } + @Test + public void test_delete_providers_in_zone() { + //action - delete zone + //should delete providers + String zoneId = generator.generate(); + IdentityZone zone = MultitenancyFixture.identityZone(zoneId,zoneId); + IdentityZoneHolder.set(zone); + String originKey = RandomStringUtils.randomAlphabetic(6); + IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, zoneId); + IdentityProvider createdIdp = db.create(idp); + assertNotNull(createdIdp); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from identity_provider where identity_zone_id=?", IdentityZoneHolder.get().getId())); + db.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get())); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from identity_provider where identity_zone_id=?", IdentityZoneHolder.get().getId())); + } + + @Test + public void test_delete_providers_in_uaa_zone() { + String zoneId = IdentityZone.getUaa().getId(); + String originKey = RandomStringUtils.randomAlphabetic(6); + IdentityProvider idp = MultitenancyFixture.identityProvider(originKey, zoneId); + IdentityProvider createdIdp = db.create(idp); + assertNotNull(createdIdp); + assertEquals(5, jdbcTemplate.queryForInt("select count(*) from identity_provider where identity_zone_id=?", IdentityZoneHolder.get().getId())); + db.onApplicationEvent(new EntityDeletedEvent<>(createdIdp)); + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from identity_provider where identity_zone_id=?", IdentityZoneHolder.get().getId())); + } + + @Test + public void test_cannot_delete_uaa_providers() { + //action try to delete uaa provider + //should not do anything + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from identity_provider where identity_zone_id=?", IdentityZoneHolder.get().getId())); + IdentityProvider uaa = db.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); + db.onApplicationEvent(new EntityDeletedEvent<>(uaa)); + assertEquals(4, jdbcTemplate.queryForInt("select count(*) from identity_provider where identity_zone_id=?", IdentityZoneHolder.get().getId())); + } + @Test public void testCreateAndUpdateIdentityProviderInDefaultZone() throws Exception { String zoneId = IdentityZone.getUaa().getId(); From d91f0cfa2f32c1fe6743f0cc29071790a37aeaf6 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 9 Dec 2015 07:46:23 -0700 Subject: [PATCH 062/103] Add in transaction delete of an identity provider https://www.pivotaltracker.com/story/show/108254860 [#108254860] --- .../uaa/zone/IdentityProviderEndpoints.java | 27 +++++++++++++- .../WEB-INF/spring/multitenant-endpoints.xml | 4 ++ ...IdentityProviderEndpointsMockMvcTests.java | 37 +++++++++++++++++-- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java b/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java index 892806d4dd0..66ec795607e 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java @@ -17,6 +17,7 @@ import org.cloudfoundry.identity.uaa.authentication.manager.DynamicLdapAuthenticationManager; import org.cloudfoundry.identity.uaa.authentication.manager.LdapLoginAuthenticationManager; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; @@ -26,6 +27,8 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.opensaml.saml2.metadata.provider.MetadataProviderException; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -33,6 +36,7 @@ import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -47,15 +51,17 @@ import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.EXPECTATION_FAILED; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; +import static org.springframework.web.bind.annotation.RequestMethod.DELETE; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; import static org.springframework.web.bind.annotation.RequestMethod.PUT; @RequestMapping("/identity-providers") @RestController -public class IdentityProviderEndpoints { +public class IdentityProviderEndpoints implements ApplicationEventPublisherAware { protected static Log logger = LogFactory.getLog(IdentityProviderEndpoints.class); @@ -64,6 +70,12 @@ public class IdentityProviderEndpoints { private final ScimGroupProvisioning scimGroupProvisioning; private final NoOpLdapLoginAuthenticationManager noOpManager = new NoOpLdapLoginAuthenticationManager(); private final SamlIdentityProviderConfigurator samlConfigurator; + private ApplicationEventPublisher publisher = null; + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.publisher = applicationEventPublisher; + } public IdentityProviderEndpoints( IdentityProviderProvisioning identityProviderProvisioning, @@ -92,6 +104,19 @@ public ResponseEntity createIdentityProvider(@RequestBody Iden return new ResponseEntity<>(createdIdp, HttpStatus.CREATED); } + @RequestMapping(value = "{id}", method = DELETE) + @Transactional + public ResponseEntity deleteIdentityProvider(@PathVariable String id) throws MetadataProviderException { + IdentityProvider existing = identityProviderProvisioning.retrieve(id); + if (publisher!=null && existing!=null) { + publisher.publishEvent(new EntityDeletedEvent<>(existing)); + return new ResponseEntity<>(existing, OK); + } else { + return new ResponseEntity<>(NOT_FOUND); + } + } + + @RequestMapping(value = "{id}", method = PUT) public ResponseEntity updateIdentityProvider(@PathVariable String id, @RequestBody IdentityProvider body) throws MetadataProviderException { IdentityProvider existing = identityProviderProvisioning.retrieve(id); diff --git a/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml index 14a0a549b0f..8874d91c907 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml @@ -115,9 +115,13 @@ + + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java index 17c914c5dbf..cd16f3607f5 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityProviderEndpointsMockMvcTests.java @@ -54,8 +54,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class IdentityProviderEndpointsMockMvcTests extends InjectedMockContextTest { @@ -98,7 +100,7 @@ public void testCreateAndUpdateIdentityProvider() throws Exception { } @Test - public void testCreateSamlProvider() throws Exception { + public void test_Create_and_Delete_SamlProvider() throws Exception { String origin = "idp-mock-saml-"+new RandomValueStringGenerator().generate(); String metadata = String.format(IdentityProviderConfiguratorTests.xmlWithoutID, "http://localhost:9999/metadata/"+origin); String accessToken = setUpAccessToken(); @@ -130,8 +132,35 @@ public void testCreateSamlProvider() throws Exception { assertEquals(attributeMappings, samlCreated.getAttributeMappings()); assertEquals(IdentityZone.getUaa().getId(), samlCreated.getZoneId()); assertEquals(provider.getOriginKey(), samlCreated.getIdpEntityAlias()); + + //no acceess token + getMockMvc().perform( + delete("/identity-providers/{id}", created.getId()) + ).andExpect(status().isUnauthorized()); + + getMockMvc().perform( + delete("/identity-providers/{id}", created.getId()) + .header("Authorization", "Bearer" + accessToken) + ).andExpect(status().isOk()); + + getMockMvc().perform( + get("/identity-providers/{id}", created.getId()) + .header("Authorization", "Bearer" + accessToken) + ).andExpect(status().isNotFound()); + + } + + @Test + public void test_delete_with_invalid_id_returns_404() throws Exception { + String accessToken = setUpAccessToken(); + getMockMvc().perform( + delete("/identity-providers/invalid-id") + .header("Authorization", "Bearer" + accessToken) + ).andExpect(status().isNotFound()); } + + @Test public void testEnsureWeRetrieveInactiveIDPsToo() throws Exception { testRetrieveIdps(false); @@ -526,11 +555,11 @@ private MvcResult updateIdentityProvider(String zoneId, IdentityProvider identit public String setUpAccessToken() throws Exception { String clientId = RandomStringUtils.randomAlphabetic(6); - BaseClientDetails client = new BaseClientDetails(clientId,null,"idps.write","password",null); + BaseClientDetails client = new BaseClientDetails(clientId,null,"idps.read,idps.write","password",null); client.setClientSecret("test-client-secret"); mockMvcUtils.createClient(getMockMvc(), adminToken, client); - ScimUser user = mockMvcUtils.createAdminForZone(getMockMvc(), adminToken, "idps.write"); - return mockMvcUtils.getUserOAuthAccessToken(getMockMvc(), client.getClientId(), client.getClientSecret(), user.getUserName(), "secr3T", "idps.write"); + ScimUser user = mockMvcUtils.createAdminForZone(getMockMvc(), adminToken, "idps.write,idps.read"); + return mockMvcUtils.getUserOAuthAccessToken(getMockMvc(), client.getClientId(), client.getClientSecret(), user.getUserName(), "secr3T", "idps.read idps.write"); } } From 8a508f99af7667a0755c47d797e3a031e326aa5c Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 9 Dec 2015 08:09:06 -0700 Subject: [PATCH 063/103] Implement zone hard delete https://www.pivotaltracker.com/story/show/108254860 [#108254860] --- .../uaa/zone/IdentityZoneEndpoints.java | 43 ++++++++++++++++++- .../zone/JdbcIdentityZoneProvisioning.java | 20 ++++++++- .../JdbcIdentityZoneProvisioningTests.java | 20 +++++++++ .../uaa/zone/IdentityProviderEndpoints.java | 2 +- .../WEB-INF/spring/multitenant-endpoints.xml | 3 ++ .../IdentityZoneEndpointsMockMvcTests.java | 20 +++++++-- 6 files changed, 102 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java index 0b42b001c9f..755ad14105a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneEndpoints.java @@ -14,12 +14,15 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.error.UaaException; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.oauth.InvalidClientDetailsException; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.MessageSource; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -28,6 +31,7 @@ import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; @@ -51,6 +55,7 @@ import static org.springframework.http.HttpStatus.CREATED; import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; import static org.springframework.web.bind.annotation.RequestMethod.DELETE; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; @@ -58,11 +63,13 @@ @RestController @RequestMapping("/identity-zones") -public class IdentityZoneEndpoints { +public class IdentityZoneEndpoints implements ApplicationEventPublisherAware { @Autowired private MessageSource messageSource; + private ApplicationEventPublisher publisher; + private static final Logger logger = LoggerFactory.getLogger(IdentityZoneEndpoints.class); private final IdentityZoneProvisioning zoneDao; private final IdentityProviderProvisioning idpDao; @@ -76,6 +83,12 @@ public IdentityZoneEndpoints(IdentityZoneProvisioning zoneDao, IdentityProviderP this.clientRegistrationService = clientRegistrationService; } + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.publisher = applicationEventPublisher; + } + + @RequestMapping(value = "{id}", method = GET) public IdentityZone getIdentityZone(@PathVariable String id) { List result = filterForCurrentZone(Arrays.asList(zoneDao.retrieve(id))); @@ -172,6 +185,34 @@ public ResponseEntity updateIdentityZone( } } + @RequestMapping(value = "{id}", method = DELETE) + @Transactional + public ResponseEntity deleteIdentityZone(@PathVariable String id) { + if (id==null) { + throw new ZoneDoesNotExistsException(id); + } + if (!IdentityZoneHolder.isUaa() && !id.equals(IdentityZoneHolder.get().getId()) ) { + throw new AccessDeniedException("Zone admins can only update their own zone."); + } + IdentityZone previous = IdentityZoneHolder.get(); + try { + logger.debug("Zone - deleting id["+id+"]"); + // make sure it exists + IdentityZone zone = zoneDao.retrieve(id); + // ignore the id in the body, the id in the path is the only one that matters + IdentityZoneHolder.set(zone); + if (publisher!=null && zone!=null) { + publisher.publishEvent(new EntityDeletedEvent<>(zone)); + logger.debug("Zone - deleted id[" + zone.getId() + "]"); + return new ResponseEntity<>(zone, OK); + } else { + return new ResponseEntity<>(UNPROCESSABLE_ENTITY); + } + } finally { + IdentityZoneHolder.set(previous); + } + } + @RequestMapping(method = POST, value = "{identityZoneId}/clients") public ResponseEntity createClient( @PathVariable String identityZoneId, @RequestBody BaseClientDetails clientDetails) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java index 7483cd9a339..c72f171df72 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java @@ -14,6 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.event.SystemDeletable; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.EmptyResultDataAccessException; @@ -30,7 +31,7 @@ import java.util.Date; import java.util.List; -public class JdbcIdentityZoneProvisioning implements IdentityZoneProvisioning { +public class JdbcIdentityZoneProvisioning implements IdentityZoneProvisioning, SystemDeletable { public static final String ID_ZONE_FIELDS = "id,version,created,lastmodified,name,subdomain,description,config"; @@ -40,6 +41,8 @@ public class JdbcIdentityZoneProvisioning implements IdentityZoneProvisioning { public static final String UPDATE_IDENTITY_ZONE_SQL = "update identity_zone set " + ID_ZONE_UPDATE_FIELDS + " where id=?"; + public static final String DELETE_IDENTITY_ZONE_SQL = "delete from identity_zone where id=?"; + public static final String IDENTITY_ZONES_QUERY = "select " + ID_ZONE_FIELDS + " from identity_zone "; public static final String IDENTITY_ZONE_BY_ID_QUERY = IDENTITY_ZONES_QUERY + "where id=?"; @@ -136,6 +139,21 @@ public void setValues(PreparedStatement ps) throws SQLException { return retrieve(identityZone.getId()); } + @Override + public int deleteByIdentityZone(String zoneId) { + return jdbcTemplate.update(DELETE_IDENTITY_ZONE_SQL, zoneId); + } + + @Override + public int deleteByOrigin(String origin, String zoneId) { + return 0; + } + + @Override + public Log getLogger() { + return logger; + } + public static final class IdentityZoneRowMapper implements RowMapper { @Override public IdentityZone mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java index 1275717b92b..fb90bbfb303 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java @@ -1,5 +1,6 @@ package org.cloudfoundry.identity.uaa.zone; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.junit.Before; import org.junit.Test; @@ -18,6 +19,25 @@ public void createDatasource() throws Exception { db = new JdbcIdentityZoneProvisioning(jdbcTemplate); } + @Test + public void test_delete_zone() { + IdentityZone identityZone = MultitenancyFixture.identityZone(generator.generate(),generator.generate()); + identityZone.setId(generator.generate()); + identityZone.setConfig(new IdentityZoneConfiguration(new TokenPolicy(3600, 7200))); + + IdentityZone createdIdZone = db.create(identityZone); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from identity_zone where id = ?", createdIdZone.getId())); + db.onApplicationEvent(new EntityDeletedEvent<>(identityZone)); + assertEquals(0, jdbcTemplate.queryForInt("select count(*) from identity_zone where id = ?", createdIdZone.getId())); + } + + @Test + public void test_cannot_delete_uaa_zone() { + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from identity_zone where id = ?", IdentityZone.getUaa().getId())); + db.onApplicationEvent(new EntityDeletedEvent<>(IdentityZone.getUaa())); + assertEquals(1, jdbcTemplate.queryForInt("select count(*) from identity_zone where id = ?", IdentityZone.getUaa().getId())); + } + @Test public void testCreateIdentityZone() throws Exception { IdentityZone identityZone = MultitenancyFixture.identityZone(generator.generate(),generator.generate()); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java b/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java index 66ec795607e..4faf35158e4 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java +++ b/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java @@ -112,7 +112,7 @@ public ResponseEntity deleteIdentityProvider(@PathVariable Str publisher.publishEvent(new EntityDeletedEvent<>(existing)); return new ResponseEntity<>(existing, OK); } else { - return new ResponseEntity<>(NOT_FOUND); + return new ResponseEntity<>(UNPROCESSABLE_ENTITY); } } diff --git a/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml index 8874d91c907..38cf02f5803 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml @@ -58,6 +58,9 @@ + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java index 4a7a70660a8..21d638b33f5 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneEndpointsMockMvcTests.java @@ -23,6 +23,7 @@ import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -64,6 +65,7 @@ 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.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.TEXT_HTML_VALUE; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -553,7 +555,6 @@ public void test_delete_zone_cleans_db() throws Exception { //ensure we have some audit records //this doesn't work yet //assertThat(template.queryForInt("select count(*) from sec_audit where identity_zone_id=?", user.getZoneId()), greaterThan(0)); - //create an external group map IdentityZoneHolder.set(zone); ScimGroupExternalMember externalMember = externalMembershipManager.mapExternalGroup(group.getId(), "externalDeleteGroup", LOGIN_SERVER); @@ -570,8 +571,17 @@ public void test_delete_zone_cleans_db() throws Exception { assertEquals(1, approvalStore.getApprovals(user.getId(), client.getClientId()).size()); //perform zone delete + getMockMvc().perform( + delete("/identity-zones/{id}", zone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .accept(APPLICATION_JSON)) + .andExpect(status().isOk()); - assertTrue("IMPLEMENT ZONE DELETE API HERE", false); + getMockMvc().perform( + delete("/identity-zones/{id}", zone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .accept(APPLICATION_JSON)) + .andExpect(status().isNotFound()); assertEquals(0, template.queryForInt("select count(*) from identity_zone where id=?", zone.getId())); @@ -584,7 +594,11 @@ public void test_delete_zone_cleans_db() throws Exception { assertEquals(0, template.queryForInt("select count(*) from users where identity_zone_id=?", zone.getId())); assertEquals(0, template.queryForInt("select count(*) from external_group_mapping where origin=?", LOGIN_SERVER)); - assertEquals(0, externalMembershipManager.getExternalGroupMapsByGroupId(group.getId(), LOGIN_SERVER).size()); + try { + externalMembershipManager.getExternalGroupMapsByGroupId(group.getId(), LOGIN_SERVER); + fail("no external groups should be found"); + } catch (ScimResourceNotFoundException e) { + } assertEquals(0, template.queryForInt("select count(*) from authz_approvals where user_id=?", user.getId())); assertEquals(0, approvalStore.getApprovals(user.getId(), client.getClientId()).size()); From 9020a716e44efbf8837f802da8f9b064a45a0111 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Wed, 9 Dec 2015 11:09:13 -0800 Subject: [PATCH 064/103] Update docs with curl command example as per pull request [#107801248] https://www.pivotaltracker.com/story/show/107801248 Signed-off-by: Paul Warren --- docs/UAA-APIs.rst | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 3b1b56f6b13..a01c91a08ad 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -1613,7 +1613,7 @@ Change Password: ``PUT /Users/{id}/password`` See `SCIM - Changing Password `_ * Request: ``PUT /Users/{id}/password`` -* Request Headers: Authorization header containing an `OAuth2`_ bearer token with:: +* Authorization: Authorization header containing an `OAuth2`_ bearer token with:: scope = password.write aud = password @@ -1622,19 +1622,27 @@ See `SCIM - Changing Password Date: Thu, 10 Dec 2015 14:09:38 -0800 Subject: [PATCH 065/103] Use SAML IDP alias as link text if none provided. [finishes #109737570] https://www.pivotaltracker.com/story/show/109737570 Signed-off-by: Jeremy Coffield --- .../uaa/login/saml/IdentityProviderConfiguratorTests.java | 5 +---- .../uaa/provider/SamlIdentityProviderDefinition.java | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java index e1cce70514d..e54d66df9d9 100755 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java @@ -172,10 +172,8 @@ public static void initializeOpenSAML() throws Exception { " simplesamlphp-url:\n" + " assertionConsumerIndex: 0\n" + " idpMetadata: http://simplesamlphp.identity.cf-app.com/saml2/idp/metadata.php\n" + - " linkText: Log in with Simple SAML PHP URL\n" + " metadataTrustCheck: false\n" + " nameID: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n" + - " showSamlLoginLink: true\n"+ " incomplete-provider:\n" + " idpMetadata: http://localhost:8081/openam/saml2/jsp/exportmetadata.jsp?entityid=http://localhost:8081/openam\n"; @@ -441,6 +439,7 @@ protected void testGetIdentityProviderDefinitions(int count, boolean addData) th } case "simplesamlphp-url" : { assertTrue(idp.isShowSamlLink()); + assertEquals("simplesamlphp-url", idp.getLinkText()); break; } default: @@ -482,7 +481,6 @@ public void testDuplicateAlias_In_LegacyConfig() throws Exception { conf.afterPropertiesSet(); } - @Test public void testDuplicate_EntityID_IsRejected() throws Exception { conf.setIdentityProviders(sampleData); @@ -493,7 +491,6 @@ public void testDuplicate_EntityID_IsRejected() throws Exception { .setMetaDataLocation("http://simplesamlphp.identity.cf-app.com/saml2/idp/metadata.php") .setIdpEntityAlias("simplesamlphp-url-2") .setNameID("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") - .setLinkText("Link Text") .setZoneId(IdentityZone.getUaa().getId()) .setShowSamlLink(true) .build(); diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java index beb30bca407..1fb7bf073ea 100644 --- a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java +++ b/models/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java @@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.provider; import com.fasterxml.jackson.annotation.JsonIgnore; +import org.springframework.util.StringUtils; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -190,7 +191,7 @@ public void setSocketFactoryClassName(String socketFactoryClassName) { } public String getLinkText() { - return linkText; + return StringUtils.hasText(linkText) ? linkText : idpEntityAlias; } public void setLinkText(String linkText) { From 88174eea0f946986734fbf678800e4e6f31c8882 Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Fri, 11 Dec 2015 10:25:03 -0800 Subject: [PATCH 066/103] Remove populating givenName and familyName from email - due to ArrayIndexOutOfBoundsException if email is not mapped [#109801138] https://www.pivotaltracker.com/story/show/109801138 Signed-off-by: Paul Warren --- .../manager/ExternalLoginAuthenticationManager.java | 8 -------- .../manager/ExternalLoginAuthenticationManagerTest.java | 2 -- 2 files changed, 10 deletions(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java index 4ab64babfa1..b5f160a0018 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java @@ -200,14 +200,6 @@ protected UaaUser getUser(Authentication request) { familyName = names.getFamilyName(); } - if(givenName == null) { - givenName = email.split("@")[0]; - } - - if(familyName == null) { - familyName = email.split("@")[1]; - } - String phoneNumber = (userDetails instanceof DialableByPhone) ? ((DialableByPhone) userDetails).getPhoneNumber() : null; String externalId = (userDetails instanceof ExternallyIdentifiable) ? ((ExternallyIdentifiable) userDetails).getExternalId() : name; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java index 28cadd05ec3..3285c4492df 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java @@ -296,8 +296,6 @@ public void testAuthenticateCreateUserWithLdapUserDetailsPrincipal() throws Exce manager.setOrigin(origin); when(user.getEmail()).thenReturn(email); when(user.getOrigin()).thenReturn(origin); - when(user.getGivenName()).thenReturn("joe"); - when(user.getFamilyName()).thenReturn("test.org"); when(uaaUserDatabase.retrieveUserByName(eq(userName),eq(origin))) .thenReturn(null) .thenReturn(user); From c5afd090dfa2e83c63222229406e7988a3f7ae70 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 14 Dec 2015 07:22:38 -0700 Subject: [PATCH 067/103] [skip ci] Add delete provider and delete zone APIs Change tabs to spaces --- docs/UAA-APIs.rst | 167 ++++++++++++++++++++++++++++++---------------- 1 file changed, 111 insertions(+), 56 deletions(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index a01c91a08ad..ba11067464a 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -432,7 +432,7 @@ Authorization Basic authentication, client ID and client secret (or ``client_id`` and ``client_secret`` can be provided as url encoded form parameters) Request Body the ``username`` and ``password`` (form encoded), e.g. - :: + :: [client_id=client] [client_secret=clientsecret] @@ -808,16 +808,16 @@ Fields *Available Fields* :: samlConfig SamlConfig Optional Various fields pertaining to SAML identity provider configuration. See ``SamlConfig`` section below for details. Token Policy ``TokenPolicy`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.TokenPolicy) - ===================== ==================== ======== ======================================================================================================================================================================== - accessTokenValidity int Optional How long the access token is valid for in seconds. - refreshTokenValidity int Optional How long the refresh token is valid for seconds. + ===================== ==================== ======== ======================================================================================================================================================================== + accessTokenValidity int Optional How long the access token is valid for in seconds. + refreshTokenValidity int Optional How long the refresh token is valid for seconds. - SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig) - ===================== ==================== ======== ======================================================================================================================================================================== - requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``false``. - wantAssertionSigned Boolean Optional Exposed SAML metadata property. If ``true``, all assertions received by the SAML provider must be signed. Defaults to ``false``. + SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig) + ===================== ==================== ======== ======================================================================================================================================================================== + requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``false``. + wantAssertionSigned Boolean Optional Exposed SAML metadata property. If ``true``, all assertions received by the SAML provider must be signed. Defaults to ``false``. - ===================== ==================== ======== ======================================================================================================================================================================== + ===================== ==================== ======== ======================================================================================================================================================================== Curl Example POST (Token contains ``zones.write`` scope) :: @@ -867,7 +867,7 @@ All operations after this, are exactly the same as against the default zone. List Identity Zones: ``GET /identity-zones`` ------------------------------------- +-------------------------------------------- ============== =========================================================================== Request ``GET /identity-zones`` @@ -875,33 +875,33 @@ Request Header Authorization: Bearer Token containing ``zones.read`` or ``zones Response code ``200 OK`` Response body *example* :: - HTTP/1.1 200 OK - Content-Type: application/json - [ - { - "id": "uaa", - "subdomain": "", - "name": "uaa", - "version": 0, - "description": "The system zone for backwards compatibility", - "created": 946710000000, - "last_modified": 946710000000 - }, - { - "id":"testzone1", - "subdomain":"testzone1", - "name":"The Twiglet Zone[testzone1]", - "version":0, - "description":"Like the Twilight Zone but tastier[testzone1].", - "created":1426260091139, - "last_modified":1426260091139 - } - ] + HTTP/1.1 200 OK + Content-Type: application/json + [ + { + "id": "uaa", + "subdomain": "", + "name": "uaa", + "version": 0, + "description": "The system zone for backwards compatibility", + "created": 946710000000, + "last_modified": 946710000000 + }, + { + "id":"testzone1", + "subdomain":"testzone1", + "name":"The Twiglet Zone[testzone1]", + "version":0, + "description":"Like the Twilight Zone but tastier[testzone1].", + "created":1426260091139, + "last_modified":1426260091139 + } + ] ============== =========================================================================== Get single identity zone: ``GET /identity-zones/{identityZoneId}`` ------------------------------------- +------------------------------------------------------------------ ============== =========================================================================== Request ``GET /identity-zones/{identityZoneId}`` @@ -909,17 +909,40 @@ Request Header Authorization: Bearer Token containing ``zones.read`` or ``zones Response code ``200 OK`` Response body *example* :: - HTTP/1.1 200 OK - Content-Type: application/json - { - "id": "identity-zone-id", - "subdomain": "test", - "name": "test", - "version": 0, - "description": "The test zone", - "created": 946710000000, - "last_modified": 946710000000 - } + HTTP/1.1 200 OK + Content-Type: application/json + { + "id": "identity-zone-id", + "subdomain": "test", + "name": "test", + "version": 0, + "description": "The test zone", + "created": 946710000000, + "last_modified": 946710000000 + } + +============== =========================================================================== + +Delete single identity zone: ``DELETE /identity-zones/{identityZoneId}`` +------------------------------------------------------------------------ + +============== =========================================================================== +Request ``DELETE /identity-zones/{identityZoneId}`` +Request Header Authorization: Bearer Token containing ``zones.write`` +Response code ``200 OK`` +Response body *example* :: + + HTTP/1.1 200 OK + Content-Type: application/json + { + "id": "identity-zone-id", + "subdomain": "test", + "name": "test", + "version": 0, + "description": "The test zone", + "created": 946710000000, + "last_modified": 946710000000 + } ============== =========================================================================== @@ -1325,6 +1348,36 @@ Curl Example POST (Testing an LDAP provider):: ================ ========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== +Deleting an Identity Provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Deleting an identity provider does a hard delete. The identity provider and all related records will be deleted. + +================ ========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== +Request ``DELETE /identity-providers/`` +Header ``X-Identity-Zone-Id`` (if using zones..admin scope against default UAA zone) +Scopes Required ``zones..admin`` or ``idps.write`` +Response body *example* (a provider contains the fields defined above) :: + + { + "provider":{ + "originKey":"ldap", + "name":"Test ldap provider", + "type":"ldap", + "config":"{\"baseUrl\":\"ldap://localhost:33389\",\"bindUserDn\":\"cn=admin,ou=Users,dc=test,dc=com\",\"bindPassword\":\"adminsecret\",\"userSearchBase\":\"dc=test,dc=com\",\"userSearchFilter\":\"cn={0}\",\"groupSearchBase\":\"ou=scopes,dc=test,dc=com\",\"groupSearchFilter\":\"member={0}\",\"mailAttributeName\":\"mail\",\"mailSubstitute\":null,\"ldapProfileFile\":\"ldap/ldap-search-and-bind.xml\",\"ldapGroupFile\":\"ldap/ldap-groups-map-to-scopes.xml\",\"mailSubstituteOverridesLdap\":false,\"autoAddGroups\":true,\"groupSearchSubTree\":true,\"maxGroupSearchDepth\":10}", + "active":true, + "identityZoneId":"testzone1" + } + } + +* Response *Codes* :: + + 200 - Ok - Successful authentication + 404 - Not Found - Invalid provider ID + 400 - Bad Request - Invalid configuration - result contains stack trace + 403 - Forbidden - insufficient scope + 500 - Internal Server Error - error information will only be in server logs +================ ========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== + User Account Management APIs ============================ @@ -1759,20 +1812,20 @@ Query for the existence of a specific username. * Response Body (for ``GET /Users?attributes=userName&filter=userName eq 'bjensen'``):: - HTTP/1.1 200 OK + HTTP/1.1 200 OK Content-Type: application/json { - "resources": [ + "resources": [ { "userName": "bjensen" } ], - "startIndex": 1, - "itemsPerPage": 100, - "totalResults": 1, - "schemas":["urn:scim:schemas:core:1.0"] - } + "startIndex": 1, + "itemsPerPage": 100, + "totalResults": 1, + "schemas":["urn:scim:schemas:core:1.0"] + } * Response Codes:: @@ -1918,8 +1971,8 @@ __ http://www.simplecloud.info/specs/draft-scim-api-01.html#create-resource { "displayName":"uaa.admin", "members":[ - { "type":"USER","authorities":["READ"],"value":"3ebe4bda-74a2-40c4-8b70-f771d9bc8b9f","origin":"uaa" } - ] + { "type":"USER","authorities":["READ"],"value":"3ebe4bda-74a2-40c4-8b70-f771d9bc8b9f","origin":"uaa" } + ] } The ``displayName`` is unique in the UAA, but is allowed to change. Each group also has a fixed primary key which is a UUID (stored in the ``id`` field of the core schema). @@ -1943,7 +1996,7 @@ relationship came from an LDAP user, it would have origin=ldap. }, "displayName":"uaa.admin", "members":[ - { "type":"USER","authorities":["READ"],"value":"3ebe4bda-74a2-40c4-8b70-f771d9bc8b9f","origin":"uaa" } + { "type":"USER","authorities":["READ"],"value":"3ebe4bda-74a2-40c4-8b70-f771d9bc8b9f","origin":"uaa" } ] } @@ -2280,8 +2333,9 @@ It is possible to substitute the ``displayName`` field with a ``groupId`` field 400 - Bad Request (unparseable, syntactically incorrect etc) 401 - Unauthorized + Remove a Group mapping: ``DELETE /Groups/External/groupId/{groupId}/externalGroup/{externalGroup}/origin/{origin}`` ---------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------- Removes the group mapping between an internal UAA groups (scope) and an external group, for example LDAP DN. The API ``DELETE /Groups/External/id/{groupId}/{externalGroup}`` is deprecated @@ -2318,8 +2372,9 @@ The API ``DELETE /Groups/External/id/{groupId}/{externalGroup}`` is deprecated 400 - Bad Request (unparseable, syntactically incorrect etc) 401 - Unauthorized + Remove a Group mapping: ``DELETE /Groups/External/displayName/{displayName}/externalGroup/{externalGroup}/origin/{origin}`` ------------------------------------------------------------------------------------------------------------ +--------------------------------------------------------------------------------------------------------------------------- Removes the group mapping between an internal UAA groups (scope) and an external group, for example LDAP DN. The API ``DELETE /Groups/External/{displayName}/{externalGroup}`` is deprecated From e7884822612dfc7aee9e141afbb9a9c0e1ee4009 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 14 Dec 2015 10:17:46 -0700 Subject: [PATCH 068/103] When refreshing SAML providers, ensure that non existent providers (deleted from DB) are also purged from the Spring Security SAML memory https://www.pivotaltracker.com/story/show/109817758 [#109817758] --- .../SamlIdentityProviderConfigurator.java | 36 ++---- .../login/saml/ZoneAwareMetadataManager.java | 23 +++- .../saml/SamlIDPRefreshMockMvcTests.java | 112 +++++------------- 3 files changed, 56 insertions(+), 115 deletions(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderConfigurator.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderConfigurator.java index d6a69160d33..531f40c70b0 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderConfigurator.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderConfigurator.java @@ -61,34 +61,14 @@ public class SamlIdentityProviderConfigurator implements InitializingBean { private BasicParserPool parserPool; private Timer dummyTimer = new Timer() { - - @Override - public void cancel() { - super.cancel(); - } - - @Override - public int purge() { - return 0; - } - - @Override - public void schedule(TimerTask task, long delay) {} - - @Override - public void schedule(TimerTask task, long delay, long period) {} - - @Override - public void schedule(TimerTask task, Date firstTime, long period) {} - - @Override - public void schedule(TimerTask task, Date time) {} - - @Override - public void scheduleAtFixedRate(TimerTask task, long delay, long period) {} - - @Override - public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {} + @Override public void cancel() { super.cancel(); } + @Override public int purge() {return 0; } + @Override public void schedule(TimerTask task, long delay) {} + @Override public void schedule(TimerTask task, long delay, long period) {} + @Override public void schedule(TimerTask task, Date firstTime, long period) {} + @Override public void schedule(TimerTask task, Date time) {} + @Override public void scheduleAtFixedRate(TimerTask task, long delay, long period) {} + @Override public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {} }; public SamlIdentityProviderConfigurator() { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java index 3a5ad404274..e7573631f68 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java @@ -17,9 +17,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -49,6 +49,7 @@ import javax.xml.namespace.QName; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -129,7 +130,9 @@ protected void refreshAllProviders(boolean ignoreTimestamp) throws MetadataProvi for (IdentityZone zone : zoneDao.retrieveAll()) { ExtensionMetadataManager manager = getManager(zone); boolean hasChanges = false; + List zoneDefinitions = new LinkedList(configurator.getIdentityProviderDefinitionsForZone(zone)); for (IdentityProvider provider : providerDao.retrieveAll(false,zone.getId())) { + zoneDefinitions.remove(provider.getConfig()); if (OriginKeys.SAML.equals(provider.getType()) && (ignoreTimestamp || lastRefresh < provider.getLastModified().getTime())) { try { SamlIdentityProviderDefinition definition = (SamlIdentityProviderDefinition)provider.getConfig(); @@ -142,11 +145,7 @@ protected void refreshAllProviders(boolean ignoreTimestamp) throws MetadataProvi } manager.addMetadataProvider(delegates[0]); } else { - log.info("Removing SAML IDP zone[" + zone.getId() + "] alias[" + definition.getIdpEntityAlias() + "]"); - ExtendedMetadataDelegate delegate = configurator.removeIdentityProviderDefinition(definition); - if (delegate!=null) { - manager.removeMetadataProvider(delegate); - } + removeSamlProvider(zone, manager, definition); } hasChanges = true; } catch (MetadataProviderException e) { @@ -157,6 +156,10 @@ protected void refreshAllProviders(boolean ignoreTimestamp) throws MetadataProvi } } } + for (SamlIdentityProviderDefinition definition : zoneDefinitions) { + removeSamlProvider(zone, manager, definition); + hasChanges = true; + } if (hasChanges) { refreshZoneManager(manager); } @@ -164,6 +167,14 @@ protected void refreshAllProviders(boolean ignoreTimestamp) throws MetadataProvi lastRefresh = System.currentTimeMillis(); } + protected void removeSamlProvider(IdentityZone zone, ExtensionMetadataManager manager, SamlIdentityProviderDefinition definition) { + log.info("Removing SAML IDP zone[" + zone.getId() + "] alias[" + definition.getIdpEntityAlias() + "]"); + ExtendedMetadataDelegate delegate = configurator.removeIdentityProviderDefinition(definition); + if (delegate!=null) { + manager.removeMetadataProvider(delegate); + } + } + protected ExtensionMetadataManager getManager(IdentityZone zone) { if (metadataManagers==null) { //called during super constructor metadataManagers = new ConcurrentHashMap<>(); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java index 6e2994c0889..b6d7ffb3548 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login.saml; +import org.cloudfoundry.identity.uaa.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.SamlConfig; @@ -125,26 +126,30 @@ public void testFallbackIDP_shows_Error_Message_Instead_Of_Default() throws Exce @Test public void testThatDBAddedXMLProviderShowsOnLoginPage() throws Exception { - assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); - IdentityProvider provider = createSamlProvider(DEFAULT_SIMPLE_SAML_METADATA, "simplesamlphp", "Log in with Simple Saml PHP Config"); + addXmlProviderToDatabase(); + } + + @Test + public void testThatDBDeletedXMLProviderDoesNotShowOnLoginPage() throws Exception { + IdentityProvider provider = addXmlProviderToDatabase(); SamlIdentityProviderDefinition definition = provider.getConfig(); - //ensure that the listener was not the one who created the provider - assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); - //this simulates what the timer does + //delete from DB + EntityDeletedEvent event = new EntityDeletedEvent(provider); + getWebApplicationContext().publishEvent(event); + //verify that provider is deleted + assertEquals(0, getWebApplicationContext().getBean(JdbcTemplate.class).queryForInt("select count(*) from identity_provider where id=?", provider.getId())); + //issue a timer zoneAwareMetadataManager.refreshAllProviders(); - assertEquals(2, zoneAwareMetadataManager.getAvailableProviders().size()); - - //ensure that we have an actual SAML provider created - - - //ensure that it exists in the link + //ensure that it the link doesn't show up getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) - .andExpect(xpath("//a[text()='" + definition.getLinkText() + "']").exists()); + .andExpect(xpath("//a[text()='" + definition.getLinkText() + "']").doesNotExist()); + //and provider should be gone + assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); } - @Test - public void test_Reject_Duplicate_Alias_and_Duplicate_Entity_ID() throws Exception { + + protected IdentityProvider addXmlProviderToDatabase() throws Exception { assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); IdentityProvider provider = createSamlProvider(DEFAULT_SIMPLE_SAML_METADATA, "simplesamlphp", "Log in with Simple Saml PHP Config"); SamlIdentityProviderDefinition definition = provider.getConfig(); @@ -152,15 +157,20 @@ public void test_Reject_Duplicate_Alias_and_Duplicate_Entity_ID() throws Excepti assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); //this simulates what the timer does zoneAwareMetadataManager.refreshAllProviders(); - assertEquals(2, zoneAwareMetadataManager.getAvailableProviders().size()); - //ensure that we have an actual SAML provider created - + assertEquals(2, zoneAwareMetadataManager.getAvailableProviders().size()); //ensure that it exists in the link getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) .andExpect(xpath("//a[text()='" + definition.getLinkText() + "']").exists()); + return provider; + } + + @Test + public void test_Reject_Duplicate_Alias_and_Duplicate_Entity_ID() throws Exception { + IdentityProvider provider = addXmlProviderToDatabase(); + // try { createSamlProvider(DEFAULT_SIMPLE_SAML_METADATA, "simplesamlphp", "Log in with Simple Saml PHP Config"); @@ -175,7 +185,7 @@ public void test_Reject_Duplicate_Alias_and_Duplicate_Entity_ID() throws Excepti zoneAwareMetadataManager.refreshAllProviders(); assertEquals(2, zoneAwareMetadataManager.getAvailableProviders().size()); - definition = provider.getConfig(); + SamlIdentityProviderDefinition definition = provider.getConfig(); //ensure that it exists in the link getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) @@ -184,26 +194,11 @@ public void test_Reject_Duplicate_Alias_and_Duplicate_Entity_ID() throws Excepti @Test public void testThatDBXMLDisabledProvider() throws Exception { - assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); - IdentityProvider provider = createSamlProvider(DEFAULT_SIMPLE_SAML_METADATA, "simplesamlphp", "Log in with Simple Saml PHP Config"); - SamlIdentityProviderDefinition definition = provider.getConfig(); - //ensure that the listener was not the one who created the provider - assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); - - //this simulates what the timer does - zoneAwareMetadataManager.refreshAllProviders(); - - //ensure that we have an actual SAML provider created - assertEquals(2, zoneAwareMetadataManager.getAvailableProviders().size()); - - //ensure that it exists in the link - getMockMvc().perform(get("/login").accept(TEXT_HTML)) - .andExpect(status().isOk()) - .andExpect(xpath("//a[text()='" + definition.getLinkText() + "']").exists()); + IdentityProvider provider = addXmlProviderToDatabase(); provider.setActive(false); provider = providerProvisioning.update(provider); - definition = provider.getConfig(); + SamlIdentityProviderDefinition definition = provider.getConfig(); //this simulates what the timer does zoneAwareMetadataManager.refreshAllProviders(); @@ -219,59 +214,14 @@ public void testThatDBXMLDisabledProvider() throws Exception { @Test public void testThatDBAddedFileProviderShowsOnLoginPage() throws Exception { - assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); - IdentityProvider provider = createSamlProvider(DEFAULT_SIMPLE_SAML_METADATA, "simplesamlphp", "Log in with Simple Saml PHP File"); + IdentityProvider provider = addXmlProviderToDatabase(); SamlIdentityProviderDefinition definition = provider.getConfig(); - //ensure that the listener was not the one who created the provider - assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); - - //this simulates what the timer does - zoneAwareMetadataManager.refreshAllProviders(); - assertEquals(2, zoneAwareMetadataManager.getAvailableProviders().size()); - - //ensure that we have an actual SAML provider created - - //ensure that it exists in the link getMockMvc().perform(get("/login").accept(TEXT_HTML)) .andExpect(status().isOk()) .andExpect(xpath("//a[text()='" + definition.getLinkText() + "']").exists()); } - @Test - public void testThatDBFileDisabledProvider() throws Exception { - assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); - IdentityProvider provider = createSamlProvider(DEFAULT_SIMPLE_SAML_METADATA, "simplesamlphp", "Log in with Simple Saml PHP File"); - SamlIdentityProviderDefinition definition = provider.getConfig(); - //ensure that the listener was not the one who created the provider - assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); - - //this simulates what the timer does - zoneAwareMetadataManager.refreshAllProviders(); - - //ensure that we have an actual SAML provider created - assertEquals(2, zoneAwareMetadataManager.getAvailableProviders().size()); - - //ensure that it exists in the link - getMockMvc().perform(get("/login").accept(TEXT_HTML)) - .andExpect(status().isOk()) - .andExpect(xpath("//a[text()='" + definition.getLinkText() + "']").exists()); - - provider.setActive(false); - provider = providerProvisioning.update(provider); - definition = provider.getConfig(); - - //this simulates what the timer does - zoneAwareMetadataManager.refreshAllProviders(); - - //ensure that we have an actual SAML provider created - assertEquals(1, zoneAwareMetadataManager.getAvailableProviders().size()); - - //ensure that it exists in the link - getMockMvc().perform(get("/login").accept(TEXT_HTML)) - .andExpect(status().isOk()) - .andExpect(xpath("//a[text()='" + definition.getLinkText() + "']").doesNotExist()); - } @Test public void testThatDBAddedUrlProviderShowsOnLoginPage() throws Exception { From 9a4a107c40ed677e3e5cbba5810e3a3e442a33a2 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 14 Dec 2015 11:18:37 -0700 Subject: [PATCH 069/103] Better logging on zone resolve https://www.pivotaltracker.com/story/show/109108014 [#109108014] https://github.com/cloudfoundry/uaa/pull/263 [finishes #109108014] --- .../uaa/zone/IdentityZoneResolvingFilter.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java index 16124d176f8..ad0d852d1c9 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java @@ -14,6 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.web.filter.OncePerRequestFilter; @@ -22,6 +23,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -31,7 +33,7 @@ * sent. * */ -public class IdentityZoneResolvingFilter extends OncePerRequestFilter { +public class IdentityZoneResolvingFilter extends OncePerRequestFilter implements InitializingBean { private IdentityZoneProvisioning dao; private Set defaultZoneHostnames = new HashSet<>(); @@ -47,10 +49,11 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse try { identityZone = dao.retrieveBySubdomain(subdomain); } catch (EmptyResultDataAccessException ex) { - logger.debug("Cannot find identity zone for subdomain " + subdomain, ex); + logger.debug("Cannot find identity zone for subdomain " + subdomain); } catch (Exception ex) { - logger.debug("Internal server error while fetching identity zone for subdomain" + subdomain, ex); - response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error while fetching identity zone for subdomain " + subdomain); + String message = "Internal server error while fetching identity zone for subdomain" + subdomain; + logger.warn(message, ex); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message); return; } } @@ -77,8 +80,10 @@ private String getSubdomain(String hostname) { } //UAA is catch all if we haven't configured anything if (defaultZoneHostnames.size()==1 && defaultZoneHostnames.contains("localhost")) { + logger.debug("No root domains configured, UAA is catch-all domain for host:"+hostname); return ""; } + logger.debug("Unable to determine subdomain for host:"+hostname+"; root domains:"+Arrays.toString(defaultZoneHostnames.toArray())); return null; } @@ -104,4 +109,10 @@ public synchronized void restoreDefaultHostnames(Set hostnames) { public Set getDefaultZoneHostnames() { return new HashSet<>(defaultZoneHostnames); } + + @Override + public void afterPropertiesSet() throws ServletException { + super.afterPropertiesSet(); + logger.info("Zone Resolving Root domains are: "+ Arrays.toString(getDefaultZoneHostnames().toArray())); + } } From e449a716aa4e66167eb5df0615a5e4cd28255fe2 Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Mon, 14 Dec 2015 12:29:36 -0800 Subject: [PATCH 070/103] Rename cloudfoundry-identity-models -> cloudfoundry-identity-model - Delete gatling/ [#110098890] https://www.pivotaltracker.com/story/show/110098890 Signed-off-by: Paul Warren --- build.gradle | 2 +- client-lib/build.gradle | 8 +- common/build.gradle | 4 +- gatling/README.md | 69 - gatling/gatling | 11 - gatling/project/Build.scala | 59 - gatling/src/main/ab/login_marissa.txt | 1 - gatling/src/main/resources/application.conf | 9 - .../main/resources/assets/js/highcharts.js | 12600 ---------------- .../src/main/resources/assets/js/highstock.js | 238 - .../main/resources/assets/js/jquery.min.js | 4 - gatling/src/main/resources/assets/js/theme.js | 127 - .../src/main/resources/assets/style/cible.png | Bin 1761 -> 0 bytes .../src/main/resources/assets/style/logo.png | Bin 28727 -> 0 bytes .../assets/style/sou-menu-fleche-ouvert.png | Bin 330 -> 0 bytes .../assets/style/sous-menu-fleche.png | Bin 278 -> 0 bytes .../assets/style/stat-fleche-bas.png | Bin 625 -> 0 bytes .../main/resources/assets/style/stat-fond.png | Bin 305 -> 0 bytes .../resources/assets/style/stat-l-roue.png | Bin 517 -> 0 bytes .../resources/assets/style/stat-l-temps.png | Bin 260 -> 0 bytes .../src/main/resources/assets/style/style.css | 133 - gatling/src/main/resources/gatling.conf | 27 - gatling/src/main/resources/logback.xml | 20 - .../main/scala/AccountLockoutSimulation.scala | 29 - .../main/scala/AuthCodeFlowSimulation.scala | 19 - .../main/scala/ScimWorkoutSimulation.scala | 59 - .../scala/UaaBaseDataCreationSimulation.scala | 46 - .../src/main/scala/UaaSmokeSimulation.scala | 91 - gatling/src/main/scala/VarzSimulation.scala | 38 - gatling/src/main/scala/acm/AcmApi.scala | 126 - gatling/src/main/scala/acm/Config.scala | 31 - gatling/src/main/scala/uaa/Config.scala | 88 - .../src/main/scala/uaa/OAuthComponents.scala | 233 - gatling/src/main/scala/uaa/ScimApi.scala | 220 - gatling/src/main/scala/uaa/ScimFeeders.scala | 96 - .../scala/uaa/UsernamePasswordFeeder.scala | 32 - gatling/src/main/scala/uaa/uaaApis.scala | 53 - gatling/src/test/scala/Engine.scala | 13 - gatling/src/test/scala/IDEPathHelper.scala | 19 - gatling/src/test/scala/Recorder.scala | 10 - login/build.gradle | 4 +- {models => model}/build.gradle | 2 +- {models => model}/build_properties.gradle | 0 .../identity/uaa/codestore/ExpiringCode.java | 0 .../identity/uaa/constants/OriginKeys.java | 0 .../uaa/impl/JsonDateDeserializer.java | 0 .../identity/uaa/impl/JsonDateSerializer.java | 0 .../uaa/invitations/InvitationsRequest.java | 0 .../uaa/invitations/InvitationsResponse.java | 0 .../uaa/login/AuthenticationResponse.java | 0 .../identity/uaa/login/AutologinRequest.java | 0 .../identity/uaa/login/AutologinResponse.java | 0 .../identity/uaa/oauth/approval/Approval.java | 0 .../impl/ApprovalsJsonDeserializer.java | 0 .../uaa/oauth/client/ClientConstants.java | 0 .../client/ClientDetailsModification.java | 0 .../uaa/oauth/client/SecretChangeRequest.java | 0 .../uaa/oauth/token/ClaimConstants.java | 0 .../identity/uaa/oauth/token/Claims.java | 0 .../uaa/oauth/token/CompositeAccessToken.java | 0 .../CompositeAccessTokenDeserializer.java | 0 .../token/CompositeAccessTokenSerializer.java | 0 .../oauth/token/VerificationKeyResponse.java | 0 .../token/VerificationKeysListResponse.java | 0 .../identity/uaa/profile/EmailChange.java | 0 .../uaa/profile/EmailChangeResponse.java | 0 .../uaa/profile/PasswordChangeRequest.java | 0 .../uaa/profile/PasswordChangeResponse.java | 0 .../uaa/profile/PasswordResetResponse.java | 0 .../uaa/profile/UserInfoResponse.java | 0 .../AbstractIdentityProviderDefinition.java | 0 .../ExternalIdentityProviderDefinition.java | 0 .../uaa/provider/IdentityProvider.java | 0 .../KeystoneIdentityProviderDefinition.java | 0 .../LdapIdentityProviderDefinition.java | 0 .../identity/uaa/provider/LockoutPolicy.java | 0 .../identity/uaa/provider/PasswordPolicy.java | 0 .../SamlIdentityProviderDefinition.java | 0 .../UaaIdentityProviderDefinition.java | 0 .../identity/uaa/resources/ActionResult.java | 0 .../identity/uaa/resources/SearchResults.java | 0 .../identity/uaa/scim/ScimCore.java | 0 .../identity/uaa/scim/ScimGroup.java | 0 .../uaa/scim/ScimGroupExternalMember.java | 0 .../identity/uaa/scim/ScimGroupMember.java | 0 .../identity/uaa/scim/ScimMeta.java | 0 .../identity/uaa/scim/ScimUser.java | 0 .../scim/impl/ScimGroupJsonDeserializer.java | 0 .../scim/impl/ScimGroupJsonSerializer.java | 0 .../scim/impl/ScimUserJsonDeserializer.java | 0 .../identity/uaa/util/JsonUtils.java | 0 .../identity/uaa/util/ObjectUtils.java | 0 .../identity/uaa/zone/IdentityZone.java | 0 .../uaa/zone/IdentityZoneConfiguration.java | 0 .../identity/uaa/zone/KeyPair.java | 0 .../identity/uaa/zone/KeyPairsMap.java | 0 .../identity/uaa/zone/SamlConfig.java | 0 .../identity/uaa/zone/TokenPolicy.java | 0 .../src/main/resources/.gitignore | 0 scim/build.gradle | 4 +- settings.gradle | 4 +- uaa/build.gradle | 2 +- 102 files changed, 15 insertions(+), 14516 deletions(-) delete mode 100644 gatling/README.md delete mode 100755 gatling/gatling delete mode 100644 gatling/project/Build.scala delete mode 100644 gatling/src/main/ab/login_marissa.txt delete mode 100644 gatling/src/main/resources/application.conf delete mode 100644 gatling/src/main/resources/assets/js/highcharts.js delete mode 100644 gatling/src/main/resources/assets/js/highstock.js delete mode 100644 gatling/src/main/resources/assets/js/jquery.min.js delete mode 100644 gatling/src/main/resources/assets/js/theme.js delete mode 100644 gatling/src/main/resources/assets/style/cible.png delete mode 100644 gatling/src/main/resources/assets/style/logo.png delete mode 100644 gatling/src/main/resources/assets/style/sou-menu-fleche-ouvert.png delete mode 100644 gatling/src/main/resources/assets/style/sous-menu-fleche.png delete mode 100644 gatling/src/main/resources/assets/style/stat-fleche-bas.png delete mode 100644 gatling/src/main/resources/assets/style/stat-fond.png delete mode 100644 gatling/src/main/resources/assets/style/stat-l-roue.png delete mode 100644 gatling/src/main/resources/assets/style/stat-l-temps.png delete mode 100644 gatling/src/main/resources/assets/style/style.css delete mode 100644 gatling/src/main/resources/gatling.conf delete mode 100644 gatling/src/main/resources/logback.xml delete mode 100644 gatling/src/main/scala/AccountLockoutSimulation.scala delete mode 100644 gatling/src/main/scala/AuthCodeFlowSimulation.scala delete mode 100644 gatling/src/main/scala/ScimWorkoutSimulation.scala delete mode 100644 gatling/src/main/scala/UaaBaseDataCreationSimulation.scala delete mode 100644 gatling/src/main/scala/UaaSmokeSimulation.scala delete mode 100644 gatling/src/main/scala/VarzSimulation.scala delete mode 100644 gatling/src/main/scala/acm/AcmApi.scala delete mode 100644 gatling/src/main/scala/acm/Config.scala delete mode 100644 gatling/src/main/scala/uaa/Config.scala delete mode 100644 gatling/src/main/scala/uaa/OAuthComponents.scala delete mode 100644 gatling/src/main/scala/uaa/ScimApi.scala delete mode 100644 gatling/src/main/scala/uaa/ScimFeeders.scala delete mode 100644 gatling/src/main/scala/uaa/UsernamePasswordFeeder.scala delete mode 100644 gatling/src/main/scala/uaa/uaaApis.scala delete mode 100644 gatling/src/test/scala/Engine.scala delete mode 100644 gatling/src/test/scala/IDEPathHelper.scala delete mode 100644 gatling/src/test/scala/Recorder.scala rename {models => model}/build.gradle (94%) rename {models => model}/build_properties.gradle (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java (100%) rename {models => model}/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java (100%) rename {models => model}/src/main/resources/.gitignore (100%) diff --git a/build.gradle b/build.gradle index db1ed91cfb3..9cf22898dcc 100644 --- a/build.gradle +++ b/build.gradle @@ -259,7 +259,7 @@ project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> apply plugin: 'com.github.kt3k.coveralls' -Project identityModels = subprojects.find { it.name.equals('cloudfoundry-identity-models') } +Project identityModel = subprojects.find { it.name.equals('cloudfoundry-identity-model') } Project identityCommon = subprojects.find { it.name.equals('cloudfoundry-identity-common') } Project identityScim = subprojects.find { it.name.equals('cloudfoundry-identity-scim') } Project identityLogin = subprojects.find { it.name.equals('cloudfoundry-identity-login') } diff --git a/client-lib/build.gradle b/client-lib/build.gradle index 872d127409e..575d8e0d081 100644 --- a/client-lib/build.gradle +++ b/client-lib/build.gradle @@ -1,12 +1,12 @@ -Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } +Project identityModel = parent.subprojects.find { it.name.equals('cloudfoundry-identity-model') } description = 'CloudFoundry Identity Client Library Jar' dependencies { - compile identityModels + compile identityModel testCompile group: 'junit', name: 'junit', version:parent.junitVersion - testCompile identityModels.configurations.testCompile.dependencies - testCompile identityModels.sourceSets.test.output + testCompile identityModel.configurations.testCompile.dependencies + testCompile identityModel.sourceSets.test.output } processResources { diff --git a/common/build.gradle b/common/build.gradle index 7efa297094a..85f10e8e9e0 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -1,9 +1,9 @@ -Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } +Project identityModel = parent.subprojects.find { it.name.equals('cloudfoundry-identity-model') } description = 'CloudFoundry Identity Common Jar' dependencies { - compile identityModels + compile identityModel compile group: 'org.passay', name: 'passay', version:'1.0' compile group: 'com.google.guava', name: 'guava', version: '18.0' compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version:parent.bcpkixVersion diff --git a/gatling/README.md b/gatling/README.md deleted file mode 100644 index 70d118f20cb..00000000000 --- a/gatling/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# Gatling Test Scripts - -## Overview - -This is a self-contained module containing scripts written for the Gatling performance tool -(https://github.com/excilys/gatling/). The tests can be used to smoke-test a UAA instance or adjusted to -perform load or performance testing. - -## Usage - -The project is designed to run gatling using the Scala `sbt` build tool. - -### Targeting a UAA - -The UAA instance to target will be derived either from the environment variable `VCAP_BVT_TARGET` or from -the target setting in the Yeti `~/.bvt/config.yml` file. If neither of these are available, the scripts will be run -against the URL `http://localhost:8080/uaa`. The UAA admin client secret of your installation should be stored in -the environment variable `VCAP_BVT_ADMIN_SECRET`. Alternatively, you can modify the file `src/main/scala/uaa/Config`. - -Running the script is slow as it runs the scala compiler each time. Using `sbt` is a much more efficient option. - - -### Running with `sbt` - -Install `sbt` version 0.12 as described in [the bt website](http://www.scala-sbt.org/release/docs/Getting-Started/Setup). -The gatling directory is also an `sbt` project and includes a custom gatling plugin. You don't need to download the gatling -bundle in this case. The required jars will be downloaded by `sbt`. - -Run sbt, and then type the `gatling` command from within the `sbt` console: - - $ sbt - [info] Loading global plugins from /home/luke/.sbt/plugins - [info] Loading project definition from /home/luke/uaa/gatling/project - [info] Set current project to gatling (in build file:/home/luke/uaa/gatling/) - > gatling - - Choose a simulation number: - [0] AccountLockoutSimulation - [1] ScimWorkoutSimulation - [2] UaaBaseDataCreationSimulation - [3] UaaSmokeSimulation - [4] VarzSimulation - -The environment variables for the UAA instance can be set as described in the previous section. - -To test a UAA instance, first run the `UaaBaseDataCreationSimulation` to populate the system. This only needs to be done -once. Then try running the `UaaSmokeSimulation` which works out the system using the created data. This simulation -runs for a fixed duration (600 seconds, by default). This can be overridden by setting the `GATLING_DURATION` -environment variable to the desired number of seconds. - -## Customization - -The simulation classes have the suffix `Simulation` and reused code is refactored out into classes in the `uaa` -package, keeping the simulation files relatively short. The number of client users and -loop counters (or duration) can be modified in the simulation files to change the load as required. A simulation -consists of a sequence of "scenarios", each of which is configured something like this: - - vmcUserLogins.configure users 100 ramp 10 protocolConfig uaaHttpConfig - -This means run the `cfUserLogins` scenario with 100 test clients and ramp up to full capacity within 10 seconds. - -If you are having problems, you can enable client-side logging by editing the logback configuration file -`src/main/resources/logback.xml`. - - - - - - diff --git a/gatling/gatling b/gatling/gatling deleted file mode 100755 index f55890bf2c6..00000000000 --- a/gatling/gatling +++ /dev/null @@ -1,11 +0,0 @@ -if [ -z "${GATLING_HOME}" ] -then - echo "GATLING_HOME must be set" - exit 1 -fi - -PWD=`pwd` - -echo $PWD - -$GATLING_HOME/bin/gatling.sh -sf $PWD/src/main/scala \ No newline at end of file diff --git a/gatling/project/Build.scala b/gatling/project/Build.scala deleted file mode 100644 index 25296c411e0..00000000000 --- a/gatling/project/Build.scala +++ /dev/null @@ -1,59 +0,0 @@ -import collection.Seq -import sbt._ -import Keys._ - -object GatlingPlugin { - val gatling = TaskKey[Unit]("gatling") - - val gatlingVersion = SettingKey[String]("gatling-version") - val gatlingResultsDirectory = SettingKey[String]("gatling-results-directory") - val gatlingDataDirectory = SettingKey[String]("gatling-data-directory") - val gatlingConfigFile = SettingKey[String]("gatling-config-file") - - lazy val gatlingSettings = Seq( - gatlingVersion := "1.3.5", - fullClasspath in gatling <<= fullClasspath or (fullClasspath in Runtime), - gatlingResultsDirectory <<= target(_.getAbsolutePath + "/gatling-results"), - gatlingDataDirectory <<= (resourceDirectory in Compile).apply(_.getAbsolutePath), - gatlingConfigFile <<= (resourceDirectory in Compile).apply(_.getAbsolutePath + "/gatling.conf"), - trapExit := true, - - libraryDependencies <++= (gatlingVersion) { gv => Seq( - "com.excilys.ebi.gatling" % "gatling-app" % gv, - "com.excilys.ebi.gatling" % "gatling-http" % gv, - "com.excilys.ebi.gatling.highcharts" % "gatling-charts-highcharts" % gv) - }, - - gatling <<= (streams, gatlingResultsDirectory, gatlingDataDirectory, gatlingConfigFile, fullClasspath in gatling, classDirectory in Compile, runner) - map { (s, grd, gdd, gcf, cp, cd, runner) => { - val args = Array("--results-folder", grd, - "--data-folder", gdd, -// "--config-file", gcf, - "--simulations-binaries-folder", cd.absolutePath) - - runner.run("com.excilys.ebi.gatling.app.Gatling", Build.data(cp), args, s.log) - } - } - ) -} - -object UaaGatlingBuild extends Build { - - import GatlingPlugin._ - - val mavenLocalRepo = "Local Maven Repository" at "file://" + Path.userHome.absolutePath +"/.m2/repository" - - val excilysReleaseRepo = "Excilys Release Repo" at "http://repository.excilys.com/content/repositories/releases" - val excilys3rdPartyRepo = "Excilys 3rd Party Repo" at "http://repository.excilys.com/content/repositories/thirdparty" - val jenkinsRepo = "Jenkins Repo" at "http://maven.jenkins-ci.org/content/repositories/releases" - val twitterRepo = "Twitter Repo" at "http://maven.twttr.com" - val typesafeRepo = "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases" - - val buildSettings = Defaults.defaultSettings ++ gatlingSettings ++ Seq ( - scalaVersion := "2.9.3", - gatlingVersion := "1.4.6", - version := "0.1-SNAPSHOT", - resolvers ++= Seq(mavenLocalRepo, excilysReleaseRepo, excilys3rdPartyRepo, jenkinsRepo, typesafeRepo, twitterRepo)) - - lazy val gatling = Project("gatling", file("."), settings = buildSettings) -} diff --git a/gatling/src/main/ab/login_marissa.txt b/gatling/src/main/ab/login_marissa.txt deleted file mode 100644 index 2a60f648eb9..00000000000 --- a/gatling/src/main/ab/login_marissa.txt +++ /dev/null @@ -1 +0,0 @@ -client_id=cf&credentials={"username":"marissa","password":"koala"}&redirect_uri=https://uaa.cloudfoundry.com/redirect/cf&response_type=token diff --git a/gatling/src/main/resources/application.conf b/gatling/src/main/resources/application.conf deleted file mode 100644 index caa076933b1..00000000000 --- a/gatling/src/main/resources/application.conf +++ /dev/null @@ -1,9 +0,0 @@ -#################################### -# Akka Actor Config File # -#################################### - -akka { - scheduler { - tick-duration = 50ms - } -} diff --git a/gatling/src/main/resources/assets/js/highcharts.js b/gatling/src/main/resources/assets/js/highcharts.js deleted file mode 100644 index d03f96af945..00000000000 --- a/gatling/src/main/resources/assets/js/highcharts.js +++ /dev/null @@ -1,12600 +0,0 @@ -// ==ClosureCompiler== -// @compilation_level SIMPLE_OPTIMIZATIONS - -/** - * @license Highcharts JS v2.1.9 (2011-11-11) - * - * (c) 2009-2011 Torstein Hønsi - * - * License: www.highcharts.com/license - */ - -// JSLint options: -/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $ */ - -(function () { -// encapsulated variables -var doc = document, - win = window, - math = Math, - mathRound = math.round, - mathFloor = math.floor, - mathCeil = math.ceil, - mathMax = math.max, - mathMin = math.min, - mathAbs = math.abs, - mathCos = math.cos, - mathSin = math.sin, - mathPI = math.PI, - deg2rad = mathPI * 2 / 360, - - - // some variables - userAgent = navigator.userAgent, - isIE = /msie/i.test(userAgent) && !win.opera, - docMode8 = doc.documentMode === 8, - isWebKit = /AppleWebKit/.test(userAgent), - isFirefox = /Firefox/.test(userAgent), - SVG_NS = 'http://www.w3.org/2000/svg', - hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect, - hasRtlBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38 - Renderer, - hasTouch = doc.documentElement.ontouchstart !== undefined, - symbolSizes = {}, - idCounter = 0, - garbageBin, - defaultOptions, - dateFormat, // function - globalAnimation, - pathAnim, - timeUnits, - - // some constants for frequently used strings - UNDEFINED, - DIV = 'div', - ABSOLUTE = 'absolute', - RELATIVE = 'relative', - HIDDEN = 'hidden', - PREFIX = 'highcharts-', - VISIBLE = 'visible', - PX = 'px', - NONE = 'none', - M = 'M', - L = 'L', - /* - * Empirical lowest possible opacities for TRACKER_FILL - * IE6: 0.002 - * IE7: 0.002 - * IE8: 0.002 - * IE9: 0.00000000001 (unlimited) - * FF: 0.00000000001 (unlimited) - * Chrome: 0.000001 - * Safari: 0.000001 - * Opera: 0.00000000001 (unlimited) - */ - TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.000001 : 0.002) + ')', // invisible but clickable - //TRACKER_FILL = 'rgba(192,192,192,0.5)', - NORMAL_STATE = '', - HOVER_STATE = 'hover', - SELECT_STATE = 'select', - MILLISECOND = 'millisecond', - SECOND = 'second', - MINUTE = 'minute', - HOUR = 'hour', - DAY = 'day', - WEEK = 'week', - MONTH = 'month', - YEAR = 'year', - - // constants for attributes - FILL = 'fill', - LINEAR_GRADIENT = 'linearGradient', - STOPS = 'stops', - STROKE = 'stroke', - STROKE_WIDTH = 'stroke-width', - - // time methods, changed based on whether or not UTC is used - makeTime, - getMinutes, - getHours, - getDay, - getDate, - getMonth, - getFullYear, - setMinutes, - setHours, - setDate, - setMonth, - setFullYear, - - // check for a custom HighchartsAdapter defined prior to this file - globalAdapter = win.HighchartsAdapter, - adapter = globalAdapter || {}, - - // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object - // and all the utility functions will be null. In that case they are populated by the - // default adapters below. - each = adapter.each, - grep = adapter.grep, - offset = adapter.offset, - map = adapter.map, - merge = adapter.merge, - addEvent = adapter.addEvent, - removeEvent = adapter.removeEvent, - fireEvent = adapter.fireEvent, - animate = adapter.animate, - stop = adapter.stop, - - // lookup over the types and the associated classes - seriesTypes = {}; - -// The Highcharts namespace -win.Highcharts = {}; - -function arrayMin(data) { - var i = 1, - min = data[0], - length = data.length; - for (; i < length; i++) { - if (data[i] < min) min = data[i]; - } - return min; -} - -function arrayMax(data) { - var i = 1, - max = data[0], - length = data.length; - for (; i < length; i++) { - if (data[i] > max) max = data[i]; - } - return max; -} - -/** - * Extend an object with the members of another - * @param {Object} a The object to be extended - * @param {Object} b The object to add to the first one - */ -function extend(a, b) { - var n; - if (!a) { - a = {}; - } - for (n in b) { - a[n] = b[n]; - } - return a; -} - -/** - * Take an array and turn into a hash with even number arguments as keys and odd numbers as - * values. Allows creating constants for commonly used style properties, attributes etc. - * Avoid it in performance critical situations like looping - */ -function hash() { - var i = 0, - args = arguments, - length = args.length, - obj = {}; - for (; i < length; i++) { - obj[args[i++]] = args[i]; - } - return obj; -} - -/** - * Shortcut for parseInt - * @param {Object} s - * @param {Number} mag Magnitude - */ -function pInt(s, mag) { - return parseInt(s, mag || 10); -} - -/** - * Check for string - * @param {Object} s - */ -function isString(s) { - return typeof s === 'string'; -} - -/** - * Check for object - * @param {Object} obj - */ -function isObject(obj) { - return typeof obj === 'object'; -} - -/** - * Check for array - * @param {Object} obj - */ -function isArray(obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; -} - -/** - * Check for number - * @param {Object} n - */ -function isNumber(n) { - return typeof n === 'number'; -} - -function log2lin(num) { - return math.log(num) / math.LN10; -} -function lin2log(num) { - return math.pow(10, num); -} - -/** - * Remove last occurence of an item from an array - * @param {Array} arr - * @param {Mixed} item - */ -function erase(arr, item) { - var i = arr.length; - while (i--) { - if (arr[i] === item) { - arr.splice(i, 1); - break; - } - } - //return arr; -} - -/** - * Returns true if the object is not null or undefined. Like MooTools' $.defined. - * @param {Object} obj - */ -function defined(obj) { - return obj !== UNDEFINED && obj !== null; -} - -/** - * Set or get an attribute or an object of attributes. Can't use jQuery attr because - * it attempts to set expando properties on the SVG element, which is not allowed. - * - * @param {Object} elem The DOM element to receive the attribute(s) - * @param {String|Object} prop The property or an abject of key-value pairs - * @param {String} value The value if a single property is set - */ -function attr(elem, prop, value) { - var key, - setAttribute = 'setAttribute', - ret; - - // if the prop is a string - if (isString(prop)) { - // set the value - if (defined(value)) { - - elem[setAttribute](prop, value); - - // get the value - } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo... - ret = elem.getAttribute(prop); - } - - // else if prop is defined, it is a hash of key/value pairs - } else if (defined(prop) && isObject(prop)) { - for (key in prop) { - elem[setAttribute](key, prop[key]); - } - } - return ret; -} -/** - * Check if an element is an array, and if not, make it into an array. Like - * MooTools' $.splat. - */ -function splat(obj) { - return isArray(obj) ? obj : [obj]; -} - - -/** - * Return the first value that is defined. Like MooTools' $.pick. - */ -function pick() { - var args = arguments, - i, - arg, - length = args.length; - for (i = 0; i < length; i++) { - arg = args[i]; - if (typeof arg !== 'undefined' && arg !== null) { - return arg; - } - } -} - -/** - * Set CSS on a given element - * @param {Object} el - * @param {Object} styles Style object with camel case property names - */ -function css(el, styles) { - if (isIE) { - if (styles && styles.opacity !== UNDEFINED) { - styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')'; - } - } - extend(el.style, styles); -} - -/** - * Utility function to create element with attributes and styles - * @param {Object} tag - * @param {Object} attribs - * @param {Object} styles - * @param {Object} parent - * @param {Object} nopad - */ -function createElement(tag, attribs, styles, parent, nopad) { - var el = doc.createElement(tag); - if (attribs) { - extend(el, attribs); - } - if (nopad) { - css(el, {padding: 0, border: NONE, margin: 0}); - } - if (styles) { - css(el, styles); - } - if (parent) { - parent.appendChild(el); - } - return el; -} - -/** - * Extend a prototyped class by new members - * @param {Object} parent - * @param {Object} members - */ -function extendClass(parent, members) { - var object = function () {}; - object.prototype = new parent(); - extend(object.prototype, members); - return object; -} - -/** - * Format a number and return a string based on input settings - * @param {Number} number The input number to format - * @param {Number} decimals The amount of decimals - * @param {String} decPoint The decimal point, defaults to the one given in the lang options - * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options - */ -function numberFormat(number, decimals, decPoint, thousandsSep) { - var lang = defaultOptions.lang, - // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/ - n = number, - c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals, - d = decPoint === undefined ? lang.decimalPoint : decPoint, - t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, - s = n < 0 ? "-" : "", - i = String(pInt(n = mathAbs(+n || 0).toFixed(c))), - j = i.length > 3 ? i.length % 3 : 0; - - return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + - (c ? d + mathAbs(n - i).toFixed(c).slice(2) : ""); -} - -/** - * Based on http://www.php.net/manual/en/function.strftime.php - * @param {String} format - * @param {Number} timestamp - * @param {Boolean} capitalize - */ -dateFormat = function (format, timestamp, capitalize) { - function pad(number, length) { - // two digits - number = number.toString().replace(/^([0-9])$/, '0$1'); - // three digits - if (length === 3) { - number = number.toString().replace(/^([0-9]{2})$/, '0$1'); - } - return number; - } - - if (!defined(timestamp) || isNaN(timestamp)) { - return 'Invalid date'; - } - format = pick(format, '%Y-%m-%d %H:%M:%S'); - - var date = new Date(timestamp), - key, // used in for constuct below - // get the basic time values - hours = date[getHours](), - day = date[getDay](), - dayOfMonth = date[getDate](), - month = date[getMonth](), - fullYear = date[getFullYear](), - lang = defaultOptions.lang, - langWeekdays = lang.weekdays, - /* // uncomment this and the 'W' format key below to enable week numbers - weekNumber = function () { - var clone = new Date(date.valueOf()), - day = clone[getDay]() == 0 ? 7 : clone[getDay](), - dayNumber; - clone.setDate(clone[getDate]() + 4 - day); - dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000); - return 1 + mathFloor(dayNumber / 7); - }, - */ - - // list all format keys - replacements = { - - // Day - 'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon' - 'A': langWeekdays[day], // Long weekday, like 'Monday' - 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31 - 'e': dayOfMonth, // Day of the month, 1 through 31 - - // Week (none implemented) - //'W': weekNumber(), - - // Month - 'b': lang.shortMonths[month], // Short month, like 'Jan' - 'B': lang.months[month], // Long month, like 'January' - 'm': pad(month + 1), // Two digit month number, 01 through 12 - - // Year - 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009 - 'Y': fullYear, // Four digits year, like 2009 - - // Time - 'H': pad(hours), // Two digits hours in 24h format, 00 through 23 - 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11 - 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12 - 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59 - 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM - 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM - 'S': pad(date.getSeconds()), // Two digits seconds, 00 through 59 - 'L': pad(timestamp % 1000, 3) // Milliseconds (naming from Ruby) - }; - - - // do the replaces - for (key in replacements) { - format = format.replace('%' + key, replacements[key]); - } - - // Optionally capitalize the string and return - return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format; -}; - -/** - * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5 - * @param {Number} interval - * @param {Array} multiples - * @param {Number} magnitude - * @param {Object} options - */ -function normalizeTickInterval(interval, multiples, magnitude, options) { - var normalized, i; - - // round to a tenfold of 1, 2, 2.5 or 5 - //magnitude = multiples ? 1 : math.pow(10, mathFloor(math.log(interval) / math.LN10)); - magnitude = pick(magnitude, 1); - normalized = interval / magnitude; - - // multiples for a linear scale - if (!multiples) { - multiples = [1, 2, 2.5, 5, 10]; - //multiples = [1, 2, 2.5, 4, 5, 7.5, 10]; - - // the allowDecimals option - if (options && (options.allowDecimals === false || options.type === 'logarithmic')) { - if (magnitude === 1) { - multiples = [1, 2, 5, 10]; - } else if (magnitude <= 0.1) { - multiples = [1 / magnitude]; - } - } - } - - // normalize the interval to the nearest multiple - for (i = 0; i < multiples.length; i++) { - interval = multiples[i]; - if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) { - break; - } - } - - // multiply back to the correct magnitude - interval *= magnitude; - - return interval; -} - -/** - * Set the tick positions to a time unit that makes sense, for example - * on the first of each month or on every Monday. Return an array - * with the time positions. Used in datetime axes as well as for grouping - * data on a datetime axis. - * - * @param {Number} tickInterval The approximate interval in axis values (ms) - * @param {Number} min The minimum in axis values - * @param {Number} max The maximum in axis values - * @param {Number} startOfWeek - * @param {Array} unitsOption - */ -function getTimeTicks(tickInterval, min, max, startOfWeek, unitsOption) { - var tickPositions = [], - i, - useUTC = defaultOptions.global.useUTC, - units = unitsOption || [[ - MILLISECOND, // unit name - [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples - ], [ - SECOND, - [1, 2, 5, 10, 15, 30] - ], [ - MINUTE, - [1, 2, 5, 10, 15, 30] - ], [ - HOUR, - [1, 2, 3, 4, 6, 8, 12] - ], [ - DAY, - [1, 2] - ], [ - WEEK, - [1, 2] - ], [ - MONTH, - [1, 2, 3, 4, 6] - ], [ - YEAR, - null - ]], - - unit = units[units.length - 1], // default unit is years - interval = timeUnits[unit[0]], - multiples = unit[1]; - - // loop through the units to find the one that best fits the tickInterval - for (i = 0; i < units.length; i++) { - unit = units[i]; - interval = timeUnits[unit[0]]; - multiples = unit[1]; - - - if (units[i + 1]) { - // lessThan is in the middle between the highest multiple and the next unit. - var lessThan = (interval * multiples[multiples.length - 1] + - timeUnits[units[i + 1][0]]) / 2; - - // break and keep the current unit - if (tickInterval <= lessThan) { - break; - } - } - } - - // prevent 2.5 years intervals, though 25, 250 etc. are allowed - if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) { - multiples = [1, 2, 5]; - } - - // get the minimum value by flooring the date - var multitude = normalizeTickInterval(tickInterval / interval, multiples), - minYear, // used in months and years as a basis for Date.UTC() - minDate = new Date(min); - - minDate.setMilliseconds(0); - - if (interval >= timeUnits[SECOND]) { // second - minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 : - multitude * mathFloor(minDate.getSeconds() / multitude)); - } - - if (interval >= timeUnits[MINUTE]) { // minute - minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 : - multitude * mathFloor(minDate[getMinutes]() / multitude)); - } - - if (interval >= timeUnits[HOUR]) { // hour - minDate[setHours](interval >= timeUnits[DAY] ? 0 : - multitude * mathFloor(minDate[getHours]() / multitude)); - } - - if (interval >= timeUnits[DAY]) { // day - minDate[setDate](interval >= timeUnits[MONTH] ? 1 : - multitude * mathFloor(minDate[getDate]() / multitude)); - } - - if (interval >= timeUnits[MONTH]) { // month - minDate[setMonth](interval >= timeUnits[YEAR] ? 0 : - multitude * mathFloor(minDate[getMonth]() / multitude)); - minYear = minDate[getFullYear](); - } - - if (interval >= timeUnits[YEAR]) { // year - minYear -= minYear % multitude; - minDate[setFullYear](minYear); - } - - // week is a special case that runs outside the hierarchy - if (interval === timeUnits[WEEK]) { - // get start of current week, independent of multitude - minDate[setDate](minDate[getDate]() - minDate[getDay]() + - pick(startOfWeek, 1)); - } - - - // get tick positions - i = 1; - minYear = minDate[getFullYear](); - var time = minDate.getTime(), - minMonth = minDate[getMonth](), - minDateDate = minDate[getDate](); - - // iterate and add tick positions at appropriate values - while (time < max) { - tickPositions.push(time); - - // if the interval is years, use Date.UTC to increase years - if (interval === timeUnits[YEAR]) { - time = makeTime(minYear + i * multitude, 0); - - // if the interval is months, use Date.UTC to increase months - } else if (interval === timeUnits[MONTH]) { - time = makeTime(minYear, minMonth + i * multitude); - - // if we're using global time, the interval is not fixed as it jumps - // one hour at the DST crossover - } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) { - time = makeTime(minYear, minMonth, minDateDate + - i * multitude * (interval === timeUnits[DAY] ? 1 : 7)); - - // else, the interval is fixed and we use simple addition - } else { - time += interval * multitude; - } - - i++; - } - // push the last time - tickPositions.push(time); - - - // record information on the chosen unit - for dynamic label formatter - tickPositions.info = { - unitName: unit[0], - unitRange: interval, - count: multitude, - totalRange: interval * multitude - }; - - return tickPositions; -} - -/** - * Helper class that contains variuos counters that are local to the chart. - */ -function ChartCounters() { - this.color = 0; - this.symbol = 0; -} - -ChartCounters.prototype = { - /** - * Wraps the color counter if it reaches the specified length. - */ - wrapColor: function (length) { - if (this.color >= length) { - this.color = 0; - } - }, - - /** - * Wraps the symbol counter if it reaches the specified length. - */ - wrapSymbol: function (length) { - if (this.symbol >= length) { - this.symbol = 0; - } - } -}; - -/** - * Utility method extracted from Tooltip code that places a tooltip in a chart without spilling over - * and not covering the point it self. - */ -function placeBox(boxWidth, boxHeight, outerLeft, outerTop, outerWidth, outerHeight, point, distance) { - // keep the box within the chart area - var pointX = point.x, - pointY = point.y, - x = pointX - boxWidth + outerLeft - distance, - y = pointY - boxHeight + outerTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip - alignedRight; - - // it is too far to the left, adjust it - if (x < 7) { - x = outerLeft + pointX + distance; - } - - // Test to see if the tooltip is too far to the right, - // if it is, move it back to be inside and then up to not cover the point. - if ((x + boxWidth) > (outerLeft + outerWidth)) { - x -= (x + boxWidth) - (outerLeft + outerWidth); - y = pointY - boxHeight + outerTop - distance; - alignedRight = true; - } - - // if it is now above the plot area, align it to the top of the plot area - if (y < outerTop + 5) { - y = outerTop + 5; - - // If the tooltip is still covering the point, move it below instead - if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) { - y = pointY + outerTop + distance; // below - } - } else if (y + boxHeight > outerTop + outerHeight) { - y = outerTop + outerHeight - boxHeight - distance; // below - } - - return {x: x, y: y}; -} - -/** - * Utility method that sorts an object array and keeping the order of equal items. - * ECMA script standard does not specify the behaviour when items are equal. - */ -function stableSort(arr, sortFunction) { - var length = arr.length, - i; - - // Add index to each item - for (i = 0; i < length; i++) { - arr[i].ss_i = i; // stable sort index - } - - arr.sort(function (a, b) { - var sortValue = sortFunction(a, b); - return sortValue === 0 ? a.ss_i - b.ss_i : sortValue; - }); - - // Remove index from items - for (i = 0; i < length; i++) { - delete arr[i].ss_i; // stable sort index - } -} - -/** - * Utility method that destroys any SVGElement or VMLElement that are properties on the given object. - * It loops all properties and invokes destroy if there is a destroy method. The property is - * then delete'ed. - */ -function destroyObjectProperties(obj) { - var n; - for (n in obj) { - // If the object is non-null and destroy is defined - if (obj[n] && obj[n].destroy) { - // Invoke the destroy - obj[n].destroy(); - } - - // Delete the property from the object. - delete obj[n]; - } -} - -/** - * The time unit lookup - */ -/*jslint white: true*/ -timeUnits = hash( - MILLISECOND, 1, - SECOND, 1000, - MINUTE, 60000, - HOUR, 3600000, - DAY, 24 * 3600000, - WEEK, 7 * 24 * 3600000, - MONTH, 30 * 24 * 3600000, - YEAR, 31556952000 -); -/*jslint white: false*/ -/** - * Path interpolation algorithm used across adapters - */ -pathAnim = { - /** - * Prepare start and end values so that the path can be animated one to one - */ - init: function (elem, fromD, toD) { - fromD = fromD || ''; - var shift = elem.shift, - bezier = fromD.indexOf('C') > -1, - numParams = bezier ? 7 : 3, - endLength, - slice, - i, - start = fromD.split(' '), - end = [].concat(toD), // copy - startBaseLine, - endBaseLine, - sixify = function (arr) { // in splines make move points have six parameters like bezier curves - i = arr.length; - while (i--) { - if (arr[i] === M) { - arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]); - } - } - }; - - if (bezier) { - sixify(start); - sixify(end); - } - - // pull out the base lines before padding - if (elem.isArea) { - startBaseLine = start.splice(start.length - 6, 6); - endBaseLine = end.splice(end.length - 6, 6); - } - - // if shifting points, prepend a dummy point to the end path - if (shift === 1) { - - end = [].concat(end).splice(0, numParams).concat(end); - } - elem.shift = 0; // reset for following animations - - // copy and append last point until the length matches the end length - if (start.length) { - endLength = end.length; - while (start.length < endLength) { - - //bezier && sixify(start); - slice = [].concat(start).splice(start.length - numParams, numParams); - if (bezier) { // disable first control point - slice[numParams - 6] = slice[numParams - 2]; - slice[numParams - 5] = slice[numParams - 1]; - } - start = start.concat(slice); - } - } - - if (startBaseLine) { // append the base lines for areas - start = start.concat(startBaseLine); - end = end.concat(endBaseLine); - } - return [start, end]; - }, - - /** - * Interpolate each value of the path and return the array - */ - step: function (start, end, pos, complete) { - var ret = [], - i = start.length, - startVal; - - if (pos === 1) { // land on the final path without adjustment points appended in the ends - ret = complete; - - } else if (i === end.length && pos < 1) { - while (i--) { - startVal = parseFloat(start[i]); - ret[i] = - isNaN(startVal) ? // a letter instruction like M or L - start[i] : - pos * (parseFloat(end[i] - startVal)) + startVal; - - } - } else { // if animation is finished or length not matching, land on right value - ret = end; - } - return ret; - } -}; - - -/** - * Set the global animation to either a given value, or fall back to the - * given chart's animation option - * @param {Object} animation - * @param {Object} chart - */ -function setAnimation(animation, chart) { - globalAnimation = pick(animation, chart.animation); -} - -/* - * Define the adapter for frameworks. If an external adapter is not defined, - * Highcharts reverts to the built-in jQuery adapter. - */ -if (globalAdapter && globalAdapter.init) { - // Initialize the adapter with the pathAnim object that takes care - // of path animations. - globalAdapter.init(pathAnim); -} -if (!globalAdapter && win.jQuery) { - var jQ = jQuery; - - /** - * Utility for iterating over an array. Parameters are reversed compared to jQuery. - * @param {Array} arr - * @param {Function} fn - */ - each = function (arr, fn) { - var i = 0, - len = arr.length; - for (; i < len; i++) { - if (fn.call(arr[i], arr[i], i, arr) === false) { - return i; - } - } - }; - - /** - * Filter an array - */ - grep = jQ.grep; - - /** - * Map an array - * @param {Array} arr - * @param {Function} fn - */ - map = function (arr, fn) { - //return jQuery.map(arr, fn); - var results = [], - i = 0, - len = arr.length; - for (; i < len; i++) { - results[i] = fn.call(arr[i], arr[i], i, arr); - } - return results; - - }; - - /** - * Deep merge two objects and return a third object - */ - merge = function () { - var args = arguments; - return jQ.extend(true, null, args[0], args[1], args[2], args[3]); - }; - - /** - * Get the position of an element relative to the top left of the page - */ - offset = function (el) { - return jQ(el).offset(); - }; - - /** - * Add an event listener - * @param {Object} el A HTML element or custom object - * @param {String} event The event type - * @param {Function} fn The event handler - */ - addEvent = function (el, event, fn) { - jQ(el).bind(event, fn); - }; - - /** - * Remove event added with addEvent - * @param {Object} el The object - * @param {String} eventType The event type. Leave blank to remove all events. - * @param {Function} handler The function to remove - */ - removeEvent = function (el, eventType, handler) { - // workaround for jQuery issue with unbinding custom events: - // http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jquery-1-4-2 - var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent'; - if (doc[func] && !el[func]) { - el[func] = function () {}; - } - - jQ(el).unbind(eventType, handler); - }; - - /** - * Fire an event on a custom object - * @param {Object} el - * @param {String} type - * @param {Object} eventArguments - * @param {Function} defaultFunction - */ - fireEvent = function (el, type, eventArguments, defaultFunction) { - var event = jQ.Event(type), - detachedType = 'detached' + type; - extend(event, eventArguments); - - // Prevent jQuery from triggering the object method that is named the - // same as the event. For example, if the event is 'select', jQuery - // attempts calling el.select and it goes into a loop. - if (el[type]) { - el[detachedType] = el[type]; - el[type] = null; - } - - // trigger it - jQ(el).trigger(event); - - // attach the method - if (el[detachedType]) { - el[type] = el[detachedType]; - el[detachedType] = null; - } - - if (defaultFunction && !event.isDefaultPrevented()) { - defaultFunction(event); - } - }; - - /** - * Animate a HTML element or SVG element wrapper - * @param {Object} el - * @param {Object} params - * @param {Object} options jQuery-like animation options: duration, easing, callback - */ - animate = function (el, params, options) { - var $el = jQ(el); - if (params.d) { - el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d - params.d = 1; // because in jQuery, animating to an array has a different meaning - } - - $el.stop(); - $el.animate(params, options); - - }; - /** - * Stop running animation - */ - stop = function (el) { - jQ(el).stop(); - }; - - - //=== Extend jQuery on init - - /*jslint unparam: true*//* allow unused param x in this function */ - jQ.extend(jQ.easing, { - easeOutQuad: function (x, t, b, c, d) { - return -c * (t /= d) * (t - 2) + b; - } - }); - /*jslint unparam: false*/ - - // extend the animate function to allow SVG animations - var jFx = jQuery.fx, - jStep = jFx.step; - - // extend some methods to check for elem.attr, which means it is a Highcharts SVG object - each(['cur', '_default', 'width', 'height'], function (fn, i) { - var obj = i ? jStep : jFx.prototype, // 'cur', the getter' relates to jFx.prototype - base = obj[fn], - elem; - - if (base) { // step.width and step.height don't exist in jQuery < 1.7 - - // create the extended function replacement - obj[fn] = function (fx) { - - // jFx.prototype.cur does not use fx argument - fx = i ? fx : this; - - // shortcut - elem = fx.elem; - - // jFX.prototype.cur returns the current value. The other ones are setters - // and returning a value has no effect. - return elem.attr ? // is SVG element wrapper - elem.attr(fx.prop, fx.now) : // apply the SVG wrapper's method - base.apply(this, arguments); // use jQuery's built-in method - }; - } - }); - - // animate paths - jStep.d = function (fx) { - var elem = fx.elem; - - - // Normally start and end should be set in state == 0, but sometimes, - // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped - // in these cases - if (!fx.started) { - var ends = pathAnim.init(elem, elem.d, elem.toD); - fx.start = ends[0]; - fx.end = ends[1]; - fx.started = true; - } - - - // interpolate each value of the path - elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD)); - - }; -} - -/** - * Set the time methods globally based on the useUTC option. Time method can be either - * local time or UTC (default). - */ -function setTimeMethods() { - var useUTC = defaultOptions.global.useUTC; - - makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) { - return new Date( - year, - month, - pick(date, 1), - pick(hours, 0), - pick(minutes, 0), - pick(seconds, 0) - ).getTime(); - }; - getMinutes = useUTC ? 'getUTCMinutes' : 'getMinutes'; - getHours = useUTC ? 'getUTCHours' : 'getHours'; - getDay = useUTC ? 'getUTCDay' : 'getDay'; - getDate = useUTC ? 'getUTCDate' : 'getDate'; - getMonth = useUTC ? 'getUTCMonth' : 'getMonth'; - getFullYear = useUTC ? 'getUTCFullYear' : 'getFullYear'; - setMinutes = useUTC ? 'setUTCMinutes' : 'setMinutes'; - setHours = useUTC ? 'setUTCHours' : 'setHours'; - setDate = useUTC ? 'setUTCDate' : 'setDate'; - setMonth = useUTC ? 'setUTCMonth' : 'setMonth'; - setFullYear = useUTC ? 'setUTCFullYear' : 'setFullYear'; - -} - -/** - * Merge the default options with custom options and return the new options structure - * @param {Object} options The new custom options - */ -function setOptions(options) { - defaultOptions = merge(defaultOptions, options); - - // apply UTC - setTimeMethods(); - - return defaultOptions; -} - -/** - * Get the updated default options. Merely exposing defaultOptions for outside modules - * isn't enough because the setOptions method creates a new object. - */ -function getOptions() { - return defaultOptions; -} - -/** - * Discard an element by moving it to the bin and delete - * @param {Object} The HTML node to discard - */ -function discardElement(element) { - // create a garbage bin element, not part of the DOM - if (!garbageBin) { - garbageBin = createElement(DIV); - } - - // move the node and empty bin - if (element) { - garbageBin.appendChild(element); - } - garbageBin.innerHTML = ''; -} - -/* **************************************************************************** - * Handle the options * - *****************************************************************************/ -var - -defaultLabelOptions = { - enabled: true, - // rotation: 0, - align: 'center', - x: 0, - y: 15, - /*formatter: function () { - return this.value; - },*/ - style: { - color: '#666', - fontSize: '11px', - lineHeight: '14px' - } -}; - -defaultOptions = { - colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', - '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'], - symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'], - lang: { - loading: 'Loading...', - months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', - 'August', 'September', 'October', 'November', 'December'], - shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - decimalPoint: '.', - resetZoom: 'Reset zoom', - resetZoomTitle: 'Reset zoom level 1:1', - thousandsSep: ',' - }, - global: { - useUTC: true - }, - chart: { - //animation: true, - //alignTicks: false, - //reflow: true, - //className: null, - //events: { load, selection }, - //margin: [null], - //marginTop: null, - //marginRight: null, - //marginBottom: null, - //marginLeft: null, - borderColor: '#4572A7', - //borderWidth: 0, - borderRadius: 5, - defaultSeriesType: 'line', - ignoreHiddenSeries: true, - //inverted: false, - //shadow: false, - spacingTop: 10, - spacingRight: 10, - spacingBottom: 15, - spacingLeft: 10, - style: { - fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font - fontSize: '12px' - }, - backgroundColor: '#FFFFFF', - //plotBackgroundColor: null, - plotBorderColor: '#C0C0C0' - //plotBorderWidth: 0, - //plotShadow: false, - //zoomType: '' - }, - title: { - text: 'Chart title', - align: 'center', - // floating: false, - // margin: 15, - // x: 0, - // verticalAlign: 'top', - y: 15, - style: { - color: '#3E576F', - fontSize: '16px' - } - - }, - subtitle: { - text: '', - align: 'center', - // floating: false - // x: 0, - // verticalAlign: 'top', - y: 30, - style: { - color: '#6D869F' - } - }, - - plotOptions: { - line: { // base series options - allowPointSelect: false, - showCheckbox: false, - animation: { - duration: 1000 - }, - //connectNulls: false, - //cursor: 'default', - //clip: true, - //dashStyle: null, - //enableMouseTracking: true, - events: {}, - //legendIndex: 0, - lineWidth: 2, - shadow: true, - // stacking: null, - marker: { - enabled: true, - //symbol: null, - lineWidth: 0, - radius: 4, - lineColor: '#FFFFFF', - //fillColor: null, - states: { // states for a single point - hover: { - //radius: base + 2 - }, - select: { - fillColor: '#FFFFFF', - lineColor: '#000000', - lineWidth: 2 - } - } - }, - point: { - events: {} - }, - dataLabels: merge(defaultLabelOptions, { - enabled: false, - y: -6, - formatter: function () { - return this.y; - } - }), - cropThreshold: 300, // draw points outside the plot area when the number of points is less than this - pointRange: 0, - //pointStart: 0, - //pointInterval: 1, - showInLegend: true, - states: { // states for the entire series - hover: { - //enabled: false, - //lineWidth: base + 1, - marker: { - // lineWidth: base + 1, - // radius: base + 1 - } - }, - select: { - marker: {} - } - }, - stickyTracking: true - //tooltip: { - //pointFormat: '{series.name}: {point.y}' - //yDecimals: null, - //xDateFormat: '%A, %b %e, %Y', - //yPrefix: '', - //ySuffix: '' - //} - // turboThreshold: 1000 - // zIndex: null - } - }, - labels: { - //items: [], - style: { - //font: defaultFont, - position: ABSOLUTE, - color: '#3E576F' - } - }, - legend: { - enabled: true, - align: 'center', - //floating: false, - layout: 'horizontal', - labelFormatter: function () { - return this.name; - }, - borderWidth: 1, - borderColor: '#909090', - borderRadius: 5, - // margin: 10, - // reversed: false, - shadow: false, - // backgroundColor: null, - style: { - padding: '5px' - }, - itemStyle: { - cursor: 'pointer', - color: '#3E576F' - }, - itemHoverStyle: { - //cursor: 'pointer', removed as of #601 - color: '#000000' - }, - itemHiddenStyle: { - color: '#C0C0C0' - }, - itemCheckboxStyle: { - position: ABSOLUTE, - width: '13px', // for IE precision - height: '13px' - }, - // itemWidth: undefined, - symbolWidth: 16, - symbolPadding: 5, - verticalAlign: 'bottom', - // width: undefined, - x: 0, - y: 0 - }, - - loading: { - // hideDuration: 100, - labelStyle: { - fontWeight: 'bold', - position: RELATIVE, - top: '1em' - }, - // showDuration: 0, - style: { - position: ABSOLUTE, - backgroundColor: 'white', - opacity: 0.5, - textAlign: 'center' - } - }, - - tooltip: { - enabled: true, - //crosshairs: null, - backgroundColor: 'rgba(255, 255, 255, .85)', - borderWidth: 2, - borderRadius: 5, - //formatter: defaultFormatter, - headerFormat: '{point.key}
', - pointFormat: '{series.name}: {point.y}
', - shadow: true, - //shared: false, - snap: hasTouch ? 25 : 10, - style: { - color: '#333333', - fontSize: '12px', - padding: '5px', - whiteSpace: 'nowrap' - } - //xDateFormat: '%A, %b %e, %Y', - //yDecimals: null, - //yPrefix: '', - //ySuffix: '' - }, - - toolbar: { - itemStyle: { - color: '#4572A7', - cursor: 'pointer' - } - }, - - credits: { - enabled: true, - text: 'Highcharts.com', - href: 'http://www.highcharts.com', - position: { - align: 'right', - x: -10, - verticalAlign: 'bottom', - y: -5 - }, - style: { - cursor: 'pointer', - color: '#909090', - fontSize: '10px' - } - } -}; - -// Axis defaults -/*jslint white: true*/ -var defaultXAxisOptions = { - // allowDecimals: null, - // alternateGridColor: null, - // categories: [], - dateTimeLabelFormats: hash( - MILLISECOND, '%H:%M:%S.%L', - SECOND, '%H:%M:%S', - MINUTE, '%H:%M', - HOUR, '%H:%M', - DAY, '%e. %b', - WEEK, '%e. %b', - MONTH, '%b \'%y', - YEAR, '%Y' - ), - endOnTick: false, - gridLineColor: '#C0C0C0', - // gridLineDashStyle: 'solid', - // gridLineWidth: 0, - // reversed: false, - - labels: defaultLabelOptions, - // { step: null }, - lineColor: '#C0D0E0', - lineWidth: 1, - //linkedTo: null, - max: null, - min: null, - minPadding: 0.01, - maxPadding: 0.01, - //minRange: null, // docs - minorGridLineColor: '#E0E0E0', - // minorGridLineDashStyle: null, - minorGridLineWidth: 1, - minorTickColor: '#A0A0A0', - //minorTickInterval: null, - minorTickLength: 2, - minorTickPosition: 'outside', // inside or outside - //minorTickWidth: 0, - //opposite: false, - //offset: 0, - //plotBands: [{ - // events: {}, - // zIndex: 1, - // labels: { align, x, verticalAlign, y, style, rotation, textAlign } - //}], - //plotLines: [{ - // events: {} - // dashStyle: {} - // zIndex: - // labels: { align, x, verticalAlign, y, style, rotation, textAlign } - //}], - //reversed: false, - // showFirstLabel: true, - // showLastLabel: true, - startOfWeek: 1, - startOnTick: false, - tickColor: '#C0D0E0', - //tickInterval: null, - tickLength: 5, - tickmarkPlacement: 'between', // on or between - tickPixelInterval: 100, - tickPosition: 'outside', - tickWidth: 1, - title: { - //text: null, - align: 'middle', // low, middle or high - //margin: 0 for horizontal, 10 for vertical axes, - //rotation: 0, - //side: 'outside', - style: { - color: '#6D869F', - //font: defaultFont.replace('normal', 'bold') - fontWeight: 'bold' - } - //x: 0, - //y: 0 - }, - type: 'linear' // linear, logarithmic or datetime -}, - -defaultYAxisOptions = merge(defaultXAxisOptions, { - endOnTick: true, - gridLineWidth: 1, - tickPixelInterval: 72, - showLastLabel: true, - labels: { - align: 'right', - x: -8, - y: 3 - }, - lineWidth: 0, - maxPadding: 0.05, - minPadding: 0.05, - startOnTick: true, - tickWidth: 0, - title: { - rotation: 270, - text: 'Y-values' - }, - stackLabels: { - enabled: false, - //align: dynamic, - //y: dynamic, - //x: dynamic, - //verticalAlign: dynamic, - //textAlign: dynamic, - //rotation: 0, - formatter: function () { - return this.total; - }, - style: defaultLabelOptions.style - } -}), - -defaultLeftAxisOptions = { - labels: { - align: 'right', - x: -8, - y: null - }, - title: { - rotation: 270 - } -}, -defaultRightAxisOptions = { - labels: { - align: 'left', - x: 8, - y: null - }, - title: { - rotation: 90 - } -}, -defaultBottomAxisOptions = { // horizontal axis - labels: { - align: 'center', - x: 0, - y: 14 - // staggerLines: null - }, - title: { - rotation: 0 - } -}, -defaultTopAxisOptions = merge(defaultBottomAxisOptions, { - labels: { - y: -5 - // staggerLines: null - } -}); -/*jslint white: false*/ - - - -// Series defaults -var defaultPlotOptions = defaultOptions.plotOptions, - defaultSeriesOptions = defaultPlotOptions.line; -//defaultPlotOptions.line = merge(defaultSeriesOptions); -defaultPlotOptions.spline = merge(defaultSeriesOptions); -defaultPlotOptions.scatter = merge(defaultSeriesOptions, { - lineWidth: 0, - states: { - hover: { - lineWidth: 0 - } - }, - tooltip: { - headerFormat: '{series.name}
', - pointFormat: 'x: {point.x}
y: {point.y}
' - } -}); -defaultPlotOptions.area = merge(defaultSeriesOptions, { - threshold: 0 - // lineColor: null, // overrides color, but lets fillColor be unaltered - // fillOpacity: 0.75, - // fillColor: null - -}); -defaultPlotOptions.areaspline = merge(defaultPlotOptions.area); -defaultPlotOptions.column = merge(defaultSeriesOptions, { - borderColor: '#FFFFFF', - borderWidth: 1, - borderRadius: 0, - //colorByPoint: undefined, - groupPadding: 0.2, - marker: null, // point options are specified in the base options - pointPadding: 0.1, - //pointWidth: null, - minPointLength: 0, - cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes - pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories - states: { - hover: { - brightness: 0.1, - shadow: false - }, - select: { - color: '#C0C0C0', - borderColor: '#000000', - shadow: false - } - }, - dataLabels: { - y: null, - verticalAlign: null - }, - threshold: 0 -}); -defaultPlotOptions.bar = merge(defaultPlotOptions.column, { - dataLabels: { - align: 'left', - x: 5, - y: 0 - } -}); -defaultPlotOptions.pie = merge(defaultSeriesOptions, { - //dragType: '', // n/a - borderColor: '#FFFFFF', - borderWidth: 1, - center: ['50%', '50%'], - colorByPoint: true, // always true for pies - dataLabels: { - // align: null, - // connectorWidth: 1, - // connectorColor: point.color, - // connectorPadding: 5, - distance: 30, - enabled: true, - formatter: function () { - return this.point.name; - }, - // softConnector: true, - y: 5 - }, - //innerSize: 0, - legendType: 'point', - marker: null, // point options are specified in the base options - size: '75%', - showInLegend: false, - slicedOffset: 10, - states: { - hover: { - brightness: 0.1, - shadow: false - } - } - -}); - -// set the default time methods -setTimeMethods(); - - -/** - * Handle color operations. The object methods are chainable. - * @param {String} input The input color in either rbga or hex format - */ -var Color = function (input) { - // declare variables - var rgba = [], result; - - /** - * Parse the input color to rgba array - * @param {String} input - */ - function init(input) { - - // rgba - result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input); - if (result) { - rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)]; - } else { // hex - result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input); - if (result) { - rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1]; - } - } - - } - /** - * Return the color a specified format - * @param {String} format - */ - function get(format) { - var ret; - - // it's NaN if gradient colors on a column chart - if (rgba && !isNaN(rgba[0])) { - if (format === 'rgb') { - ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')'; - } else if (format === 'a') { - ret = rgba[3]; - } else { - ret = 'rgba(' + rgba.join(',') + ')'; - } - } else { - ret = input; - } - return ret; - } - - /** - * Brighten the color - * @param {Number} alpha - */ - function brighten(alpha) { - if (isNumber(alpha) && alpha !== 0) { - var i; - for (i = 0; i < 3; i++) { - rgba[i] += pInt(alpha * 255); - - if (rgba[i] < 0) { - rgba[i] = 0; - } - if (rgba[i] > 255) { - rgba[i] = 255; - } - } - } - return this; - } - /** - * Set the color's opacity to a given alpha value - * @param {Number} alpha - */ - function setOpacity(alpha) { - rgba[3] = alpha; - return this; - } - - // initialize: parse the input - init(input); - - // public methods - return { - get: get, - brighten: brighten, - setOpacity: setOpacity - }; -}; - - -/** - * A wrapper object for SVG elements - */ -function SVGElement() {} - -SVGElement.prototype = { - /** - * Initialize the SVG renderer - * @param {Object} renderer - * @param {String} nodeName - */ - init: function (renderer, nodeName) { - var wrapper = this; - wrapper.element = doc.createElementNS(SVG_NS, nodeName); - wrapper.renderer = renderer; - /** - * A collection of attribute setters. These methods, if defined, are called right before a certain - * attribute is set on an element wrapper. Returning false prevents the default attribute - * setter to run. Returning a value causes the default setter to set that value. Used in - * Renderer.label. - */ - wrapper.attrSetters = {}; - }, - /** - * Animate a given attribute - * @param {Object} params - * @param {Number} options The same options as in jQuery animation - * @param {Function} complete Function to perform at the end of animation - */ - animate: function (params, options, complete) { - var animOptions = pick(options, globalAnimation, true); - if (animOptions) { - animOptions = merge(animOptions); - if (complete) { // allows using a callback with the global animation without overwriting it - animOptions.complete = complete; - } - animate(this, params, animOptions); - } else { - this.attr(params); - if (complete) { - complete(); - } - } - }, - /** - * Set or get a given attribute - * @param {Object|String} hash - * @param {Mixed|Undefined} val - */ - attr: function (hash, val) { - var wrapper = this, - key, - value, - result, - i, - child, - element = wrapper.element, - nodeName = element.nodeName, - renderer = wrapper.renderer, - skipAttr, - attrSetters = wrapper.attrSetters, - shadows = wrapper.shadows, - htmlNode = wrapper.htmlNode, - hasSetSymbolSize, - ret = wrapper; - - // single key-value pair - if (isString(hash) && defined(val)) { - key = hash; - hash = {}; - hash[key] = val; - } - - // used as a getter: first argument is a string, second is undefined - if (isString(hash)) { - key = hash; - if (nodeName === 'circle') { - key = { x: 'cx', y: 'cy' }[key] || key; - } else if (key === 'strokeWidth') { - key = 'stroke-width'; - } - ret = attr(element, key) || wrapper[key] || 0; - - if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step - ret = parseFloat(ret); - } - - // setter - } else { - - for (key in hash) { - skipAttr = false; // reset - value = hash[key]; - - // check for a specific attribute setter - result = attrSetters[key] && attrSetters[key](value, key); - - if (result !== false) { - - if (result !== UNDEFINED) { - value = result; // the attribute setter has returned a new value to set - } - - // paths - if (key === 'd') { - if (value && value.join) { // join path - value = value.join(' '); - } - if (/(NaN| {2}|^$)/.test(value)) { - value = 'M 0 0'; - } - wrapper.d = value; // shortcut for animations - - // update child tspans x values - } else if (key === 'x' && nodeName === 'text') { - for (i = 0; i < element.childNodes.length; i++) { - child = element.childNodes[i]; - // if the x values are equal, the tspan represents a linebreak - if (attr(child, 'x') === attr(element, 'x')) { - //child.setAttribute('x', value); - attr(child, 'x', value); - } - } - - if (wrapper.rotation) { - attr(element, 'transform', 'rotate(' + wrapper.rotation + ' ' + value + ' ' + - pInt(hash.y || attr(element, 'y')) + ')'); - } - - // apply gradients - } else if (key === 'fill') { - value = renderer.color(value, element, key); - - // circle x and y - } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) { - key = { x: 'cx', y: 'cy' }[key] || key; - - // rectangle border radius - } else if (nodeName === 'rect' && key === 'r') { - attr(element, { - rx: value, - ry: value - }); - skipAttr = true; - - // translation and text rotation - } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') { - wrapper[key] = value; - wrapper.updateTransform(); - skipAttr = true; - - // apply opacity as subnode (required by legacy WebKit and Batik) - } else if (key === 'stroke') { - value = renderer.color(value, element, key); - - // emulate VML's dashstyle implementation - } else if (key === 'dashstyle') { - key = 'stroke-dasharray'; - value = value && value.toLowerCase(); - if (value === 'solid') { - value = NONE; - } else if (value) { - value = value - .replace('shortdashdotdot', '3,1,1,1,1,1,') - .replace('shortdashdot', '3,1,1,1') - .replace('shortdot', '1,1,') - .replace('shortdash', '3,1,') - .replace('longdash', '8,3,') - .replace(/dot/g, '1,3,') - .replace('dash', '4,3,') - .replace(/,$/, '') - .split(','); // ending comma - - i = value.length; - while (i--) { - value[i] = pInt(value[i]) * hash['stroke-width']; - } - value = value.join(','); - } - - // special - } else if (key === 'isTracker') { - wrapper[key] = value; - - // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2 - // is unable to cast them. Test again with final IE9. - } else if (key === 'width') { - value = pInt(value); - - // Text alignment - } else if (key === 'align') { - key = 'text-anchor'; - value = { left: 'start', center: 'middle', right: 'end' }[value]; - - // Title requires a subnode, #431 - } else if (key === 'title') { - var title = doc.createElementNS(SVG_NS, 'title'); - title.appendChild(doc.createTextNode(value)); - element.appendChild(title); - } - - // jQuery animate changes case - if (key === 'strokeWidth') { - key = 'stroke-width'; - } - - // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461) - if (isWebKit && key === 'stroke-width' && value === 0) { - value = 0.000001; - } - - // symbols - if (wrapper.symbolName && /^(x|y|r|start|end|innerR|anchorX|anchorY)/.test(key)) { - - - if (!hasSetSymbolSize) { - wrapper.symbolAttr(hash); - hasSetSymbolSize = true; - } - skipAttr = true; - } - - // let the shadow follow the main element - if (shadows && /^(width|height|visibility|x|y|d|transform)$/.test(key)) { - i = shadows.length; - while (i--) { - attr(shadows[i], key, value); - } - } - - // validate heights - if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) { - value = 0; - } - - - - - if (key === 'text') { - // only one node allowed - wrapper.textStr = value; - if (wrapper.added) { - renderer.buildText(wrapper); - } - } else if (!skipAttr) { - attr(element, key, value); - } - - } - - // Issue #38 - if (htmlNode && (key === 'x' || key === 'y' || - key === 'translateX' || key === 'translateY' || key === 'visibility')) { - var bBox, - arr = htmlNode.length ? htmlNode : [this], - length = arr.length, - itemWrapper, - j; - - for (j = 0; j < length; j++) { - itemWrapper = arr[j]; - bBox = itemWrapper.getBBox(); - htmlNode = itemWrapper.htmlNode; // reassign to child item - css(htmlNode, extend(wrapper.styles, { - left: (bBox.x + (wrapper.translateX || 0)) + PX, - top: (bBox.y + (wrapper.translateY || 0)) + PX - })); - - if (key === 'visibility') { - css(htmlNode, { - visibility: value - }); - } - } - } - - } - - } - return ret; - }, - - /** - * If one of the symbol size affecting parameters are changed, - * check all the others only once for each call to an element's - * .attr() method - * @param {Object} hash - */ - symbolAttr: function (hash) { - var wrapper = this; - - each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) { - wrapper[key] = pick(hash[key], wrapper[key]); - }); - - wrapper.attr({ - d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper) - }); - }, - - /** - * Apply a clipping path to this object - * @param {String} id - */ - clip: function (clipRect) { - return this.attr('clip-path', 'url(' + this.renderer.url + '#' + clipRect.id + ')'); - }, - - /** - * Calculate the coordinates needed for drawing a rectangle crisply and return the - * calculated attributes - * @param {Number} strokeWidth - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - crisp: function (strokeWidth, x, y, width, height) { - - var wrapper = this, - key, - attribs = {}, - values = {}, - normalizer; - - strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0; - normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors - - // normalize for crisp edges - values.x = mathFloor(x || wrapper.x || 0) + normalizer; - values.y = mathFloor(y || wrapper.y || 0) + normalizer; - values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer); - values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer); - values.strokeWidth = strokeWidth; - - for (key in values) { - if (wrapper[key] !== values[key]) { // only set attribute if changed - wrapper[key] = attribs[key] = values[key]; - } - } - - return attribs; - }, - - /** - * Set styles for the element - * @param {Object} styles - */ - css: function (styles) { - /*jslint unparam: true*//* allow unused param a in the regexp function below */ - var elemWrapper = this, - elem = elemWrapper.element, - textWidth = styles && styles.width && elem.nodeName === 'text', - n, - serializedCss = '', - hyphenate = function (a, b) { return '-' + b.toLowerCase(); }; - /*jslint unparam: false*/ - - // convert legacy - if (styles && styles.color) { - styles.fill = styles.color; - } - - // Merge the new styles with the old ones - styles = extend( - elemWrapper.styles, - styles - ); - - // store object - elemWrapper.styles = styles; - - // serialize and set style attribute - if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute - if (textWidth) { - delete styles.width; - } - css(elemWrapper.element, styles); - } else { - for (n in styles) { - serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';'; - } - elemWrapper.attr({ - style: serializedCss - }); - } - - - // re-build text - if (textWidth && elemWrapper.added) { - elemWrapper.renderer.buildText(elemWrapper); - } - - return elemWrapper; - }, - - /** - * Add an event listener - * @param {String} eventType - * @param {Function} handler - */ - on: function (eventType, handler) { - var fn = handler; - // touch - if (hasTouch && eventType === 'click') { - eventType = 'touchstart'; - fn = function (e) { - e.preventDefault(); - handler(); - }; - } - // simplest possible event model for internal use - this.element['on' + eventType] = fn; - return this; - }, - - - /** - * Move an object and its children by x and y values - * @param {Number} x - * @param {Number} y - */ - translate: function (x, y) { - return this.attr({ - translateX: x, - translateY: y - }); - }, - - /** - * Invert a group, rotate and flip - */ - invert: function () { - var wrapper = this; - wrapper.inverted = true; - wrapper.updateTransform(); - return wrapper; - }, - - /** - * Private method to update the transform attribute based on internal - * properties - */ - updateTransform: function () { - var wrapper = this, - translateX = wrapper.translateX || 0, - translateY = wrapper.translateY || 0, - inverted = wrapper.inverted, - rotation = wrapper.rotation, - transform = []; - - // flipping affects translate as adjustment for flipping around the group's axis - if (inverted) { - translateX += wrapper.attr('width'); - translateY += wrapper.attr('height'); - } - - // apply translate - if (translateX || translateY) { - transform.push('translate(' + translateX + ',' + translateY + ')'); - } - - // apply rotation - if (inverted) { - transform.push('rotate(90) scale(-1,1)'); - } else if (rotation) { // text rotation - transform.push('rotate(' + rotation + ' ' + wrapper.x + ' ' + wrapper.y + ')'); - } - - if (transform.length) { - attr(wrapper.element, 'transform', transform.join(' ')); - } - }, - /** - * Bring the element to the front - */ - toFront: function () { - var element = this.element; - element.parentNode.appendChild(element); - return this; - }, - - - /** - * Break down alignment options like align, verticalAlign, x and y - * to x and y relative to the chart. - * - * @param {Object} alignOptions - * @param {Boolean} alignByTranslate - * @param {Object} box The box to align to, needs a width and height - * - */ - align: function (alignOptions, alignByTranslate, box) { - var elemWrapper = this; - - if (!alignOptions) { // called on resize - alignOptions = elemWrapper.alignOptions; - alignByTranslate = elemWrapper.alignByTranslate; - } else { // first call on instanciate - elemWrapper.alignOptions = alignOptions; - elemWrapper.alignByTranslate = alignByTranslate; - if (!box) { // boxes other than renderer handle this internally - elemWrapper.renderer.alignedObjects.push(elemWrapper); - } - } - - box = pick(box, elemWrapper.renderer); - - var align = alignOptions.align, - vAlign = alignOptions.verticalAlign, - x = (box.x || 0) + (alignOptions.x || 0), // default: left align - y = (box.y || 0) + (alignOptions.y || 0), // default: top align - attribs = {}; - - - // align - if (/^(right|center)$/.test(align)) { - x += (box.width - (alignOptions.width || 0)) / - { right: 1, center: 2 }[align]; - } - attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x); - - - // vertical align - if (/^(bottom|middle)$/.test(vAlign)) { - y += (box.height - (alignOptions.height || 0)) / - ({ bottom: 1, middle: 2 }[vAlign] || 1); - - } - attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y); - - // animate only if already placed - elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs); - elemWrapper.placed = true; - elemWrapper.alignAttr = attribs; - - return elemWrapper; - }, - - /** - * Get the bounding box (width, height, x and y) for the element - */ - getBBox: function () { - var bBox, - width, - height, - rotation = this.rotation, - rad = rotation * deg2rad; - - try { // fails in Firefox if the container has display: none - // use extend because IE9 is not allowed to change width and height in case - // of rotation (below) - bBox = extend({}, this.element.getBBox()); - } catch (e) { - bBox = { width: 0, height: 0 }; - } - width = bBox.width; - height = bBox.height; - - // adjust for rotated text - if (rotation) { - bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad)); - bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad)); - } - - return bBox; - }, - - /** - * Show the element - */ - show: function () { - return this.attr({ visibility: VISIBLE }); - }, - - /** - * Hide the element - */ - hide: function () { - return this.attr({ visibility: HIDDEN }); - }, - - /** - * Add the element - * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined - * to append the element to the renderer.box. - */ - add: function (parent) { - - var renderer = this.renderer, - parentWrapper = parent || renderer, - parentNode = parentWrapper.element || renderer.box, - childNodes = parentNode.childNodes, - element = this.element, - zIndex = attr(element, 'zIndex'), - otherElement, - otherZIndex, - i, - inserted; - - // mark as inverted - this.parentInverted = parent && parent.inverted; - - // build formatted text - if (this.textStr !== undefined) { - renderer.buildText(this); - } - - // register html spans in groups - if (parent && this.htmlNode) { - if (!parent.htmlNode) { - parent.htmlNode = []; - } - parent.htmlNode.push(this); - } - - // mark the container as having z indexed children - if (zIndex) { - parentWrapper.handleZ = true; - zIndex = pInt(zIndex); - } - - // insert according to this and other elements' zIndex - if (parentWrapper.handleZ) { // this element or any of its siblings has a z index - for (i = 0; i < childNodes.length; i++) { - otherElement = childNodes[i]; - otherZIndex = attr(otherElement, 'zIndex'); - if (otherElement !== element && ( - // insert before the first element with a higher zIndex - pInt(otherZIndex) > zIndex || - // if no zIndex given, insert before the first element with a zIndex - (!defined(zIndex) && defined(otherZIndex)) - - )) { - parentNode.insertBefore(element, otherElement); - inserted = true; - break; - } - } - } - - // default: append at the end - if (!inserted) { - parentNode.appendChild(element); - } - - // mark as added - this.added = true; - - // fire an event for internal hooks - fireEvent(this, 'add'); - - return this; - }, - - /** - * Removes a child either by removeChild or move to garbageBin. - * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. - */ - safeRemoveChild: function (element) { - var parentNode = element.parentNode; - if (parentNode) { - parentNode.removeChild(element); - } - }, - - /** - * Destroy the element and element wrapper - */ - destroy: function () { - var wrapper = this, - element = wrapper.element || {}, - shadows = wrapper.shadows, - box = wrapper.box, - key, - i; - - // remove events - element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null; - stop(wrapper); // stop running animations - - if (wrapper.clipPath) { - wrapper.clipPath = wrapper.clipPath.destroy(); - } - - // Destroy stops in case this is a gradient object - if (wrapper.stops) { - for (i = 0; i < wrapper.stops.length; i++) { - wrapper.stops[i] = wrapper.stops[i].destroy(); - } - wrapper.stops = null; - } - - // remove element - wrapper.safeRemoveChild(element); - - // destroy shadows - if (shadows) { - each(shadows, function (shadow) { - wrapper.safeRemoveChild(shadow); - }); - } - - // destroy label box - if (box) { - box.destroy(); - } - - // remove from alignObjects - erase(wrapper.renderer.alignedObjects, wrapper); - - for (key in wrapper) { - delete wrapper[key]; - } - - return null; - }, - - /** - * Empty a group element - */ - empty: function () { - var element = this.element, - childNodes = element.childNodes, - i = childNodes.length; - - while (i--) { - element.removeChild(childNodes[i]); - } - }, - - /** - * Add a shadow to the element. Must be done after the element is added to the DOM - * @param {Boolean} apply - */ - shadow: function (apply, group) { - var shadows = [], - i, - shadow, - element = this.element, - - // compensate for inverted plot area - transform = this.parentInverted ? '(-1,-1)' : '(1,1)'; - - - if (apply) { - for (i = 1; i <= 3; i++) { - shadow = element.cloneNode(0); - attr(shadow, { - 'isShadow': 'true', - 'stroke': 'rgb(0, 0, 0)', - 'stroke-opacity': 0.05 * i, - 'stroke-width': 7 - 2 * i, - 'transform': 'translate' + transform, - 'fill': NONE - }); - - if (group) { - group.element.appendChild(shadow); - } else { - element.parentNode.insertBefore(shadow, element); - } - - shadows.push(shadow); - } - - this.shadows = shadows; - } - return this; - - } -}; - - -/** - * The default SVG renderer - */ -var SVGRenderer = function () { - this.init.apply(this, arguments); -}; -SVGRenderer.prototype = { - - Element: SVGElement, - - /** - * Initialize the SVGRenderer - * @param {Object} container - * @param {Number} width - * @param {Number} height - * @param {Boolean} forExport - */ - init: function (container, width, height, forExport) { - var renderer = this, - loc = location, - boxWrapper; - - boxWrapper = renderer.createElement('svg') - .attr({ - xmlns: SVG_NS, - version: '1.1' - }); - container.appendChild(boxWrapper.element); - - // object properties - renderer.box = boxWrapper.element; - renderer.boxWrapper = boxWrapper; - renderer.alignedObjects = []; - renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, ''); // page url used for internal references - renderer.defs = this.createElement('defs').add(); - renderer.forExport = forExport; - renderer.gradients = []; // Array where gradient SvgElements are stored - - renderer.setSize(width, height, false); - - }, - - /** - * Destroys the renderer and its allocated members. - */ - destroy: function () { - var renderer = this, - i, - rendererGradients = renderer.gradients, - rendererDefs = renderer.defs; - renderer.box = null; - renderer.boxWrapper = renderer.boxWrapper.destroy(); - - // Call destroy on all gradient elements - if (rendererGradients) { // gradients are null in VMLRenderer - for (i = 0; i < rendererGradients.length; i++) { - renderer.gradients[i] = rendererGradients[i].destroy(); - } - renderer.gradients = null; - } - - // Defs are null in VMLRenderer - // Otherwise, destroy them here. - if (rendererDefs) { - renderer.defs = rendererDefs.destroy(); - } - - renderer.alignedObjects = null; - - return null; - }, - - /** - * Create a wrapper for an SVG element - * @param {Object} nodeName - */ - createElement: function (nodeName) { - var wrapper = new this.Element(); - wrapper.init(this, nodeName); - return wrapper; - }, - - - /** - * Parse a simple HTML string into SVG tspans - * - * @param {Object} textNode The parent text SVG node - */ - buildText: function (wrapper) { - var textNode = wrapper.element, - lines = pick(wrapper.textStr, '').toString() - .replace(/<(b|strong)>/g, '') - .replace(/<(i|em)>/g, '') - .replace(//g, '') - .split(//g), - childNodes = textNode.childNodes, - styleRegex = /style="([^"]+)"/, - hrefRegex = /href="([^"]+)"/, - parentX = attr(textNode, 'x'), - textStyles = wrapper.styles, - renderAsHtml = textStyles && wrapper.useHTML && !this.forExport, - htmlNode = wrapper.htmlNode, - //arr, issue #38 workaround - width = textStyles && pInt(textStyles.width), - textLineHeight = textStyles && textStyles.lineHeight, - lastLine, - GET_COMPUTED_STYLE = 'getComputedStyle', - i = childNodes.length; - - // remove old text - while (i--) { - textNode.removeChild(childNodes[i]); - } - - if (width && !wrapper.added) { - this.box.appendChild(textNode); // attach it to the DOM to read offset width - } - - // remove empty line at end - if (lines[lines.length - 1] === '') { - lines.pop(); - } - - // build the lines - each(lines, function (line, lineNo) { - var spans, spanNo = 0, lineHeight; - - line = line.replace(//g, '|||'); - spans = line.split('|||'); - - each(spans, function (span) { - if (span !== '' || spans.length === 1) { - var attributes = {}, - tspan = doc.createElementNS(SVG_NS, 'tspan'); - if (styleRegex.test(span)) { - attr( - tspan, - 'style', - span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2') - ); - } - if (hrefRegex.test(span)) { - attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"'); - css(tspan, { cursor: 'pointer' }); - } - - span = (span.replace(/<(.|\n)*?>/g, '') || ' ') - .replace(/</g, '<') - .replace(/>/g, '>'); - - // issue #38 workaround. - /*if (reverse) { - arr = []; - i = span.length; - while (i--) { - arr.push(span.charAt(i)); - } - span = arr.join(''); - }*/ - - // add the text node - tspan.appendChild(doc.createTextNode(span)); - - if (!spanNo) { // first span in a line, align it to the left - attributes.x = parentX; - } else { - // Firefox ignores spaces at the front or end of the tspan - attributes.dx = 3; // space - } - - // first span on subsequent line, add the line height - if (!spanNo) { - if (lineNo) { - - // allow getting the right offset height in exporting in IE - if (!hasSVG && wrapper.renderer.forExport) { - css(tspan, { display: 'block' }); - } - - // Webkit and opera sometimes return 'normal' as the line height. In that - // case, webkit uses offsetHeight, while Opera falls back to 18 - lineHeight = win[GET_COMPUTED_STYLE] && - pInt(win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height')); - - if (!lineHeight || isNaN(lineHeight)) { - lineHeight = textLineHeight || lastLine.offsetHeight || 18; - } - attr(tspan, 'dy', lineHeight); - } - lastLine = tspan; // record for use in next line - } - - // add attributes - attr(tspan, attributes); - - // append it - textNode.appendChild(tspan); - - spanNo++; - - // check width and apply soft breaks - if (width) { - var words = span.replace(/-/g, '- ').split(' '), - tooLong, - actualWidth, - rest = []; - - while (words.length || rest.length) { - actualWidth = wrapper.getBBox().width; - tooLong = actualWidth > width; - if (!tooLong || words.length === 1) { // new line needed - words = rest; - rest = []; - if (words.length) { - tspan = doc.createElementNS(SVG_NS, 'tspan'); - attr(tspan, { - dy: textLineHeight || 16, - x: parentX - }); - textNode.appendChild(tspan); - - if (actualWidth > width) { // a single word is pressing it out - width = actualWidth; - } - } - } else { // append to existing line tspan - tspan.removeChild(tspan.firstChild); - rest.unshift(words.pop()); - } - if (words.length) { - tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-'))); - } - } - } - } - }); - }); - - // Fix issue #38 and allow HTML in tooltips and other labels - if (renderAsHtml) { - if (!htmlNode) { - htmlNode = wrapper.htmlNode = createElement('span', null, extend(textStyles, { - position: ABSOLUTE, - top: 0, - left: 0 - }), this.box.parentNode); - } - htmlNode.innerHTML = wrapper.textStr; - - i = childNodes.length; - while (i--) { - childNodes[i].style.visibility = HIDDEN; - } - } - }, - - /** - * Create a button with preset states - * @param {String} text - * @param {Number} x - * @param {Number} y - * @param {Function} callback - * @param {Object} normalState - * @param {Object} hoverState - * @param {Object} pressedState - */ - button: function (text, x, y, callback, normalState, hoverState, pressedState) { - var label = this.label(text, x, y), - curState = 0, - stateOptions, - stateStyle, - normalStyle, - hoverStyle, - pressedStyle, - STYLE = 'style', - verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 }; - - // prepare the attributes - /*jslint white: true*/ - normalState = merge(hash( - STROKE_WIDTH, 1, - STROKE, '#999', - FILL, hash( - LINEAR_GRADIENT, verticalGradient, - STOPS, [ - [0, '#FFF'], - [1, '#DDD'] - ] - ), - 'r', 3, - 'padding', 3, - STYLE, hash( - 'color', 'black' - ) - ), normalState); - /*jslint white: false*/ - normalStyle = normalState[STYLE]; - delete normalState[STYLE]; - - /*jslint white: true*/ - hoverState = merge(normalState, hash( - STROKE, '#68A', - FILL, hash( - LINEAR_GRADIENT, verticalGradient, - STOPS, [ - [0, '#FFF'], - [1, '#ACF'] - ] - ) - ), hoverState); - /*jslint white: false*/ - hoverStyle = hoverState[STYLE]; - delete hoverState[STYLE]; - - /*jslint white: true*/ - pressedState = merge(normalState, hash( - STROKE, '#68A', - FILL, hash( - LINEAR_GRADIENT, verticalGradient, - STOPS, [ - [0, '#9BD'], - [1, '#CDF'] - ] - ) - ), pressedState); - /*jslint white: false*/ - pressedStyle = pressedState[STYLE]; - delete pressedState[STYLE]; - - // add the events - addEvent(label.element, 'mouseenter', function () { - label.attr(hoverState) - .css(hoverStyle); - }); - addEvent(label.element, 'mouseleave', function () { - stateOptions = [normalState, hoverState, pressedState][curState]; - stateStyle = [normalStyle, hoverStyle, pressedStyle][curState]; - label.attr(stateOptions) - .css(stateStyle); - }); - - label.setState = function (state) { - curState = state; - if (!state) { - label.attr(normalState) - .css(normalStyle); - } else if (state === 2) { - label.attr(pressedState) - .css(pressedStyle); - } - }; - - return label - .on('click', function () { - callback.call(label); - }) - .attr(normalState) - .css(extend({ cursor: 'default' }, normalStyle)); - }, - - /** - * Make a straight line crisper by not spilling out to neighbour pixels - * @param {Array} points - * @param {Number} width - */ - crispLine: function (points, width) { - // points format: [M, 0, 0, L, 100, 0] - // normalize to a crisp line - if (points[1] === points[4]) { - points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2); - } - if (points[2] === points[5]) { - points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2); - } - return points; - }, - - - /** - * Draw a path - * @param {Array} path An SVG path in array form - */ - path: function (path) { - return this.createElement('path').attr({ - d: path, - fill: NONE - }); - }, - - /** - * Draw and return an SVG circle - * @param {Number} x The x position - * @param {Number} y The y position - * @param {Number} r The radius - */ - circle: function (x, y, r) { - var attr = isObject(x) ? - x : - { - x: x, - y: y, - r: r - }; - - return this.createElement('circle').attr(attr); - }, - - /** - * Draw and return an arc - * @param {Number} x X position - * @param {Number} y Y position - * @param {Number} r Radius - * @param {Number} innerR Inner radius like used in donut charts - * @param {Number} start Starting angle - * @param {Number} end Ending angle - */ - arc: function (x, y, r, innerR, start, end) { - // arcs are defined as symbols for the ability to set - // attributes in attr and animate - - if (isObject(x)) { - y = x.y; - r = x.r; - innerR = x.innerR; - start = x.start; - end = x.end; - x = x.x; - } - return this.symbol('arc', x || 0, y || 0, r || 0, r || 0, { - innerR: innerR || 0, - start: start || 0, - end: end || 0 - }); - }, - - /** - * Draw and return a rectangle - * @param {Number} x Left position - * @param {Number} y Top position - * @param {Number} width - * @param {Number} height - * @param {Number} r Border corner radius - * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing - */ - rect: function (x, y, width, height, r, strokeWidth) { - if (isObject(x)) { - y = x.y; - width = x.width; - height = x.height; - r = x.r; - strokeWidth = x.strokeWidth; - x = x.x; - } - var wrapper = this.createElement('rect').attr({ - rx: r, - ry: r, - fill: NONE - }); - - return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))); - }, - - /** - * Resize the box and re-align all aligned elements - * @param {Object} width - * @param {Object} height - * @param {Boolean} animate - * - */ - setSize: function (width, height, animate) { - var renderer = this, - alignedObjects = renderer.alignedObjects, - i = alignedObjects.length; - - renderer.width = width; - renderer.height = height; - - renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({ - width: width, - height: height - }); - - while (i--) { - alignedObjects[i].align(); - } - }, - - /** - * Create a group - * @param {String} name The group will be given a class name of 'highcharts-{name}'. - * This can be used for styling and scripting. - */ - g: function (name) { - var elem = this.createElement('g'); - return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem; - }, - - /** - * Display an image - * @param {String} src - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - image: function (src, x, y, width, height) { - var attribs = { - preserveAspectRatio: NONE - }, - elemWrapper; - - // optional properties - if (arguments.length > 1) { - extend(attribs, { - x: x, - y: y, - width: width, - height: height - }); - } - - elemWrapper = this.createElement('image').attr(attribs); - - // set the href in the xlink namespace - if (elemWrapper.element.setAttributeNS) { - elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink', - 'href', src); - } else { - // could be exporting in IE - // using href throws "not supported" in ie7 and under, requries regex shim to fix later - elemWrapper.element.setAttribute('hc-svg-href', src); - } - - return elemWrapper; - }, - - /** - * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object. - * - * @param {Object} symbol - * @param {Object} x - * @param {Object} y - * @param {Object} radius - * @param {Object} options - */ - symbol: function (symbol, x, y, width, height, options) { - - var obj, - - // get the symbol definition function - symbolFn = this.symbols[symbol], - - // check if there's a path defined for this symbol - path = symbolFn && symbolFn( - mathRound(x), - mathRound(y), - width, - height, - options - ), - - imageRegex = /^url\((.*?)\)$/, - imageSrc, - imageSize; - - if (path) { - - obj = this.path(path); - // expando properties for use in animate and attr - extend(obj, { - symbolName: symbol, - x: x, - y: y, - width: width, - height: height - }); - if (options) { - extend(obj, options); - } - - - // image symbols - } else if (imageRegex.test(symbol)) { - - var centerImage = function (img, size) { - img.attr({ - width: size[0], - height: size[1] - }).translate( - -mathRound(size[0] / 2), - -mathRound(size[1] / 2) - ); - }; - - imageSrc = symbol.match(imageRegex)[1]; - imageSize = symbolSizes[imageSrc]; - - // create the image synchronously, add attribs async - obj = this.image(imageSrc) - .attr({ - x: x, - y: y - }); - - if (imageSize) { - centerImage(obj, imageSize); - } else { - // initialize image to be 0 size so export will still function if there's no cached sizes - obj.attr({ width: 0, height: 0 }); - - // create a dummy JavaScript image to get the width and height - createElement('img', { - onload: function () { - var img = this; - - centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]); - }, - src: imageSrc - }); - } - } - - return obj; - }, - - /** - * An extendable collection of functions for defining symbol paths. - */ - symbols: { - 'circle': function (x, y, w, h) { - var cpw = 0.166 * w; - return [ - M, x + w / 2, y, - 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h, - 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y, - 'Z' - ]; - }, - - 'square': function (x, y, w, h) { - return [ - M, x, y, - L, x + w, y, - x + w, y + h, - x, y + h, - 'Z' - ]; - }, - - 'triangle': function (x, y, w, h) { - return [ - M, x + w / 2, y, - L, x + w, y + h, - x, y + h, - 'Z' - ]; - }, - - 'triangle-down': function (x, y, w, h) { - return [ - M, x, y, - L, x + w, y, - x + w / 2, y + h, - 'Z' - ]; - }, - 'diamond': function (x, y, w, h) { - return [ - M, x + w / 2, y, - L, x + w, y + h / 2, - x + w / 2, y + h, - x, y + h / 2, - 'Z' - ]; - }, - 'arc': function (x, y, w, h, options) { - var start = options.start, - radius = options.r || w || h, - end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs - innerRadius = options.innerR, - cosStart = mathCos(start), - sinStart = mathSin(start), - cosEnd = mathCos(end), - sinEnd = mathSin(end), - longArc = options.end - start < mathPI ? 0 : 1; - - return [ - M, - x + radius * cosStart, - y + radius * sinStart, - 'A', // arcTo - radius, // x radius - radius, // y radius - 0, // slanting - longArc, // long or short arc - 1, // clockwise - x + radius * cosEnd, - y + radius * sinEnd, - L, - x + innerRadius * cosEnd, - y + innerRadius * sinEnd, - 'A', // arcTo - innerRadius, // x radius - innerRadius, // y radius - 0, // slanting - longArc, // long or short arc - 0, // clockwise - x + innerRadius * cosStart, - y + innerRadius * sinStart, - - 'Z' // close - ]; - } - }, - - /** - * Define a clipping rectangle - * @param {String} id - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - clipRect: function (x, y, width, height) { - var wrapper, - id = PREFIX + idCounter++, - - clipPath = this.createElement('clipPath').attr({ - id: id - }).add(this.defs); - - wrapper = this.rect(x, y, width, height, 0).add(clipPath); - wrapper.id = id; - wrapper.clipPath = clipPath; - - return wrapper; - }, - - - /** - * Take a color and return it if it's a string, make it a gradient if it's a - * gradient configuration object. Prior to Highstock, an array was used to define - * a linear gradient with pixel positions relative to the SVG. In newer versions - * we change the coordinates to apply relative to the shape, using coordinates - * 0-1 within the shape. To preserve backwards compatibility, linearGradient - * in this definition is an object of x1, y1, x2 and y2. - * - * @param {Object} color The color or config object - */ - color: function (color, elem, prop) { - var colorObject, - regexRgba = /^rgba/; - if (color && color.linearGradient) { - var renderer = this, - linearGradient = color[LINEAR_GRADIENT], - relativeToShape = !linearGradient.length, // keep backwards compatibility - id = PREFIX + idCounter++, - gradientObject, - stopColor, - stopOpacity; - - gradientObject = renderer.createElement(LINEAR_GRADIENT) - .attr(extend({ - id: id, - x1: linearGradient.x1 || linearGradient[0] || 0, - y1: linearGradient.y1 || linearGradient[1] || 0, - x2: linearGradient.x2 || linearGradient[2] || 0, - y2: linearGradient.y2 || linearGradient[3] || 0 - }, relativeToShape ? null : { gradientUnits: 'userSpaceOnUse' })) - .add(renderer.defs); - - // Keep a reference to the gradient object so it is possible to destroy it later - renderer.gradients.push(gradientObject); - - // The gradient needs to keep a list of stops to be able to destroy them - gradientObject.stops = []; - each(color.stops, function (stop) { - var stopObject; - if (regexRgba.test(stop[1])) { - colorObject = Color(stop[1]); - stopColor = colorObject.get('rgb'); - stopOpacity = colorObject.get('a'); - } else { - stopColor = stop[1]; - stopOpacity = 1; - } - stopObject = renderer.createElement('stop').attr({ - offset: stop[0], - 'stop-color': stopColor, - 'stop-opacity': stopOpacity - }).add(gradientObject); - - // Add the stop element to the gradient - gradientObject.stops.push(stopObject); - }); - - return 'url(' + this.url + '#' + id + ')'; - - // Webkit and Batik can't show rgba. - } else if (regexRgba.test(color)) { - colorObject = Color(color); - attr(elem, prop + '-opacity', colorObject.get('a')); - - return colorObject.get('rgb'); - - - } else { - // Remove the opacity attribute added above. Does not throw if the attribute is not there. - elem.removeAttribute(prop + '-opacity'); - - return color; - } - - }, - - - /** - * Add text to the SVG object - * @param {String} str - * @param {Number} x Left position - * @param {Number} y Top position - * @param {Boolean} useHTML Use HTML to render the text - */ - text: function (str, x, y, useHTML) { - - // declare variables - var renderer = this, - defaultChartStyle = defaultOptions.chart.style, - wrapper; - - x = mathRound(pick(x, 0)); - y = mathRound(pick(y, 0)); - - wrapper = renderer.createElement('text') - .attr({ - x: x, - y: y, - text: str - }) - .css({ - fontFamily: defaultChartStyle.fontFamily, - fontSize: defaultChartStyle.fontSize - }); - - wrapper.x = x; - wrapper.y = y; - wrapper.useHTML = useHTML; - return wrapper; - }, - - /** - * Add a label, a text item that can hold a colored or gradient background - * as well as a border and shadow. - * @param {string} str - * @param {Number} x - * @param {Number} y - * @param {String} shape - * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the - * coordinates it should be pinned to - * @param {Number} anchorY - */ - label: function (str, x, y, shape, anchorX, anchorY) { - - var renderer = this, - wrapper = renderer.g(), - text = renderer.text() - .attr({ - zIndex: 1 - }) - .add(wrapper), - box, - bBox, - align = 'left', - padding = 3, - width, - height, - wrapperX, - wrapperY, - crispAdjust = 0, - deferredAttr = {}, - attrSetters = wrapper.attrSetters; - - /** - * This function runs after the label is added to the DOM (when the bounding box is - * available), and after the text of the label is updated to detect the new bounding - * box and reflect it in the border box. - */ - function updateBoxSize() { - bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) && - text.getBBox(true); - wrapper.width = (width || bBox.width) + 2 * padding; - wrapper.height = (height || bBox.height) + 2 * padding; - - // create the border box if it is not already present - if (!box) { - wrapper.box = box = shape ? - renderer.symbol(shape, 0, 0, wrapper.width, wrapper.height) : - renderer.rect(0, 0, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]); - box.add(wrapper); - } - - // apply the box attributes - box.attr(merge({ - width: wrapper.width, - height: wrapper.height - }, deferredAttr)); - deferredAttr = null; - } - - /** - * This function runs after setting text or padding, but only if padding is changed - */ - function updateTextPadding() { - var styles = wrapper.styles, - textAlign = styles && styles.textAlign, - x = padding, - y = padding + mathRound(pInt(wrapper.element.style.fontSize || 11) * 1.2); - - // compensate for alignment - if (defined(width) && (textAlign === 'center' || textAlign === 'right')) { - x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width); - } - - // update if anything changed - if (x !== text.x || y !== text.y) { - text.attr({ - x: x, - y: y - }); - } - - // record current values - text.x = x; - text.y = y; - } - - /** - * Set a box attribute, or defer it if the box is not yet created - * @param {Object} key - * @param {Object} value - */ - function boxAttr(key, value) { - if (box) { - box.attr(key, value); - } else { - deferredAttr[key] = value; - } - } - - function getSizeAfterAdd() { - wrapper.attr({ - text: str, // alignment is available now - x: x, - y: y, - anchorX: anchorX, - anchorY: anchorY - }); - } - - /** - * After the text element is added, get the desired size of the border box - * and add it before the text in the DOM. - */ - addEvent(wrapper, 'add', getSizeAfterAdd); - - /* - * Add specific attribute setters. - */ - - // only change local variables - attrSetters.width = function (value) { - width = value; - return false; - }; - attrSetters.height = function (value) { - height = value; - return false; - }; - attrSetters.padding = function (value) { - padding = value; - updateTextPadding(); - - return false; - }; - - // change local variable and set attribue as well - attrSetters.align = function (value) { - align = value; - return false; // prevent setting text-anchor on the group - }; - - // apply these to the box and the text alike - attrSetters.text = function (value, key) { - text.attr(key, value); - updateBoxSize(); - updateTextPadding(); - return false; - }; - - // apply these to the box but not to the text - attrSetters[STROKE_WIDTH] = function (value, key) { - crispAdjust = value % 2 / 2; - boxAttr(key, value); - return false; - }; - attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) { - boxAttr(key, value); - return false; - }; - attrSetters.anchorX = function (value, key) { - anchorX = value; - boxAttr(key, value + crispAdjust - wrapperX); - return false; - }; - attrSetters.anchorY = function (value, key) { - anchorY = value; - boxAttr(key, value - wrapperY); - return false; - }; - - // rename attributes - attrSetters.x = function (value) { - wrapperX = value; - wrapperX -= { left: 0, center: 0.5, right: 1 }[align] * ((width || bBox.width) + padding); - - wrapper.attr('translateX', mathRound(wrapperX)); - return false; - }; - attrSetters.y = function (value) { - wrapperY = value; - wrapper.attr('translateY', mathRound(value)); - return false; - }; - - // Redirect certain methods to either the box or the text - var baseCss = wrapper.css; - return extend(wrapper, { - /** - * Pick up some properties and apply them to the text instead of the wrapper - */ - css: function (styles) { - if (styles) { - var textStyles = {}; - styles = merge({}, styles); // create a copy to avoid altering the original object (#537) - each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight'], function (prop) { - if (styles[prop] !== UNDEFINED) { - textStyles[prop] = styles[prop]; - delete styles[prop]; - } - }); - text.css(textStyles); - } - return baseCss.call(wrapper, styles); - }, - /** - * Return the bounding box of the box, not the group - */ - getBBox: function () { - return box.getBBox(); - }, - /** - * Apply the shadow to the box - */ - shadow: function (b) { - box.shadow(b); - return wrapper; - }, - /** - * Destroy and release memory. - */ - destroy: function () { - removeEvent(wrapper, 'add', getSizeAfterAdd); - - // Added by button implementation - removeEvent(wrapper.element, 'mouseenter'); - removeEvent(wrapper.element, 'mouseleave'); - - if (text) { - // Destroy the text element - text = text.destroy(); - } - // Call base implementation to destroy the rest - SVGElement.prototype.destroy.call(wrapper); - } - }); - } -}; // end SVGRenderer - - -// general renderer -Renderer = SVGRenderer; - - -/* **************************************************************************** - * * - * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE * - * * - * For applications and websites that don't need IE support, like platform * - * targeted mobile apps and web apps, this code can be removed. * - * * - *****************************************************************************/ - -/** - * @constructor - */ -var VMLRenderer; -if (!hasSVG) { - -/** - * The VML element wrapper. - */ -var VMLElement = extendClass(SVGElement, { - - /** - * Initialize a new VML element wrapper. It builds the markup as a string - * to minimize DOM traffic. - * @param {Object} renderer - * @param {Object} nodeName - */ - init: function (renderer, nodeName) { - var wrapper = this, - markup = ['<', nodeName, ' filled="f" stroked="f"'], - style = ['position: ', ABSOLUTE, ';']; - - // divs and shapes need size - if (nodeName === 'shape' || nodeName === DIV) { - style.push('left:0;top:0;width:10px;height:10px;'); - } - if (docMode8) { - style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE); - } - - markup.push(' style="', style.join(''), '"/>'); - - // create element with default attributes and style - if (nodeName) { - markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ? - markup.join('') - : renderer.prepVML(markup); - wrapper.element = createElement(markup); - } - - wrapper.renderer = renderer; - wrapper.attrSetters = {}; - }, - - /** - * Add the node to the given parent - * @param {Object} parent - */ - add: function (parent) { - var wrapper = this, - renderer = wrapper.renderer, - element = wrapper.element, - box = renderer.box, - inverted = parent && parent.inverted, - - // get the parent node - parentNode = parent ? - parent.element || parent : - box; - - - // if the parent group is inverted, apply inversion on all children - if (inverted) { // only on groups - renderer.invertChild(element, parentNode); - } - - // issue #140 workaround - related to #61 and #74 - if (docMode8 && parentNode.gVis === HIDDEN) { - css(element, { visibility: HIDDEN }); - } - - // append it - parentNode.appendChild(element); - - // align text after adding to be able to read offset - wrapper.added = true; - if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) { - wrapper.updateTransform(); - } - - // fire an event for internal hooks - fireEvent(wrapper, 'add'); - - return wrapper; - }, - - /** - * In IE8 documentMode 8, we need to recursively set the visibility down in the DOM - * tree for nested groups. Related to #61, #586. - */ - toggleChildren: function (element, visibility) { - var childNodes = element.childNodes, - i = childNodes.length; - - while (i--) { - - // apply the visibility - css(childNodes[i], { visibility: visibility }); - - // we have a nested group, apply it to its children again - if (childNodes[i].nodeName === 'DIV') { - this.toggleChildren(childNodes[i], visibility); - } - } - }, - - /** - * Get or set attributes - */ - attr: function (hash, val) { - var wrapper = this, - key, - value, - i, - result, - element = wrapper.element || {}, - elemStyle = element.style, - nodeName = element.nodeName, - renderer = wrapper.renderer, - symbolName = wrapper.symbolName, - hasSetSymbolSize, - shadows = wrapper.shadows, - skipAttr, - attrSetters = wrapper.attrSetters, - ret = wrapper; - - // single key-value pair - if (isString(hash) && defined(val)) { - key = hash; - hash = {}; - hash[key] = val; - } - - // used as a getter, val is undefined - if (isString(hash)) { - key = hash; - if (key === 'strokeWidth' || key === 'stroke-width') { - ret = wrapper.strokeweight; - } else { - ret = wrapper[key]; - } - - // setter - } else { - for (key in hash) { - value = hash[key]; - skipAttr = false; - - // check for a specific attribute setter - result = attrSetters[key] && attrSetters[key](value, key); - - if (result !== false) { - - if (result !== UNDEFINED) { - value = result; // the attribute setter has returned a new value to set - } - - - // prepare paths - // symbols - if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) { - // if one of the symbol size affecting parameters are changed, - // check all the others only once for each call to an element's - // .attr() method - if (!hasSetSymbolSize) { - - wrapper.symbolAttr(hash); - - hasSetSymbolSize = true; - } - skipAttr = true; - - } else if (key === 'd') { - value = value || []; - wrapper.d = value.join(' '); // used in getter for animation - - // convert paths - i = value.length; - var convertedPath = []; - while (i--) { - - // Multiply by 10 to allow subpixel precision. - // Substracting half a pixel seems to make the coordinates - // align with SVG, but this hasn't been tested thoroughly - if (isNumber(value[i])) { - convertedPath[i] = mathRound(value[i] * 10) - 5; - } else if (value[i] === 'Z') { // close the path - convertedPath[i] = 'x'; - } else { - convertedPath[i] = value[i]; - } - - } - value = convertedPath.join(' ') || 'x'; - element.path = value; - - // update shadows - if (shadows) { - i = shadows.length; - while (i--) { - shadows[i].path = value; - } - } - skipAttr = true; - - // directly mapped to css - } else if (key === 'zIndex' || key === 'visibility') { - - // workaround for #61 and #586 - if (docMode8 && key === 'visibility' && nodeName === 'DIV') { - element.gVis = value; - wrapper.toggleChildren(element, value); - if (value === VISIBLE) { // #74 - value = null; - } - } - - if (value) { - elemStyle[key] = value; - } - - - - skipAttr = true; - - // width and height - } else if (key === 'width' || key === 'height') { - - value = mathMax(0, value); // don't set width or height below zero (#311) - - this[key] = value; // used in getter - - // clipping rectangle special - if (wrapper.updateClipping) { - wrapper[key] = value; - wrapper.updateClipping(); - } else { - // normal - elemStyle[key] = value; - } - - skipAttr = true; - - // x and y - } else if (/^(x|y)$/.test(key)) { - - wrapper[key] = value; // used in getter - - if (element.tagName === 'SPAN') { - wrapper.updateTransform(); - - } else { - elemStyle[{ x: 'left', y: 'top' }[key]] = value; - } - - // class name - } else if (key === 'class') { - // IE8 Standards mode has problems retrieving the className - element.className = value; - - // stroke - } else if (key === 'stroke') { - - value = renderer.color(value, element, key); - - key = 'strokecolor'; - - // stroke width - } else if (key === 'stroke-width' || key === 'strokeWidth') { - element.stroked = value ? true : false; - key = 'strokeweight'; - wrapper[key] = value; // used in getter, issue #113 - if (isNumber(value)) { - value += PX; - } - - // dashStyle - } else if (key === 'dashstyle') { - var strokeElem = element.getElementsByTagName('stroke')[0] || - createElement(renderer.prepVML(['']), null, null, element); - strokeElem[key] = value || 'solid'; - wrapper.dashstyle = value; /* because changing stroke-width will change the dash length - and cause an epileptic effect */ - skipAttr = true; - - // fill - } else if (key === 'fill') { - - if (nodeName === 'SPAN') { // text color - elemStyle.color = value; - } else { - element.filled = value !== NONE ? true : false; - - value = renderer.color(value, element, key); - - key = 'fillcolor'; - } - - // translation for animation - } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'align') { - if (key === 'align') { - key = 'textAlign'; - } - wrapper[key] = value; - wrapper.updateTransform(); - - skipAttr = true; - - // text for rotated and non-rotated elements - } else if (key === 'text') { - this.bBox = null; - element.innerHTML = value; - skipAttr = true; - } - - // let the shadow follow the main element - if (shadows && key === 'visibility') { - i = shadows.length; - while (i--) { - shadows[i].style[key] = value; - } - } - - - - if (!skipAttr) { - if (docMode8) { // IE8 setAttribute bug - element[key] = value; - } else { - attr(element, key, value); - } - } - - } - } - } - return ret; - }, - - /** - * Set the element's clipping to a predefined rectangle - * - * @param {String} id The id of the clip rectangle - */ - clip: function (clipRect) { - var wrapper = this, - clipMembers = clipRect.members; - - clipMembers.push(wrapper); - wrapper.destroyClip = function () { - erase(clipMembers, wrapper); - }; - return wrapper.css(clipRect.getCSS(wrapper.inverted)); - }, - - /** - * Set styles for the element - * @param {Object} styles - */ - css: function (styles) { - var wrapper = this, - element = wrapper.element, - textWidth = styles && element.tagName === 'SPAN' && styles.width; - - if (textWidth) { - delete styles.width; - wrapper.textWidth = textWidth; - wrapper.updateTransform(); - } - - wrapper.styles = extend(wrapper.styles, styles); - css(wrapper.element, styles); - - return wrapper; - }, - - /** - * Removes a child either by removeChild or move to garbageBin. - * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. - */ - safeRemoveChild: function (element) { - // discardElement will detach the node from its parent before attaching it - // to the garbage bin. Therefore it is important that the node is attached and have parent. - var parentNode = element.parentNode; - if (parentNode) { - discardElement(element); - } - }, - - /** - * Extend element.destroy by removing it from the clip members array - */ - destroy: function () { - var wrapper = this; - - if (wrapper.destroyClip) { - wrapper.destroyClip(); - } - - return SVGElement.prototype.destroy.apply(wrapper); - }, - - /** - * Remove all child nodes of a group, except the v:group element - */ - empty: function () { - var element = this.element, - childNodes = element.childNodes, - i = childNodes.length, - node; - - while (i--) { - node = childNodes[i]; - node.parentNode.removeChild(node); - } - }, - - /** - * VML override for calculating the bounding box based on offsets - * @param {Boolean} refresh Whether to force a fresh value from the DOM or to - * use the cached value - * - * @return {Object} A hash containing values for x, y, width and height - */ - - getBBox: function (refresh) { - var wrapper = this, - element = wrapper.element, - bBox = wrapper.bBox; - - // faking getBBox in exported SVG in legacy IE - if (!bBox || refresh) { - // faking getBBox in exported SVG in legacy IE - if (element.nodeName === 'text') { - element.style.position = ABSOLUTE; - } - - bBox = wrapper.bBox = { - x: element.offsetLeft, - y: element.offsetTop, - width: element.offsetWidth, - height: element.offsetHeight - }; - } - - return bBox; - }, - - /** - * Add an event listener. VML override for normalizing event parameters. - * @param {String} eventType - * @param {Function} handler - */ - on: function (eventType, handler) { - // simplest possible event model for internal use - this.element['on' + eventType] = function () { - var evt = win.event; - evt.target = evt.srcElement; - handler(evt); - }; - return this; - }, - - - /** - * VML override private method to update elements based on internal - * properties based on SVG transform - */ - updateTransform: function () { - // aligning non added elements is expensive - if (!this.added) { - this.alignOnAdd = true; - return; - } - - var wrapper = this, - elem = wrapper.element, - translateX = wrapper.translateX || 0, - translateY = wrapper.translateY || 0, - x = wrapper.x || 0, - y = wrapper.y || 0, - align = wrapper.textAlign || 'left', - alignCorrection = { left: 0, center: 0.5, right: 1 }[align], - nonLeft = align && align !== 'left', - shadows = wrapper.shadows; - - // apply translate - if (translateX || translateY) { - css(elem, { - marginLeft: translateX, - marginTop: translateY - }); - if (shadows) { // used in labels/tooltip - each(shadows, function (shadow) { - css(shadow, { - marginLeft: translateX + 1, - marginTop: translateY + 1 - }); - }); - } - } - - // apply inversion - if (wrapper.inverted) { // wrapper is a group - each(elem.childNodes, function (child) { - wrapper.renderer.invertChild(child, elem); - }); - } - - if (elem.tagName === 'SPAN') { - - var width, height, - rotation = wrapper.rotation, - lineHeight, - radians = 0, - costheta = 1, - sintheta = 0, - quad, - textWidth = pInt(wrapper.textWidth), - xCorr = wrapper.xCorr || 0, - yCorr = wrapper.yCorr || 0, - currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','); - - if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed - - if (defined(rotation)) { - radians = rotation * deg2rad; // deg to rad - costheta = mathCos(radians); - sintheta = mathSin(radians); - - // Adjust for alignment and rotation. - // Test case: http://highcharts.com/tests/?file=text-rotation - css(elem, { - filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta, - ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta, - ', sizingMethod=\'auto expand\')'].join('') : NONE - }); - } - - width = pick(wrapper.elemWidth, elem.offsetWidth); - height = pick(wrapper.elemHeight, elem.offsetHeight); - - // update textWidth - if (width > textWidth) { - css(elem, { - width: textWidth + PX, - display: 'block', - whiteSpace: 'normal' - }); - width = textWidth; - } - - // correct x and y - lineHeight = mathRound((pInt(elem.style.fontSize) || 12) * 1.2); - xCorr = costheta < 0 && -width; - yCorr = sintheta < 0 && -height; - - // correct for lineHeight and corners spilling out after rotation - quad = costheta * sintheta < 0; - xCorr += sintheta * lineHeight * (quad ? 1 - alignCorrection : alignCorrection); - yCorr -= costheta * lineHeight * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1); - - // correct for the length/height of the text - if (nonLeft) { - xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1); - if (rotation) { - yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1); - } - css(elem, { - textAlign: align - }); - } - - // record correction - wrapper.xCorr = xCorr; - wrapper.yCorr = yCorr; - } - - // apply position with correction - css(elem, { - left: x + xCorr, - top: y + yCorr - }); - - // record current text transform - wrapper.cTT = currentTextTransform; - } - }, - - /** - * Apply a drop shadow by copying elements and giving them different strokes - * @param {Boolean} apply - */ - shadow: function (apply, group) { - var shadows = [], - i, - element = this.element, - renderer = this.renderer, - shadow, - elemStyle = element.style, - markup, - path = element.path; - - // some times empty paths are not strings - if (path && typeof path.value !== 'string') { - path = 'x'; - } - - if (apply) { - for (i = 1; i <= 3; i++) { - markup = ['']; - shadow = createElement(renderer.prepVML(markup), - null, { - left: pInt(elemStyle.left) + 1, - top: pInt(elemStyle.top) + 1 - } - ); - - // apply the opacity - markup = ['']; - createElement(renderer.prepVML(markup), null, null, shadow); - - - // insert it - if (group) { - group.element.appendChild(shadow); - } else { - element.parentNode.insertBefore(shadow, element); - } - - // record it - shadows.push(shadow); - - } - - this.shadows = shadows; - } - return this; - - } -}); - -/** - * The VML renderer - */ -VMLRenderer = function () { - this.init.apply(this, arguments); -}; -VMLRenderer.prototype = merge(SVGRenderer.prototype, { // inherit SVGRenderer - - Element: VMLElement, - isIE8: userAgent.indexOf('MSIE 8.0') > -1, - - - /** - * Initialize the VMLRenderer - * @param {Object} container - * @param {Number} width - * @param {Number} height - */ - init: function (container, width, height) { - var renderer = this, - boxWrapper; - - renderer.alignedObjects = []; - - boxWrapper = renderer.createElement(DIV); - container.appendChild(boxWrapper.element); - - - // generate the containing box - renderer.box = boxWrapper.element; - renderer.boxWrapper = boxWrapper; - - - renderer.setSize(width, height, false); - - // The only way to make IE6 and IE7 print is to use a global namespace. However, - // with IE8 the only way to make the dynamic shapes visible in screen and print mode - // seems to be to add the xmlns attribute and the behaviour style inline. - if (!doc.namespaces.hcv) { - - doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml'); - - // setup default css - doc.createStyleSheet().cssText = - 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' + - '{ behavior:url(#default#VML); display: inline-block; } '; - - } - }, - - /** - * Define a clipping rectangle. In VML it is accomplished by storing the values - * for setting the CSS style to all associated members. - * - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - clipRect: function (x, y, width, height) { - - // create a dummy element - var clipRect = this.createElement(); - - // mimic a rectangle with its style object for automatic updating in attr - return extend(clipRect, { - members: [], - left: x, - top: y, - width: width, - height: height, - getCSS: function (inverted) { - var rect = this,//clipRect.element.style, - top = rect.top, - left = rect.left, - right = left + rect.width, - bottom = top + rect.height, - ret = { - clip: 'rect(' + - mathRound(inverted ? left : top) + 'px,' + - mathRound(inverted ? bottom : right) + 'px,' + - mathRound(inverted ? right : bottom) + 'px,' + - mathRound(inverted ? top : left) + 'px)' - }; - - // issue 74 workaround - if (!inverted && docMode8) { - extend(ret, { - width: right + PX, - height: bottom + PX - }); - } - return ret; - }, - - // used in attr and animation to update the clipping of all members - updateClipping: function () { - each(clipRect.members, function (member) { - member.css(clipRect.getCSS(member.inverted)); - }); - } - }); - - }, - - - /** - * Take a color and return it if it's a string, make it a gradient if it's a - * gradient configuration object, and apply opacity. - * - * @param {Object} color The color or config object - */ - color: function (color, elem, prop) { - var colorObject, - regexRgba = /^rgba/, - markup; - - if (color && color[LINEAR_GRADIENT]) { - - var stopColor, - stopOpacity, - linearGradient = color[LINEAR_GRADIENT], - x1 = linearGradient.x1 || linearGradient[0] || 0, - y1 = linearGradient.y1 || linearGradient[1] || 0, - x2 = linearGradient.x2 || linearGradient[2] || 0, - y2 = linearGradient.y2 || linearGradient[3] || 0, - angle, - color1, - opacity1, - color2, - opacity2; - - each(color.stops, function (stop, i) { - if (regexRgba.test(stop[1])) { - colorObject = Color(stop[1]); - stopColor = colorObject.get('rgb'); - stopOpacity = colorObject.get('a'); - } else { - stopColor = stop[1]; - stopOpacity = 1; - } - - if (!i) { // first - color1 = stopColor; - opacity1 = stopOpacity; - } else { - color2 = stopColor; - opacity2 = stopOpacity; - } - }); - - // calculate the angle based on the linear vector - angle = 90 - math.atan( - (y2 - y1) / // y vector - (x2 - x1) // x vector - ) * 180 / mathPI; - - - // when colors attribute is used, the meanings of opacity and o:opacity2 - // are reversed. - markup = ['<', prop, ' colors="0% ', color1, ',100% ', color2, '" angle="', angle, - '" opacity="', opacity2, '" o:opacity2="', opacity1, - '" type="gradient" focus="100%" method="any" />']; - createElement(this.prepVML(markup), null, null, elem); - - - // if the color is an rgba color, split it and add a fill node - // to hold the opacity component - } else if (regexRgba.test(color) && elem.tagName !== 'IMG') { - - colorObject = Color(color); - - markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>']; - createElement(this.prepVML(markup), null, null, elem); - - return colorObject.get('rgb'); - - - } else { - var strokeNodes = elem.getElementsByTagName(prop); - if (strokeNodes.length) { - strokeNodes[0].opacity = 1; - } - return color; - } - - }, - - /** - * Take a VML string and prepare it for either IE8 or IE6/IE7. - * @param {Array} markup A string array of the VML markup to prepare - */ - prepVML: function (markup) { - var vmlStyle = 'display:inline-block;behavior:url(#default#VML);', - isIE8 = this.isIE8; - - markup = markup.join(''); - - if (isIE8) { // add xmlns and style inline - markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />'); - if (markup.indexOf('style="') === -1) { - markup = markup.replace('/>', ' style="' + vmlStyle + '" />'); - } else { - markup = markup.replace('style="', 'style="' + vmlStyle); - } - - } else { // add namespace - markup = markup.replace('<', ' 1) { - obj.css({ - left: x, - top: y, - width: width, - height: height - }); - } - return obj; - }, - - /** - * VML uses a shape for rect to overcome bugs and rotation problems - */ - rect: function (x, y, width, height, r, strokeWidth) { - - if (isObject(x)) { - y = x.y; - width = x.width; - height = x.height; - strokeWidth = x.strokeWidth; - x = x.x; - } - var wrapper = this.symbol('rect'); - wrapper.r = r; - - return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))); - }, - - /** - * In the VML renderer, each child of an inverted div (group) is inverted - * @param {Object} element - * @param {Object} parentNode - */ - invertChild: function (element, parentNode) { - var parentStyle = parentNode.style; - - css(element, { - flip: 'x', - left: pInt(parentStyle.width) - 10, - top: pInt(parentStyle.height) - 10, - rotation: -90 - }); - }, - - /** - * Symbol definitions that override the parent SVG renderer's symbols - * - */ - symbols: { - // VML specific arc function - arc: function (x, y, w, h, options) { - var start = options.start, - end = options.end, - radius = options.r || w || h, - cosStart = mathCos(start), - sinStart = mathSin(start), - cosEnd = mathCos(end), - sinEnd = mathSin(end), - innerRadius = options.innerR, - circleCorrection = 0.07 / radius, - innerCorrection = (innerRadius && 0.1 / innerRadius) || 0; - - if (end - start === 0) { // no angle, don't show it. - return ['x']; - - //} else if (end - start == 2 * mathPI) { // full circle - } else if (2 * mathPI - end + start < circleCorrection) { // full circle - // empirical correction found by trying out the limits for different radii - cosEnd = -circleCorrection; - } else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem - cosEnd = mathCos(start + innerCorrection); - } - - return [ - 'wa', // clockwise arc to - x - radius, // left - y - radius, // top - x + radius, // right - y + radius, // bottom - x + radius * cosStart, // start x - y + radius * sinStart, // start y - x + radius * cosEnd, // end x - y + radius * sinEnd, // end y - - - 'at', // anti clockwise arc to - x - innerRadius, // left - y - innerRadius, // top - x + innerRadius, // right - y + innerRadius, // bottom - x + innerRadius * cosEnd, // start x - y + innerRadius * sinEnd, // start y - x + innerRadius * cosStart, // end x - y + innerRadius * sinStart, // end y - - 'x', // finish path - 'e' // close - ]; - - }, - // Add circle symbol path. This performs significantly faster than v:oval. - circle: function (x, y, w, h) { - - return [ - 'wa', // clockwisearcto - x, // left - y, // top - x + w, // right - y + h, // bottom - x + w, // start x - y + h / 2, // start y - x + w, // end x - y + h / 2, // end y - //'x', // finish path - 'e' // close - ]; - }, - /** - * Add rectangle symbol path which eases rotation and omits arcsize problems - * compared to the built-in VML roundrect shape - * - * @param {Number} left Left position - * @param {Number} top Top position - * @param {Number} r Border radius - * @param {Object} options Width and height - */ - - rect: function (left, top, width, height, options) { - /*for (var n in r) { - logTime && console .log(n) - }*/ - - if (!defined(options)) { - return []; - } - var right = left + width, - bottom = top + height, - r = mathMin(options.r || 0, width, height); - - return [ - M, - left + r, top, - - L, - right - r, top, - 'wa', - right - 2 * r, top, - right, top + 2 * r, - right - r, top, - right, top + r, - - L, - right, bottom - r, - 'wa', - right - 2 * r, bottom - 2 * r, - right, bottom, - right, bottom - r, - right - r, bottom, - - L, - left + r, bottom, - 'wa', - left, bottom - 2 * r, - left + 2 * r, bottom, - left + r, bottom, - left, bottom - r, - - L, - left, top + r, - 'wa', - left, top, - left + 2 * r, top + 2 * r, - left, top + r, - left + r, top, - - - 'x', - 'e' - ]; - - } - } -}); - - // general renderer - Renderer = VMLRenderer; -} - -/* **************************************************************************** - * * - * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE * - * * - *****************************************************************************/ - -/** - * The chart class - * @param {Object} options - * @param {Function} callback Function to run when the chart has loaded - */ -function Chart(options, callback) { - - defaultXAxisOptions = merge(defaultXAxisOptions, defaultOptions.xAxis); - defaultYAxisOptions = merge(defaultYAxisOptions, defaultOptions.yAxis); - defaultOptions.xAxis = defaultOptions.yAxis = null; - - // Handle regular options - var seriesOptions = options.series; // skip merging data points to increase performance - options.series = null; - options = merge(defaultOptions, options); // do the merge - options.series = seriesOptions; // set back the series data - - // Define chart variables - var optionsChart = options.chart, - optionsMargin = optionsChart.margin, - margin = isObject(optionsMargin) ? - optionsMargin : - [optionsMargin, optionsMargin, optionsMargin, optionsMargin], - optionsMarginTop = pick(optionsChart.marginTop, margin[0]), - optionsMarginRight = pick(optionsChart.marginRight, margin[1]), - optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]), - optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]), - spacingTop = optionsChart.spacingTop, - spacingRight = optionsChart.spacingRight, - spacingBottom = optionsChart.spacingBottom, - spacingLeft = optionsChart.spacingLeft, - spacingBox, - chartTitleOptions, - chartSubtitleOptions, - plotTop, - marginRight, - marginBottom, - plotLeft, - axisOffset, - renderTo, - renderToClone, - container, - containerId, - containerWidth, - containerHeight, - chartWidth, - chartHeight, - oldChartWidth, - oldChartHeight, - chartBackground, - plotBackground, - plotBGImage, - plotBorder, - chart = this, - chartEvents = optionsChart.events, - runChartClick = chartEvents && !!chartEvents.click, - eventType, - isInsidePlot, // function - tooltip, - mouseIsDown, - loadingDiv, - loadingSpan, - loadingShown, - plotHeight, - plotWidth, - tracker, - trackerGroup, - placeTrackerGroup, - legend, - legendWidth, - legendHeight, - chartPosition, - hasCartesianSeries = optionsChart.showAxes, - isResizing = 0, - axes = [], - maxTicks, // handle the greatest amount of ticks on grouped axes - series = [], - inverted, - renderer, - tooltipTick, - tooltipInterval, - hoverX, - drawChartBox, // function - getMargins, // function - resetMargins, // function - setChartSize, // function - resize, - zoom, // function - zoomOut; // function - - - /** - * Create a new axis object - * @param {Object} options - */ - function Axis(userOptions) { - - // Define variables - var isXAxis = userOptions.isX, - opposite = userOptions.opposite, // needed in setOptions - horiz = inverted ? !isXAxis : isXAxis, - side = horiz ? - (opposite ? 0 : 2) : // top : bottom - (opposite ? 1 : 3), // right : left - stacks = {}, - - options = merge( - isXAxis ? defaultXAxisOptions : defaultYAxisOptions, - [defaultTopAxisOptions, defaultRightAxisOptions, - defaultBottomAxisOptions, defaultLeftAxisOptions][side], - userOptions - ), - - axis = this, - axisTitle, - type = options.type, - isDatetimeAxis = type === 'datetime', - isLog = type === 'logarithmic', - offset = options.offset || 0, - xOrY = isXAxis ? 'x' : 'y', - axisLength = 0, - oldAxisLength, - transA, // translation factor - transB, // translation addend - oldTransA, // used for prerendering - axisLeft, - axisTop, - axisWidth, - axisHeight, - axisBottom, - axisRight, - translate, // fn - getPlotLinePath, // fn - axisGroup, - gridGroup, - axisLine, - dataMin, - dataMax, - minRange = options.minRange || options.maxZoom, - range = options.range, - userMin, - userMax, - oldUserMin, - oldUserMax, - max = null, - min = null, - oldMin, - oldMax, - minPadding = options.minPadding, - maxPadding = options.maxPadding, - minPixelPadding = 0, - isLinked = defined(options.linkedTo), - ignoreMinPadding, // can be set to true by a column or bar series - ignoreMaxPadding, - usePercentage, - events = options.events, - eventType, - plotLinesAndBands = [], - tickInterval, - minorTickInterval, - magnitude, - tickPositions, // array containing predefined positions - tickPositioner = options.tickPositioner, - ticks = {}, - minorTicks = {}, - alternateBands = {}, - tickAmount, - labelOffset, - axisTitleMargin,// = options.title.margin, - dateTimeLabelFormat, - categories = options.categories, - labelFormatter = options.labels.formatter || // can be overwritten by dynamic format - function () { - var value = this.value, - ret; - - if (dateTimeLabelFormat) { // datetime axis - ret = dateFormat(dateTimeLabelFormat, value); - - } else if (tickInterval % 1000000 === 0) { // use M abbreviation - ret = (value / 1000000) + 'M'; - - } else if (tickInterval % 1000 === 0) { // use k abbreviation - ret = (value / 1000) + 'k'; - - } else if (!categories && value >= 1000) { // add thousands separators - ret = numberFormat(value, 0); - - } else { // strings (categories) and small numbers - ret = value; - } - return ret; - }, - - staggerLines = horiz && options.labels.staggerLines, - reversed = options.reversed, - tickmarkOffset = (categories && options.tickmarkPlacement === 'between') ? 0.5 : 0; - - /** - * The Tick class - */ - function Tick(pos, minor) { - var tick = this; - tick.pos = pos; - tick.minor = minor; - tick.isNew = true; - - if (!minor) { - tick.addLabel(); - } - } - Tick.prototype = { - - /** - * Write the tick label - */ - addLabel: function () { - var tick = this, - pos = tick.pos, - labelOptions = options.labels, - str, - width = (categories && horiz && categories.length && - !labelOptions.step && !labelOptions.staggerLines && - !labelOptions.rotation && - plotWidth / categories.length) || - (!horiz && plotWidth / 2), - isFirst = pos === tickPositions[0], - isLast = pos === tickPositions[tickPositions.length - 1], - css, - value = categories && defined(categories[pos]) ? categories[pos] : pos, - label = tick.label; - - // set properties for access in render method - tick.isFirst = isFirst; - tick.isLast = isLast; - - // get the string - str = labelFormatter.call({ - isFirst: isFirst, - isLast: isLast, - dateTimeLabelFormat: dateTimeLabelFormat, - value: isLog ? lin2log(value) : value - }); - - - // prepare CSS - css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX }; - css = extend(css, labelOptions.style); - - // first call - if (!defined(label)) { - tick.label = - defined(str) && labelOptions.enabled ? - renderer.text( - str, - 0, - 0, - labelOptions.useHTML - ) - .attr({ - align: labelOptions.align, - rotation: labelOptions.rotation - }) - // without position absolute, IE export sometimes is wrong - .css(css) - .add(axisGroup) : - null; - - // update - } else if (label) { - label.attr({ - text: str - }) - .css(css); - } - }, - /** - * Get the offset height or width of the label - */ - getLabelSize: function () { - var label = this.label; - return label ? - ((this.labelBBox = label.getBBox()))[horiz ? 'height' : 'width'] : - 0; - }, - /** - * Put everything in place - * - * @param index {Number} - * @param old {Boolean} Use old coordinates to prepare an animation into new position - */ - render: function (index, old) { - var tick = this, - major = !tick.minor, - label = tick.label, - pos = tick.pos, - labelOptions = options.labels, - gridLine = tick.gridLine, - gridLineWidth = major ? options.gridLineWidth : options.minorGridLineWidth, - gridLineColor = major ? options.gridLineColor : options.minorGridLineColor, - dashStyle = major ? - options.gridLineDashStyle : - options.minorGridLineDashStyle, - gridLinePath, - mark = tick.mark, - markPath, - tickLength = major ? options.tickLength : options.minorTickLength, - tickWidth = major ? options.tickWidth : (options.minorTickWidth || 0), - tickColor = major ? options.tickColor : options.minorTickColor, - tickPosition = major ? options.tickPosition : options.minorTickPosition, - step = labelOptions.step, - cHeight = (old && oldChartHeight) || chartHeight, - attribs, - x, - y; - - // get x and y position for ticks and labels - x = horiz ? - translate(pos + tickmarkOffset, null, null, old) + transB : - axisLeft + offset + (opposite ? ((old && oldChartWidth) || chartWidth) - axisRight - axisLeft : 0); - - y = horiz ? - cHeight - axisBottom + offset - (opposite ? axisHeight : 0) : - cHeight - translate(pos + tickmarkOffset, null, null, old) - transB; - - // create the grid line - if (gridLineWidth) { - gridLinePath = getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old); - - if (gridLine === UNDEFINED) { - attribs = { - stroke: gridLineColor, - 'stroke-width': gridLineWidth - }; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - if (major) { - attribs.zIndex = 1; - } - tick.gridLine = gridLine = - gridLineWidth ? - renderer.path(gridLinePath) - .attr(attribs).add(gridGroup) : - null; - } - - // If the parameter 'old' is set, the current call will be followed - // by another call, therefore do not do any animations this time - if (!old && gridLine && gridLinePath) { - gridLine.animate({ - d: gridLinePath - }); - } - } - - // create the tick mark - if (tickWidth) { - - // negate the length - if (tickPosition === 'inside') { - tickLength = -tickLength; - } - if (opposite) { - tickLength = -tickLength; - } - - markPath = renderer.crispLine([ - M, - x, - y, - L, - x + (horiz ? 0 : -tickLength), - y + (horiz ? tickLength : 0) - ], tickWidth); - - if (mark) { // updating - mark.animate({ - d: markPath - }); - } else { // first time - tick.mark = renderer.path( - markPath - ).attr({ - stroke: tickColor, - 'stroke-width': tickWidth - }).add(axisGroup); - } - } - - // the label is created on init - now move it into place - if (label && !isNaN(x)) { - x = x + labelOptions.x - (tickmarkOffset && horiz ? - tickmarkOffset * transA * (reversed ? -1 : 1) : 0); - y = y + labelOptions.y - (tickmarkOffset && !horiz ? - tickmarkOffset * transA * (reversed ? 1 : -1) : 0); - - // vertically centered - if (!defined(labelOptions.y)) { - y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2; - } - - - // correct for staggered labels - if (staggerLines) { - y += (index / (step || 1) % staggerLines) * 16; - } - - // apply show first and show last - if ((tick.isFirst && !pick(options.showFirstLabel, 1)) || - (tick.isLast && !pick(options.showLastLabel, 1))) { - label.hide(); - } else { - // show those that may have been previously hidden, either by show first/last, or by step - label.show(); - } - - // apply step - if (step && index % step) { - // show those indices dividable by step - label.hide(); - } - - label[tick.isNew ? 'attr' : 'animate']({ - x: x, - y: y - }); - } - - tick.isNew = false; - }, - /** - * Destructor for the tick prototype - */ - destroy: function () { - destroyObjectProperties(this); - } - }; - - /** - * The object wrapper for plot lines and plot bands - * @param {Object} options - */ - function PlotLineOrBand(options) { - var plotLine = this; - if (options) { - plotLine.options = options; - plotLine.id = options.id; - } - - //plotLine.render() - return plotLine; - } - - PlotLineOrBand.prototype = { - - /** - * Render the plot line or plot band. If it is already existing, - * move it. - */ - render: function () { - var plotLine = this, - options = plotLine.options, - optionsLabel = options.label, - label = plotLine.label, - width = options.width, - to = options.to, - from = options.from, - value = options.value, - toPath, // bands only - dashStyle = options.dashStyle, - svgElem = plotLine.svgElem, - path = [], - addEvent, - eventType, - xs, - ys, - x, - y, - color = options.color, - zIndex = options.zIndex, - events = options.events, - attribs; - - // logarithmic conversion - if (isLog) { - from = log2lin(from); - to = log2lin(to); - value = log2lin(value); - } - - // plot line - if (width) { - path = getPlotLinePath(value, width); - attribs = { - stroke: color, - 'stroke-width': width - }; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - } else if (defined(from) && defined(to)) { // plot band - // keep within plot area - from = mathMax(from, min); - to = mathMin(to, max); - - toPath = getPlotLinePath(to); - path = getPlotLinePath(from); - if (path && toPath) { - path.push( - toPath[4], - toPath[5], - toPath[1], - toPath[2] - ); - } else { // outside the axis area - path = null; - } - attribs = { - fill: color - }; - } else { - return; - } - // zIndex - if (defined(zIndex)) { - attribs.zIndex = zIndex; - } - - // common for lines and bands - if (svgElem) { - if (path) { - svgElem.animate({ - d: path - }, null, svgElem.onGetPath); - } else { - svgElem.hide(); - svgElem.onGetPath = function () { - svgElem.show(); - }; - } - } else if (path && path.length) { - plotLine.svgElem = svgElem = renderer.path(path) - .attr(attribs).add(); - - // events - if (events) { - addEvent = function (eventType) { - svgElem.on(eventType, function (e) { - events[eventType].apply(plotLine, [e]); - }); - }; - for (eventType in events) { - addEvent(eventType); - } - } - } - - // the plot band/line label - if (optionsLabel && defined(optionsLabel.text) && path && path.length && axisWidth > 0 && axisHeight > 0) { - // apply defaults - optionsLabel = merge({ - align: horiz && toPath && 'center', - x: horiz ? !toPath && 4 : 10, - verticalAlign : !horiz && toPath && 'middle', - y: horiz ? toPath ? 16 : 10 : toPath ? 6 : -4, - rotation: horiz && !toPath && 90 - }, optionsLabel); - - // add the SVG element - if (!label) { - plotLine.label = label = renderer.text( - optionsLabel.text, - 0, - 0 - ) - .attr({ - align: optionsLabel.textAlign || optionsLabel.align, - rotation: optionsLabel.rotation, - zIndex: zIndex - }) - .css(optionsLabel.style) - .add(); - } - - // get the bounding box and align the label - xs = [path[1], path[4], pick(path[6], path[1])]; - ys = [path[2], path[5], pick(path[7], path[2])]; - x = arrayMin(xs); - y = arrayMin(ys); - - label.align(optionsLabel, false, { - x: x, - y: y, - width: arrayMax(xs) - x, - height: arrayMax(ys) - y - }); - label.show(); - - } else if (label) { // move out of sight - label.hide(); - } - - // chainable - return plotLine; - }, - - /** - * Remove the plot line or band - */ - destroy: function () { - var obj = this; - - destroyObjectProperties(obj); - - // remove it from the lookup - erase(plotLinesAndBands, obj); - } - }; - - /** - * The class for stack items - */ - function StackItem(options, isNegative, x, stackOption) { - var stackItem = this; - - // Tells if the stack is negative - stackItem.isNegative = isNegative; - - // Save the options to be able to style the label - stackItem.options = options; - - // Save the x value to be able to position the label later - stackItem.x = x; - - // Save the stack option on the series configuration object - stackItem.stack = stackOption; - - // The align options and text align varies on whether the stack is negative and - // if the chart is inverted or not. - // First test the user supplied value, then use the dynamic. - stackItem.alignOptions = { - align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'), - verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')), - y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)), - x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0) - }; - - stackItem.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center'); - } - - StackItem.prototype = { - destroy: function () { - destroyObjectProperties(this); - }, - - /** - * Sets the total of this stack. Should be called when a serie is hidden or shown - * since that will affect the total of other stacks. - */ - setTotal: function (total) { - this.total = total; - this.cum = total; - }, - - /** - * Renders the stack total label and adds it to the stack label group. - */ - render: function (group) { - var stackItem = this, // aliased this - str = stackItem.options.formatter.call(stackItem); // format the text in the label - - // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden - if (stackItem.label) { - stackItem.label.attr({text: str, visibility: HIDDEN}); - // Create new label - } else { - stackItem.label = - chart.renderer.text(str, 0, 0) // dummy positions, actual position updated with setOffset method in columnseries - .css(stackItem.options.style) // apply style - .attr({align: stackItem.textAlign, // fix the text-anchor - rotation: stackItem.options.rotation, // rotation - visibility: HIDDEN }) // hidden until setOffset is called - .add(group); // add to the labels-group - } - }, - - /** - * Sets the offset that the stack has from the x value and repositions the label. - */ - setOffset: function (xOffset, xWidth) { - var stackItem = this, // aliased this - neg = stackItem.isNegative, // special treatment is needed for negative stacks - y = axis.translate(stackItem.total), // stack value translated mapped to chart coordinates - yZero = axis.translate(0), // stack origin - h = mathAbs(y - yZero), // stack height - x = chart.xAxis[0].translate(stackItem.x) + xOffset, // stack x position - plotHeight = chart.plotHeight, - stackBox = { // this is the box for the complete stack - x: inverted ? (neg ? y : y - h) : x, - y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y), - width: inverted ? h : xWidth, - height: inverted ? xWidth : h - }; - - if (stackItem.label) { - stackItem.label - .align(stackItem.alignOptions, null, stackBox) // align the label to the box - .attr({visibility: VISIBLE}); // set visibility - } - } - }; - - /** - * Get the minimum and maximum for the series of each axis - */ - function getSeriesExtremes() { - var posStack = [], - negStack = [], - i; - - // reset dataMin and dataMax in case we're redrawing - dataMin = dataMax = null; - - // loop through this axis' series - each(axis.series, function (series) { - - if (series.visible || !optionsChart.ignoreHiddenSeries) { - - var seriesOptions = series.options, - stacking, - posPointStack, - negPointStack, - stackKey, - stackOption, - negKey, - xData, - yData, - x, - y, - threshold = seriesOptions.threshold, - yDataLength, - distance, - activeYData = [], - activeCounter = 0; - - // Get dataMin and dataMax for X axes - if (isXAxis) { - xData = series.xData; - dataMin = mathMin(pick(dataMin, xData[0]), arrayMin(xData)); - dataMax = mathMax(pick(dataMax, xData[0]), arrayMax(xData)); - - // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data - } else { - var isNegative, - pointStack, - key, - cropped = series.cropped, - xExtremes = series.xAxis.getExtremes(), - findPointRange, - pointRange, - j, - hasModifyValue = !!series.modifyValue; - - - // Handle stacking - stacking = seriesOptions.stacking; - usePercentage = stacking === 'percent'; - - // create a stack for this particular series type - if (stacking) { - stackOption = series.options.stack; - stackKey = series.type + pick(stackOption, ''); - negKey = '-' + stackKey; - series.stackKey = stackKey; // used in translate - - posPointStack = posStack[stackKey] || []; // contains the total values for each x - posStack[stackKey] = posPointStack; - - negPointStack = negStack[negKey] || []; - negStack[negKey] = negPointStack; - } - if (usePercentage) { - dataMin = 0; - dataMax = 99; - } - - // get clipped and grouped data - series.processData(); - - // processData can alter series.pointRange, so this goes after - findPointRange = series.pointRange === null; - - xData = series.processedXData; - yData = series.processedYData; - yDataLength = yData.length; - - - // loop over the non-null y values and read them into a local array - for (i = 0; i < yDataLength; i++) { - x = xData[i]; - y = yData[i]; - if (y !== null && y !== UNDEFINED) { - - // read stacked values into a stack based on the x value, - // the sign of y and the stack key - if (stacking) { - isNegative = y < 0; - pointStack = isNegative ? negPointStack : posPointStack; - key = isNegative ? negKey : stackKey; - - y = pointStack[x] = - defined(pointStack[x]) ? - pointStack[x] + y : y; - - - // add the series - if (!stacks[key]) { - stacks[key] = {}; - } - - // If the StackItem is there, just update the values, - // if not, create one first - if (!stacks[key][x]) { - stacks[key][x] = new StackItem(options.stackLabels, isNegative, x, stackOption); - } - stacks[key][x].setTotal(y); - - - // general hook, used for Highstock compare values feature - } else if (hasModifyValue) { - y = series.modifyValue(y); - } - - // get the smallest distance between points - if (i) { - distance = mathAbs(xData[i] - xData[i - 1]); - pointRange = pointRange === UNDEFINED ? distance : mathMin(distance, pointRange); - } - - // for points within the visible range, including the first point outside the - // visible range, consider y extremes - if (cropped || ((xData[i + 1] || x) >= xExtremes.min && (xData[i - 1] || x) <= xExtremes.max)) { - - j = y.length; - if (j) { // array, like ohlc data - while (j--) { - if (y[j] !== null) { - activeYData[activeCounter++] = y[j]; - } - } - } else { - activeYData[activeCounter++] = y; - } - } - } - } - - // record the least unit distance - if (findPointRange) { - series.pointRange = pointRange || 1; - } - series.closestPointRange = pointRange; - - - // Get the dataMin and dataMax so far. If percentage is used, the min and max are - // always 0 and 100. If the length of activeYData is 0, continue with null values. - if (!usePercentage && activeYData.length) { - dataMin = mathMin(pick(dataMin, activeYData[0]), arrayMin(activeYData)); - dataMax = mathMax(pick(dataMax, activeYData[0]), arrayMax(activeYData)); - } - - - // todo: instead of checking useThreshold, just set the threshold to 0 - // in area and column-like chart types - if (series.useThreshold && threshold !== null) { - if (dataMin >= threshold) { - dataMin = threshold; - ignoreMinPadding = true; - } else if (dataMax < threshold) { - dataMax = threshold; - ignoreMaxPadding = true; - } - } - } - } - }); - - } - - /** - * Translate from axis value to pixel position on the chart, or back - * - */ - translate = function (val, backwards, cvsCoord, old, handleLog) { - var sign = 1, - cvsOffset = 0, - localA = old ? oldTransA : transA, - localMin = old ? oldMin : min, - returnValue; - - if (!localA) { - localA = transA; - } - - if (cvsCoord) { - sign *= -1; // canvas coordinates inverts the value - cvsOffset = axisLength; - } - if (reversed) { // reversed axis - sign *= -1; - cvsOffset -= sign * axisLength; - } - - if (backwards) { // reverse translation - if (reversed) { - val = axisLength - val; - } - returnValue = val / localA + localMin; // from chart pixel to value - if (isLog && handleLog) { - returnValue = lin2log(returnValue); - } - - } else { // normal translation, from axis value to pixel, relative to plot - if (isLog && handleLog) { - val = log2lin(val); - } - returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding); - } - - return returnValue; - }; - - /** - * Create the path for a plot line that goes from the given value on - * this axis, across the plot to the opposite side - * @param {Number} value - * @param {Number} lineWidth Used for calculation crisp line - * @param {Number] old Use old coordinates (for resizing and rescaling) - */ - getPlotLinePath = function (value, lineWidth, old) { - var x1, - y1, - x2, - y2, - translatedValue = translate(value, null, null, old), - cHeight = (old && oldChartHeight) || chartHeight, - cWidth = (old && oldChartWidth) || chartWidth, - skip; - - x1 = x2 = mathRound(translatedValue + transB); - y1 = y2 = mathRound(cHeight - translatedValue - transB); - - if (isNaN(translatedValue)) { // no min or max - skip = true; - - } else if (horiz) { - y1 = axisTop; - y2 = cHeight - axisBottom; - if (x1 < axisLeft || x1 > axisLeft + axisWidth) { - skip = true; - } - } else { - x1 = axisLeft; - x2 = cWidth - axisRight; - - if (y1 < axisTop || y1 > axisTop + axisHeight) { - skip = true; - } - } - return skip ? - null : - renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0); - }; - - /** - * Fix JS round off float errors - * @param {Number} num - */ - function correctFloat(num) { - var invMag, ret = num; - magnitude = pick(magnitude, math.pow(10, mathFloor(math.log(tickInterval) / math.LN10))); - - if (magnitude < 1) { - invMag = mathRound(1 / magnitude) * 10; - ret = mathRound(num * invMag) / invMag; - } - return ret; - } - - /** - * Set the tick positions of a linear axis to round values like whole tens or every five. - */ - function setLinearTickPositions() { - - var i, - roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval), - roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval); - - tickPositions = []; - - // populate the intermediate values - i = correctFloat(roundedMin); - while (i <= roundedMax) { - tickPositions.push(i); - i = correctFloat(i + tickInterval); - } - - } - - /** - * Adjust the min and max for the minimum range - */ - function adjustForMinRange(secondPass) { - var zoomOffset, - halfPointRange = (axis.pointRange || 0) / 2, - spaceAvailable = dataMax - dataMin > minRange, - minArgs, - maxArgs; - - // set the automatic minimum range based on the closest point distance - if (secondPass && minRange === UNDEFINED) { - minRange = isXAxis && !defined(options.min) && !defined(options.max) ? - mathMin(axis.closestPointRange * 5, dataMax - dataMin) : - null; - } - - // if minRange is exceeded, adjust - if (max - min < minRange) { - - zoomOffset = (minRange - max + min) / 2; - - // if min and max options have been set, don't go beyond it - minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)]; - if (spaceAvailable) { // if space is available, stay within the data range - minArgs[2] = dataMin - halfPointRange; - } - min = mathMax(0, arrayMax(minArgs)); - - maxArgs = [min + minRange, pick(options.max, min + minRange)]; - if (spaceAvailable) { // if space is availabe, stay within the data range - maxArgs[2] = dataMax + halfPointRange; - } - max = mathMin(0, arrayMin(maxArgs)); - - // now if the max is adjusted, adjust the min back - if (max - min < minRange) { - minArgs[0] = max - minRange; - minArgs[1] = pick(options.min, max - minRange); - min = mathMax(0, arrayMax(minArgs)); - } - } - } - - /** - * Set the tick positions to round values and optionally extend the extremes - * to the nearest tick - */ - function setTickPositions(secondPass) { - var length, - linkedParent, - linkedParentExtremes, - tickIntervalOption = options.tickInterval, - tickPixelIntervalOption = options.tickPixelInterval; - - // linked axis gets the extremes from the parent axis - if (isLinked) { - linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo]; - linkedParentExtremes = linkedParent.getExtremes(); - min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin); - max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax); - } else { // initial min and max from the extreme data values - min = pick(userMin, options.min, dataMin); - max = pick(userMax, options.max, dataMax); - } - - if (isLog) { - min = log2lin(min); - max = log2lin(max); - } - - // handle zoomed range - if (range) { - userMin = min = max - range; - userMax = max; - if (secondPass) { - range = null; // don't use it when running setExtremes - } - } - - // adjust min and max for the minimum range - adjustForMinRange(secondPass); - - // pad the values to get clear of the chart's edges - if (!categories && !usePercentage && !isLinked && defined(min) && defined(max)) { - length = (max - min) || 1; - if (!defined(options.min) && !defined(userMin) && minPadding && (dataMin < 0 || !ignoreMinPadding)) { - min -= length * minPadding; - } - if (!defined(options.max) && !defined(userMax) && maxPadding && (dataMax > 0 || !ignoreMaxPadding)) { - max += length * maxPadding; - } - } - - // get tickInterval - if (min === max || min === undefined || max === undefined) { - tickInterval = 1; - } else if (isLinked && !tickIntervalOption && - tickPixelIntervalOption === linkedParent.options.tickPixelInterval) { - tickInterval = linkedParent.tickInterval; - } else { - tickInterval = pick( - tickIntervalOption, - categories ? // for categoried axis, 1 is default, for linear axis use tickPix - 1 : - (max - min) * tickPixelIntervalOption / (axisLength || 1) - ); - } - - if (!isDatetimeAxis) { // linear - magnitude = math.pow(10, mathFloor(math.log(tickInterval) / math.LN10)); - if (!defined(options.tickInterval)) { - tickInterval = normalizeTickInterval(tickInterval, null, magnitude, options); - } - } - axis.tickInterval = tickInterval; // record for linked axis - - // get minorTickInterval - minorTickInterval = options.minorTickInterval === 'auto' && tickInterval ? - tickInterval / 5 : options.minorTickInterval; - - // find the tick positions - tickPositions = options.tickPositions || (tickPositioner && tickPositioner.apply(axis, [min, max])); // docs - if (!tickPositions) { - if (isDatetimeAxis) { - tickPositions = getTimeTicks(tickInterval, min, max, options.startOfWeek, options.units); // docs - dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositions.info.unitName]; - } else { - setLinearTickPositions(); - } - } - - if (!isLinked) { - - // reset min/max or remove extremes based on start/end on tick - var roundedMin = tickPositions[0], - roundedMax = tickPositions[tickPositions.length - 1]; - - if (options.startOnTick) { - min = roundedMin; - } else if (min > roundedMin) { - tickPositions.shift(); - } - - if (options.endOnTick) { - max = roundedMax; - } else if (max < roundedMax) { - tickPositions.pop(); - } - - // record the greatest number of ticks for multi axis - if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation - maxTicks = { - x: 0, - y: 0 - }; - } - - if (!isDatetimeAxis && tickPositions.length > maxTicks[xOrY] && options.alignTicks !== false) { - maxTicks[xOrY] = tickPositions.length; - } - } - - - } - - /** - * When using multiple axes, adjust the number of ticks to match the highest - * number of ticks in that group - */ - function adjustTickAmount() { - - if (maxTicks && maxTicks[xOrY] && !isDatetimeAxis && !categories && !isLinked && options.alignTicks !== false) { // only apply to linear scale - var oldTickAmount = tickAmount, - calculatedTickAmount = tickPositions.length; - - // set the axis-level tickAmount to use below - tickAmount = maxTicks[xOrY]; - - if (calculatedTickAmount < tickAmount) { - while (tickPositions.length < tickAmount) { - tickPositions.push(correctFloat( - tickPositions[tickPositions.length - 1] + tickInterval - )); - } - transA *= (calculatedTickAmount - 1) / (tickAmount - 1); - max = tickPositions[tickPositions.length - 1]; - - } - if (defined(oldTickAmount) && tickAmount !== oldTickAmount) { - axis.isDirty = true; - } - } - - } - - /** - * Set the scale based on data min and max, user set min and max or options - * - */ - function setScale() { - var type, - i, - isDirtyData; - - oldMin = min; - oldMax = max; - oldAxisLength = axisLength; - - // set the new axisLength - axisLength = horiz ? axisWidth : axisHeight; - - // is there new data? - each(axis.series, function (series) { - if (series.isDirtyData || series.isDirty || - series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well - isDirtyData = true; - } - }); - - // do we really need to go through all this? - if (axisLength !== oldAxisLength || isDirtyData || isLinked || - userMin !== oldUserMin || userMax !== oldUserMax) { - - // get data extremes if needed - getSeriesExtremes(); - - // get fixed positions based on tickInterval - setTickPositions(); - - // record old values to decide whether a rescale is necessary later on (#540) - oldUserMin = userMin; - oldUserMax = userMax; - - // the translation factor used in translate function - oldTransA = transA; - transA = axisLength / ((max - min + (axis.pointRange || 0)) || 1); - - // reset stacks - if (!isXAxis) { - for (type in stacks) { - for (i in stacks[type]) { - stacks[type][i].cum = stacks[type][i].total; - } - } - } - - // Mark as dirty if it is not already set to dirty and extremes have changed. #595. - if (!axis.isDirty) { - axis.isDirty = chart.isDirtyBox || min !== oldMin || max !== oldMax; - } - } - } - - /** - * Set the extremes and optionally redraw - * @param {Number} newMin - * @param {Number} newMax - * @param {Boolean} redraw - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * - */ - function setExtremes(newMin, newMax, redraw, animation) { - - redraw = pick(redraw, true); // defaults to true - - fireEvent(axis, 'setExtremes', { // fire an event to enable syncing of multiple charts - min: newMin, - max: newMax - }, function () { // the default event handler - - userMin = newMin; - userMax = newMax; - - - // redraw - if (redraw) { - chart.redraw(animation); - } - }); - - // this event contains the min and max values that may be modified by padding etc. - fireEvent(axis, 'afterSetExtremes', { - min: min, - max: max - }); - } - - /** - * Update the axis metrics - */ - function setAxisSize() { - - var offsetLeft = options.offsetLeft || 0, - offsetRight = options.offsetRight || 0, - range = max - min, - pointRange = 0, - closestPointRange, - seriesClosestPointRange; - - // basic values - axisLeft = pick(options.left, plotLeft + offsetLeft); - axisTop = pick(options.top, plotTop); - axisWidth = pick(options.width, plotWidth - offsetLeft + offsetRight); - axisHeight = pick(options.height, plotHeight); - axisBottom = chartHeight - axisHeight - axisTop; - axisRight = chartWidth - axisWidth - axisLeft; - axisLength = horiz ? axisWidth : axisHeight; - - // adjust translation for padding - if (isXAxis) { - each(axis.series, function (series) { - pointRange = mathMax(pointRange, series.pointRange); - seriesClosestPointRange = series.closestPointRange; - if (!series.noSharedTooltip && defined(seriesClosestPointRange)) { - closestPointRange = defined(closestPointRange) ? - mathMin(closestPointRange, seriesClosestPointRange) : - seriesClosestPointRange; - } - }); - // pointRange means the width reserved for each point, like in a column chart - if ((defined(userMin) || defined(userMax)) && pointRange > tickInterval / 2) { - // prevent great padding when zooming tightly in to view columns - pointRange = 0; - } - axis.pointRange = pointRange; - - // closestPointRange means the closest distance between points. In columns - // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange - // is some other value - axis.closestPointRange = closestPointRange; - } - - // secondary values - transA = axisLength / ((range + pointRange) || 1); - transB = horiz ? axisLeft : axisBottom; // translation addend - minPixelPadding = transA * (pointRange / 2); - - // expose to use in Series object and navigator - axis.left = axisLeft; - axis.top = axisTop; - axis.len = axisLength; - - } - - /** - * Get the actual axis extremes - */ - function getExtremes() { - return { - min: min, - max: max, - dataMin: dataMin, - dataMax: dataMax, - userMin: userMin, - userMax: userMax - }; - } - - /** - * Get the zero plane either based on zero or on the min or max value. - * Used in bar and area plots - */ - function getThreshold(threshold) { - if (min > threshold || threshold === null) { - threshold = min; - } else if (max < threshold) { - threshold = max; - } - - return translate(threshold, 0, 1); - } - - /** - * Add a plot band or plot line after render time - * - * @param options {Object} The plotBand or plotLine configuration object - */ - function addPlotBandOrLine(options) { - var obj = new PlotLineOrBand(options).render(); - plotLinesAndBands.push(obj); - return obj; - } - - /** - * Render the tick labels to a preliminary position to get their sizes - */ - function getOffset() { - - var hasData = axis.series.length && defined(min) && defined(max), - showAxis = hasData || pick(options.showEmpty, true), // docs - titleOffset = 0, - titleMargin = 0, - axisTitleOptions = options.title, - labelOptions = options.labels, - directionFactor = [-1, 1, 1, -1][side], - n; - - if (!axisGroup) { - axisGroup = renderer.g('axis') - .attr({ zIndex: 7 }) - .add(); - gridGroup = renderer.g('grid') - .attr({ zIndex: options.gridZIndex || 1 }) // docs - .add(); - } - - labelOffset = 0; // reset - - if (hasData || isLinked) { - each(tickPositions, function (pos) { - if (!ticks[pos]) { - ticks[pos] = new Tick(pos); - } else { - ticks[pos].addLabel(); // update labels depending on tick interval - } - - }); - - each(tickPositions, function (pos) { - // left side must be align: right and right side must have align: left for labels - if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) { - - // get the highest offset - labelOffset = mathMax( - ticks[pos].getLabelSize(), - labelOffset - ); - } - - }); - - if (staggerLines) { - labelOffset += (staggerLines - 1) * 16; - } - - } else { // doesn't have data - for (n in ticks) { - ticks[n].destroy(); - delete ticks[n]; - } - } - - if (axisTitleOptions && axisTitleOptions.text) { - if (!axisTitle) { - axisTitle = axis.axisTitle = renderer.text( - axisTitleOptions.text, - 0, - 0, - axisTitleOptions.useHTML - ) - .attr({ - zIndex: 7, - rotation: axisTitleOptions.rotation || 0, - align: - axisTitleOptions.textAlign || - { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align] - }) - .css(axisTitleOptions.style) - .add(); - axisTitle.isNew = true; - } - - if (showAxis) { - titleOffset = axisTitle.getBBox()[horiz ? 'height' : 'width']; - titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10); - } - - // hide or show the title depending on whether showEmpty is set - axisTitle[showAxis ? 'show' : 'hide'](); - - - } - - // handle automatic or user set offset - offset = directionFactor * pick(options.offset, axisOffset[side]); - - axisTitleMargin = - pick(axisTitleOptions.offset, // docs - labelOffset + titleMargin + - (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x']) - ); - - axisOffset[side] = mathMax( - axisOffset[side], - axisTitleMargin + titleOffset + directionFactor * offset - ); - - } - - /** - * Render the axis - */ - function render() { - var axisTitleOptions = options.title, - stackLabelOptions = options.stackLabels, - alternateGridColor = options.alternateGridColor, - lineWidth = options.lineWidth, - lineLeft, - lineTop, - linePath, - hasRendered = chart.hasRendered, - slideInTicks = hasRendered && defined(oldMin) && !isNaN(oldMin), - hasData = axis.series.length && defined(min) && defined(max), - showAxis = hasData || pick(options.showEmpty, true); - - // If the series has data draw the ticks. Else only the line and title - if (hasData || isLinked) { - - // minor ticks - if (minorTickInterval && !categories) { - var pos = min + (tickPositions[0] - min) % minorTickInterval; - for (; pos <= max; pos += minorTickInterval) { - if (!minorTicks[pos]) { - minorTicks[pos] = new Tick(pos, true); - } - - // render new ticks in old position - if (slideInTicks && minorTicks[pos].isNew) { - minorTicks[pos].render(null, true); - } - - - minorTicks[pos].isActive = true; - minorTicks[pos].render(); - } - } - - // major ticks - each(tickPositions, function (pos, i) { - // linked axes need an extra check to find out if - if (!isLinked || (pos >= min && pos <= max)) { - - // render new ticks in old position - if (slideInTicks && ticks[pos].isNew) { - ticks[pos].render(i, true); - } - - ticks[pos].isActive = true; - ticks[pos].render(i); - } - }); - - // alternate grid color - if (alternateGridColor) { - each(tickPositions, function (pos, i) { - if (i % 2 === 0 && pos < max) { - if (!alternateBands[pos]) { - alternateBands[pos] = new PlotLineOrBand(); - } - alternateBands[pos].options = { - from: pos, - to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max, - color: alternateGridColor - }; - alternateBands[pos].render(); - alternateBands[pos].isActive = true; - } - }); - } - - // custom plot lines and bands - if (!axis._addedPlotLB) { // only first time - each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) { - plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render()); - }); - axis._addedPlotLB = true; - } - - - - } // end if hasData - - // remove inactive ticks - each([ticks, minorTicks, alternateBands], function (coll) { - var pos; - for (pos in coll) { - if (!coll[pos].isActive) { - coll[pos].destroy(); - delete coll[pos]; - } else { - coll[pos].isActive = false; // reset - } - } - }); - - - - - // Static items. As the axis group is cleared on subsequent calls - // to render, these items are added outside the group. - // axis line - if (lineWidth) { - lineLeft = axisLeft + (opposite ? axisWidth : 0) + offset; - lineTop = chartHeight - axisBottom - (opposite ? axisHeight : 0) + offset; - - linePath = renderer.crispLine([ - M, - horiz ? - axisLeft : - lineLeft, - horiz ? - lineTop : - axisTop, - L, - horiz ? - chartWidth - axisRight : - lineLeft, - horiz ? - lineTop : - chartHeight - axisBottom - ], lineWidth); - if (!axisLine) { - axisLine = renderer.path(linePath) - .attr({ - stroke: options.lineColor, - 'stroke-width': lineWidth, - zIndex: 7 - }) - .add(); - } else { - axisLine.animate({ d: linePath }); - } - - // show or hide the line depending on options.showEmpty - axisLine[showAxis ? 'show' : 'hide'](); - - } - - if (axisTitle && showAxis) { - // compute anchor points for each of the title align options - var margin = horiz ? axisLeft : axisTop, - fontSize = pInt(axisTitleOptions.style.fontSize || 12), - // the position in the length direction of the axis - alongAxis = { - low: margin + (horiz ? 0 : axisLength), - middle: margin + axisLength / 2, - high: margin + (horiz ? axisLength : 0) - }[axisTitleOptions.align], - - // the position in the perpendicular direction of the axis - offAxis = (horiz ? axisTop + axisHeight : axisLeft) + - (horiz ? 1 : -1) * // horizontal axis reverses the margin - (opposite ? -1 : 1) * // so does opposite axes - axisTitleMargin + - (side === 2 ? fontSize : 0); - - axisTitle[axisTitle.isNew ? 'attr' : 'animate']({ - x: horiz ? - alongAxis : - offAxis + (opposite ? axisWidth : 0) + offset + - (axisTitleOptions.x || 0), // x - y: horiz ? - offAxis - (opposite ? axisHeight : 0) + offset : - alongAxis + (axisTitleOptions.y || 0) // y - }); - axisTitle.isNew = false; - } - - // Stacked totals: - if (stackLabelOptions && stackLabelOptions.enabled) { - var stackKey, oneStack, stackCategory, - stackTotalGroup = axis.stackTotalGroup; - - // Create a separate group for the stack total labels - if (!stackTotalGroup) { - axis.stackTotalGroup = stackTotalGroup = - renderer.g('stack-labels') - .attr({ - visibility: VISIBLE, - zIndex: 6 - }) - .translate(plotLeft, plotTop) - .add(); - } - - // Render each stack total - for (stackKey in stacks) { - oneStack = stacks[stackKey]; - for (stackCategory in oneStack) { - oneStack[stackCategory].render(stackTotalGroup); - } - } - } - // End stacked totals - - axis.isDirty = false; - } - - /** - * Remove a plot band or plot line from the chart by id - * @param {Object} id - */ - function removePlotBandOrLine(id) { - var i = plotLinesAndBands.length; - while (i--) { - if (plotLinesAndBands[i].id === id) { - plotLinesAndBands[i].destroy(); - } - } - } - - /** - * Redraw the axis to reflect changes in the data or axis extremes - */ - function redraw() { - - // hide tooltip and hover states - if (tracker.resetTracker) { - tracker.resetTracker(); - } - - // render the axis - render(); - - // move plot lines and bands - each(plotLinesAndBands, function (plotLine) { - plotLine.render(); - }); - - // mark associated series as dirty and ready for redraw - each(axis.series, function (series) { - series.isDirty = true; - }); - - } - - /** - * Set new axis categories and optionally redraw - * @param {Array} newCategories - * @param {Boolean} doRedraw - */ - function setCategories(newCategories, doRedraw) { - // set the categories - axis.categories = userOptions.categories = categories = newCategories; - - // force reindexing tooltips - each(axis.series, function (series) { - series.translate(); - series.setTooltipPoints(true); - }); - - - // optionally redraw - axis.isDirty = true; - - if (pick(doRedraw, true)) { - chart.redraw(); - } - } - - /** - * Destroys an Axis instance. - */ - function destroy() { - var stackKey; - - // Remove the events - removeEvent(axis); - - // Destroy each stack total - for (stackKey in stacks) { - destroyObjectProperties(stacks[stackKey]); - - stacks[stackKey] = null; - } - - // Destroy stack total group - if (axis.stackTotalGroup) { - axis.stackTotalGroup = axis.stackTotalGroup.destroy(); - } - - // Destroy collections - each([ticks, minorTicks, alternateBands, plotLinesAndBands], function (coll) { - destroyObjectProperties(coll); - }); - - // Destroy local variables - each([axisLine, axisGroup, gridGroup, axisTitle], function (obj) { - if (obj) { - obj.destroy(); - } - }); - axisLine = axisGroup = gridGroup = axisTitle = null; - } - - - // Run Axis - - // Register - axes.push(axis); - chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis); - - // inverted charts have reversed xAxes as default - if (inverted && isXAxis && reversed === UNDEFINED) { - reversed = true; - } - - - // expose some variables - extend(axis, { - addPlotBand: addPlotBandOrLine, - addPlotLine: addPlotBandOrLine, - adjustTickAmount: adjustTickAmount, - categories: categories, - getExtremes: getExtremes, - getPlotLinePath: getPlotLinePath, - getThreshold: getThreshold, - isXAxis: isXAxis, - options: options, - plotLinesAndBands: plotLinesAndBands, - getOffset: getOffset, - render: render, - setAxisSize: setAxisSize, - setCategories: setCategories, - setExtremes: setExtremes, - setScale: setScale, - setTickPositions: setTickPositions, - translate: translate, - redraw: redraw, - removePlotBand: removePlotBandOrLine, - removePlotLine: removePlotBandOrLine, - reversed: reversed, - series: [], // populated by Series - stacks: stacks, - destroy: destroy - }); - - // register event listeners - for (eventType in events) { - addEvent(axis, eventType, events[eventType]); - } - - // set min and max - //setScale(); - - } // end Axis - - - /** - * The toolbar object - */ - function Toolbar() { - var buttons = {}; - - /*jslint unparam: true*//* allow the unused param title until Toolbar rewrite*/ - function add(id, text, title, fn) { - if (!buttons[id]) { - var button = renderer.text( - text, - 0, - 0 - ) - .css(options.toolbar.itemStyle) - .align({ - align: 'right', - x: -marginRight - 20, - y: plotTop + 30 - }) - .on('click', fn) - .attr({ - align: 'right', - zIndex: 20 - }) - .add(); - buttons[id] = button; - } - } - /*jslint unparam: false*/ - - function remove(id) { - discardElement(buttons[id].element); - buttons[id] = null; - } - - // public - return { - add: add, - remove: remove - }; - } - - /** - * The tooltip object - * @param {Object} options Tooltip options - */ - function Tooltip(options) { - var currentSeries, - borderWidth = options.borderWidth, - crosshairsOptions = options.crosshairs, - crosshairs = [], - style = options.style, - shared = options.shared, - padding = pInt(style.padding), - tooltipIsHidden = true, - currentX = 0, - currentY = 0; - - // remove padding CSS and apply padding on box instead - style.padding = 0; - - // create the label - var label = renderer.label('', 0, 0) - .attr({ - padding: padding, - fill: options.backgroundColor, - 'stroke-width': borderWidth, - r: options.borderRadius, - zIndex: 8 - }) - .css(style) - .hide() - .add() - .shadow(options.shadow); - - /** - * Destroy the tooltip and its elements. - */ - function destroy() { - each(crosshairs, function (crosshair) { - if (crosshair) { - crosshair.destroy(); - } - }); - - // Destroy and clear local variables - if (label) { - label = label.destroy(); - } - } - - /** - * In case no user defined formatter is given, this will be used - */ - function defaultFormatter() { - var pThis = this, - items = pThis.points || splat(pThis), - series = items[0].series, - s; - - // build the header - s = [series.tooltipHeaderFormatter(items[0].key)]; - - // build the values - each(items, function (item) { - series = item.series; - s.push((series.tooltipFormatter && series.tooltipFormatter(item)) || - item.point.tooltipFormatter(series.tooltipOptions.pointFormat)); - }); - return s.join(''); - } - - /** - * Provide a soft movement for the tooltip - * - * @param {Number} finalX - * @param {Number} finalY - */ - function move(finalX, finalY) { - - // get intermediate values for animation - currentX = tooltipIsHidden ? finalX : (2 * currentX + finalX) / 3; - currentY = tooltipIsHidden ? finalY : (currentY + finalY) / 2; - - // move to the intermediate value - label.attr({ x: currentX, y: currentY }); - - // run on next tick of the mouse tracker - if (mathAbs(finalX - currentX) > 1 || mathAbs(finalY - currentY) > 1) { - tooltipTick = function () { - move(finalX, finalY); - }; - } else { - tooltipTick = null; - } - } - - /** - * Hide the tooltip - */ - function hide() { - if (!tooltipIsHidden) { - var hoverPoints = chart.hoverPoints; - - label.hide(); - - // hide previous hoverPoints and set new - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - chart.hoverPoints = null; - - - tooltipIsHidden = true; - } - - } - - /** - * Hide the crosshairs - */ - function hideCrosshairs() { - each(crosshairs, function (crosshair) { - if (crosshair) { - crosshair.hide(); - } - }); - } - - /** - * Refresh the tooltip's text and position. - * @param {Object} point - * - */ - function refresh(point) { - var x, - y, - show, - plotX, - plotY, - textConfig = {}, - text, - pointConfig = [], - tooltipPos = point.tooltipPos, - formatter = options.formatter || defaultFormatter, - hoverPoints = chart.hoverPoints, - placedTooltipPoint; - - // shared tooltip, array is sent over - if (shared && !(point.series && point.series.noSharedTooltip)) { - plotY = 0; - - // hide previous hoverPoints and set new - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - chart.hoverPoints = point; - - each(point, function (item) { - item.setState(HOVER_STATE); - plotY += item.plotY; // for average - - pointConfig.push(item.getLabelConfig()); - }); - - plotX = point[0].plotX; - plotY = mathRound(plotY) / point.length; // mathRound because Opera 10 has problems here - - textConfig = { - x: point[0].category - }; - textConfig.points = pointConfig; - point = point[0]; - - // single point tooltip - } else { - textConfig = point.getLabelConfig(); - } - text = formatter.call(textConfig); - - // register the current series - currentSeries = point.series; - - // get the reference point coordinates (pie charts use tooltipPos) - plotX = pick(plotX, point.plotX); - plotY = pick(plotY, point.plotY); - - x = mathRound(tooltipPos ? tooltipPos[0] : (inverted ? plotWidth - plotY : plotX)); - y = mathRound(tooltipPos ? tooltipPos[1] : (inverted ? plotHeight - plotX : plotY)); - - - // hide tooltip if the point falls outside the plot - show = shared || !point.series.isCartesian || isInsidePlot(x, y); - - // update the inner HTML - if (text === false || !show) { - hide(); - } else { - - // show it - if (tooltipIsHidden) { - label.show(); - tooltipIsHidden = false; - } - - // update text - label.attr({ - text: text - }); - - // set the stroke color of the box - label.attr({ - stroke: options.borderColor || point.color || currentSeries.color || '#606060' - }); - - placedTooltipPoint = placeBox(label.width, label.height, plotLeft, plotTop, - plotWidth, plotHeight, {x: x, y: y}, pick(options.distance, 12)); - - // do the move - move(mathRound(placedTooltipPoint.x), mathRound(placedTooltipPoint.y)); - } - - - // crosshairs - if (crosshairsOptions) { - crosshairsOptions = splat(crosshairsOptions); // [x, y] - - var path, - i = crosshairsOptions.length, - attribs, - axis; - - while (i--) { - axis = point.series[i ? 'yAxis' : 'xAxis']; - if (crosshairsOptions[i] && axis) { - path = axis - .getPlotLinePath(point[i ? 'y' : 'x'], 1); - if (crosshairs[i]) { - crosshairs[i].attr({ d: path, visibility: VISIBLE }); - - } else { - attribs = { - 'stroke-width': crosshairsOptions[i].width || 1, - stroke: crosshairsOptions[i].color || '#C0C0C0', - zIndex: crosshairsOptions[i].zIndex || 2 - }; - if (crosshairsOptions[i].dashStyle) { - attribs.dashstyle = crosshairsOptions[i].dashStyle; - } - crosshairs[i] = renderer.path(path) - .attr(attribs) - .add(); - } - } - } - } - } - - - - // public members - return { - shared: shared, - refresh: refresh, - hide: hide, - hideCrosshairs: hideCrosshairs, - destroy: destroy - }; - } - - /** - * The mouse tracker object - * @param {Object} options - */ - function MouseTracker(options) { - - - var mouseDownX, - mouseDownY, - hasDragged, - selectionMarker, - zoomType = optionsChart.zoomType, - zoomX = /x/.test(zoomType), - zoomY = /y/.test(zoomType), - zoomHor = (zoomX && !inverted) || (zoomY && inverted), - zoomVert = (zoomY && !inverted) || (zoomX && inverted); - - /** - * Add crossbrowser support for chartX and chartY - * @param {Object} e The event object in standard browsers - */ - function normalizeMouseEvent(e) { - var ePos, - pageZoomFix = isWebKit && - doc.width / doc.body.scrollWidth - - 1, // #224, #348 - chartPosLeft, - chartPosTop, - chartX, - chartY; - - // common IE normalizing - e = e || win.event; - if (!e.target) { - e.target = e.srcElement; - } - - // jQuery only copies over some properties. IE needs e.x and iOS needs touches. - if (e.originalEvent) { - e = e.originalEvent; - } - - // The same for MooTools. It renames e.pageX to e.page.x. #445. - if (e.event) { - e = e.event; - } - - // iOS - ePos = e.touches ? e.touches.item(0) : e; - - // get mouse position - chartPosition = offset(container); - chartPosLeft = chartPosition.left; - chartPosTop = chartPosition.top; - - // chartX and chartY - if (isIE) { // IE including IE9 that has pageX but in a different meaning - chartX = e.x; - chartY = e.y; - } else { - chartX = ePos.pageX - chartPosLeft; - chartY = ePos.pageY - chartPosTop; - } - - // correct for page zoom bug in WebKit - if (pageZoomFix) { - chartX += mathRound((pageZoomFix + 1) * chartPosLeft - chartPosLeft); - chartY += mathRound((pageZoomFix + 1) * chartPosTop - chartPosTop); - } - - return extend(e, { - chartX: chartX, - chartY: chartY - }); - } - - /** - * Get the click position in terms of axis values. - * - * @param {Object} e A mouse event - */ - function getMouseCoordinates(e) { - var coordinates = { - xAxis: [], - yAxis: [] - }; - each(axes, function (axis) { - var translate = axis.translate, - isXAxis = axis.isXAxis, - isHorizontal = inverted ? !isXAxis : isXAxis; - - coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({ - axis: axis, - value: translate( - isHorizontal ? - e.chartX - plotLeft : - plotHeight - e.chartY + plotTop, - true - ) - }); - }); - return coordinates; - } - - /** - * With line type charts with a single tracker, get the point closest to the mouse - */ - function onmousemove(e) { - var point, - points, - hoverPoint = chart.hoverPoint, - hoverSeries = chart.hoverSeries, - i, - j, - distance = chartWidth, - index = inverted ? e.chartY : e.chartX - plotLeft; // wtf? - - // shared tooltip - if (tooltip && options.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) { - points = []; - - // loop over all series and find the ones with points closest to the mouse - i = series.length; - for (j = 0; j < i; j++) { - if (series[j].visible && - series[j].options.enableMouseTracking !== false && - !series[j].noSharedTooltip && series[j].tooltipPoints.length) { - point = series[j].tooltipPoints[index]; - point._dist = mathAbs(index - point.plotX); - distance = mathMin(distance, point._dist); - points.push(point); - } - } - // remove furthest points - i = points.length; - while (i--) { - if (points[i]._dist > distance) { - points.splice(i, 1); - } - } - // refresh the tooltip if necessary - if (points.length && (points[0].plotX !== hoverX)) { - tooltip.refresh(points); - hoverX = points[0].plotX; - } - } - - // separate tooltip and general mouse events - if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker - - // get the point - point = hoverSeries.tooltipPoints[index]; - - // a new point is hovered, refresh the tooltip - if (point && point !== hoverPoint) { - - // trigger the events - point.onMouseOver(); - - } - } - } - - - - /** - * Reset the tracking by hiding the tooltip, the hover series state and the hover point - */ - function resetTracker() { - var hoverSeries = chart.hoverSeries, - hoverPoint = chart.hoverPoint; - - if (hoverPoint) { - hoverPoint.onMouseOut(); - } - - if (hoverSeries) { - hoverSeries.onMouseOut(); - } - - if (tooltip) { - tooltip.hide(); - tooltip.hideCrosshairs(); - } - - hoverX = null; - } - - /** - * Mouse up or outside the plot area - */ - function drop() { - if (selectionMarker) { - var selectionData = { - xAxis: [], - yAxis: [] - }, - selectionBox = selectionMarker.getBBox(), - selectionLeft = selectionBox.x - plotLeft, - selectionTop = selectionBox.y - plotTop; - - - // a selection has been made - if (hasDragged) { - - // record each axis' min and max - each(axes, function (axis) { - if (axis.options.zoomEnabled !== false) { - var translate = axis.translate, - isXAxis = axis.isXAxis, - isHorizontal = inverted ? !isXAxis : isXAxis, - selectionMin = translate( - isHorizontal ? - selectionLeft : - plotHeight - selectionTop - selectionBox.height, - true, - 0, - 0, - 1 - ), - selectionMax = translate( - isHorizontal ? - selectionLeft + selectionBox.width : - plotHeight - selectionTop, - true, - 0, - 0, - 1 - ); - - selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({ - axis: axis, - min: mathMin(selectionMin, selectionMax), // for reversed axes, - max: mathMax(selectionMin, selectionMax) - }); - } - }); - fireEvent(chart, 'selection', selectionData, zoom); - - } - selectionMarker = selectionMarker.destroy(); - } - - css(container, { cursor: 'auto' }); - - chart.mouseIsDown = mouseIsDown = hasDragged = false; - removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop); - - } - - /** - * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea. - */ - function hideTooltipOnMouseMove(e) { - var pageX = defined(e.pageX) ? e.pageX : e.page.x, // In mootools the event is wrapped and the page x/y position is named e.page.x - pageY = defined(e.pageX) ? e.pageY : e.page.y; // Ref: http://mootools.net/docs/core/Types/DOMEvent - - if (chartPosition && - !isInsidePlot(pageX - chartPosition.left - plotLeft, - pageY - chartPosition.top - plotTop)) { - resetTracker(); - } - } - - /** - * When mouse leaves the container, hide the tooltip. - */ - function hideTooltipOnMouseLeave() { - resetTracker(); - chartPosition = null; // also reset the chart position, used in #149 fix - } - - /** - * Set the JS events on the container element - */ - function setDOMEvents() { - var lastWasOutsidePlot = true; - /* - * Record the starting position of a dragoperation - */ - container.onmousedown = function (e) { - e = normalizeMouseEvent(e); - - // issue #295, dragging not always working in Firefox - if (!hasTouch && e.preventDefault) { - e.preventDefault(); - } - - // record the start position - chart.mouseIsDown = mouseIsDown = true; - mouseDownX = e.chartX; - mouseDownY = e.chartY; - - addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop); - }; - - // The mousemove, touchmove and touchstart event handler - var mouseMove = function (e) { - - // let the system handle multitouch operations like two finger scroll - // and pinching - if (e && e.touches && e.touches.length > 1) { - return; - } - - // normalize - e = normalizeMouseEvent(e); - if (!hasTouch) { // not for touch devices - e.returnValue = false; - } - - var chartX = e.chartX, - chartY = e.chartY, - isOutsidePlot = !isInsidePlot(chartX - plotLeft, chartY - plotTop); - - // on touch devices, only trigger click if a handler is defined - if (hasTouch && e.type === 'touchstart') { - if (attr(e.target, 'isTracker')) { - if (!chart.runTrackerClick) { - e.preventDefault(); - } - } else if (!runChartClick && !isOutsidePlot) { - e.preventDefault(); - } - } - - // cancel on mouse outside - if (isOutsidePlot) { - - /*if (!lastWasOutsidePlot) { - // reset the tracker - resetTracker(); - }*/ - - // drop the selection if any and reset mouseIsDown and hasDragged - //drop(); - if (chartX < plotLeft) { - chartX = plotLeft; - } else if (chartX > plotLeft + plotWidth) { - chartX = plotLeft + plotWidth; - } - - if (chartY < plotTop) { - chartY = plotTop; - } else if (chartY > plotTop + plotHeight) { - chartY = plotTop + plotHeight; - } - - } - - if (mouseIsDown && e.type !== 'touchstart') { // make selection - - // determine if the mouse has moved more than 10px - hasDragged = Math.sqrt( - Math.pow(mouseDownX - chartX, 2) + - Math.pow(mouseDownY - chartY, 2) - ); - if (hasDragged > 10) { - var clickedInside = isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop), - hoverPoints = chart.hoverPoints; - - // make a selection - if (hasCartesianSeries && (zoomX || zoomY) && clickedInside) { - if (!selectionMarker) { - selectionMarker = renderer.rect( - plotLeft, - plotTop, - zoomHor ? 1 : plotWidth, - zoomVert ? 1 : plotHeight, - 0 - ) - .attr({ - fill: optionsChart.selectionMarkerFill || 'rgba(69,114,167,0.25)', - zIndex: 7 - }) - .add(); - } - } - - // adjust the width of the selection marker - if (selectionMarker && zoomHor) { - var xSize = chartX - mouseDownX; - selectionMarker.attr({ - width: mathAbs(xSize), - x: (xSize > 0 ? 0 : xSize) + mouseDownX - }); - } - // adjust the height of the selection marker - if (selectionMarker && zoomVert) { - var ySize = chartY - mouseDownY; - selectionMarker.attr({ - height: mathAbs(ySize), - y: (ySize > 0 ? 0 : ySize) + mouseDownY - }); - } - - // panning - if (clickedInside && !selectionMarker && optionsChart.panning) { - - var xAxis = chart.xAxis[0], - halfPointRange = xAxis.pointRange / 2, - extremes = xAxis.getExtremes(), - newMin = xAxis.translate(mouseDownX - chartX, true) + halfPointRange, - newMax = xAxis.translate(mouseDownX + plotWidth - chartX, true) - halfPointRange; - - // remove active points for shared tooltip - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - if (newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) { - xAxis.setExtremes(newMin, newMax, true, false); - } - - mouseDownX = chartX; - css(container, { cursor: 'move' }); - } - } - - } else if (!isOutsidePlot) { - // show the tooltip - onmousemove(e); - } - - lastWasOutsidePlot = isOutsidePlot; - - // when outside plot, allow touch-drag by returning true - return isOutsidePlot || !hasCartesianSeries; - }; - - /* - * When the mouse enters the container, run mouseMove - */ - container.onmousemove = mouseMove; - - /* - * When the mouse leaves the container, hide the tracking (tooltip). - */ - addEvent(container, 'mouseleave', hideTooltipOnMouseLeave); - - // issue #149 workaround - // The mouseleave event above does not always fire. Whenever the mouse is moving - // outside the plotarea, hide the tooltip - addEvent(doc, 'mousemove', hideTooltipOnMouseMove); - - container.ontouchstart = function (e) { - // For touch devices, use touchmove to zoom - if (zoomX || zoomY) { - container.onmousedown(e); - } - // Show tooltip and prevent the lower mouse pseudo event - mouseMove(e); - }; - - /* - * Allow dragging the finger over the chart to read the values on touch - * devices - */ - container.ontouchmove = mouseMove; - - /* - * Allow dragging the finger over the chart to read the values on touch - * devices - */ - container.ontouchend = function () { - if (hasDragged) { - resetTracker(); - } - }; - - - // MooTools 1.2.3 doesn't fire this in IE when using addEvent - container.onclick = function (e) { - var hoverPoint = chart.hoverPoint; - e = normalizeMouseEvent(e); - - e.cancelBubble = true; // IE specific - - - if (!hasDragged) { - if (hoverPoint && attr(e.target, 'isTracker')) { - var plotX = hoverPoint.plotX, - plotY = hoverPoint.plotY; - - // add page position info - extend(hoverPoint, { - pageX: chartPosition.left + plotLeft + - (inverted ? plotWidth - plotY : plotX), - pageY: chartPosition.top + plotTop + - (inverted ? plotHeight - plotX : plotY) - }); - - // the series click event - fireEvent(hoverPoint.series, 'click', extend(e, { - point: hoverPoint - })); - - // the point click event - hoverPoint.firePointEvent('click', e); - - } else { - extend(e, getMouseCoordinates(e)); - - // fire a click event in the chart - if (isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) { - fireEvent(chart, 'click', e); - } - } - - - } - // reset mouseIsDown and hasDragged - hasDragged = false; - }; - - } - - /** - * Destroys the MouseTracker object and disconnects DOM events. - */ - function destroy() { - // Destroy the tracker group element - if (chart.trackerGroup) { - chart.trackerGroup = trackerGroup = chart.trackerGroup.destroy(); - } - - removeEvent(container, 'mouseleave', hideTooltipOnMouseLeave); - removeEvent(doc, 'mousemove', hideTooltipOnMouseMove); - container.onclick = container.onmousedown = container.onmousemove = container.ontouchstart = container.ontouchend = container.ontouchmove = null; - } - - /** - * Create the image map that listens for mouseovers - */ - placeTrackerGroup = function () { - - // first create - plot positions is not final at this stage - if (!trackerGroup) { - chart.trackerGroup = trackerGroup = renderer.g('tracker') - .attr({ zIndex: 9 }) - .add(); - - // then position - this happens on load and after resizing and changing - // axis or box positions - } else { - trackerGroup.translate(plotLeft, plotTop); - if (inverted) { - trackerGroup.attr({ - width: chart.plotWidth, - height: chart.plotHeight - }).invert(); - } - } - }; - - - // Run MouseTracker - placeTrackerGroup(); - if (options.enabled) { - chart.tooltip = tooltip = Tooltip(options); - } - - setDOMEvents(); - - // set the fixed interval ticking for the smooth tooltip - tooltipInterval = setInterval(function () { - if (tooltipTick) { - tooltipTick(); - } - }, 32); - - // expose properties - extend(this, { - zoomX: zoomX, - zoomY: zoomY, - resetTracker: resetTracker, - normalizeMouseEvent: normalizeMouseEvent, - destroy: destroy - }); - } - - - - /** - * The overview of the chart's series - */ - var Legend = function () { - - var options = chart.options.legend; - - if (!options.enabled) { - return; - } - - var horizontal = options.layout === 'horizontal', - symbolWidth = options.symbolWidth, - symbolPadding = options.symbolPadding, - allItems, - style = options.style, - itemStyle = options.itemStyle, - itemHoverStyle = options.itemHoverStyle, - itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle), - padding = pInt(style.padding), - y = 18, - initialItemX = 4 + padding + symbolWidth + symbolPadding, - itemX, - itemY, - lastItemY, - itemHeight = 0, - box, - legendBorderWidth = options.borderWidth, - legendBackgroundColor = options.backgroundColor, - legendGroup, - offsetWidth, - widthOption = options.width, - series = chart.series, - reversedLegend = options.reversed; - - - - /** - * Set the colors for the legend item - * @param {Object} item A Series or Point instance - * @param {Object} visible Dimmed or colored - */ - function colorizeItem(item, visible) { - var legendItem = item.legendItem, - legendLine = item.legendLine, - legendSymbol = item.legendSymbol, - hiddenColor = itemHiddenStyle.color, - textColor = visible ? options.itemStyle.color : hiddenColor, - symbolColor = visible ? item.color : hiddenColor; - - if (legendItem) { - legendItem.css({ fill: textColor }); - } - if (legendLine) { - legendLine.attr({ stroke: symbolColor }); - } - if (legendSymbol) { - legendSymbol.attr({ - stroke: symbolColor, - fill: symbolColor - }); - } - } - - /** - * Position the legend item - * @param {Object} item A Series or Point instance - * @param {Object} visible Dimmed or colored - */ - function positionItem(item, itemX, itemY) { - var legendItem = item.legendItem, - legendLine = item.legendLine, - legendSymbol = item.legendSymbol, - checkbox = item.checkbox; - if (legendItem) { - legendItem.attr({ - x: itemX, - y: itemY - }); - } - if (legendLine) { - legendLine.translate(itemX, itemY - 4); - } - if (legendSymbol) { - legendSymbol.attr({ - x: itemX + legendSymbol.xOff, - y: itemY + legendSymbol.yOff - }); - } - if (checkbox) { - checkbox.x = itemX; - checkbox.y = itemY; - } - } - - /** - * Destroy a single legend item - * @param {Object} item The series or point - */ - function destroyItem(item) { - var checkbox = item.checkbox; - - // destroy SVG elements - each(['legendItem', 'legendLine', 'legendSymbol'], function (key) { - if (item[key]) { - item[key].destroy(); - } - }); - - if (checkbox) { - discardElement(item.checkbox); - } - - - } - - /** - * Destroys the legend. - */ - function destroy() { - if (box) { - box = box.destroy(); - } - - if (legendGroup) { - legendGroup = legendGroup.destroy(); - } - } - - /** - * Position the checkboxes after the width is determined - */ - function positionCheckboxes() { - each(allItems, function (item) { - var checkbox = item.checkbox, - alignAttr = legendGroup.alignAttr; - if (checkbox) { - css(checkbox, { - left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 40) + PX, - top: (alignAttr.translateY + checkbox.y - 11) + PX - }); - } - }); - } - - /** - * Render a single specific legend item - * @param {Object} item A series or point - */ - function renderItem(item) { - var bBox, - itemWidth, - legendSymbol, - symbolX, - symbolY, - simpleSymbol, - radius, - li = item.legendItem, - series = item.series || item, - itemOptions = series.options, - strokeWidth = (itemOptions && itemOptions.borderWidth) || 0; - - - if (!li) { // generate it once, later move it - - // let these series types use a simple symbol - simpleSymbol = /^(bar|pie|area|column)$/.test(series.type); - - // generate the list item text - item.legendItem = li = renderer.text( - options.labelFormatter.call(item), - 0, - 0 - ) - .css(item.visible ? itemStyle : itemHiddenStyle) - .on('mouseover', function () { - item.setState(HOVER_STATE); - li.css(itemHoverStyle); - }) - .on('mouseout', function () { - li.css(item.visible ? itemStyle : itemHiddenStyle); - item.setState(); - }) - .on('click', function () { - var strLegendItemClick = 'legendItemClick', - fnLegendItemClick = function () { - item.setVisible(); - }; - - // click the name or symbol - if (item.firePointEvent) { // point - item.firePointEvent(strLegendItemClick, null, fnLegendItemClick); - } else { - fireEvent(item, strLegendItemClick, null, fnLegendItemClick); - } - }) - .attr({ zIndex: 2 }) - .add(legendGroup); - - // draw the line - if (!simpleSymbol && itemOptions && itemOptions.lineWidth) { - var attrs = { - 'stroke-width': itemOptions.lineWidth, - zIndex: 2 - }; - if (itemOptions.dashStyle) { - attrs.dashstyle = itemOptions.dashStyle; - } - item.legendLine = renderer.path([ - M, - -symbolWidth - symbolPadding, - 0, - L, - -symbolPadding, - 0 - ]) - .attr(attrs) - .add(legendGroup); - } - - // draw a simple symbol - if (simpleSymbol) { // bar|pie|area|column - - legendSymbol = renderer.rect( - (symbolX = -symbolWidth - symbolPadding), - (symbolY = -11), - symbolWidth, - 12, - 2 - ).attr({ - //'stroke-width': 0, - zIndex: 3 - }).add(legendGroup); - } else if (itemOptions && itemOptions.marker && itemOptions.marker.enabled) { // draw the marker - radius = itemOptions.marker.radius; - legendSymbol = renderer.symbol( - item.symbol, - (symbolX = -symbolWidth / 2 - symbolPadding - radius), - (symbolY = -4 - radius), - 2 * radius, - 2 * radius - ) - .attr(item.pointAttr[NORMAL_STATE]) - .attr({ zIndex: 3 }) - .add(legendGroup); - - } - if (legendSymbol) { - legendSymbol.xOff = symbolX + (strokeWidth % 2 / 2); - legendSymbol.yOff = symbolY + (strokeWidth % 2 / 2); - } - - item.legendSymbol = legendSymbol; - - // colorize the items - colorizeItem(item, item.visible); - - - // add the HTML checkbox on top - if (itemOptions && itemOptions.showCheckbox) { - item.checkbox = createElement('input', { - type: 'checkbox', - checked: item.selected, - defaultChecked: item.selected // required by IE7 - }, options.itemCheckboxStyle, container); - - addEvent(item.checkbox, 'click', function (event) { - var target = event.target; - fireEvent(item, 'checkboxClick', { - checked: target.checked - }, - function () { - item.select(); - } - ); - }); - } - } - - - // calculate the positions for the next line - bBox = li.getBBox(); - - itemWidth = item.legendItemWidth = - options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding; - itemHeight = bBox.height; - - // if the item exceeds the width, start a new line - if (horizontal && itemX - initialItemX + itemWidth > - (widthOption || (chartWidth - 2 * padding - initialItemX))) { - itemX = initialItemX; - itemY += itemHeight; - } - lastItemY = itemY; - - // position the newly generated or reordered items - positionItem(item, itemX, itemY); - - // advance - if (horizontal) { - itemX += itemWidth; - } else { - itemY += itemHeight; - } - - // the width of the widest item - offsetWidth = widthOption || mathMax( - horizontal ? itemX - initialItemX : itemWidth, - offsetWidth - ); - - - - // add it all to an array to use below - //allItems.push(item); - } - - /** - * Render the legend. This method can be called both before and after - * chart.render. If called after, it will only rearrange items instead - * of creating new ones. - */ - function renderLegend() { - itemX = initialItemX; - itemY = y; - offsetWidth = 0; - lastItemY = 0; - - if (!legendGroup) { - legendGroup = renderer.g('legend') - .attr({ zIndex: 10 }) // in front of trackers, #414 - .add(); - } - - - // add each series or point - allItems = []; - each(series, function (serie) { - var seriesOptions = serie.options; - - if (!seriesOptions.showInLegend) { - return; - } - - // use points or series for the legend item depending on legendType - allItems = allItems.concat(seriesOptions.legendType === 'point' ? - serie.data : - serie - ); - - }); - - // sort by legendIndex - stableSort(allItems, function (a, b) { - return (a.options.legendIndex || 0) - (b.options.legendIndex || 0); - }); - - // reversed legend - if (reversedLegend) { - allItems.reverse(); - } - - // render the items - each(allItems, renderItem); - - - // Draw the border - legendWidth = widthOption || offsetWidth; - legendHeight = lastItemY - y + itemHeight; - - if (legendBorderWidth || legendBackgroundColor) { - legendWidth += 2 * padding; - legendHeight += 2 * padding; - - if (!box) { - box = renderer.rect( - 0, - 0, - legendWidth, - legendHeight, - options.borderRadius, - legendBorderWidth || 0 - ).attr({ - stroke: options.borderColor, - 'stroke-width': legendBorderWidth || 0, - fill: legendBackgroundColor || NONE - }) - .add(legendGroup) - .shadow(options.shadow); - box.isNew = true; - - } else if (legendWidth > 0 && legendHeight > 0) { - box[box.isNew ? 'attr' : 'animate']( - box.crisp(null, null, null, legendWidth, legendHeight) - ); - box.isNew = false; - } - - // hide the border if no items - box[allItems.length ? 'show' : 'hide'](); - } - - // 1.x compatibility: positioning based on style - var props = ['left', 'right', 'top', 'bottom'], - prop, - i = 4; - while (i--) { - prop = props[i]; - if (style[prop] && style[prop] !== 'auto') { - options[i < 2 ? 'align' : 'verticalAlign'] = prop; - options[i < 2 ? 'x' : 'y'] = pInt(style[prop]) * (i % 2 ? -1 : 1); - } - } - - if (allItems.length) { - legendGroup.align(extend(options, { - width: legendWidth, - height: legendHeight - }), true, spacingBox); - } - - if (!isResizing) { - positionCheckboxes(); - } - } - - - // run legend - renderLegend(); - - // move checkboxes - addEvent(chart, 'endResize', positionCheckboxes); - - // expose - return { - colorizeItem: colorizeItem, - destroyItem: destroyItem, - renderLegend: renderLegend, - destroy: destroy - }; - }; - - - - - - - /** - * Initialize an individual series, called internally before render time - */ - function initSeries(options) { - var type = options.type || optionsChart.type || optionsChart.defaultSeriesType, - typeClass = seriesTypes[type], - serie, - hasRendered = chart.hasRendered; - - // an inverted chart can't take a column series and vice versa - if (hasRendered) { - if (inverted && type === 'column') { - typeClass = seriesTypes.bar; - } else if (!inverted && type === 'bar') { - typeClass = seriesTypes.column; - } - } - - serie = new typeClass(); - - serie.init(chart, options); - - // set internal chart properties - if (!hasRendered && serie.inverted) { - inverted = true; - } - if (serie.isCartesian) { - hasCartesianSeries = serie.isCartesian; - } - - series.push(serie); - - return serie; - } - - /** - * Add a series dynamically after time - * - * @param {Object} options The config options - * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true. - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * - * @return {Object} series The newly created series object - */ - function addSeries(options, redraw, animation) { - var series; - - if (options) { - setAnimation(animation, chart); - redraw = pick(redraw, true); // defaults to true - - fireEvent(chart, 'addSeries', { options: options }, function () { - series = initSeries(options); - series.isDirty = true; - - chart.isDirtyLegend = true; // the series array is out of sync with the display - if (redraw) { - chart.redraw(); - } - }); - } - - return series; - } - - /** - * Check whether a given point is within the plot area - * - * @param {Number} x Pixel x relative to the plot area - * @param {Number} y Pixel y relative to the plot area - */ - isInsidePlot = function (x, y) { - return x >= 0 && - x <= plotWidth && - y >= 0 && - y <= plotHeight; - }; - - /** - * Adjust all axes tick amounts - */ - function adjustTickAmounts() { - if (optionsChart.alignTicks !== false) { - each(axes, function (axis) { - axis.adjustTickAmount(); - }); - } - maxTicks = null; - } - - /** - * Redraw legend, axes or series based on updated data - * - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - function redraw(animation) { - var redrawLegend = chart.isDirtyLegend, - hasStackedSeries, - isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed? - seriesLength = series.length, - i = seriesLength, - clipRect = chart.clipRect, - serie; - - setAnimation(animation, chart); - - // link stacked series - while (i--) { - serie = series[i]; - if (serie.isDirty && serie.options.stacking) { - hasStackedSeries = true; - break; - } - } - if (hasStackedSeries) { // mark others as dirty - i = seriesLength; - while (i--) { - serie = series[i]; - if (serie.options.stacking) { - serie.isDirty = true; - } - } - } - - // handle updated data in the series - each(series, function (serie) { - if (serie.isDirty) { // prepare the data so axis can read it - if (serie.options.legendType === 'point') { - redrawLegend = true; - } - } - }); - - // handle added or removed series - if (redrawLegend && legend.renderLegend) { // series or pie points are added or removed - // draw legend graphics - legend.renderLegend(); - - chart.isDirtyLegend = false; - } - - - if (hasCartesianSeries) { - if (!isResizing) { - - // reset maxTicks - maxTicks = null; - - // set axes scales - each(axes, function (axis) { - axis.setScale(); - }); - } - adjustTickAmounts(); - getMargins(); - - // redraw axes - each(axes, function (axis) { - if (axis.isDirty) { - axis.redraw(); - //isDirtyBox = true; // force redrawing subsequent axes - } - }); - - - } - - // the plot areas size has changed - if (isDirtyBox) { - drawChartBox(); - placeTrackerGroup(); - - // move clip rect - if (clipRect) { - stop(clipRect); - clipRect.animate({ // for chart resize - width: chart.plotSizeX, - height: chart.plotSizeY + 1 - }); - } - - } - - - // redraw affected series - each(series, function (serie) { - if (serie.isDirty && serie.visible && - (!serie.isCartesian || serie.xAxis)) { // issue #153 - serie.redraw(); - } - }); - - - // hide tooltip and hover states - if (tracker && tracker.resetTracker) { - tracker.resetTracker(); - } - - // fire the event - fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw - } - - - - /** - * Dim the chart and show a loading text or symbol - * @param {String} str An optional text to show in the loading label instead of the default one - */ - function showLoading(str) { - var loadingOptions = options.loading; - - // create the layer at the first call - if (!loadingDiv) { - loadingDiv = createElement(DIV, { - className: PREFIX + 'loading' - }, extend(loadingOptions.style, { - left: plotLeft + PX, - top: plotTop + PX, - width: plotWidth + PX, - height: plotHeight + PX, - zIndex: 10, - display: NONE - }), container); - - loadingSpan = createElement( - 'span', - null, - loadingOptions.labelStyle, - loadingDiv - ); - - } - - // update text - loadingSpan.innerHTML = str || options.lang.loading; - - // show it - if (!loadingShown) { - css(loadingDiv, { opacity: 0, display: '' }); - animate(loadingDiv, { - opacity: loadingOptions.style.opacity - }, { - duration: loadingOptions.showDuration || 0 - }); - loadingShown = true; - } - } - /** - * Hide the loading layer - */ - function hideLoading() { - if (loadingDiv) { - animate(loadingDiv, { - opacity: 0 - }, { - duration: options.loading.hideDuration || 100, - complete: function () { - css(loadingDiv, { display: NONE }); - } - }); - } - loadingShown = false; - } - - /** - * Get an axis, series or point object by id. - * @param id {String} The id as given in the configuration options - */ - function get(id) { - var i, - j, - points; - - // search axes - for (i = 0; i < axes.length; i++) { - if (axes[i].options.id === id) { - return axes[i]; - } - } - - // search series - for (i = 0; i < series.length; i++) { - if (series[i].options.id === id) { - return series[i]; - } - } - - // search points - for (i = 0; i < series.length; i++) { - points = series[i].points; - for (j = 0; j < points.length; j++) { - if (points[j].id === id) { - return points[j]; - } - } - } - return null; - } - - /** - * Create the Axis instances based on the config options - */ - function getAxes() { - var xAxisOptions = options.xAxis || {}, - yAxisOptions = options.yAxis || {}, - optionsArray, - axis; - - // make sure the options are arrays and add some members - xAxisOptions = splat(xAxisOptions); - each(xAxisOptions, function (axis, i) { - axis.index = i; - axis.isX = true; - }); - - yAxisOptions = splat(yAxisOptions); - each(yAxisOptions, function (axis, i) { - axis.index = i; - }); - - // concatenate all axis options into one array - optionsArray = xAxisOptions.concat(yAxisOptions); - - each(optionsArray, function (axisOptions) { - axis = new Axis(axisOptions); - }); - - adjustTickAmounts(); - } - - - /** - * Get the currently selected points from all series - */ - function getSelectedPoints() { - var points = []; - each(series, function (serie) { - points = points.concat(grep(serie.points, function (point) { - return point.selected; - })); - }); - return points; - } - - /** - * Get the currently selected series - */ - function getSelectedSeries() { - return grep(series, function (serie) { - return serie.selected; - }); - } - - /** - * Zoom out to 1:1 - */ - zoomOut = function () { - fireEvent(chart, 'selection', { resetSelection: true }, zoom); - chart.toolbar.remove('zoom'); - - }; - /** - * Zoom into a given portion of the chart given by axis coordinates - * @param {Object} event - */ - zoom = function (event) { - - // add button to reset selection - var lang = defaultOptions.lang, - animate = chart.pointCount < 100; - - if (chart.resetZoomEnabled !== false) { // hook for Stock charts etc. - chart.toolbar.add('zoom', lang.resetZoom, lang.resetZoomTitle, zoomOut); - } - - // if zoom is called with no arguments, reset the axes - if (!event || event.resetSelection) { - each(axes, function (axis) { - if (axis.options.zoomEnabled !== false) { - axis.setExtremes(null, null, true, animate); - } - }); - } else { // else, zoom in on all axes - each(event.xAxis.concat(event.yAxis), function (axisData) { - var axis = axisData.axis; - - // don't zoom more than minRange - if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) { - axis.setExtremes(axisData.min, axisData.max, true, animate); - } - }); - } - }; - - /** - * Show the title and subtitle of the chart - * - * @param titleOptions {Object} New title options - * @param subtitleOptions {Object} New subtitle options - * - */ - function setTitle(titleOptions, subtitleOptions) { - - chartTitleOptions = merge(options.title, titleOptions); - chartSubtitleOptions = merge(options.subtitle, subtitleOptions); - - // add title and subtitle - each([ - ['title', titleOptions, chartTitleOptions], - ['subtitle', subtitleOptions, chartSubtitleOptions] - ], function (arr) { - var name = arr[0], - title = chart[name], - titleOptions = arr[1], - chartTitleOptions = arr[2]; - - if (title && titleOptions) { - title = title.destroy(); // remove old - } - if (chartTitleOptions && chartTitleOptions.text && !title) { - chart[name] = renderer.text( - chartTitleOptions.text, - 0, - 0, - chartTitleOptions.useHTML - ) - .attr({ - align: chartTitleOptions.align, - 'class': PREFIX + name, - zIndex: 1 - }) - .css(chartTitleOptions.style) - .add() - .align(chartTitleOptions, false, spacingBox); - } - }); - - } - - /** - * Get chart width and height according to options and container size - */ - function getChartSize() { - - containerWidth = (renderToClone || renderTo).offsetWidth; - containerHeight = (renderToClone || renderTo).offsetHeight; - chart.chartWidth = chartWidth = optionsChart.width || containerWidth || 600; - chart.chartHeight = chartHeight = optionsChart.height || - // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7: - (containerHeight > 19 ? containerHeight : 400); - } - - - /** - * Get the containing element, determine the size and create the inner container - * div to hold the chart - */ - function getContainer() { - renderTo = optionsChart.renderTo; - containerId = PREFIX + idCounter++; - - if (isString(renderTo)) { - renderTo = doc.getElementById(renderTo); - } - - // remove previous chart - renderTo.innerHTML = ''; - - // If the container doesn't have an offsetWidth, it has or is a child of a node - // that has display:none. We need to temporarily move it out to a visible - // state to determine the size, else the legend and tooltips won't render - // properly - if (!renderTo.offsetWidth) { - renderToClone = renderTo.cloneNode(0); - css(renderToClone, { - position: ABSOLUTE, - top: '-9999px', - display: '' - }); - doc.body.appendChild(renderToClone); - } - - // get the width and height - getChartSize(); - - // create the inner container - chart.container = container = createElement(DIV, { - className: PREFIX + 'container' + - (optionsChart.className ? ' ' + optionsChart.className : ''), - id: containerId - }, extend({ - position: RELATIVE, - overflow: HIDDEN, // needed for context menu (avoid scrollbars) and - // content overflow in IE - width: chartWidth + PX, - height: chartHeight + PX, - textAlign: 'left', - lineHeight: 'normal' // #427 - }, optionsChart.style), - renderToClone || renderTo - ); - - chart.renderer = renderer = - optionsChart.forExport ? // force SVG, used for SVG export - new SVGRenderer(container, chartWidth, chartHeight, true) : - new Renderer(container, chartWidth, chartHeight); - - // Issue 110 workaround: - // In Firefox, if a div is positioned by percentage, its pixel position may land - // between pixels. The container itself doesn't display this, but an SVG element - // inside this container will be drawn at subpixel precision. In order to draw - // sharp lines, this must be compensated for. This doesn't seem to work inside - // iframes though (like in jsFiddle). - var subPixelFix, rect; - if (isFirefox && container.getBoundingClientRect) { - subPixelFix = function () { - css(container, { left: 0, top: 0 }); - rect = container.getBoundingClientRect(); - css(container, { - left: (-(rect.left - pInt(rect.left))) + PX, - top: (-(rect.top - pInt(rect.top))) + PX - }); - }; - - // run the fix now - subPixelFix(); - - // run it on resize - addEvent(win, 'resize', subPixelFix); - - // remove it on chart destroy - addEvent(chart, 'destroy', function () { - removeEvent(win, 'resize', subPixelFix); - }); - } - } - - /** - * Calculate margins by rendering axis labels in a preliminary position. Title, - * subtitle and legend have already been rendered at this stage, but will be - * moved into their final positions - */ - getMargins = function () { - var legendOptions = options.legend, - legendMargin = pick(legendOptions.margin, 10), - legendX = legendOptions.x, - legendY = legendOptions.y, - align = legendOptions.align, - verticalAlign = legendOptions.verticalAlign, - titleOffset; - - resetMargins(); - - // adjust for title and subtitle - if ((chart.title || chart.subtitle) && !defined(optionsMarginTop)) { - titleOffset = mathMax( - (chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0, - (chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0 - ); - if (titleOffset) { - plotTop = mathMax(plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop); - } - } - // adjust for legend - if (legendOptions.enabled && !legendOptions.floating) { - if (align === 'right') { // horizontal alignment handled first - if (!defined(optionsMarginRight)) { - marginRight = mathMax( - marginRight, - legendWidth - legendX + legendMargin + spacingRight - ); - } - } else if (align === 'left') { - if (!defined(optionsMarginLeft)) { - plotLeft = mathMax( - plotLeft, - legendWidth + legendX + legendMargin + spacingLeft - ); - } - - } else if (verticalAlign === 'top') { - if (!defined(optionsMarginTop)) { - plotTop = mathMax( - plotTop, - legendHeight + legendY + legendMargin + spacingTop - ); - } - - } else if (verticalAlign === 'bottom') { - if (!defined(optionsMarginBottom)) { - marginBottom = mathMax( - marginBottom, - legendHeight - legendY + legendMargin + spacingBottom - ); - } - } - } - - // adjust for scroller - if (chart.extraBottomMargin) { - marginBottom += chart.extraBottomMargin; - } - if (chart.extraTopMargin) { - plotTop += chart.extraTopMargin; - } - - // pre-render axes to get labels offset width - if (hasCartesianSeries) { - each(axes, function (axis) { - axis.getOffset(); - }); - } - - if (!defined(optionsMarginLeft)) { - plotLeft += axisOffset[3]; - } - if (!defined(optionsMarginTop)) { - plotTop += axisOffset[0]; - } - if (!defined(optionsMarginBottom)) { - marginBottom += axisOffset[2]; - } - if (!defined(optionsMarginRight)) { - marginRight += axisOffset[1]; - } - - setChartSize(); - - }; - - /** - * Add the event handlers necessary for auto resizing - * - */ - function initReflow() { - var reflowTimeout; - function reflow() { - var width = optionsChart.width || renderTo.offsetWidth, - height = optionsChart.height || renderTo.offsetHeight; - - if (width && height) { // means container is display:none - if (width !== containerWidth || height !== containerHeight) { - clearTimeout(reflowTimeout); - reflowTimeout = setTimeout(function () { - resize(width, height, false); - }, 100); - } - containerWidth = width; - containerHeight = height; - } - } - addEvent(win, 'resize', reflow); - addEvent(chart, 'destroy', function () { - removeEvent(win, 'resize', reflow); - }); - } - - /** - * Fires endResize event on chart instance. - */ - function fireEndResize() { - fireEvent(chart, 'endResize', null, function () { - isResizing -= 1; - }); - } - - /** - * Resize the chart to a given width and height - * @param {Number} width - * @param {Number} height - * @param {Object|Boolean} animation - */ - resize = function (width, height, animation) { - var chartTitle = chart.title, - chartSubtitle = chart.subtitle; - - isResizing += 1; - - // set the animation for the current process - setAnimation(animation, chart); - - oldChartHeight = chartHeight; - oldChartWidth = chartWidth; - if (defined(width)) { - chart.chartWidth = chartWidth = mathRound(width); - } - if (defined(height)) { - chart.chartHeight = chartHeight = mathRound(height); - } - - css(container, { - width: chartWidth + PX, - height: chartHeight + PX - }); - renderer.setSize(chartWidth, chartHeight, animation); - - // update axis lengths for more correct tick intervals: - plotWidth = chartWidth - plotLeft - marginRight; - plotHeight = chartHeight - plotTop - marginBottom; - - // handle axes - maxTicks = null; - each(axes, function (axis) { - axis.isDirty = true; - axis.setScale(); - }); - - // make sure non-cartesian series are also handled - each(series, function (serie) { - serie.isDirty = true; - }); - - chart.isDirtyLegend = true; // force legend redraw - chart.isDirtyBox = true; // force redraw of plot and chart border - - getMargins(); - - // move titles - if (chartTitle) { - chartTitle.align(null, null, spacingBox); - } - if (chartSubtitle) { - chartSubtitle.align(null, null, spacingBox); - } - - redraw(animation); - - - oldChartHeight = null; - fireEvent(chart, 'resize'); - - // fire endResize and set isResizing back - // If animation is disabled, fire without delay - if (globalAnimation === false) { - fireEndResize(); - } else { // else set a timeout with the animation duration - setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500); - } - }; - - /** - * Set the public chart properties. This is done before and after the pre-render - * to determine margin sizes - */ - setChartSize = function () { - - chart.plotLeft = plotLeft = mathRound(plotLeft); - chart.plotTop = plotTop = mathRound(plotTop); - chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - marginRight); - chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - marginBottom); - - chart.plotSizeX = inverted ? plotHeight : plotWidth; - chart.plotSizeY = inverted ? plotWidth : plotHeight; - - spacingBox = { - x: spacingLeft, - y: spacingTop, - width: chartWidth - spacingLeft - spacingRight, - height: chartHeight - spacingTop - spacingBottom - }; - - each(axes, function (axis) { - if (axis.isDirty) { - axis.setAxisSize(); - } - }); - }; - - /** - * Initial margins before auto size margins are applied - */ - resetMargins = function () { - plotTop = pick(optionsMarginTop, spacingTop); - marginRight = pick(optionsMarginRight, spacingRight); - marginBottom = pick(optionsMarginBottom, spacingBottom); - plotLeft = pick(optionsMarginLeft, spacingLeft); - axisOffset = [0, 0, 0, 0]; // top, right, bottom, left - }; - - /** - * Draw the borders and backgrounds for chart and plot area - */ - drawChartBox = function () { - var chartBorderWidth = optionsChart.borderWidth || 0, - chartBackgroundColor = optionsChart.backgroundColor, - plotBackgroundColor = optionsChart.plotBackgroundColor, - plotBackgroundImage = optionsChart.plotBackgroundImage, - mgn, - plotSize = { - x: plotLeft, - y: plotTop, - width: plotWidth, - height: plotHeight - }; - - // Chart area - mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0); - - if (chartBorderWidth || chartBackgroundColor) { - if (!chartBackground) { - chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn, - optionsChart.borderRadius, chartBorderWidth) - .attr({ - stroke: optionsChart.borderColor, - 'stroke-width': chartBorderWidth, - fill: chartBackgroundColor || NONE - }) - .add() - .shadow(optionsChart.shadow); - } else { // resize - chartBackground.animate( - chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn) - ); - } - } - - - // Plot background - if (plotBackgroundColor) { - if (!plotBackground) { - plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0) - .attr({ - fill: plotBackgroundColor - }) - .add() - .shadow(optionsChart.plotShadow); - } else { - plotBackground.animate(plotSize); - } - } - if (plotBackgroundImage) { - if (!plotBGImage) { - plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight) - .add(); - } else { - plotBGImage.animate(plotSize); - } - } - - // Plot area border - if (optionsChart.plotBorderWidth) { - if (!plotBorder) { - plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, optionsChart.plotBorderWidth) - .attr({ - stroke: optionsChart.plotBorderColor, - 'stroke-width': optionsChart.plotBorderWidth, - zIndex: 4 - }) - .add(); - } else { - plotBorder.animate( - plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight) - ); - } - } - - // reset - chart.isDirtyBox = false; - }; - - /** - * Detect whether the chart is inverted, either by setting the chart.inverted option - * or adding a bar series to the configuration options - */ - function setInverted() { - var BAR = 'bar', - isInverted = ( - inverted || // it is set before - optionsChart.inverted || - optionsChart.type === BAR || // default series type - optionsChart.defaultSeriesType === BAR // backwards compatible - ), - seriesOptions = options.series, - i = seriesOptions && seriesOptions.length; - - // check if a bar series is present in the config options - while (!isInverted && i--) { - if (seriesOptions[i].type === BAR) { - isInverted = true; - } - } - - // set the chart property and the chart scope variable - chart.inverted = inverted = isInverted; - } - - /** - * Render all graphics for the chart - */ - function render() { - var labels = options.labels, - credits = options.credits, - creditsHref; - - // Title - setTitle(); - - - // Legend - legend = chart.legend = new Legend(); - - // Get margins by pre-rendering axes - // set axes scales - each(axes, function (axis) { - axis.setScale(); - }); - getMargins(); - each(axes, function (axis) { - axis.setTickPositions(true); // update to reflect the new margins - }); - adjustTickAmounts(); - getMargins(); // second pass to check for new labels - - - // Draw the borders and backgrounds - drawChartBox(); - - // Axes - if (hasCartesianSeries) { - each(axes, function (axis) { - axis.render(); - }); - } - - - // The series - if (!chart.seriesGroup) { - chart.seriesGroup = renderer.g('series-group') - .attr({ zIndex: 3 }) - .add(); - } - each(series, function (serie) { - serie.translate(); - serie.setTooltipPoints(); - serie.render(); - }); - - - // Labels - if (labels.items) { - each(labels.items, function () { - var style = extend(labels.style, this.style), - x = pInt(style.left) + plotLeft, - y = pInt(style.top) + plotTop + 12; - - // delete to prevent rewriting in IE - delete style.left; - delete style.top; - - renderer.text( - this.html, - x, - y - ) - .attr({ zIndex: 2 }) - .css(style) - .add(); - - }); - } - - // Toolbar (don't redraw) - if (!chart.toolbar) { - chart.toolbar = Toolbar(); - } - - // Credits - if (credits.enabled && !chart.credits) { - creditsHref = credits.href; - chart.credits = renderer.text( - credits.text, - 0, - 0 - ) - .on('click', function () { - if (creditsHref) { - location.href = creditsHref; - } - }) - .attr({ - align: credits.position.align, - zIndex: 8 - }) - .css(credits.style) - .add() - .align(credits.position); - } - - placeTrackerGroup(); - - // Set flag - chart.hasRendered = true; - - // If the chart was rendered outside the top container, put it back in - if (renderToClone) { - renderTo.appendChild(container); - discardElement(renderToClone); - //updatePosition(container); - } - } - - /** - * Clean up memory usage - */ - function destroy() { - var i, - parentNode = container && container.parentNode; - - // If the chart is destroyed already, do nothing. - // This will happen if if a script invokes chart.destroy and - // then it will be called again on win.unload - if (chart === null) { - return; - } - - // fire the chart.destoy event - fireEvent(chart, 'destroy'); - - // remove events - removeEvent(chart); - - // ==== Destroy collections: - // Destroy axes - i = axes.length; - while (i--) { - axes[i] = axes[i].destroy(); - } - - // Destroy each series - i = series.length; - while (i--) { - series[i] = series[i].destroy(); - } - - // ==== Destroy chart properties: - each(['title', 'subtitle', 'seriesGroup', 'clipRect', 'credits', 'tracker', 'scroller', 'rangeSelector'], function (name) { - var prop = chart[name]; - - if (prop) { - chart[name] = prop.destroy(); - } - }); - - // ==== Destroy local variables: - each([chartBackground, plotBorder, plotBackground, legend, tooltip, renderer, tracker], function (obj) { - if (obj && obj.destroy) { - obj.destroy(); - } - }); - chartBackground = plotBorder = plotBackground = legend = tooltip = renderer = tracker = null; - - // remove container and all SVG - if (container) { // can break in IE when destroyed before finished loading - container.innerHTML = ''; - removeEvent(container); - if (parentNode) { - discardElement(container); - } - - // IE6 leak - container = null; - } - - // memory and CPU leak - clearInterval(tooltipInterval); - - // clean it all up - for (i in chart) { - delete chart[i]; - } - - chart = null; - options = null; - } - /** - * Prepare for first rendering after all data are loaded - */ - function firstRender() { - - // VML namespaces can't be added until after complete. Listening - // for Perini's doScroll hack is not enough. - var ONREADYSTATECHANGE = 'onreadystatechange', - COMPLETE = 'complete'; - // Note: in spite of JSLint's complaints, win == win.top is required - /*jslint eqeq: true*/ - if (!hasSVG && win == win.top && doc.readyState !== COMPLETE) { - /*jslint eqeq: false*/ - doc.attachEvent(ONREADYSTATECHANGE, function () { - doc.detachEvent(ONREADYSTATECHANGE, firstRender); - if (doc.readyState === COMPLETE) { - firstRender(); - } - }); - return; - } - - // create the container - getContainer(); - - // Run an early event after the container and renderer are established - fireEvent(chart, 'init'); - - // Initialize range selector for stock charts - if (Highcharts.RangeSelector && options.rangeSelector.enabled) { - chart.rangeSelector = new Highcharts.RangeSelector(chart); - } - - resetMargins(); - setChartSize(); - - // Set the common inversion and transformation for inverted series after initSeries - setInverted(); - - // get axes - getAxes(); - - // Initialize the series - each(options.series || [], function (serieOptions) { - initSeries(serieOptions); - }); - - // Run an event where series and axes can be added - //fireEvent(chart, 'beforeRender'); - - // Initialize scroller for stock charts - if (Highcharts.Scroller && (options.navigator.enabled || options.scrollbar.enabled)) { - chart.scroller = new Highcharts.Scroller(chart); - } - - chart.render = render; - - // depends on inverted and on margins being set - chart.tracker = tracker = new MouseTracker(options.tooltip); - - - render(); - - // run callbacks - if (callback) { - callback.apply(chart, [chart]); - } - each(chart.callbacks, function (fn) { - fn.apply(chart, [chart]); - }); - - fireEvent(chart, 'load'); - - } - - // Run chart - - // Set up auto resize - if (optionsChart.reflow !== false) { - addEvent(chart, 'load', initReflow); - } - - // Chart event handlers - if (chartEvents) { - for (eventType in chartEvents) { - addEvent(chart, eventType, chartEvents[eventType]); - } - } - - - chart.options = options; - chart.series = series; - - - chart.xAxis = []; - chart.yAxis = []; - - - - - // Expose methods and variables - chart.addSeries = addSeries; - chart.animation = pick(optionsChart.animation, true); - chart.Axis = Axis; - chart.destroy = destroy; - chart.get = get; - chart.getSelectedPoints = getSelectedPoints; - chart.getSelectedSeries = getSelectedSeries; - chart.hideLoading = hideLoading; - chart.initSeries = initSeries; - chart.isInsidePlot = isInsidePlot; - chart.redraw = redraw; - chart.setSize = resize; - chart.setTitle = setTitle; - chart.showLoading = showLoading; - chart.pointCount = 0; - chart.counters = new ChartCounters(); - /* - if ($) $(function() { - $container = $('#container'); - var origChartWidth, - origChartHeight; - if ($container) { - $('') - .insertBefore($container) - .click(function() { - if (origChartWidth === UNDEFINED) { - origChartWidth = chartWidth; - origChartHeight = chartHeight; - } - chart.resize(chartWidth *= 1.1, chartHeight *= 1.1); - }); - $('') - .insertBefore($container) - .click(function() { - if (origChartWidth === UNDEFINED) { - origChartWidth = chartWidth; - origChartHeight = chartHeight; - } - chart.resize(chartWidth *= 0.9, chartHeight *= 0.9); - }); - $('') - .insertBefore($container) - .click(function() { - if (origChartWidth === UNDEFINED) { - origChartWidth = chartWidth; - origChartHeight = chartHeight; - } - chart.resize(origChartWidth, origChartHeight); - }); - } - }) - */ - - - - - firstRender(); - - -} // end Chart - -// Hook for exporting module -Chart.prototype.callbacks = []; -/** - * The Point object and prototype. Inheritable and used as base for PiePoint - */ -var Point = function () {}; -Point.prototype = { - - /** - * Initialize the point - * @param {Object} series The series object containing this point - * @param {Object} options The data in either number, array or object format - */ - init: function (series, options, x) { - var point = this, - counters = series.chart.counters, - defaultColors; - point.series = series; - point.applyOptions(options, x); - point.pointAttr = {}; - - if (series.options.colorByPoint) { - defaultColors = series.chart.options.colors; - if (!point.options) { - point.options = {}; - } - point.color = point.options.color = point.color || defaultColors[counters.color++]; - - // loop back to zero - counters.wrapColor(defaultColors.length); - } - - series.chart.pointCount++; - return point; - }, - /** - * Apply the options containing the x and y data and possible some extra properties. - * This is called on point init or from point.update. - * - * @param {Object} options - */ - applyOptions: function (options, x) { - var point = this, - series = point.series, - optionsType = typeof options; - - point.config = options; - - // onedimensional array input - if (optionsType === 'number' || options === null) { - point.y = options; - } else if (typeof options[0] === 'number') { // two-dimentional array - point.x = options[0]; - point.y = options[1]; - } else if (optionsType === 'object' && typeof options.length !== 'number') { // object input - // copy options directly to point - extend(point, options); - point.options = options; - } else if (typeof options[0] === 'string') { // categorized data with name in first position - point.name = options[0]; - point.y = options[1]; - } - - /* - * If no x is set by now, get auto incremented value. All points must have an - * x value, however the y value can be null to create a gap in the series - */ - - // todo: skip this? It is only used in applyOptions, in translate it should not be used - if (point.x === UNDEFINED) { - point.x = x === UNDEFINED ? series.autoIncrement() : x; - } - - }, - - /** - * Destroy a point to clear memory. Its reference still stays in series.data. - */ - destroy: function () { - var point = this, - series = point.series, - hoverPoints = series.chart.hoverPoints, - prop; - - series.chart.pointCount--; - - if (hoverPoints) { - point.setState(); - erase(hoverPoints, point); - } - if (point === series.chart.hoverPoint) { - point.onMouseOut(); - } - series.chart.hoverPoints = null; - - // remove all events - if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive - removeEvent(point); - point.destroyElements(); - } - - if (point.legendItem) { // pies have legend items - point.series.chart.legend.destroyItem(point); - } - - for (prop in point) { - point[prop] = null; - } - - - }, - - /** - * Destroy SVG elements associated with the point - */ - destroyElements: function () { - var point = this, - props = ['graphic', 'tracker', 'dataLabel', 'group', 'connector', 'shadowGroup'], - prop, - i = 6; - while (i--) { - prop = props[i]; - if (point[prop]) { - point[prop] = point[prop].destroy(); - } - } - }, - - /** - * Return the configuration hash needed for the data label and tooltip formatters - */ - getLabelConfig: function () { - var point = this; - return { - x: point.category, - y: point.y, - key: point.name || point.category, - series: point.series, - point: point, - percentage: point.percentage, - total: point.total || point.stackTotal - }; - }, - - /** - * Toggle the selection status of a point - * @param {Boolean} selected Whether to select or unselect the point. - * @param {Boolean} accumulate Whether to add to the previous selection. By default, - * this happens if the control key (Cmd on Mac) was pressed during clicking. - */ - select: function (selected, accumulate) { - var point = this, - series = point.series, - chart = series.chart; - - selected = pick(selected, !point.selected); - - // fire the event with the defalut handler - point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () { - point.selected = selected; - point.setState(selected && SELECT_STATE); - - // unselect all other points unless Ctrl or Cmd + click - if (!accumulate) { - each(chart.getSelectedPoints(), function (loopPoint) { - if (loopPoint.selected && loopPoint !== point) { - loopPoint.selected = false; - loopPoint.setState(NORMAL_STATE); - loopPoint.firePointEvent('unselect'); - } - }); - } - }); - }, - - onMouseOver: function () { - var point = this, - series = point.series, - chart = series.chart, - tooltip = chart.tooltip, - hoverPoint = chart.hoverPoint; - - // set normal state to previous series - if (hoverPoint && hoverPoint !== point) { - hoverPoint.onMouseOut(); - } - - // trigger the event - point.firePointEvent('mouseOver'); - - // update the tooltip - if (tooltip && (!tooltip.shared || series.noSharedTooltip)) { - tooltip.refresh(point); - } - - // hover this - point.setState(HOVER_STATE); - chart.hoverPoint = point; - }, - - onMouseOut: function () { - var point = this; - point.firePointEvent('mouseOut'); - - point.setState(); - point.series.chart.hoverPoint = null; - }, - - /** - * Extendable method for formatting each point's tooltip line - * - * @return {String} A string to be concatenated in to the common tooltip text - */ - tooltipFormatter: function (pointFormat) { - var point = this, - series = point.series, - seriesTooltipOptions = series.tooltipOptions, - split = String(point.y).split('.'), - originalDecimals = split[1] ? split[1].length : 0, - match = pointFormat.match(/\{(series|point)\.[a-zA-Z]+\}/g), - splitter = /[\.}]/, - obj, - key, - replacement, - i; - - // loop over the variables defined on the form {series.name}, {point.y} etc - for (i in match) { - key = match[i]; - - if (isString(key) && key !== pointFormat) { // IE matches more than just the variables - obj = key.indexOf('point') === 1 ? point : series; - - if (key === '{point.y}') { // add some preformatting - replacement = (seriesTooltipOptions.yPrefix || '') + - numberFormat(point.y, pick(seriesTooltipOptions.yDecimals, originalDecimals)) + - (seriesTooltipOptions.ySuffix || ''); - - } else { // automatic replacement - replacement = obj[match[i].split(splitter)[1]]; - } - - pointFormat = pointFormat.replace(match[i], replacement); - } - } - - return pointFormat; - }, - - /** - * Update the point with new options (typically x/y data) and optionally redraw the series. - * - * @param {Object} options Point options as defined in the series.data array - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * - */ - update: function (options, redraw, animation) { - var point = this, - series = point.series, - graphic = point.graphic, - i, - data = series.data, - dataLength = data.length, - chart = series.chart; - - redraw = pick(redraw, true); - - // fire the event with a default handler of doing the update - point.firePointEvent('update', { options: options }, function () { - - point.applyOptions(options); - - // update visuals - if (isObject(options)) { - series.getAttribs(); - if (graphic) { - graphic.attr(point.pointAttr[series.state]); - } - } - - // record changes in the parallel arrays - for (i = 0; i < dataLength; i++) { - if (data[i] === point) { - series.xData[i] = point.x; - series.yData[i] = point.y; - series.options.data[i] = options; - break; - } - } - - // redraw - series.isDirty = true; - series.isDirtyData = true; - if (redraw) { - chart.redraw(animation); - } - }); - }, - - /** - * Remove a point and optionally redraw the series and if necessary the axes - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - remove: function (redraw, animation) { - var point = this, - series = point.series, - chart = series.chart, - i, - data = series.data, - dataLength = data.length; - - setAnimation(animation, chart); - redraw = pick(redraw, true); - - // fire the event with a default handler of removing the point - point.firePointEvent('remove', null, function () { - - //erase(series.data, point); - - for (i = 0; i < dataLength; i++) { - if (data[i] === point) { - - // splice all the parallel arrays - data.splice(i, 1); - series.options.data.splice(i, 1); - series.xData.splice(i, 1); - series.yData.splice(i, 1); - break; - } - } - - point.destroy(); - - - // redraw - series.isDirty = true; - series.isDirtyData = true; - if (redraw) { - chart.redraw(); - } - }); - - - }, - - /** - * Fire an event on the Point object. Must not be renamed to fireEvent, as this - * causes a name clash in MooTools - * @param {String} eventType - * @param {Object} eventArgs Additional event arguments - * @param {Function} defaultFunction Default event handler - */ - firePointEvent: function (eventType, eventArgs, defaultFunction) { - var point = this, - series = this.series, - seriesOptions = series.options; - - // load event handlers on demand to save time on mouseover/out - if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) { - this.importEvents(); - } - - // add default handler if in selection mode - if (eventType === 'click' && seriesOptions.allowPointSelect) { - defaultFunction = function (event) { - // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera - point.select(null, event.ctrlKey || event.metaKey || event.shiftKey); - }; - } - - fireEvent(this, eventType, eventArgs, defaultFunction); - }, - /** - * Import events from the series' and point's options. Only do it on - * demand, to save processing time on hovering. - */ - importEvents: function () { - if (!this.hasImportedEvents) { - var point = this, - options = merge(point.series.options.point, point.options), - events = options.events, - eventType; - - point.events = events; - - for (eventType in events) { - addEvent(point, eventType, events[eventType]); - } - this.hasImportedEvents = true; - - } - }, - - /** - * Set the point's state - * @param {String} state - */ - setState: function (state) { - var point = this, - plotX = point.plotX, - plotY = point.plotY, - series = point.series, - stateOptions = series.options.states, - markerOptions = defaultPlotOptions[series.type].marker && series.options.marker, - normalDisabled = markerOptions && !markerOptions.enabled, - markerStateOptions = markerOptions && markerOptions.states[state], - stateDisabled = markerStateOptions && markerStateOptions.enabled === false, - stateMarkerGraphic = series.stateMarkerGraphic, - chart = series.chart, - radius, - pointAttr = point.pointAttr; - - state = state || NORMAL_STATE; // empty string - - if ( - // already has this state - state === point.state || - // selected points don't respond to hover - (point.selected && state !== SELECT_STATE) || - // series' state options is disabled - (stateOptions[state] && stateOptions[state].enabled === false) || - // point marker's state options is disabled - (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled))) - - ) { - return; - } - - // apply hover styles to the existing point - if (point.graphic) { - radius = pointAttr[state].r; - point.graphic.attr(merge( - pointAttr[state], - radius ? extend({ // new symbol attributes - x: plotX - radius, - y: plotY - radius - }, point.graphic.symbolName ? { // don't apply to image symbols #507 - width: 2 * radius, - height: 2 * radius - } : {}) : {} - )); - } else { - // if a graphic is not applied to each point in the normal state, create a shared - // graphic for the hover state - if (state) { - if (!stateMarkerGraphic) { - radius = markerOptions.radius; - series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol( - series.symbol, - -radius, - -radius, - 2 * radius, - 2 * radius - ) - .attr(pointAttr[state]) - .add(series.group); - } - - stateMarkerGraphic.translate( - plotX, - plotY - ); - } - - if (stateMarkerGraphic) { - stateMarkerGraphic[state ? 'show' : 'hide'](); - } - } - - point.state = state; - } -}; - -/** - * @classDescription The base function which all other series types inherit from. The data in the series is stored - * in various arrays. - * - * - First, series.options.data contains all the original config options for - * each point whether added by options or methods like series.addPoint. - * - Next, series.data contains those values converted to points, but in case the series data length - * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It - * only contains the points that have been created on demand. - * - Then there's series.points that contains all currently visible point objects. In case of cropping, - * the cropped-away points are not part of this array. The series.points array starts at series.cropStart - * compared to series.data and series.options.data. If however the series data is grouped, these can't - * be correlated one to one. - * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points. - * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points. - * - * @param {Object} chart - * @param {Object} options - */ -var Series = function () {}; - -Series.prototype = { - - isCartesian: true, - type: 'line', - pointClass: Point, - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'lineColor', - 'stroke-width': 'lineWidth', - fill: 'fillColor', - r: 'radius' - }, - init: function (chart, options) { - var series = this, - eventType, - events, - //pointEvent, - index = chart.series.length; - - series.chart = chart; - series.options = options = series.setOptions(options); // merge with plotOptions - - // bind the axes - series.bindAxes(); - - // set some variables - extend(series, { - index: index, - name: options.name || 'Series ' + (index + 1), - state: NORMAL_STATE, - pointAttr: {}, - visible: options.visible !== false, // true by default - selected: options.selected === true // false by default - }); - - // register event listeners - events = options.events; - for (eventType in events) { - addEvent(series, eventType, events[eventType]); - } - if ( - (events && events.click) || - (options.point && options.point.events && options.point.events.click) || - options.allowPointSelect - ) { - chart.runTrackerClick = true; - } - - series.getColor(); - series.getSymbol(); - - // set the data - series.setData(options.data, false); - - }, - - - - /** - * Set the xAxis and yAxis properties of cartesian series, and register the series - * in the axis.series array - */ - bindAxes: function () { - var series = this, - seriesOptions = series.options, - chart = series.chart, - axisOptions; - - if (series.isCartesian) { - - each(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis - - each(chart[AXIS], function (axis) { // loop through the chart's axis objects - - axisOptions = axis.options; - - // apply if the series xAxis or yAxis option mathches the number of the - // axis, or if undefined, use the first axis - if ((seriesOptions[AXIS] === axisOptions.index) || - (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) { - - // register this series in the axis.series lookup - axis.series.push(series); - - // set this series.xAxis or series.yAxis reference - series[AXIS] = axis; - - // mark dirty for redraw - axis.isDirty = true; - } - }); - - }); - } - }, - - - /** - * Return an auto incremented x value based on the pointStart and pointInterval options. - * This is only used if an x value is not given for the point that calls autoIncrement. - */ - autoIncrement: function () { - var series = this, - options = series.options, - xIncrement = series.xIncrement; - - xIncrement = pick(xIncrement, options.pointStart, 0); - - series.pointInterval = pick(series.pointInterval, options.pointInterval, 1); - - series.xIncrement = xIncrement + series.pointInterval; - return xIncrement; - }, - - /** - * Divide the series data into segments divided by null values. - */ - getSegments: function () { - var series = this, - lastNull = -1, - segments = [], - i, - points = series.points; - - // if connect nulls, just remove null points - if (series.options.connectNulls) { - i = points.length - 1; - while (i--) { - if (points[i].y === null) { - points.splice(i, 1); - } - } - segments = [points]; - - // else, split on null points - } else { - each(points, function (point, i) { - if (point.y === null) { - if (i > lastNull + 1) { - segments.push(points.slice(lastNull + 1, i)); - } - lastNull = i; - } else if (i === points.length - 1) { // last value - segments.push(points.slice(lastNull + 1, i + 1)); - } - }); - } - - // register it - series.segments = segments; - }, - /** - * Set the series options by merging from the options tree - * @param {Object} itemOptions - */ - setOptions: function (itemOptions) { - var series = this, - chart = series.chart, - chartOptions = chart.options, - plotOptions = chartOptions.plotOptions, - data = itemOptions.data, - options; - - itemOptions.data = null; // remove from merge to prevent looping over the data set - - options = merge( - plotOptions[this.type], - plotOptions.series, - itemOptions - ); - options.data = data; - - // the tooltip options are merged between global and series specific options - series.tooltipOptions = merge(chartOptions.tooltip, options.tooltip); - - return options; - - }, - /** - * Get the series' color - */ - getColor: function () { - var defaultColors = this.chart.options.colors, - counters = this.chart.counters; - this.color = this.options.color || defaultColors[counters.color++] || '#0000ff'; - counters.wrapColor(defaultColors.length); - }, - /** - * Get the series' symbol - */ - getSymbol: function () { - var defaultSymbols = this.chart.options.symbols, - counters = this.chart.counters; - this.symbol = this.options.marker.symbol || defaultSymbols[counters.symbol++]; - counters.wrapSymbol(defaultSymbols.length); - }, - - /** - * Add a point dynamically after chart load time - * @param {Object} options Point options as given in series.data - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean} shift If shift is true, a point is shifted off the start - * of the series as one is appended to the end. - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - addPoint: function (options, redraw, shift, animation) { - var series = this, - data = series.data, - graph = series.graph, - area = series.area, - chart = series.chart, - xData = series.xData, - yData = series.yData, - currentShift = (graph && graph.shift) || 0, - dataOptions = series.options.data, - point; - //point = (new series.pointClass()).init(series, options); - - setAnimation(animation, chart); - - if (graph && shift) { // make graph animate sideways - graph.shift = currentShift + 1; - } - if (area) { - area.shift = currentShift + 1; - area.isArea = true; - } - redraw = pick(redraw, true); - - - // Get options and push the point to xData, yData and series.options. In series.generatePoints - // the Point instance will be created on demand and pushed to the series.data array. - point = { series: series }; - series.pointClass.prototype.applyOptions.apply(point, [options]); - xData.push(point.x); - yData.push(point.y); - dataOptions.push(options); - - - // Shift the first point off the parallel arrays - // todo: consider series.removePoint(i) method - if (shift) { - if (data[0]) { - data[0].remove(false); - } else { - data.shift(); - xData.shift(); - yData.shift(); - dataOptions.shift(); - } - } - series.getAttribs(); - - // redraw - series.isDirty = true; - series.isDirtyData = true; - if (redraw) { - chart.redraw(); - } - }, - - /** - * Replace the series data with a new set of data - * @param {Object} data - * @param {Object} redraw - */ - setData: function (data, redraw) { - var series = this, - oldData = series.points, - options = series.options, - initialColor = series.initialColor, - chart = series.chart, - firstPoint = null, - i; - - // reset properties - series.xIncrement = null; - series.pointRange = (series.xAxis && series.xAxis.categories && 1) || options.pointRange; - - if (defined(initialColor)) { // reset colors for pie - chart.counters.color = initialColor; - } - - // parallel arrays - var xData = [], - yData = [], - dataLength = data.length, - turboThreshold = options.turboThreshold || 1000, - pt, - ohlc = series.valueCount === 4; - - // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The - // first value is tested, and we assume that all the rest are defined the same - // way. Although the 'for' loops are similar, they are repeated inside each - // if-else conditional for max performance. - if (dataLength > turboThreshold) { - - // find the first non-null point - i = 0; - while (firstPoint === null && i < dataLength) { - firstPoint = data[i]; - i++; - } - - - if (isNumber(firstPoint)) { // assume all points are numbers - var x = pick(options.pointStart, 0), - pointInterval = pick(options.pointInterval, 1); - - for (i = 0; i < dataLength; i++) { - xData[i] = x; - yData[i] = data[i]; - x += pointInterval; - } - series.xIncrement = x; - } else if (isArray(firstPoint)) { // assume all points are arrays - if (ohlc) { // [x, o, h, l, c] - for (i = 0; i < dataLength; i++) { - pt = data[i]; - xData[i] = pt[0]; - yData[i] = pt.slice(1, 5); - } - } else { // [x, y] - for (i = 0; i < dataLength; i++) { - pt = data[i]; - xData[i] = pt[0]; - yData[i] = pt[1]; - } - } - } - } else { - for (i = 0; i < dataLength; i++) { - pt = { series: series }; - series.pointClass.prototype.applyOptions.apply(pt, [data[i]]); - xData[i] = pt.x; - yData[i] = ohlc ? [pt.open, pt.high, pt.low, pt.close] : pt.y; - } - } - - series.data = []; - series.options.data = data; - series.xData = xData; - series.yData = yData; - - // destroy old points - i = (oldData && oldData.length) || 0; - while (i--) { - if (oldData[i] && oldData[i].destroy) { - oldData[i].destroy(); - } - } - - // redraw - series.isDirty = series.isDirtyData = chart.isDirtyBox = true; - if (pick(redraw, true)) { - chart.redraw(false); - } - }, - - /** - * Remove a series and optionally redraw the chart - * - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - - remove: function (redraw, animation) { - var series = this, - chart = series.chart; - redraw = pick(redraw, true); - - if (!series.isRemoving) { /* prevent triggering native event in jQuery - (calling the remove function from the remove event) */ - series.isRemoving = true; - - // fire the event with a default handler of removing the point - fireEvent(series, 'remove', null, function () { - - - // destroy elements - series.destroy(); - - - // redraw - chart.isDirtyLegend = chart.isDirtyBox = true; - if (redraw) { - chart.redraw(animation); - } - }); - - } - series.isRemoving = false; - }, - - /** - * Process the data by cropping away unused data points if the series is longer - * than the crop threshold. This saves computing time for lage series. - */ - processData: function () { - var series = this, - processedXData = series.xData, // copied during slice operation below - processedYData = series.yData, - dataLength = processedXData.length, - cropStart = 0, - cropEnd = dataLength, - cropped, - i, // loop variable - cropThreshold = series.options.cropThreshold; // todo: consider combining it with turboThreshold - - // If the series data or axes haven't changed, don't go through this. Return false to pass - // the message on to override methods like in data grouping. - if (series.isCartesian && !series.isDirty && !series.xAxis.isDirty && !series.yAxis.isDirty) { - return false; - } - - // optionally filter out points outside the plot area - if (!cropThreshold || dataLength > cropThreshold || series.forceCrop) { - var extremes = series.xAxis.getExtremes(), - min = extremes.min, - max = extremes.max; - - // it's outside current extremes - if (processedXData[dataLength - 1] < min || processedXData[0] > max) { - processedXData = []; - processedYData = []; - - // only crop if it's actually spilling out - } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) { - - // iterate up to find slice start - for (i = 0; i < dataLength; i++) { - if (processedXData[i] >= min) { - cropStart = mathMax(0, i - 1); - break; - } - } - // proceed to find slice end - for (; i < dataLength; i++) { - if (processedXData[i] > max) { - cropEnd = i + 1; - break; - } - } - processedXData = processedXData.slice(cropStart, cropEnd); - processedYData = processedYData.slice(cropStart, cropEnd); - cropped = true; - } - } - - series.cropped = cropped; // undefined or true - series.cropStart = cropStart; - series.processedXData = processedXData; - series.processedYData = processedYData; - }, - - /** - * Generate the data point after the data has been processed by cropping away - * unused points and optionally grouped in Highcharts Stock. - */ - generatePoints: function () { - var series = this, - options = series.options, - dataOptions = options.data, - data = series.data, - dataLength, - processedXData = series.processedXData, - processedYData = series.processedYData, - pointClass = series.pointClass, - processedDataLength = processedXData.length, - cropStart = series.cropStart || 0, - cursor, - hasGroupedData = series.hasGroupedData, - point, - points = [], - i; - - if (!data && !hasGroupedData) { - var arr = []; - arr.length = dataOptions.length; - data = series.data = arr; - } - - for (i = 0; i < processedDataLength; i++) { - cursor = cropStart + i; - if (!hasGroupedData) { - if (data[cursor]) { - point = data[cursor]; - } else { - data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]); - } - points[i] = point; - } else { - // splat the y data in case of ohlc data array - points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i]))); - } - } - - // hide cropped-away points - this only runs when the number of points is above cropThreshold - if (data && processedDataLength !== (dataLength = data.length)) { - for (i = 0; i < dataLength; i++) { - if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points - i += processedDataLength; - } - if (data[i]) { - data[i].destroyElements(); - } - } - } - - series.data = data; - series.points = points; - }, - - /** - * Translate data points from raw data values to chart specific positioning data - * needed later in drawPoints, drawGraph and drawTracker. - */ - translate: function () { - if (!this.processedXData) { // hidden series - this.processData(); - } - this.generatePoints(); - var series = this, - chart = series.chart, - options = series.options, - stacking = options.stacking, - xAxis = series.xAxis, - categories = xAxis.categories, - yAxis = series.yAxis, - points = series.points, - dataLength = points.length, - hasModifyValue = !!series.modifyValue, - i; - - for (i = 0; i < dataLength; i++) { - var point = points[i], - xValue = point.x, - yValue = point.y, - yBottom = point.low, - stack = yAxis.stacks[(yValue < 0 ? '-' : '') + series.stackKey], - pointStack, - pointStackTotal; - - // get the plotX translation - point.plotX = mathRound(series.xAxis.translate(xValue) * 10) / 10; // Math.round fixes #591 - - // calculate the bottom y value for stacked series - if (stacking && series.visible && stack && stack[xValue]) { - pointStack = stack[xValue]; - pointStackTotal = pointStack.total; - pointStack.cum = yBottom = pointStack.cum - yValue; // start from top - yValue = yBottom + yValue; - - if (stacking === 'percent') { - yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0; - yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0; - } - - point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0; - point.stackTotal = pointStackTotal; - } - - if (defined(yBottom)) { - point.yBottom = yAxis.translate(yBottom, 0, 1, 0, 1); - } - - // general hook, used for Highstock compare mode - if (hasModifyValue) { - yValue = series.modifyValue(yValue, point); - } - - // set the y value - if (yValue !== null) { - point.plotY = mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10; // Math.round fixes #591 - } - - // set client related positions for mouse tracking - point.clientX = chart.inverted ? - chart.plotHeight - point.plotX : - point.plotX; // for mouse tracking - - // some API data - point.category = categories && categories[point.x] !== UNDEFINED ? - categories[point.x] : point.x; - - - } - - // now that we have the cropped data, build the segments - series.getSegments(); - }, - /** - * Memoize tooltip texts and positions - */ - setTooltipPoints: function (renew) { - var series = this, - chart = series.chart, - inverted = chart.inverted, - points = [], - pointsLength, - plotSize = mathRound((inverted ? chart.plotTop : chart.plotLeft) + chart.plotSizeX), - low, - high, - xAxis = series.xAxis, - point, - i, - tooltipPoints = []; // a lookup array for each pixel in the x dimension - - // don't waste resources if tracker is disabled - if (series.options.enableMouseTracking === false) { - return; - } - - // renew - if (renew) { - series.tooltipPoints = null; - } - - // concat segments to overcome null values - each(series.segments || series.points, function (segment) { - points = points.concat(segment); - }); - - // loop the concatenated points and apply each point to all the closest - // pixel positions - if (xAxis && xAxis.reversed) { - points = points.reverse();//reverseArray(points); - } - - //each(points, function (point, i) { - pointsLength = points.length; - for (i = 0; i < pointsLength; i++) { - point = points[i]; - low = points[i - 1] ? points[i - 1]._high + 1 : 0; - high = point._high = points[i + 1] ? - (mathFloor((point.plotX + (points[i + 1] ? points[i + 1].plotX : plotSize)) / 2)) : - plotSize; - - while (low <= high) { - tooltipPoints[inverted ? plotSize - low++ : low++] = point; - } - } - series.tooltipPoints = tooltipPoints; - }, - - /** - * Format the header of the tooltip - */ - tooltipHeaderFormatter: function (key) { - var series = this, - tooltipOptions = series.tooltipOptions, - xDateFormat = tooltipOptions.xDateFormat || '%A, %b %e, %Y', - xAxis = series.xAxis, - isDateTime = xAxis && xAxis.options.type === 'datetime'; - - return tooltipOptions.headerFormat - .replace('{point.key}', isDateTime ? dateFormat(xDateFormat, key) : key) - .replace('{series.name}', series.name) - .replace('{series.color}', series.color); - }, - - /** - * Series mouse over handler - */ - onMouseOver: function () { - var series = this, - chart = series.chart, - hoverSeries = chart.hoverSeries; - - if (!hasTouch && chart.mouseIsDown) { - return; - } - - // set normal state to previous series - if (hoverSeries && hoverSeries !== series) { - hoverSeries.onMouseOut(); - } - - // trigger the event, but to save processing time, - // only if defined - if (series.options.events.mouseOver) { - fireEvent(series, 'mouseOver'); - } - - // hover this - series.setState(HOVER_STATE); - chart.hoverSeries = series; - }, - - /** - * Series mouse out handler - */ - onMouseOut: function () { - // trigger the event only if listeners exist - var series = this, - options = series.options, - chart = series.chart, - tooltip = chart.tooltip, - hoverPoint = chart.hoverPoint; - - // trigger mouse out on the point, which must be in this series - if (hoverPoint) { - hoverPoint.onMouseOut(); - } - - // fire the mouse out event - if (series && options.events.mouseOut) { - fireEvent(series, 'mouseOut'); - } - - - // hide the tooltip - if (tooltip && !options.stickyTracking && !tooltip.shared) { - tooltip.hide(); - } - - // set normal state - series.setState(); - chart.hoverSeries = null; - }, - - /** - * Animate in the series - */ - animate: function (init) { - var series = this, - chart = series.chart, - clipRect = series.clipRect, - animation = series.options.animation; - - if (animation && !isObject(animation)) { - animation = {}; - } - - if (init) { // initialize the animation - if (!clipRect.isAnimating) { // apply it only for one of the series - clipRect.attr('width', 0); - clipRect.isAnimating = true; - } - - } else { // run the animation - clipRect.animate({ - width: chart.plotSizeX - }, animation); - - // delete this function to allow it only once - this.animate = null; - } - }, - - - /** - * Draw the markers - */ - drawPoints: function () { - var series = this, - pointAttr, - points = series.points, - chart = series.chart, - plotX, - plotY, - i, - point, - radius, - graphic; - - if (series.options.marker.enabled) { - i = points.length; - while (i--) { - point = points[i]; - plotX = point.plotX; - plotY = point.plotY; - graphic = point.graphic; - - // only draw the point if y is defined - if (plotY !== UNDEFINED && !isNaN(plotY)) { - - // shortcuts - pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]; - radius = pointAttr.r; - - if (graphic) { // update - graphic.animate(extend({ - x: plotX - radius, - y: plotY - radius - }, graphic.symbolName ? { // don't apply to image symbols #507 - width: 2 * radius, - height: 2 * radius - } : {})); - } else { - point.graphic = chart.renderer.symbol( - pick(point.marker && point.marker.symbol, series.symbol), - plotX - radius, - plotY - radius, - 2 * radius, - 2 * radius - ) - .attr(pointAttr) - .add(series.group); - } - } - } - } - - }, - - /** - * Convert state properties from API naming conventions to SVG attributes - * - * @param {Object} options API options object - * @param {Object} base1 SVG attribute object to inherit from - * @param {Object} base2 Second level SVG attribute object to inherit from - */ - convertAttribs: function (options, base1, base2, base3) { - var conversion = this.pointAttrToOptions, - attr, - option, - obj = {}; - - options = options || {}; - base1 = base1 || {}; - base2 = base2 || {}; - base3 = base3 || {}; - - for (attr in conversion) { - option = conversion[attr]; - obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]); - } - return obj; - }, - - /** - * Get the state attributes. Each series type has its own set of attributes - * that are allowed to change on a point's state change. Series wide attributes are stored for - * all series, and additionally point specific attributes are stored for all - * points with individual marker options. If such options are not defined for the point, - * a reference to the series wide attributes is stored in point.pointAttr. - */ - getAttribs: function () { - var series = this, - normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options, - stateOptions = normalOptions.states, - stateOptionsHover = stateOptions[HOVER_STATE], - pointStateOptionsHover, - seriesColor = series.color, - normalDefaults = { - stroke: seriesColor, - fill: seriesColor - }, - points = series.points, - i, - point, - seriesPointAttr = [], - pointAttr, - pointAttrToOptions = series.pointAttrToOptions, - hasPointSpecificOptions, - key; - - // series type specific modifications - if (series.options.marker) { // line, spline, area, areaspline, scatter - - // if no hover radius is given, default to normal radius + 2 - stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2; - stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1; - - } else { // column, bar, pie - - // if no hover color is given, brighten the normal color - stateOptionsHover.color = stateOptionsHover.color || - Color(stateOptionsHover.color || seriesColor) - .brighten(stateOptionsHover.brightness).get(); - } - - // general point attributes for the series normal state - seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults); - - // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius - each([HOVER_STATE, SELECT_STATE], function (state) { - seriesPointAttr[state] = - series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]); - }); - - // set it - series.pointAttr = seriesPointAttr; - - - // Generate the point-specific attribute collections if specific point - // options are given. If not, create a referance to the series wide point - // attributes - i = points.length; - while (i--) { - point = points[i]; - normalOptions = (point.options && point.options.marker) || point.options; - if (normalOptions && normalOptions.enabled === false) { - normalOptions.radius = 0; - } - hasPointSpecificOptions = false; - - // check if the point has specific visual options - if (point.options) { - for (key in pointAttrToOptions) { - if (defined(normalOptions[pointAttrToOptions[key]])) { - hasPointSpecificOptions = true; - } - } - } - - - - // a specific marker config object is defined for the individual point: - // create it's own attribute collection - if (hasPointSpecificOptions) { - - pointAttr = []; - stateOptions = normalOptions.states || {}; // reassign for individual point - pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {}; - - // if no hover color is given, brighten the normal color - if (!series.options.marker) { // column, bar, point - pointStateOptionsHover.color = - Color(pointStateOptionsHover.color || point.options.color) - .brighten(pointStateOptionsHover.brightness || - stateOptionsHover.brightness).get(); - - } - - // normal point state inherits series wide normal state - pointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, seriesPointAttr[NORMAL_STATE]); - - // inherit from point normal and series hover - pointAttr[HOVER_STATE] = series.convertAttribs( - stateOptions[HOVER_STATE], - seriesPointAttr[HOVER_STATE], - pointAttr[NORMAL_STATE] - ); - // inherit from point normal and series hover - pointAttr[SELECT_STATE] = series.convertAttribs( - stateOptions[SELECT_STATE], - seriesPointAttr[SELECT_STATE], - pointAttr[NORMAL_STATE] - ); - - - - // no marker config object is created: copy a reference to the series-wide - // attribute collection - } else { - pointAttr = seriesPointAttr; - } - - point.pointAttr = pointAttr; - - } - - }, - - - /** - * Clear DOM objects and free up memory - */ - destroy: function () { - var series = this, - chart = series.chart, - seriesClipRect = series.clipRect, - issue134 = /AppleWebKit\/533/.test(userAgent), - destroy, - i, - data = series.data || [], - point, - prop, - axis; - - // add event hook - fireEvent(series, 'destroy'); - - // remove all events - removeEvent(series); - - // erase from axes - each(['xAxis', 'yAxis'], function (AXIS) { - axis = series[AXIS]; - if (axis) { - erase(axis.series, series); - axis.isDirty = true; - } - }); - - // remove legend items - if (series.legendItem) { - series.chart.legend.destroyItem(series); - } - - // destroy all points with their elements - i = data.length; - while (i--) { - point = data[i]; - if (point && point.destroy) { - point.destroy(); - } - } - series.points = null; - - // If this series clipRect is not the global one (which is removed on chart.destroy) we - // destroy it here. - if (seriesClipRect && seriesClipRect !== chart.clipRect) { - series.clipRect = seriesClipRect.destroy(); - } - - // destroy all SVGElements associated to the series - each(['area', 'graph', 'dataLabelsGroup', 'group', 'tracker'], function (prop) { - if (series[prop]) { - - // issue 134 workaround - destroy = issue134 && prop === 'group' ? - 'hide' : - 'destroy'; - - series[prop][destroy](); - } - }); - - // remove from hoverSeries - if (chart.hoverSeries === series) { - chart.hoverSeries = null; - } - erase(chart.series, series); - - // clear all members - for (prop in series) { - delete series[prop]; - } - }, - - /** - * Draw the data labels - */ - drawDataLabels: function () { - if (this.options.dataLabels.enabled) { - var series = this, - x, - y, - points = series.points, - seriesOptions = series.options, - options = seriesOptions.dataLabels, - str, - dataLabelsGroup = series.dataLabelsGroup, - chart = series.chart, - xAxis = series.xAxis, - groupLeft = xAxis ? xAxis.left : chart.plotLeft, - yAxis = series.yAxis, - groupTop = yAxis ? yAxis.top : chart.plotTop, - renderer = chart.renderer, - inverted = chart.inverted, - seriesType = series.type, - color, - stacking = seriesOptions.stacking, - isBarLike = seriesType === 'column' || seriesType === 'bar', - vAlignIsNull = options.verticalAlign === null, - yIsNull = options.y === null; - - if (isBarLike) { - if (stacking) { - // In stacked series the default label placement is inside the bars - if (vAlignIsNull) { - options = merge(options, {verticalAlign: 'middle'}); - } - - // If no y delta is specified, try to create a good default - if (yIsNull) { - options = merge(options, {y: {top: 14, middle: 4, bottom: -6}[options.verticalAlign]}); - } - } else { - // In non stacked series the default label placement is on top of the bars - if (vAlignIsNull) { - options = merge(options, {verticalAlign: 'top'}); - } - } - } - - - // create a separate group for the data labels to avoid rotation - if (!dataLabelsGroup) { - dataLabelsGroup = series.dataLabelsGroup = - renderer.g('data-labels') - .attr({ - visibility: series.visible ? VISIBLE : HIDDEN, - zIndex: 6 - }) - .translate(groupLeft, groupTop) - .add(); - } else { - dataLabelsGroup.translate(groupLeft, groupTop); - } - - // determine the color - color = options.color; - if (color === 'auto') { // 1.0 backwards compatibility - color = null; - } - options.style.color = pick(color, series.color, 'black'); - - // make the labels for each point - each(points, function (point) { - var barX = point.barX, - plotX = (barX && barX + point.barW / 2) || point.plotX || -999, - plotY = pick(point.plotY, -999), - dataLabel = point.dataLabel, - align = options.align, - individualYDelta = yIsNull ? (point.y >= 0 ? -6 : 12) : options.y; - - // get the string - str = options.formatter.call(point.getLabelConfig()); - x = (inverted ? chart.plotWidth - plotY : plotX) + options.x; - y = (inverted ? chart.plotHeight - plotX : plotY) + individualYDelta; - - // in columns, align the string to the column - if (seriesType === 'column') { - x += { left: -1, right: 1 }[align] * point.barW / 2 || 0; - } - - if (inverted && point.y < 0) { - align = 'right'; - x -= 10; - } - - // update existing label - if (dataLabel) { - // vertically centered - if (inverted && !options.y) { - y = y + pInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2; - } - dataLabel - .attr({ - text: str - }).animate({ - x: x, - y: y - }); - // create new label - } else if (defined(str)) { - dataLabel = point.dataLabel = renderer.text( - str, - x, - y - ) - .attr({ - align: align, - rotation: options.rotation, - zIndex: 1 - }) - .css(options.style) - .add(dataLabelsGroup); - // vertically centered - if (inverted && !options.y) { - dataLabel.attr({ - y: y + pInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2 - }); - } - } - - if (isBarLike && seriesOptions.stacking && dataLabel) { - var barY = point.barY, - barW = point.barW, - barH = point.barH; - - dataLabel.align(options, null, - { - x: inverted ? chart.plotWidth - barY - barH : barX, - y: inverted ? chart.plotHeight - barX - barW : barY, - width: inverted ? barH : barW, - height: inverted ? barW : barH - }); - } - }); - } - }, - - /** - * Draw the actual graph - */ - drawGraph: function () { - var series = this, - options = series.options, - chart = series.chart, - graph = series.graph, - graphPath = [], - fillColor, - area = series.area, - group = series.group, - color = options.lineColor || series.color, - lineWidth = options.lineWidth, - dashStyle = options.dashStyle, - segmentPath, - renderer = chart.renderer, - translatedThreshold = series.yAxis.getThreshold(options.threshold), - useArea = /^area/.test(series.type), - singlePoints = [], // used in drawTracker - areaPath = [], - attribs; - - - // divide into segments and build graph and area paths - each(series.segments, function (segment) { - segmentPath = []; - - // build the segment line - each(segment, function (point, i) { - - if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object - segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i)); - - } else { - - // moveTo or lineTo - segmentPath.push(i ? L : M); - - // step line? - if (i && options.step) { - var lastPoint = segment[i - 1]; - segmentPath.push( - point.plotX, - lastPoint.plotY - ); - } - - // normal line to next point - segmentPath.push( - point.plotX, - point.plotY - ); - } - }); - - // add the segment to the graph, or a single point for tracking - if (segment.length > 1) { - graphPath = graphPath.concat(segmentPath); - } else { - singlePoints.push(segment[0]); - } - - // build the area - if (useArea) { - var areaSegmentPath = [], - i, - segLength = segmentPath.length; - for (i = 0; i < segLength; i++) { - areaSegmentPath.push(segmentPath[i]); - } - if (segLength === 3) { // for animation from 1 to two points - areaSegmentPath.push(L, segmentPath[1], segmentPath[2]); - } - if (options.stacking && series.type !== 'areaspline') { - - // Follow stack back. Todo: implement areaspline. A general solution could be to - // reverse the entire graphPath of the previous series, though may be hard with - // splines and with series with different extremes - for (i = segment.length - 1; i >= 0; i--) { - - // step line? - if (i < segment.length - 1 && options.step) { - areaSegmentPath.push(segment[i + 1].plotX, segment[i].yBottom); - } - - areaSegmentPath.push(segment[i].plotX, segment[i].yBottom); - } - - } else { // follow zero line back - areaSegmentPath.push( - L, - segment[segment.length - 1].plotX, - translatedThreshold, - L, - segment[0].plotX, - translatedThreshold - ); - } - areaPath = areaPath.concat(areaSegmentPath); - } - }); - - // used in drawTracker: - series.graphPath = graphPath; - series.singlePoints = singlePoints; - - // draw the area if area series or areaspline - if (useArea) { - fillColor = pick( - options.fillColor, - Color(series.color).setOpacity(options.fillOpacity || 0.75).get() - ); - if (area) { - area.animate({ d: areaPath }); - - } else { - // draw the area - series.area = series.chart.renderer.path(areaPath) - .attr({ - fill: fillColor - }).add(group); - } - } - - // draw the graph - if (graph) { - stop(graph); // cancel running animations, #459 - graph.animate({ d: graphPath }); - - } else { - if (lineWidth) { - attribs = { - 'stroke': color, - 'stroke-width': lineWidth - }; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - - series.graph = renderer.path(graphPath) - .attr(attribs).add(group).shadow(options.shadow); - } - } - }, - - - /** - * Render the graph and markers - */ - render: function () { - var series = this, - chart = series.chart, - group, - setInvert, - options = series.options, - doClip = options.clip !== false, - animation = options.animation, - doAnimation = animation && series.animate, - duration = doAnimation ? (animation && animation.duration) || 500 : 0, - clipRect = series.clipRect, - renderer = chart.renderer; - - - // Add plot area clipping rectangle. If this is before chart.hasRendered, - // create one shared clipRect. - - // Todo: since creating the clip property, the clipRect is created but - // never used when clip is false. A better way would be that the animation - // would run, then the clipRect destroyed. - if (!clipRect) { - clipRect = series.clipRect = !chart.hasRendered && chart.clipRect ? - chart.clipRect : - renderer.clipRect(0, 0, chart.plotSizeX, chart.plotSizeY + 1); - if (!chart.clipRect) { - chart.clipRect = clipRect; - } - } - - - // the group - if (!series.group) { - group = series.group = renderer.g('series'); - - if (chart.inverted) { - setInvert = function () { - group.attr({ - width: chart.plotWidth, - height: chart.plotHeight - }).invert(); - }; - - setInvert(); // do it now - addEvent(chart, 'resize', setInvert); // do it on resize - addEvent(series, 'destroy', function () { - removeEvent(chart, 'resize', setInvert); - }); - } - - if (doClip) { - group.clip(series.clipRect); - } - group.attr({ - visibility: series.visible ? VISIBLE : HIDDEN, - zIndex: options.zIndex - }) - .translate(series.xAxis.left, series.yAxis.top) - .add(chart.seriesGroup); - } - - series.drawDataLabels(); - - // initiate the animation - if (doAnimation) { - series.animate(true); - } - - // cache attributes for shapes - series.getAttribs(); - - // draw the graph if any - if (series.drawGraph) { - series.drawGraph(); - } - - // draw the points - series.drawPoints(); - - // draw the mouse tracking area - if (series.options.enableMouseTracking !== false) { - series.drawTracker(); - } - - // run the animation - if (doAnimation) { - series.animate(); - } - - // finish the individual clipRect - setTimeout(function () { - clipRect.isAnimating = false; - group = series.group; // can be destroyed during the timeout - if (group && clipRect !== chart.clipRect && clipRect.renderer) { - if (doClip) { - group.clip((series.clipRect = chart.clipRect)); - } - clipRect.destroy(); - } - }, duration); - - series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see - // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see - - }, - - /** - * Redraw the series after an update in the axes. - */ - redraw: function () { - var series = this, - chart = series.chart, - wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after - group = series.group; - - // reposition on resize - if (group) { - if (chart.inverted) { - group.attr({ - width: chart.plotWidth, - height: chart.plotHeight - }); - } - - group.animate({ - translateX: series.xAxis.left, - translateY: series.yAxis.top - }); - } - - series.translate(); - series.setTooltipPoints(true); - - series.render(); - if (wasDirtyData) { - fireEvent(series, 'updatedData'); - } - }, - - /** - * Set the state of the graph - */ - setState: function (state) { - var series = this, - options = series.options, - graph = series.graph, - stateOptions = options.states, - lineWidth = options.lineWidth; - - state = state || NORMAL_STATE; - - if (series.state !== state) { - series.state = state; - - if (stateOptions[state] && stateOptions[state].enabled === false) { - return; - } - - if (state) { - lineWidth = stateOptions[state].lineWidth || lineWidth + 1; - } - - if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML - graph.attr({ // use attr because animate will cause any other animation on the graph to stop - 'stroke-width': lineWidth - }, state ? 0 : 500); - } - } - }, - - /** - * Set the visibility of the graph - * - * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED, - * the visibility is toggled. - */ - setVisible: function (vis, redraw) { - var series = this, - chart = series.chart, - legendItem = series.legendItem, - seriesGroup = series.group, - seriesTracker = series.tracker, - dataLabelsGroup = series.dataLabelsGroup, - showOrHide, - i, - points = series.points, - point, - ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, - oldVisibility = series.visible; - - // if called without an argument, toggle visibility - series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis; - showOrHide = vis ? 'show' : 'hide'; - - // show or hide series - if (seriesGroup) { // pies don't have one - seriesGroup[showOrHide](); - } - - // show or hide trackers - if (seriesTracker) { - seriesTracker[showOrHide](); - } else if (points) { - i = points.length; - while (i--) { - point = points[i]; - if (point.tracker) { - point.tracker[showOrHide](); - } - } - } - - - if (dataLabelsGroup) { - dataLabelsGroup[showOrHide](); - } - - if (legendItem) { - chart.legend.colorizeItem(series, vis); - } - - - // rescale or adapt to resized chart - series.isDirty = true; - // in a stack, all other series are affected - if (series.options.stacking) { - each(chart.series, function (otherSeries) { - if (otherSeries.options.stacking && otherSeries.visible) { - otherSeries.isDirty = true; - } - }); - } - - if (ignoreHiddenSeries) { - chart.isDirtyBox = true; - } - if (redraw !== false) { - chart.redraw(); - } - - fireEvent(series, showOrHide); - }, - - /** - * Show the graph - */ - show: function () { - this.setVisible(true); - }, - - /** - * Hide the graph - */ - hide: function () { - this.setVisible(false); - }, - - - /** - * Set the selected state of the graph - * - * @param selected {Boolean} True to select the series, false to unselect. If - * UNDEFINED, the selection state is toggled. - */ - select: function (selected) { - var series = this; - // if called without an argument, toggle - series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected; - - if (series.checkbox) { - series.checkbox.checked = selected; - } - - fireEvent(series, selected ? 'select' : 'unselect'); - }, - - - /** - * Draw the tracker object that sits above all data labels and markers to - * track mouse events on the graph or points. For the line type charts - * the tracker uses the same graphPath, but with a greater stroke width - * for better control. - */ - drawTracker: function () { - var series = this, - options = series.options, - trackerPath = [].concat(series.graphPath), - trackerPathLength = trackerPath.length, - chart = series.chart, - snap = chart.options.tooltip.snap, - tracker = series.tracker, - cursor = options.cursor, - css = cursor && { cursor: cursor }, - singlePoints = series.singlePoints, - singlePoint, - i; - - // Extend end points. A better way would be to use round linecaps, - // but those are not clickable in VML. - if (trackerPathLength) { - i = trackerPathLength + 1; - while (i--) { - if (trackerPath[i] === M) { // extend left side - trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L); - } - if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side - trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]); - } - } - } - - // handle single points - for (i = 0; i < singlePoints.length; i++) { - singlePoint = singlePoints[i]; - trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY, - L, singlePoint.plotX + snap, singlePoint.plotY); - } - - // draw the tracker - if (tracker) { - tracker.attr({ d: trackerPath }); - - } else { // create - series.tracker = chart.renderer.path(trackerPath) - .attr({ - isTracker: true, - stroke: TRACKER_FILL, - fill: NONE, - 'stroke-width' : options.lineWidth + 2 * snap, - visibility: series.visible ? VISIBLE : HIDDEN, - zIndex: options.zIndex || 1 - }) - .on(hasTouch ? 'touchstart' : 'mouseover', function () { - if (chart.hoverSeries !== series) { - series.onMouseOver(); - } - }) - .on('mouseout', function () { - if (!options.stickyTracking) { - series.onMouseOut(); - } - }) - .css(css) - .add(chart.trackerGroup); - } - - } - -}; // end Series prototype - - -/** - * LineSeries object - */ -var LineSeries = extendClass(Series); -seriesTypes.line = LineSeries; - -/** - * AreaSeries object - */ -var AreaSeries = extendClass(Series, { - type: 'area', - useThreshold: true -}); -seriesTypes.area = AreaSeries; - - - - -/** - * SplineSeries object - */ -var SplineSeries = extendClass(Series, { - type: 'spline', - - /** - * Draw the actual graph - */ - getPointSpline: function (segment, point, i) { - var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc - denom = smoothing + 1, - plotX = point.plotX, - plotY = point.plotY, - lastPoint = segment[i - 1], - nextPoint = segment[i + 1], - leftContX, - leftContY, - rightContX, - rightContY, - ret; - - // find control points - if (i && i < segment.length - 1) { - var lastX = lastPoint.plotX, - lastY = lastPoint.plotY, - nextX = nextPoint.plotX, - nextY = nextPoint.plotY, - correction; - - leftContX = (smoothing * plotX + lastX) / denom; - leftContY = (smoothing * plotY + lastY) / denom; - rightContX = (smoothing * plotX + nextX) / denom; - rightContY = (smoothing * plotY + nextY) / denom; - - // have the two control points make a straight line through main point - correction = ((rightContY - leftContY) * (rightContX - plotX)) / - (rightContX - leftContX) + plotY - rightContY; - - leftContY += correction; - rightContY += correction; - - // to prevent false extremes, check that control points are between - // neighbouring points' y values - if (leftContY > lastY && leftContY > plotY) { - leftContY = mathMax(lastY, plotY); - rightContY = 2 * plotY - leftContY; // mirror of left control point - } else if (leftContY < lastY && leftContY < plotY) { - leftContY = mathMin(lastY, plotY); - rightContY = 2 * plotY - leftContY; - } - if (rightContY > nextY && rightContY > plotY) { - rightContY = mathMax(nextY, plotY); - leftContY = 2 * plotY - rightContY; - } else if (rightContY < nextY && rightContY < plotY) { - rightContY = mathMin(nextY, plotY); - leftContY = 2 * plotY - rightContY; - } - - // record for drawing in next point - point.rightContX = rightContX; - point.rightContY = rightContY; - - } - - // moveTo or lineTo - if (!i) { - ret = [M, plotX, plotY]; - } else { // curve from last point to this - ret = [ - 'C', - lastPoint.rightContX || lastPoint.plotX, - lastPoint.rightContY || lastPoint.plotY, - leftContX || plotX, - leftContY || plotY, - plotX, - plotY - ]; - lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later - } - return ret; - } -}); -seriesTypes.spline = SplineSeries; - - - -/** - * AreaSplineSeries object - */ -var AreaSplineSeries = extendClass(SplineSeries, { - type: 'areaspline', - useThreshold: true -}); -seriesTypes.areaspline = AreaSplineSeries; - -/** - * ColumnSeries object - */ -var ColumnSeries = extendClass(Series, { - type: 'column', - useThreshold: true, - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'borderColor', - 'stroke-width': 'borderWidth', - fill: 'color', - r: 'borderRadius' - }, - init: function () { - Series.prototype.init.apply(this, arguments); - - var series = this, - chart = series.chart; - - // if the series is added dynamically, force redraw of other - // series affected by a new column - if (chart.hasRendered) { - each(chart.series, function (otherSeries) { - if (otherSeries.type === series.type) { - otherSeries.isDirty = true; - } - }); - } - }, - - /** - * Translate each point to the plot area coordinate system and find shape positions - */ - translate: function () { - var series = this, - chart = series.chart, - options = series.options, - stacking = options.stacking, - borderWidth = options.borderWidth, - columnCount = 0, - xAxis = series.xAxis, - reversedXAxis = xAxis.reversed, - stackGroups = {}, - stackKey, - columnIndex; - - Series.prototype.translate.apply(series); - - // Get the total number of column type series. - // This is called on every series. Consider moving this logic to a - // chart.orderStacks() function and call it on init, addSeries and removeSeries - each(chart.series, function (otherSeries) { - if (otherSeries.type === series.type && otherSeries.visible && - series.options.group === otherSeries.options.group) { // used in Stock charts navigator series - if (otherSeries.options.stacking) { - stackKey = otherSeries.stackKey; - if (stackGroups[stackKey] === UNDEFINED) { - stackGroups[stackKey] = columnCount++; - } - columnIndex = stackGroups[stackKey]; - } else { - columnIndex = columnCount++; - } - otherSeries.columnIndex = columnIndex; - } - }); - - // calculate the width and position of each column based on - // the number of column series in the plot, the groupPadding - // and the pointPadding options - var points = series.points, - pointRange = pick(series.pointRange, xAxis.pointRange), - categoryWidth = mathAbs(xAxis.translate(0) - xAxis.translate(pointRange)), - groupPadding = categoryWidth * options.groupPadding, - groupWidth = categoryWidth - 2 * groupPadding, - pointOffsetWidth = groupWidth / columnCount, - optionPointWidth = options.pointWidth, - pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 : - pointOffsetWidth * options.pointPadding, - pointWidth = mathCeil(mathMax(pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), 1)), - colIndex = (reversedXAxis ? columnCount - - series.columnIndex : series.columnIndex) || 0, - pointXOffset = pointPadding + (groupPadding + colIndex * - pointOffsetWidth - (categoryWidth / 2)) * - (reversedXAxis ? -1 : 1), - threshold = options.threshold, - translatedThreshold = series.yAxis.getThreshold(threshold), - minPointLength = pick(options.minPointLength, 5); - - // record the new values - each(points, function (point) { - var plotY = point.plotY, - yBottom = point.yBottom || translatedThreshold, - barX = point.plotX + pointXOffset, - barY = mathCeil(mathMin(plotY, yBottom)), - barH = mathCeil(mathMax(plotY, yBottom) - barY), - stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey], - trackerY, - shapeArgs; - - // Record the offset'ed position and width of the bar to be able to align the stacking total correctly - if (stacking && series.visible && stack && stack[point.x]) { - stack[point.x].setOffset(pointXOffset, pointWidth); - } - - // handle options.minPointLength and tracker for small points - if (mathAbs(barH) < minPointLength) { - if (minPointLength) { - barH = minPointLength; - barY = - mathAbs(barY - translatedThreshold) > minPointLength ? // stacked - yBottom - minPointLength : // keep position - translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0); - } - trackerY = barY - 3; - } - - extend(point, { - barX: barX, - barY: barY, - barW: pointWidth, - barH: barH - }); - - // create shape type and shape args that are reused in drawPoints and drawTracker - point.shapeType = 'rect'; - shapeArgs = extend(chart.renderer.Element.prototype.crisp.apply({}, [ - borderWidth, - barX, - barY, - pointWidth, - barH - ]), { - r: options.borderRadius - }); - if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border - shapeArgs.y -= 1; - shapeArgs.height += 1; - } - point.shapeArgs = shapeArgs; - - // make small columns responsive to mouse - point.trackerArgs = defined(trackerY) && merge(point.shapeArgs, { - height: mathMax(6, barH + 3), - y: trackerY - }); - }); - - }, - - getSymbol: function () { - }, - - /** - * Columns have no graph - */ - drawGraph: function () {}, - - /** - * Draw the columns. For bars, the series.group is rotated, so the same coordinates - * apply for columns and bars. This method is inherited by scatter series. - * - */ - drawPoints: function () { - var series = this, - options = series.options, - renderer = series.chart.renderer, - graphic, - shapeArgs; - - - // draw the columns - each(series.points, function (point) { - var plotY = point.plotY; - if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { - graphic = point.graphic; - shapeArgs = point.shapeArgs; - if (graphic) { // update - stop(graphic); - graphic.animate(shapeArgs); - - } else { - point.graphic = graphic = renderer[point.shapeType](shapeArgs) - .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]) - .add(series.group) - .shadow(options.shadow); - } - - } - }); - }, - /** - * Draw the individual tracker elements. - * This method is inherited by scatter and pie charts too. - */ - drawTracker: function () { - var series = this, - chart = series.chart, - renderer = chart.renderer, - shapeArgs, - tracker, - trackerLabel = +new Date(), - options = series.options, - cursor = options.cursor, - css = cursor && { cursor: cursor }, - rel; - - each(series.points, function (point) { - tracker = point.tracker; - shapeArgs = point.trackerArgs || point.shapeArgs; - delete shapeArgs.strokeWidth; - if (point.y !== null) { - if (tracker) {// update - tracker.attr(shapeArgs); - - } else { - point.tracker = - renderer[point.shapeType](shapeArgs) - .attr({ - isTracker: trackerLabel, - fill: TRACKER_FILL, - visibility: series.visible ? VISIBLE : HIDDEN, - zIndex: options.zIndex || 1 - }) - .on(hasTouch ? 'touchstart' : 'mouseover', function (event) { - rel = event.relatedTarget || event.fromElement; - if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) { - series.onMouseOver(); - } - point.onMouseOver(); - - }) - .on('mouseout', function (event) { - if (!options.stickyTracking) { - rel = event.relatedTarget || event.toElement; - if (attr(rel, 'isTracker') !== trackerLabel) { - series.onMouseOut(); - } - } - }) - .css(css) - .add(point.group || chart.trackerGroup); // pies have point group - see issue #118 - } - } - }); - }, - - - /** - * Animate the column heights one by one from zero - * @param {Boolean} init Whether to initialize the animation or run it - */ - animate: function (init) { - var series = this, - points = series.points; - - if (!init) { // run the animation - /* - * Note: Ideally the animation should be initialized by calling - * series.group.hide(), and then calling series.group.show() - * after the animation was started. But this rendered the shadows - * invisible in IE8 standards mode. If the columns flicker on large - * datasets, this is the cause. - */ - - each(points, function (point) { - var graphic = point.graphic, - shapeArgs = point.shapeArgs; - - if (graphic) { - // start values - graphic.attr({ - height: 0, - y: series.yAxis.translate(0, 0, 1) - }); - - // animate - graphic.animate({ - height: shapeArgs.height, - y: shapeArgs.y - }, series.options.animation); - } - }); - - - // delete this function to allow it only once - series.animate = null; - } - - }, - /** - * Remove this series from the chart - */ - remove: function () { - var series = this, - chart = series.chart; - - // column and bar series affects other series of the same type - // as they are either stacked or grouped - if (chart.hasRendered) { - each(chart.series, function (otherSeries) { - if (otherSeries.type === series.type) { - otherSeries.isDirty = true; - } - }); - } - - Series.prototype.remove.apply(series, arguments); - } -}); -seriesTypes.column = ColumnSeries; - -var BarSeries = extendClass(ColumnSeries, { - type: 'bar', - init: function () { - this.inverted = true; - ColumnSeries.prototype.init.apply(this, arguments); - } -}); -seriesTypes.bar = BarSeries; - -/** - * The scatter series class - */ -var ScatterSeries = extendClass(Series, { - type: 'scatter', - - /** - * Extend the base Series' translate method by adding shape type and - * arguments for the point trackers - */ - translate: function () { - var series = this; - - Series.prototype.translate.apply(series); - - each(series.points, function (point) { - point.shapeType = 'circle'; - point.shapeArgs = { - x: point.plotX, - y: point.plotY, - r: series.chart.options.tooltip.snap - }; - }); - }, - - - /** - * Create individual tracker elements for each point - */ - //drawTracker: ColumnSeries.prototype.drawTracker, - drawTracker: function () { - var series = this, - cursor = series.options.cursor, - css = cursor && { cursor: cursor }, - graphic; - - each(series.points, function (point) { - graphic = point.graphic; - if (graphic) { // doesn't exist for null points - graphic - .attr({ isTracker: true }) - .on('mouseover', function () { - series.onMouseOver(); - point.onMouseOver(); - }) - .on('mouseout', function () { - if (!series.options.stickyTracking) { - series.onMouseOut(); - } - }) - .css(css); - } - }); - - }//, - - /** - * Cleaning the data is not necessary in a scatter plot - */ - //cleanData: function () {} -}); -seriesTypes.scatter = ScatterSeries; - -/** - * Extended point object for pies - */ -var PiePoint = extendClass(Point, { - /** - * Initiate the pie slice - */ - init: function () { - - Point.prototype.init.apply(this, arguments); - - var point = this, - toggleSlice; - - //visible: options.visible !== false, - extend(point, { - visible: point.visible !== false, - name: pick(point.name, 'Slice') - }); - - // add event listener for select - toggleSlice = function () { - point.slice(); - }; - addEvent(point, 'select', toggleSlice); - addEvent(point, 'unselect', toggleSlice); - - return point; - }, - - /** - * Toggle the visibility of the pie slice - * @param {Boolean} vis Whether to show the slice or not. If undefined, the - * visibility is toggled - */ - setVisible: function (vis) { - var point = this, - chart = point.series.chart, - tracker = point.tracker, - dataLabel = point.dataLabel, - connector = point.connector, - shadowGroup = point.shadowGroup, - method; - - // if called without an argument, toggle visibility - point.visible = vis = vis === UNDEFINED ? !point.visible : vis; - - method = vis ? 'show' : 'hide'; - - point.group[method](); - if (tracker) { - tracker[method](); - } - if (dataLabel) { - dataLabel[method](); - } - if (connector) { - connector[method](); - } - if (shadowGroup) { - shadowGroup[method](); - } - if (point.legendItem) { - chart.legend.colorizeItem(point, vis); - } - }, - - /** - * Set or toggle whether the slice is cut out from the pie - * @param {Boolean} sliced When undefined, the slice state is toggled - * @param {Boolean} redraw Whether to redraw the chart. True by default. - */ - slice: function (sliced, redraw, animation) { - var point = this, - series = point.series, - chart = series.chart, - slicedTranslation = point.slicedTranslation, - translation; - - setAnimation(animation, chart); - - // redraw is true by default - redraw = pick(redraw, true); - - // if called without an argument, toggle - sliced = point.sliced = defined(sliced) ? sliced : !point.sliced; - - translation = { - translateX: (sliced ? slicedTranslation[0] : chart.plotLeft), - translateY: (sliced ? slicedTranslation[1] : chart.plotTop) - }; - point.group.animate(translation); - if (point.shadowGroup) { - point.shadowGroup.animate(translation); - } - - } -}); - -/** - * The Pie series class - */ -var PieSeries = extendClass(Series, { - type: 'pie', - isCartesian: false, - pointClass: PiePoint, - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'borderColor', - 'stroke-width': 'borderWidth', - fill: 'color' - }, - - /** - * Pies have one color each point - */ - getColor: function () { - // record first color for use in setData - this.initialColor = this.chart.counters.color; - }, - - /** - * Animate the column heights one by one from zero - */ - animate: function () { - var series = this, - points = series.points; - - each(points, function (point) { - var graphic = point.graphic, - args = point.shapeArgs, - up = -mathPI / 2; - - if (graphic) { - // start values - graphic.attr({ - r: 0, - start: up, - end: up - }); - - // animate - graphic.animate({ - r: args.r, - start: args.start, - end: args.end - }, series.options.animation); - } - }); - - // delete this function to allow it only once - series.animate = null; - - }, - - /** - * Extend the basic setData method by running processData and generatePoints immediately, - * in order to access the points from the legend. - */ - setData: function () { - Series.prototype.setData.apply(this, arguments); - this.processData(); - this.generatePoints(); - }, - /** - * Do translation for pie slices - */ - translate: function () { - this.generatePoints(); - - var total = 0, - series = this, - cumulative = -0.25, // start at top - precision = 1000, // issue #172 - options = series.options, - slicedOffset = options.slicedOffset, - connectorOffset = slicedOffset + options.borderWidth, - positions = options.center.concat([options.size, options.innerSize || 0]), - chart = series.chart, - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - start, - end, - angle, - points = series.points, - circ = 2 * mathPI, - fraction, - smallestSize = mathMin(plotWidth, plotHeight), - isPercent, - radiusX, // the x component of the radius vector for a given point - radiusY, - labelDistance = options.dataLabels.distance; - - // get positions - either an integer or a percentage string must be given - positions = map(positions, function (length, i) { - - isPercent = /%$/.test(length); - return isPercent ? - // i == 0: centerX, relative to width - // i == 1: centerY, relative to height - // i == 2: size, relative to smallestSize - // i == 4: innerSize, relative to smallestSize - [plotWidth, plotHeight, smallestSize, smallestSize][i] * - pInt(length) / 100 : - length; - }); - - // utility for getting the x value from a given y, used for anticollision logic in data labels - series.getX = function (y, left) { - - angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance)); - - return positions[0] + - (left ? -1 : 1) * - (mathCos(angle) * (positions[2] / 2 + labelDistance)); - }; - - // set center for later use - series.center = positions; - - // get the total sum - each(points, function (point) { - total += point.y; - }); - - each(points, function (point) { - // set start and end angle - fraction = total ? point.y / total : 0; - start = mathRound(cumulative * circ * precision) / precision; - cumulative += fraction; - end = mathRound(cumulative * circ * precision) / precision; - - // set the shape - point.shapeType = 'arc'; - point.shapeArgs = { - x: positions[0], - y: positions[1], - r: positions[2] / 2, - innerR: positions[3] / 2, - start: start, - end: end - }; - - // center for the sliced out slice - angle = (end + start) / 2; - point.slicedTranslation = map([ - mathCos(angle) * slicedOffset + chart.plotLeft, - mathSin(angle) * slicedOffset + chart.plotTop - ], mathRound); - - // set the anchor point for tooltips - radiusX = mathCos(angle) * positions[2] / 2; - radiusY = mathSin(angle) * positions[2] / 2; - point.tooltipPos = [ - positions[0] + radiusX * 0.7, - positions[1] + radiusY * 0.7 - ]; - - // set the anchor point for data labels - point.labelPos = [ - positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector - positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a - positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie - positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a - positions[0] + radiusX, // landing point for connector - positions[1] + radiusY, // a/a - labelDistance < 0 ? // alignment - 'center' : - angle < circ / 4 ? 'left' : 'right', // alignment - angle // center angle - ]; - - // API properties - point.percentage = fraction * 100; - point.total = total; - - }); - - - this.setTooltipPoints(); - }, - - /** - * Render the slices - */ - render: function () { - var series = this; - - // cache attributes for shapes - series.getAttribs(); - - this.drawPoints(); - - // draw the mouse tracking area - if (series.options.enableMouseTracking !== false) { - series.drawTracker(); - } - - this.drawDataLabels(); - - if (series.options.animation && series.animate) { - series.animate(); - } - - // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see - series.isDirty = false; // means data is in accordance with what you see - }, - - /** - * Draw the data points - */ - drawPoints: function () { - var series = this, - chart = series.chart, - renderer = chart.renderer, - groupTranslation, - //center, - graphic, - group, - shadow = series.options.shadow, - shadowGroup, - shapeArgs; - - // draw the slices - each(series.points, function (point) { - graphic = point.graphic; - shapeArgs = point.shapeArgs; - group = point.group; - shadowGroup = point.shadowGroup; - - // put the shadow behind all points - if (shadow && !shadowGroup) { - shadowGroup = point.shadowGroup = renderer.g('shadow') - .attr({ zIndex: 4 }) - .add(); - } - - // create the group the first time - if (!group) { - group = point.group = renderer.g('point') - .attr({ zIndex: 5 }) - .add(); - } - - // if the point is sliced, use special translation, else use plot area traslation - groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop]; - group.translate(groupTranslation[0], groupTranslation[1]); - if (shadowGroup) { - shadowGroup.translate(groupTranslation[0], groupTranslation[1]); - } - - // draw the slice - if (graphic) { - graphic.animate(shapeArgs); - } else { - point.graphic = - renderer.arc(shapeArgs) - .attr(extend( - point.pointAttr[NORMAL_STATE], - { 'stroke-linejoin': 'round' } - )) - .add(point.group) - .shadow(shadow, shadowGroup); - } - - // detect point specific visibility - if (point.visible === false) { - point.setVisible(false); - } - - }); - - }, - - /** - * Override the base drawDataLabels method by pie specific functionality - */ - drawDataLabels: function () { - var series = this, - data = series.data, - point, - chart = series.chart, - options = series.options.dataLabels, - connectorPadding = pick(options.connectorPadding, 10), - connectorWidth = pick(options.connectorWidth, 1), - connector, - connectorPath, - softConnector = pick(options.softConnector, true), - distanceOption = options.distance, - seriesCenter = series.center, - radius = seriesCenter[2] / 2, - centerY = seriesCenter[1], - outside = distanceOption > 0, - dataLabel, - labelPos, - labelHeight, - halves = [// divide the points into right and left halves for anti collision - [], // right - [] // left - ], - x, - y, - visibility, - rankArr, - sort, - i = 2, - j; - - // get out if not enabled - if (!options.enabled) { - return; - } - - // run parent method - Series.prototype.drawDataLabels.apply(series); - - // arrange points for detection collision - each(data, function (point) { - if (point.dataLabel) { // it may have been cancelled in the base method (#407) - halves[ - point.labelPos[7] < mathPI / 2 ? 0 : 1 - ].push(point); - } - }); - halves[1].reverse(); - - // define the sorting algorithm - sort = function (a, b) { - return b.y - a.y; - }; - - // assume equal label heights - labelHeight = halves[0][0] && halves[0][0].dataLabel && pInt(halves[0][0].dataLabel.styles.lineHeight); - - /* Loop over the points in each quartile, starting from the top and bottom - * of the pie to detect overlapping labels. - */ - while (i--) { - - var slots = [], - slotsLength, - usedSlots = [], - points = halves[i], - pos, - length = points.length, - slotIndex; - - - // build the slots - for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) { - slots.push(pos); - // visualize the slot - /* - var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0), - slotY = pos + chart.plotTop; - if (!isNaN(slotX)) { - chart.renderer.rect(slotX, slotY - 7, 100, labelHeight) - .attr({ - 'stroke-width': 1, - stroke: 'silver' - }) - .add(); - chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4) - .attr({ - fill: 'silver' - }).add(); - } - // */ - } - slotsLength = slots.length; - - // if there are more values than available slots, remove lowest values - if (length > slotsLength) { - // create an array for sorting and ranking the points within each quarter - rankArr = [].concat(points); - rankArr.sort(sort); - j = length; - while (j--) { - rankArr[j].rank = j; - } - j = length; - while (j--) { - if (points[j].rank >= slotsLength) { - points.splice(j, 1); - } - } - length = points.length; - } - - // The label goes to the nearest open slot, but not closer to the edge than - // the label's index. - for (j = 0; j < length; j++) { - - point = points[j]; - labelPos = point.labelPos; - - var closest = 9999, - distance, - slotI; - - // find the closest slot index - for (slotI = 0; slotI < slotsLength; slotI++) { - distance = mathAbs(slots[slotI] - labelPos[1]); - if (distance < closest) { - closest = distance; - slotIndex = slotI; - } - } - - // if that slot index is closer to the edges of the slots, move it - // to the closest appropriate slot - if (slotIndex < j && slots[j] !== null) { // cluster at the top - slotIndex = j; - } else if (slotsLength < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom - slotIndex = slotsLength - length + j; - while (slots[slotIndex] === null) { // make sure it is not taken - slotIndex++; - } - } else { - // Slot is taken, find next free slot below. In the next run, the next slice will find the - // slot above these, because it is the closest one - while (slots[slotIndex] === null) { // make sure it is not taken - slotIndex++; - } - } - - usedSlots.push({ i: slotIndex, y: slots[slotIndex] }); - slots[slotIndex] = null; // mark as taken - } - // sort them in order to fill in from the top - usedSlots.sort(sort); - - - // now the used slots are sorted, fill them up sequentially - for (j = 0; j < length; j++) { - - point = points[j]; - labelPos = point.labelPos; - dataLabel = point.dataLabel; - var slot = usedSlots.pop(), - naturalY = labelPos[1]; - - visibility = point.visible === false ? HIDDEN : VISIBLE; - slotIndex = slot.i; - - // if the slot next to currrent slot is free, the y value is allowed - // to fall back to the natural position - y = slot.y; - if ((naturalY > y && slots[slotIndex + 1] !== null) || - (naturalY < y && slots[slotIndex - 1] !== null)) { - y = naturalY; - } - - // get the x - use the natural x position for first and last slot, to prevent the top - // and botton slice connectors from touching each other on either side - x = series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i); - - // move or place the data label - dataLabel - .attr({ - visibility: visibility, - align: labelPos[6] - })[dataLabel.moved ? 'animate' : 'attr']({ - x: x + options.x + - ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0), - y: y + options.y - }); - dataLabel.moved = true; - - // draw the connector - if (outside && connectorWidth) { - connector = point.connector; - - connectorPath = softConnector ? [ - M, - x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label - 'C', - x, y, // first break, next to the label - 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5], - labelPos[2], labelPos[3], // second break - L, - labelPos[4], labelPos[5] // base - ] : [ - M, - x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label - L, - labelPos[2], labelPos[3], // second break - L, - labelPos[4], labelPos[5] // base - ]; - - if (connector) { - connector.animate({ d: connectorPath }); - connector.attr('visibility', visibility); - - } else { - point.connector = connector = series.chart.renderer.path(connectorPath).attr({ - 'stroke-width': connectorWidth, - stroke: options.connectorColor || point.color || '#606060', - visibility: visibility, - zIndex: 3 - }) - .translate(chart.plotLeft, chart.plotTop) - .add(); - } - } - } - } - }, - - /** - * Draw point specific tracker objects. Inherit directly from column series. - */ - drawTracker: ColumnSeries.prototype.drawTracker, - - /** - * Pies don't have point marker symbols - */ - getSymbol: function () {} - -}); -seriesTypes.pie = PieSeries; - - -// global variables -extend(Highcharts, { - Chart: Chart, - dateFormat: dateFormat, - pathAnim: pathAnim, - getOptions: getOptions, - hasRtlBug: hasRtlBug, - numberFormat: numberFormat, - Point: Point, - Color: Color, - Renderer: Renderer, - seriesTypes: seriesTypes, - setOptions: setOptions, - Series: Series, - - // Expose utility funcitons for modules - addEvent: addEvent, - removeEvent: removeEvent, - createElement: createElement, - discardElement: discardElement, - css: css, - each: each, - extend: extend, - map: map, - merge: merge, - pick: pick, - splat: splat, - extendClass: extendClass, - product: 'Highcharts', - version: '2.1.9' -}); -}()); diff --git a/gatling/src/main/resources/assets/js/highstock.js b/gatling/src/main/resources/assets/js/highstock.js deleted file mode 100644 index 354f96cf593..00000000000 --- a/gatling/src/main/resources/assets/js/highstock.js +++ /dev/null @@ -1,238 +0,0 @@ -/* - Highstock JS v1.1.2 (2011-12-23) - - (c) 2009-2011 Torstein H?nsi - - License: www.highcharts.com/license -*/ -(function(){function I(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function Ka(){for(var a=0,b=arguments,c=b.length,d={};a3?c.length%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(f?b+Ha(a-c).toFixed(f).slice(2):"")}function qc(a,b,c,d){var e,c= -q(c,1);e=a/c;if(!b&&(b=[1,2,2.5,5,10],d&&(d.allowDecimals===!1||d.type==="logarithmic")))c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c]);for(d=0;d=L[qa]&&b.setSeconds(j>=L[ab]?0:a*Ra(b.getSeconds()/a));if(j>=L[ab])b[sc](j>=L[Va]?0:a*Ra(b[hc]()/a));if(j>=L[Va])b[tc](j>=L[ma]?0:a*Ra(b[ic]()/a));if(j>=L[ma])b[jc](j>=L[Pa]?1:a*Ra(b[ub]()/a));j>=L[Pa]&&(b[uc](j>=L[bb]?0:a*Ra(b[Xb]()/a)),m=b[Yb]());j>=L[bb]&&(m-=m%a,b[vc](m));if(j===L[Ea])b[jc](b[ub]()-b[kc]()+q(d,1));e=1;m=b[Yb]();d=b.getTime();k=b[Xb]();for(b=b[ub]();d< -c;)f.push(d),j===L[bb]?d=Zb(m+e*a,0):j===L[Pa]?d=Zb(m,k+e*a):!h&&(j===L[ma]||j===L[Ea])?d=Zb(m,k,b+e*a*(j===L[ma]?1:7)):(d+=j*a,j<=L[Va]&&d%L[ma]===0&&(g[d]=ma)),e++;f.push(d);f.info={unitName:i[0],unitRange:j,count:a,higherRanks:g,totalRange:j*a};return f}function wc(){this.symbol=this.color=0}function Mc(a,b,c,d,e,f,g,h){var k=g.x,g=g.y,i=k-a+c-h,j=g-b+d+15,l;i<7&&(i=c+k+h);i+a>c+e&&(i-=i+a-(c+e),j=g-b+d-h,l=!0);j=j&&g<=j+b&&(j=g+d+h)):j+b>d+f&&(j=d+f-b-h);return{x:i,y:j}}function Nc(a, -b){var c=a.length,d,e;for(e=0;ec&&(c=a[b]);return c}function Cb(a){for(var b in a)a[b]&&a[b].destroy&&a[b].destroy(),delete a[b]}function Kb(a,b){Nb=q(a,b.animation)}function xc(){var a=la.global.useUTC;Zb=a?Date.UTC:function(a,c,d,e,f,g){return(new Date(a,c,q(d,1), -q(e,0),q(f,0),q(g,0))).getTime()};hc=a?"getUTCMinutes":"getMinutes";ic=a?"getUTCHours":"getHours";kc=a?"getUTCDay":"getDay";ub=a?"getUTCDate":"getDate";Xb=a?"getUTCMonth":"getMonth";Yb=a?"getUTCFullYear":"getFullYear";sc=a?"setUTCMinutes":"setMinutes";tc=a?"setUTCHours":"setHours";jc=a?"setUTCDate":"setDate";uc=a?"setUTCMonth":"setMonth";vc=a?"setUTCFullYear":"setFullYear"}function Db(a){$b||($b=Y(vb));a&&$b.appendChild(a);$b.innerHTML=""}function Eb(){}function ac(a,b){function c(a){function b(a, -c){this.pos=a;this.type=c||"";this.isNew=!0;c||this.addLabel()}function c(a){if(a)this.options=a,this.id=a.id;return this}function d(a,b,c,e){this.isNegative=b;this.options=a;this.x=c;this.stack=e;this.alignOptions={align:a.align||(ea?b?"left":"right":"center"),verticalAlign:a.verticalAlign||(ea?"middle":b?"bottom":"top"),y:q(a.y,ea?4:b?14:-6),x:q(a.x,ea?b?-6:6:0)};this.textAlign=a.textAlign||(ea?b?"right":"left":"center")}function e(){var a=[],b=[],c;Ua=C=null;n(D.series,function(e){if(e.visible|| -!v.ignoreHiddenSeries){var f=e.options,g,h,j,k,m,l,o,Q,p,D=f.threshold,fa,n=[],yc=0;if(i)f=e.xData,f.length&&(Ua=wa(q(Ua,f[0]),Jb(f)),C=R(q(C,f[0]),Bb(f)));else{var u,t,r,H=e.cropped,s=e.xAxis.getExtremes(),va=!!e.modifyValue;g=f.stacking;lb=g==="percent";if(g)m=f.stack,k=e.type+q(m,""),l="-"+k,e.stackKey=k,h=a[k]||[],a[k]=h,j=b[l]||[],b[l]=j;lb&&(Ua=0,C=99);e.processData();f=e.processedXData;o=e.processedYData;fa=o.length;for(c=0;c=s.min&&(f[c-1]||Q)<=s.max))if(Q=p.length)for(;Q--;)p[Q]!==null&&(n[yc++]=p[Q]);else n[yc++]=p;!lb&&n.length&&(Ua=wa(q(Ua,n[0]),Jb(n)),C=R(q(C,n[0]),Bb(n)));e.useThreshold&&D!==null&&(Ua>=D?(Ua=D,ka=!0):CP,k;a&&P===B&&(P=i&&!z(A.min)&&!z(A.max)?wa(D.closestPointRange*5,C-Ua):null);S-N0||!pa))S+=b*ma}Ca=N===S||N===void 0||S===void 0?1:Y&&!d&&e===c.options.tickPixelInterval?c.tickInterval:q(d,Sa?1:(S-N)*e/(s||1));a&&D.postProcessTickInterval&&(Ca=D.postProcessTickInterval(Ca));t||(ya=sa.pow(10,Ra(sa.log(Ca)/sa.LN10)),z(A.tickInterval)||(Ca=qc(Ca,null,ya,A)));D.tickInterval=Ca;rb=A.minorTickInterval==="auto"&&Ca? -Ca/5:A.minorTickInterval;ca=A.tickPositions||Ja&&Ja.apply(D,[N,S]);if(!ca)if(t)ca=rc(Ca,N,S,A.startOfWeek,A.units);else{var m,d=f(Ra(N/Ca)*Ca);c=f(bc(S/Ca)*Ca);for(ca=[];d<=c;){ca.push(d);d=f(d+Ca);if(d===m)break;m=d}}a&&U(D,"afterSetTickPositions",{tickPositions:ca});if(!Y&&(a=ca[0],m=ca[ca.length-1],A.startOnTick?N=a:N>a&&ca.shift(),A.endOnTick?S=m:Sjb[va]&&A.alignTicks!==!1))jb[va]=ca.length}function h(a){a=(new c(a)).render();ua.push(a);return a}function j(){var a= -A.title,d=A.stackLabels,e=A.alternateGridColor,f=A.lineWidth,g,k,i=o.hasRendered&&z(db)&&!isNaN(db),Q=(g=D.series.length&&z(N)&&z(S))||q(A.showEmpty,!0);if(g||Y){if(rb&&!Sa)for(g=N+(ca[0]-N)%rb;g<=S;g+=rb)ja[g]||(ja[g]=new b(g,"minor")),i&&ja[g].isNew&&ja[g].render(null,!0),ja[g].isActive=!0,ja[g].render();n(ca,function(a,c){if(!Y||a>=N&&a<=S)Qa[a]||(Qa[a]=new b(a)),i&&Qa[a].isNew&&Qa[a].render(c,!0),Qa[a].isActive=!0,Qa[a].render(c)});e&&n(ca,function(a,b){if(b%2===0&&a=1E3?Wb(a,0):a},Xa=m&&A.labels.staggerLines,Fb=A.reversed,qa=Sa&&A.tickmarkPlacement==="between"?0.5:0;b.prototype={addLabel:function(){var a=this.pos,b=A.labels,c=Sa&&m&&Sa.length&&!b.step&&!b.staggerLines&&!b.rotation&&Aa/Sa.length||!m&&Aa/2,d=a===ca[0],e=a===ca[ca.length-1],f=Sa&&z(Sa[a])?Sa[a]:a,g=this.label,h;if(t)h=ca.info,h=A.dateTimeLabelFormats[h.higherRanks[a]||h.unitName];this.isFirst=d;this.isLast=e;a=Pa.call({isFirst:d, -isLast:e,dateTimeLabelFormat:h,value:r?pc(f):f});c=c&&{width:R(1,y(c-2*(b.padding||10)))+Fa};c=I(c,b.style);z(g)?g&&g.attr({text:a}).css(c):this.label=z(a)&&b.enabled?Z.text(a,0,0,b.useHTML).attr({align:b.align,rotation:b.rotation}).css(c).add(J):null},getLabelSize:function(){var a=this.label;return a?(this.labelBBox=a.getBBox())[m?"height":"width"]:0},render:function(a,b){var c=this.type,d=this.label,e=this.pos,f=A.labels,g=this.gridLine,h=c?c+"Grid":"grid",j=c?c+"Tick":"tick",k=A[h+"LineWidth"], -i=A[h+"LineColor"],o=A[h+"LineDashStyle"],Q=A[j+"Length"],h=A[j+"Width"]||0,p=A[j+"Color"],D=A[j+"Position"],j=this.mark,w=f.step,fa=b&&Ka||Ia,n;n=m?L(e+qa,null,null,b)+E:Da+H+(l?(b&&Ma||ia)-F-Da:0);fa=m?fa-Za+H-(l?kb:0):fa-L(e+qa,null,null,b)-E;if(k){e=M(e+qa,k,b);if(g===B){g={stroke:i,"stroke-width":k};if(o)g.dashstyle=o;if(!c)g.zIndex=1;this.gridLine=g=k?Z.path(e).attr(g).add(eb):null}!b&&g&&e&&g.animate({d:e})}if(h)D==="inside"&&(Q=-Q),l&&(Q=-Q),c=Z.crispLine([ta,n,fa,ha,n+(m?0:-Q),fa+(m?Q:0)], -h),j?j.animate({d:c}):this.mark=Z.path(c).attr({stroke:p,"stroke-width":h}).add(J);d&&!isNaN(n)&&(n=n+f.x-(qa&&m?qa*ba*(Fb?-1:1):0),fa=fa+f.y-(qa&&!m?qa*ba*(Fb?1:-1):0),z(f.y)||(fa+=O(d.styles.lineHeight)*0.9-d.getBBox().height/2),Xa&&(fa+=a/(w||1)%Xa*16),this.isFirst&&!q(A.showFirstLabel,1)||this.isLast&&!q(A.showLastLabel,1)?d.hide():d.show(),w&&a%w&&d.hide(),d[this.isNew?"attr":"animate"]({x:n,y:fa}));this.isNew=!1},destroy:function(){Cb(this)}};c.prototype={render:function(){var a=this,b=a.options, -c=b.label,d=a.label,e=b.width,f=b.to,g=b.from,h=b.value,j,k=b.dashStyle,i=a.svgElem,l=[],o,Q,p=b.color;Q=b.zIndex;var fa=b.events;r&&(g=Hb(g),f=Hb(f),h=Hb(h));if(e){if(l=M(h,e),b={stroke:p,"stroke-width":e},k)b.dashstyle=k}else if(z(g)&&z(f))g=R(g,N),f=wa(f,S),j=M(f),(l=M(g))&&j?l.push(j[4],j[5],j[1],j[2]):l=null,b={fill:p};else return;if(z(Q))b.zIndex=Q;if(i)l?i.animate({d:l},null,i.onGetPath):(i.hide(),i.onGetPath=function(){i.show()});else if(l&&l.length&&(a.svgElem=i=Z.path(l).attr(b).add(),fa))for(o in k= -function(b){i.on(b,function(c){fa[b].apply(a,[c])})},fa)k(o);if(c&&z(c.text)&&l&&l.length&&x>0&&kb>0){c=G({align:m&&j&&"center",x:m?!j&&4:10,verticalAlign:!m&&j&&"middle",y:m?j?16:10:j?6:-4,rotation:m&&!j&&90},c);if(!d)a.label=d=Z.text(c.text,0,0).attr({align:c.textAlign||c.align,rotation:c.rotation,zIndex:Q}).css(c.style).add();j=[l[1],l[4],q(l[6],l[1])];l=[l[2],l[5],q(l[7],l[2])];o=Jb(j);Q=Jb(l);d.align(c,!1,{x:o,y:Q,width:Bb(j)-o,height:Bb(l)-Q});d.show()}else d&&d.hide();return a},destroy:function(){Cb(this); -Ib(ua,this)}};d.prototype={destroy:function(){Cb(this)},setTotal:function(a){this.cum=this.total=a},render:function(a){var b=this.options.formatter.call(this);this.label?this.label.attr({text:b,visibility:Ta}):this.label=o.renderer.text(b,0,0).css(this.options.style).attr({align:this.textAlign,rotation:this.options.rotation,visibility:Ta}).add(a)},setOffset:function(a,b){var c=this.isNegative,d=D.translate(this.total),e=D.translate(0),e=Ha(d-e),f=o.xAxis[0].translate(this.x)+a,g=o.plotHeight,c={x:ea? -c?d:d-e:f,y:ea?g-f-b:c?g-d-e:g-d,width:ea?e:b,height:ea?b:e};this.label&&this.label.align(this.alignOptions,null,c).attr({visibility:Wa})}};L=function(a,b,c,d,e){var f=1,g=0,h=d?da:ba,d=d?db:N,e=A.ordinal||r&&e;h||(h=ba);c&&(f*=-1,g=s);Fb&&(f*=-1,g-=f*s);b?(Fb&&(a=s-a),a=a/h+d,e&&(a=D.lin2val(a))):(e&&(a=D.val2lin(a)),a=f*(a-d)*h+g+f*na);return a};M=function(a,b,c){var d,e,f,a=L(a,null,null,c),g=c&&Ka||Ia,h=c&&Ma||ia,j,c=e=y(a+E);d=f=y(g-a-E);if(isNaN(a))j=!0;else if(m){if(d=za,f=g-Za,cDa+ -x)j=!0}else if(c=Da,e=h-F,dza+kb)j=!0;return j?null:Z.crispLine([ta,c,d,ha,e,f],b||0)};Ga.push(D);o[i?"xAxis":"yAxis"].push(D);ea&&i&&Fb===B&&(Fb=!0);I(D,{addPlotBand:h,addPlotLine:h,adjustTickAmount:function(){if(jb&&jb[va]&&!t&&!Sa&&!Y&&A.alignTicks!==!1){var a=Ya,b=ca.length;Ya=jb[va];if(ba||a===null?a=N:SCa/2)d=0;D.pointRange=d;D.closestPointRange=e}D.translationSlope=ba=s/(c+d||1);E=m?Da:Za;na=ba*(d/2);D.left=Da;D.top=za;D.len=s},setCategories:function(b,c){D.categories=a.categories=Sa=b;n(D.series,function(a){a.translate(); -a.setTooltipPoints(!0)});D.isDirty=!0;q(c,!0)&&o.redraw()},setExtremes:function(a,b,c,d){c=q(c,!0);U(D,"setExtremes",{min:a,max:b},function(){Oa=a;gb=b;c&&o.redraw(d)});U(D,"afterSetExtremes",{min:N,max:S})},setScale:function(){var a,b,c;db=N;aa=S;Na=s;s=m?x:kb;n(D.series,function(a){if(a.isDirtyData||a.isDirty||a.xAxis.isDirty)c=!0});if(s!==Na||c||Y||Oa!==X||gb!==Mb){e();g();X=Oa;Mb=gb;da=ba;D.translationSlope=ba=s/(S-N+(D.pointRange||0)||1);if(!i)for(a in w)for(b in w[a])w[a][b].cum=w[a][b].total; -if(!D.isDirty)D.isDirty=o.isDirtyBox||N!==db||S!==aa}},setTickPositions:g,translate:L,redraw:function(){xb.resetTracker&&xb.resetTracker();A.ordinal&&g(!0);j();n(ua,function(a){a.render()});n(D.series,function(a){a.isDirty=!0})},removePlotBand:k,removePlotLine:k,reversed:Fb,series:[],stacks:w,destroy:function(){var a;ra(D);for(a in w)Cb(w[a]),w[a]=null;if(D.stackTotalGroup)D.stackTotalGroup=D.stackTotalGroup.destroy();n([Qa,ja,xa,ua],function(a){Cb(a)});n([T,J,eb,fa],function(a){a&&a.destroy()}); -T=J=eb=fa=null}});for(Ea in u)W(D,Ea,u[Ea]);if(r)D.val2lin=Hb,D.lin2val=pc}function d(){var b={};return{add:function(c,d,e,f){b[c]||(d=Z.text(d,0,0).css(a.toolbar.itemStyle).align({align:"right",x:-J-20,y:V+30}).on("click",f).attr({align:"right",zIndex:20}).add(),b[c]=d)},remove:function(a){Db(b[a].element);b[a]=null}}}function e(a){function b(){var a=this.points||tb(this),c=a[0].series,d;d=[c.tooltipHeaderFormatter(a[0].key)];n(a,function(a){c=a.series;d.push(c.tooltipFormatter&&c.tooltipFormatter(a)|| -a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});return d.join("")}function c(a,b){l=m?a:(2*l+a)/3;p=m?b:(p+b)/2;w.attr({x:l,y:p});$a=Ha(a-l)>1||Ha(b-p)>1?function(){c(a,b)}:null}function d(){if(!m){var a=o.hoverPoints;w.hide();a&&n(a,function(a){a.setState()});o.hoverPoints=null;m=!0}}var e,f=a.borderWidth,g=a.crosshairs,h=[],j=a.style,k=a.shared,i=O(j.padding),m=!0,l=0,p=0;j.padding=0;var w=Z.label("",0,0).attr({padding:i,fill:a.backgroundColor,"stroke-width":f,r:a.borderRadius,zIndex:8}).css(j).hide().add().shadow(a.shadow); -return{shared:k,refresh:function(f){var j,i,l,p,r={},u=[];l=f.tooltipPos;j=a.formatter||b;r=o.hoverPoints;k&&(!f.series||!f.series.noSharedTooltip)?(p=0,r&&n(r,function(a){a.setState()}),o.hoverPoints=f,n(f,function(a){a.setState(cb);p+=a.plotY;u.push(a.getLabelConfig())}),i=f[0].plotX,p=y(p)/f.length,r={x:f[0].category},r.points=u,f=f[0]):r=f.getLabelConfig();r=j.call(r);e=f.series;i=q(i,f.plotX);p=q(p,f.plotY);j=y(l?l[0]:ea?Aa-p:i);i=y(l?l[1]:ea?Ba-i:p);l=k||!f.series.isCartesian||Xa(j,i);r===!1|| -!l?d():(m&&(w.show(),m=!1),w.attr({text:r}),w.attr({stroke:a.borderColor||f.color||e.color||"#606060"}),i=Mc(w.width,w.height,$,V,Aa,Ba,{x:j,y:i},q(a.distance,12)),c(y(i.x),y(i.y)));if(g){g=tb(g);for(i=g.length;i--;)if(l=f.series[i?"yAxis":"xAxis"],g[i]&&l)if(l=l.getPlotLinePath(f[i?"y":"x"],1),h[i])h[i].attr({d:l,visibility:Wa});else{j={"stroke-width":g[i].width||1,stroke:g[i].color||"#C0C0C0",zIndex:g[i].zIndex||2};if(g[i].dashStyle)j.dashstyle=g[i].dashStyle;h[i]=Z.path(l).attr(j).add()}}},hide:d, -hideCrosshairs:function(){n(h,function(a){a&&a.hide()})},destroy:function(){n(h,function(a){a&&a.destroy()});w&&(w=w.destroy())}}}function f(a){function b(a){var c,d=Ac&&T.width/T.body.scrollWidth-1,e,f,g,a=a||ja.event;if(!a.target)a.target=a.srcElement;if(a.originalEvent)a=a.originalEvent;if(a.event)a=a.event;c=a.touches?a.touches.item(0):a;qb=Bc(F);e=qb.left;f=qb.top;Vb?(g=a.x,c=a.y):(g=c.pageX-e,c=c.pageY-f);d&&(g+=y((d+1)*e-e),c+=y((d+1)*f-f));return I(a,{chartX:g,chartY:c})}function c(a){var b= -{xAxis:[],yAxis:[]};n(Ga,function(c){var d=c.translate,e=c.isXAxis;b[e?"xAxis":"yAxis"].push({axis:c,value:d((ea?!e:e)?a.chartX-$:Ba-a.chartY+V,!0)})});return b}function d(){var a=o.hoverSeries,b=o.hoverPoint;if(b)b.onMouseOut();if(a)a.onMouseOut();xa&&(xa.hide(),xa.hideCrosshairs());ab=null}function f(){if(l){var a={xAxis:[],yAxis:[]},b=l.getBBox(),c=b.x-$,d=b.y-V;k&&(n(Ga,function(e){if(e.options.zoomEnabled!==!1){var f=e.translate,g=e.isXAxis,h=ea?!g:g,j=f(h?c:Ba-d-b.height,!0,0,0,1),f=f(h?c+b.width: -Ba-d,!0,0,0,1);a[g?"xAxis":"yAxis"].push({axis:e,min:wa(j,f),max:R(j,f)})}}),U(o,"selection",a,pb));l=l.destroy()}C(F,{cursor:"auto"});o.mouseIsDown=Da=k=!1;ra(T,ua?"touchend":"mouseup",f)}function g(a){var b=z(a.pageX)?a.pageX:a.page.x,a=z(a.pageX)?a.pageY:a.page.y;qb&&!Xa(b-qb.left-$,a-qb.top-V)&&d()}function h(){d();qb=null}var j,i,k,l,m=v.zoomType,p=/x/.test(m),w=/y/.test(m),r=p&&!ea||w&&ea,u=w&&!ea||p&&ea;rb=function(){db?(db.translate($,V),ea&&db.attr({width:o.plotWidth,height:o.plotHeight}).invert()): -o.trackerGroup=db=Z.g("tracker").attr({zIndex:9}).add()};rb();if(a.enabled)o.tooltip=xa=e(a);(function(){F.onmousedown=function(a){a=b(a);!ua&&a.preventDefault&&a.preventDefault();o.mouseIsDown=Da=!0;o.mouseDownX=j=a.chartX;i=a.chartY;W(T,ua?"touchend":"mouseup",f)};var e=function(c){if(!c||!(c.touches&&c.touches.length>1)){c=b(c);if(!ua)c.returnValue=!1;var d=c.chartX,e=c.chartY,f=!Xa(d-$,e-V);ua&&c.type==="touchstart"&&(P(c.target,"isTracker")?o.runTrackerClick||c.preventDefault():!bb&&!f&&c.preventDefault()); -f&&(d<$?d=$:d>$+Aa&&(d=$+Aa),eV+Ba&&(e=V+Ba));if(Da&&c.type!=="touchstart"){if(k=Math.sqrt(Math.pow(j-d,2)+Math.pow(i-e,2)),k>10){var g=Xa(j-$,i-V);if(Ea&&(p||w)&&g)l||(l=Z.rect($,V,r?1:Aa,u?1:Ba,0).attr({fill:v.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add());l&&r&&(c=d-j,l.attr({width:Ha(c),x:(c>0?0:c)+j}));l&&u&&(e-=i,l.attr({height:Ha(e),y:(e>0?0:e)+i}));g&&!l&&v.panning&&o.pan(d)}}else if(!f){var h,d=o.hoverPoint,e=o.hoverSeries,m,n,g=ia,t=ea?c.chartY:c.chartX-$;if(xa&& -a.shared&&(!e||!e.noSharedTooltip)){h=[];m=ga.length;for(n=0;ng&&h.splice(m,1);if(h.length&&h[0].plotX!==ab)xa.refresh(h),ab=h[0].plotX}if(e&&e.tracker&&(c=e.tooltipPoints[t])&&c!==d)c.onMouseOver()}return f||!Ea}};F.onmousemove=e;W(F,"mouseleave",h);W(T,"mousemove",g);F.ontouchstart= -function(a){if(p||w)F.onmousedown(a);e(a)};F.ontouchmove=e;F.ontouchend=function(){k&&d()};F.onclick=function(a){var d=o.hoverPoint,a=b(a);a.cancelBubble=!0;if(!k)if(d&&P(a.target,"isTracker")){var e=d.plotX,f=d.plotY;I(d,{pageX:qb.left+$+(ea?Aa-f:e),pageY:qb.top+V+(ea?Ba-e:f)});U(d.series,"click",I(a,{point:d}));d.firePointEvent("click",a)}else I(a,c(a)),Xa(a.chartX-$,a.chartY-V)&&U(o,"click",a);k=!1}})();ib=setInterval(function(){$a&&$a()},32);I(this,{zoomX:p,zoomY:w,resetTracker:d,normalizeMouseEvent:b, -destroy:function(){if(o.trackerGroup)o.trackerGroup=db=o.trackerGroup.destroy();ra(F,"mouseleave",h);ra(T,"mousemove",g);F.onclick=F.onmousedown=F.onmousemove=F.ontouchstart=F.ontouchend=F.ontouchmove=null}})}function g(a){var b=a.type||v.type||v.defaultSeriesType,c=aa[b],d=o.hasRendered;if(d)if(ea&&b==="column")c=aa.bar;else if(!ea&&b==="bar")c=aa.column;b=new c;b.init(o,a);!d&&b.inverted&&(ea=!0);if(b.isCartesian)Ea=b.isCartesian;ga.push(b);return b}function h(){v.alignTicks!==!1&&n(Ga,function(a){a.adjustTickAmount()}); -jb=null}function k(a){var b=o.isDirtyLegend,c,d=o.isDirtyBox,e=ga.length,f=e,g=o.clipRect;for(Kb(a,o);f--;)if(a=ga[f],a.isDirty&&a.options.stacking){c=!0;break}if(c)for(f=e;f--;)if(a=ga[f],a.options.stacking)a.isDirty=!0;n(ga,function(a){a.isDirty&&a.options.legendType==="point"&&(b=!0)});if(b&&Ya.renderLegend)Ya.renderLegend(),o.isDirtyLegend=!1;Ea&&(Pa||(jb=null,n(Ga,function(a){a.setScale()})),h(),qa(),n(Ga,function(a){a.isDirty&&a.redraw()}));d&&(hb(),rb(),g&&(Lb(g),g.animate({width:o.plotSizeX, -height:o.plotSizeY+1})));n(ga,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});xb&&xb.resetTracker&&xb.resetTracker();U(o,"redraw")}function i(){var b=a.xAxis||{},d=a.yAxis||{},b=tb(b);n(b,function(a,b){a.index=b;a.isX=!0});d=tb(d);n(d,function(a,b){a.index=b});b=b.concat(d);n(b,function(a){new c(a)});h()}function j(b,c){eb=G(a.title,b);L=G(a.subtitle,c);n([["title",b,eb],["subtitle",c,L]],function(a){var b=a[0],c=o[b],d=a[1],a=a[2];c&&d&&(c=c.destroy());a&&a.text&&!c&&(o[b]= -Z.text(a.text,0,0,a.useHTML).attr({align:a.align,"class":fb+b,zIndex:1}).css(a.style).add().align(a,!1,x))})}function l(){M=v.renderTo;ka=fb+mc++;Ab(M)&&(M=T.getElementById(M));M.innerHTML="";M.offsetWidth||(X=M.cloneNode(0),C(X,{position:Gb,top:"-9999px",display:""}),T.body.appendChild(X));na=(X||M).offsetWidth;lb=(X||M).offsetHeight;o.chartWidth=ia=v.width||na||600;o.chartHeight=Ia=v.height||(lb>19?lb:400);o.container=F=Y(vb,{className:fb+"container"+(v.className?" "+v.className:""),id:ka},I({position:Cc, -overflow:Ta,width:ia+Fa,height:Ia+Fa,textAlign:"left",lineHeight:"normal"},v.style),X||M);o.renderer=Z=v.forExport?new Ob(F,ia,Ia,!0):new Pb(F,ia,Ia);var a,b;Dc&&F.getBoundingClientRect&&(a=function(){C(F,{left:0,top:0});b=F.getBoundingClientRect();C(F,{left:-(b.left-O(b.left))+Fa,top:-(b.top-O(b.top))+Fa})},a(),W(ja,"resize",a),W(o,"destroy",function(){ra(ja,"resize",a)}))}function m(){function a(){var c=v.width||M.offsetWidth,d=v.height||M.offsetHeight;if(c&&d){if(c!==na||d!==lb)clearTimeout(b), -b=setTimeout(function(){ob(c,d,!1)},100);na=c;lb=d}}var b;W(ja,"resize",a);W(o,"destroy",function(){ra(ja,"resize",a)})}function p(){o&&U(o,"endResize",null,function(){Pa-=1})}function H(){for(var b=ea||v.inverted||v.type==="bar"||v.defaultSeriesType==="bar",c=a.series,d=c&&c.length;!b&&d--;)c[d].type==="bar"&&(b=!0);o.inverted=ea=b}function s(){var b=a.labels,c=a.credits,e;j();Ya=o.legend=new Eb;n(Ga,function(a){a.setScale()});qa();n(Ga,function(a){a.setTickPositions(!0)});h();qa();hb();Ea&&n(Ga, -function(a){a.render()});if(!o.seriesGroup)o.seriesGroup=Z.g("series-group").attr({zIndex:3}).add();n(ga,function(a){a.translate();a.setTooltipPoints();a.render()});b.items&&n(b.items,function(){var a=I(b.style,this.style),c=O(a.left)+$,d=O(a.top)+V+12;delete a.left;delete a.top;Z.text(this.html,c,d).attr({zIndex:2}).css(a).add()});if(!o.toolbar)o.toolbar=d();if(c.enabled&&!o.credits)e=c.href,o.credits=Z.text(c.text,0,0).on("click",function(){if(e)location.href=e}).attr({align:c.position.align,zIndex:8}).css(c.style).add().align(c.position); -rb();o.hasRendered=!0;X&&(M.appendChild(F),Db(X))}function t(){if(!Qb&&ja==ja.top&&T.readyState!=="complete")T.attachEvent("onreadystatechange",function(){T.detachEvent("onreadystatechange",t);T.readyState==="complete"&&t()});else{l();U(o,"init");if(Highcharts.RangeSelector&&a.rangeSelector.enabled)o.rangeSelector=new Highcharts.RangeSelector(o);mb();nb();H();i();n(a.series||[],function(a){g(a)});if(Highcharts.Scroller&&(a.navigator.enabled||a.scrollbar.enabled))o.scroller=new Highcharts.Scroller(o); -o.render=s;o.tracker=xb=new f(a.tooltip);s();b&&b.apply(o,[o]);n(o.callbacks,function(a){a.apply(o,[o])});U(o,"load")}}var r=a.series;a.series=null;a=G(la,a);a.series=r;var v=a.chart,r=v.margin,r=sb(r)?r:[r,r,r,r],u=q(v.marginTop,r[0]),Na=q(v.marginRight,r[1]),va=q(v.marginBottom,r[2]),E=q(v.marginLeft,r[3]),Za=v.spacingTop,w=v.spacingRight,ba=v.spacingBottom,da=v.spacingLeft,x,eb,L,V,J,Oa,$,K,M,X,F,ka,na,lb,ia,Ia,Ma,Ka,ma,pa,Ja,ya,o=this,bb=(r=v.events)&&!!r.click,Va,Xa,xa,Da,za,gb,Mb,Ba,Aa,xb,db, -rb,Ya,yb,zb,qb,Ea=v.showAxes,Pa=0,Ga=[],jb,ga=[],ea,Z,$a,ib,ab,hb,qa,mb,nb,ob,pb,ub,Eb=function(){function a(b,c){var d=b.legendItem,e=b.legendLine,g=b.legendSymbol,h=p.color,j=c?f.itemStyle.color:h,h=c?b.color:h;d&&d.css({fill:j});e&&e.attr({stroke:h});g&&g.attr({stroke:h,fill:h})}function b(a,c,d){var e=a.legendItem,f=a.legendLine,g=a.legendSymbol,a=a.checkbox;e&&e.attr({x:c,y:d});f&&f.translate(c,d-4);g&&g.attr({x:c+g.xOff,y:d+g.yOff});if(a)a.x=c,a.y=d}function c(){n(i,function(a){var b=a.checkbox, -c=E.alignAttr;b&&C(b,{left:c.translateX+a.legendItemWidth+b.x-40+Fa,top:c.translateY+b.y-11+Fa})})}function d(c){var e,i,k,o,n=c.legendItem;o=c.series||c;var r=o.options,ba=r&&r.borderWidth||0;if(!n){o=/^(bar|pie|area|column)$/.test(o.type);c.legendItem=n=Z.text(f.labelFormatter.call(c),0,0).css(c.visible?l:p).on("mouseover",function(){c.setState(cb);n.css(m)}).on("mouseout",function(){n.css(c.visible?l:p);c.setState()}).on("click",function(){var a=function(){c.setVisible()};c.firePointEvent?c.firePointEvent("legendItemClick", -null,a):U(c,"legendItemClick",null,a)}).attr({zIndex:2}).add(E);if(!o&&r&&r.lineWidth){var Na={"stroke-width":r.lineWidth,zIndex:2};if(r.dashStyle)Na.dashstyle=r.dashStyle;c.legendLine=Z.path([ta,-h-j,0,ha,-j,0]).attr(Na).add(E)}if(o)k=Z.rect(e=-h-j,i=-11,h,12,2).attr({zIndex:3}).add(E);else if(r&&r.marker&&r.marker.enabled)k=r.marker.radius,k=Z.symbol(c.symbol,e=-h/2-j-k,i=-4-k,2*k,2*k).attr(c.pointAttr[oa]).attr({zIndex:3}).add(E);if(k)k.xOff=e+ba%2/2,k.yOff=i+ba%2/2;c.legendSymbol=k;a(c,c.visible); -if(r&&r.showCheckbox)c.checkbox=Y("input",{type:"checkbox",checked:c.selected,defaultChecked:c.selected},f.itemCheckboxStyle,F),W(c.checkbox,"click",function(a){U(c,"checkboxClick",{checked:a.target.checked},function(){c.select()})})}e=n.getBBox();i=c.legendItemWidth=f.itemWidth||h+j+e.width+w;s=e.height;if(g&&t-u+i>(za||ia-2*w-u))t=u,v+=va+s+q;H=v+q;b(c,t,v);g?t+=i:v+=va+s+q;da=za||R(g?t-u:i,da)}function e(){t=u;v=w+va+r-5;H=da=0;E||(E=Z.g("legend").attr({zIndex:10}).add());i=[];n(z,function(a){var b= -a.options;b.showInLegend&&(i=i.concat(a.legendItems||(b.legendType==="point"?a.data:a)))});Nc(i,function(a,b){return(a.options.legendIndex||0)-(b.options.legendIndex||0)});y&&i.reverse();n(i,d);yb=za||da;zb=H-r+s;if(Na||Da){yb+=2*w;zb+=2*w;if(ba){if(yb>0&&zb>0)ba[ba.isNew?"attr":"animate"](ba.crisp(null,null,null,yb,zb)),ba.isNew=!1}else ba=Z.rect(0,0,yb,zb,f.borderRadius,Na||0).attr({stroke:f.borderColor,"stroke-width":Na||0,fill:Da||La}).add(E).shadow(f.shadow),ba.isNew=!0;ba[i.length?"show":"hide"]()}for(var a= -["left","right","top","bottom"],b,g=4;g--;)b=a[g],k[b]&&k[b]!=="auto"&&(f[g<2?"align":"verticalAlign"]=b,f[g<2?"x":"y"]=O(k[b])*(g%2?-1:1));i.length&&E.align(I(f,{width:yb,height:zb}),!0,x);Pa||c()}var f=o.options.legend;if(f.enabled){var g=f.layout==="horizontal",h=f.symbolWidth,j=f.symbolPadding,i,k=f.style,l=f.itemStyle,m=f.itemHoverStyle,p=G(l,f.itemHiddenStyle),w=f.padding||O(k.padding),r=18,u=4+w+h+j,t,v,H,s=0,va=f.itemMarginTop||0,q=f.itemMarginBottom||0,ba,Na=f.borderWidth,Da=f.backgroundColor, -E,da,za=f.width,z=o.series,y=f.reversed;e();W(o,"endResize",c);return{colorizeItem:a,destroyItem:function(a){var b=a.checkbox;n(["legendItem","legendLine","legendSymbol"],function(b){a[b]&&a[b].destroy()});b&&Db(a.checkbox)},renderLegend:e,destroy:function(){ba&&(ba=ba.destroy());E&&(E=E.destroy())}}}};Xa=function(a,b){return a>=0&&a<=Aa&&b>=0&&b<=Ba};ub=function(){U(o,"selection",{resetSelection:!0},pb);o.toolbar.remove("zoom")};pb=function(a){var b=la.lang,c=o.pointCount<100;o.resetZoomEnabled!== -!1&&o.toolbar.add("zoom",b.resetZoom,b.resetZoomTitle,ub);!a||a.resetSelection?n(Ga,function(a){a.options.zoomEnabled!==!1&&a.setExtremes(null,null,!0,c)}):n(a.xAxis.concat(a.yAxis),function(a){var b=a.axis;o.tracker[b.isXAxis?"zoomX":"zoomY"]&&b.setExtremes(a.min,a.max,!0,c)})};o.pan=function(a){var b=o.xAxis[0],c=o.mouseDownX,d=b.pointRange/2,e=b.getExtremes(),f=b.translate(c-a,!0)+d,c=b.translate(c+Aa-a,!0)-d;(d=o.hoverPoints)&&n(d,function(a){a.setState()});f>wa(e.dataMin,e.min)&&c= -a)this.color=0},wrapSymbol:function(a){if(this.symbol>=a)this.symbol=0}};L=Ka(pb,1,qa,1E3,ab,6E4,Va,36E5,ma,864E5,Ea,6048E5,Pa,2592E6,bb,31556952E3);Sb={init:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c),h,k,i=function(a){for(g=a.length;g--;)a[g]===ta&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(i(b),i(c));a.isArea&&(h=b.splice(b.length-6,6),k=c.splice(c.length-6,6));d===1&&(c=[].concat(c).splice(0,f).concat(c));a.shift=0;if(b.length)for(a= -c.length;b.length{point.key}
',pointFormat:'{series.name}: {point.y}
',shadow:!0,snap:ua?25:10,style:{color:"#333333",fontSize:"12px", -padding:"5px",whiteSpace:"nowrap"}},toolbar:{itemStyle:{color:"#4572A7",cursor:"pointer"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"10px"}}};var cc={dateTimeLabelFormats:Ka(pb,"%H:%M:%S.%L",qa,"%H:%M:%S",ab,"%H:%M",Va,"%H:%M",ma,"%e. %b",Ea,"%e. %b",Pa,"%b '%y",bb,"%Y"),endOnTick:!1,gridLineColor:"#C0C0C0",labels:x,lineColor:"#C0D0E0",lineWidth:1,max:null, -min:null,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:5,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#6D869F",fontWeight:"bold"}},type:"linear"},lc=G(cc,{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{align:"right",x:-8,y:3},lineWidth:0, -maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Y-values"},stackLabels:{enabled:!1,formatter:function(){return this.total},style:x.style}}),Qc={labels:{align:"right",x:-8,y:null},title:{rotation:270}},Pc={labels:{align:"left",x:8,y:null},title:{rotation:90}},zc={labels:{align:"center",x:0,y:14},title:{rotation:0}},Oc=G(zc,{labels:{y:-5}}),J=la.plotOptions,x=J.line;J.spline=G(x);J.scatter=G(x,{lineWidth:0,states:{hover:{lineWidth:0}},tooltip:{headerFormat:'{series.name}
', -pointFormat:"x: {point.x}
y: {point.y}
"}});J.area=G(x,{threshold:0});J.areaspline=G(J.area);J.column=G(x,{borderColor:"#FFFFFF",borderWidth:1,borderRadius:0,groupPadding:0.2,marker:null,pointPadding:0.1,minPointLength:0,cropThreshold:50,pointRange:null,states:{hover:{brightness:0.1,shadow:!1},select:{color:"#C0C0C0",borderColor:"#000000",shadow:!1}},dataLabels:{y:null,verticalAlign:null},threshold:0});J.bar=G(J.column,{dataLabels:{align:"left",x:5,y:0}});J.pie=G(x,{borderColor:"#FFFFFF", -borderWidth:1,center:["50%","50%"],colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name},y:5},legendType:"point",marker:null,size:"75%",showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}}});xc();var $a=function(a){var b=[],c;(function(a){(c=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(a))?b=[O(c[1]),O(c[2]),O(c[3]),parseFloat(c[4],10)]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))&& -(b=[O(c[1],16),O(c[2],16),O(c[3],16),1])})(a);return{get:function(c){return b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"?b[3]:"rgba("+b.join(",")+")":a},brighten:function(a){if(Ub(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=O(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},setOpacity:function(a){b[3]=a;return this}}};Eb.prototype={init:function(a,b){this.element=T.createElementNS("http://www.w3.org/2000/svg",b);this.renderer=a;this.attrSetters={}},animate:function(a,b, -c){b=q(b,Nb,!0);Lb(this);if(b){b=G(b);if(c)b.complete=c;dc(this,a,b)}else this.attr(a),c&&c()},attr:function(a,b){var c,d,e,f,g=this.element,h=g.nodeName,k=this.renderer,i,j=this.attrSetters,l=this.shadows,m=this.htmlNode,p,n=this;Ab(a)&&z(b)&&(c=a,a={},a[c]=b);if(Ab(a))c=a,h==="circle"?c={x:"cx",y:"cy"}[c]||c:c==="strokeWidth"&&(c="stroke-width"),n=P(g,c)||this[c]||0,c!=="d"&&c!=="visibility"&&(n=parseFloat(n));else for(c in a){i=!1;d=a[c];e=j[c]&&j[c](d,c);if(e!==!1){e!==B&&(d=e);if(c==="d")d&& -d.join&&(d=d.join(" ")),/(NaN| {2}|^$)/.test(d)&&(d="M 0 0"),this.d=d;else if(c==="x"&&h==="text"){for(e=0;eg||!z(g)&&z(b))){d.insertBefore(f,a);h=!0;break}h||d.appendChild(f);this.added=!0;U(this,"add");return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.box,e,f;b.onclick=b.onmouseout=b.onmouseover= -b.onmousemove=null;Lb(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f/g,'').replace(/<(i|em)>/g,'').replace(/
/g,"").split(//g),d=b.childNodes,e=/style="([^"]+)"/,f=/href="([^"]+)"/,g=P(b,"x"),h=a.styles,k=h&&a.useHTML&&!this.forExport,i=a.htmlNode,j=h&&O(h.width),l=h&&h.lineHeight,m,p=d.length;p--;)b.removeChild(d[p]);j&&!a.added&&this.box.appendChild(b);c[c.length-1]===""&&c.pop();n(c, -function(c,d){var h,i=0,k,c=c.replace(//g,"|||");h=c.split("|||");n(h,function(c){if(c!==""||h.length===1){var p={},n=T.createElementNS("http://www.w3.org/2000/svg","tspan");e.test(c)&&P(n,"style",c.match(e)[1].replace(/(;| |^)color([ :])/,"$1fill$2"));f.test(c)&&(P(n,"onclick",'location.href="'+c.match(f)[1]+'"'),C(n,{cursor:"pointer"}));c=(c.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"<").replace(/>/g,">");n.appendChild(T.createTextNode(c));i? -p.dx=3:p.x=g;if(!i){if(d){!Qb&&a.renderer.forExport&&C(n,{display:"block"});k=ja.getComputedStyle&&O(ja.getComputedStyle(m,null).getPropertyValue("line-height"));if(!k||isNaN(k))k=l||m.offsetHeight||18;P(n,"dy",k)}m=n}P(n,p);b.appendChild(n);i++;if(j)for(var c=c.replace(/-/g,"- ").split(" "),q,H=[];c.length||H.length;)q=a.getBBox().width,p=q>j,!p||c.length===1?(c=H,H=[],c.length&&(n=T.createElementNS("http://www.w3.org/2000/svg","tspan"),P(n,{dy:l||16,x:g}),b.appendChild(n),q>j&&(j=q))):(n.removeChild(n.firstChild), -H.unshift(c.pop())),c.length&&n.appendChild(T.createTextNode(c.join(" ").replace(/- /g,"-")))}})});if(k){if(!i)i=a.htmlNode=Y("span",null,I(h,{position:Gb,top:0,left:0}),this.box.parentNode);i.innerHTML=a.textStr;for(p=d.length;p--;)d[p].style.visibility=Ta}},button:function(a,b,c,d,e,f,g){var h=this.label(a,b,c),k=0,i,j,l,m,p,a={x1:0,y1:0,x2:0,y2:1},e=G(Ka("stroke-width",1,"stroke","#999","fill",Ka("linearGradient",a,"stops",[[0,"#FFF"],[1,"#DDD"]]),"r",3,"padding",3,"style",Ka("color","black")), -e);l=e.style;delete e.style;f=G(e,Ka("stroke","#68A","fill",Ka("linearGradient",a,"stops",[[0,"#FFF"],[1,"#ACF"]])),f);m=f.style;delete f.style;g=G(e,Ka("stroke","#68A","fill",Ka("linearGradient",a,"stops",[[0,"#9BD"],[1,"#CDF"]])),g);p=g.style;delete g.style;W(h.element,"mouseenter",function(){h.attr(f).css(m)});W(h.element,"mouseleave",function(){i=[e,f,g][k];j=[l,m,p][k];h.attr(i).css(j)});h.setState=function(a){(k=a)?a===2&&h.attr(g).css(p):h.attr(e).css(l)};return h.on("click",function(){d.call(h)}).attr(e).css(I({cursor:"default"}, -l))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=y(a[1])+b%2/2);a[2]===a[5]&&(a[2]=a[5]=y(a[2])+b%2/2);return a},path:function(a){return this.createElement("path").attr({d:a,fill:La})},circle:function(a,b,c){a=sb(a)?a:{x:a,y:b,r:c};return this.createElement("circle").attr(a)},arc:function(a,b,c,d,e,f){if(sb(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;return this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0})},rect:function(a,b,c,d,e,f){if(sb(a))b=a.y,c=a.width,d=a.height, -e=a.r,f=a.strokeWidth,a=a.x;e=this.createElement("rect").attr({rx:e,ry:e,fill:La});return e.attr(e.crisp(f,a,b,R(c,0),R(d,0)))},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[q(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return z(a)?b.attr({"class":fb+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:La};arguments.length>1&&I(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f); -f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(y(b),y(c),d,e,f),k=/^url\((.*?)\)$/,i;if(h)g=this.path(h),I(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&I(g,f);else if(k.test(a)){var j=function(a,b){a.attr({width:b[0],height:b[1]}).translate(-y(b[0]/2),-y(b[1]/2))};i=a.match(k)[1];a=Fc[i];g=this.image(i).attr({x:b,y:c});a?j(g,a):(g.attr({width:0, -height:0}),Y("img",{onload:function(){j(g,Fc[i]=[this.width,this.height])},src:i}))}return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return[ta,a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return[ta,a,b,ha,a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return[ta,a+c/2,b,ha,a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return[ta,a,b,ha,a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return[ta,a+c/2,b,ha,a+c,b+d/2,a+c/2,b+d,a, -b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-1.0E-6,d=e.innerR,h=Ma(f),k=pa(f),i=Ma(g),g=pa(g),e=e.end-f');if(b)c=b===vb||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=Y(c);this.renderer=a;this.attrSetters={}},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);Rb&&d.gVis===Ta&&C(c,{visibility:Ta});d.appendChild(c);this.added= -!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();U(this,"add");return this},toggleChildren:function(a,b){for(var c=a.childNodes,d=c.length;d--;)C(c[d],{visibility:b}),c[d].nodeName==="DIV"&&this.toggleChildren(c[d],b)},attr:function(a,b){var c,d,e,f=this.element||{},g=f.style,h=f.nodeName,k=this.renderer,i=this.symbolName,j,l=this.shadows,m,p=this.attrSetters,n=this;Ab(a)&&z(b)&&(c=a,a={},a[c]=b);if(Ab(a))c=a,n=c==="strokeWidth"||c==="stroke-width"?this.strokeweight:this[c]; -else for(c in a)if(d=a[c],m=!1,e=p[c]&&p[c](d,c),e!==!1){e!==B&&(d=e);if(i&&/^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(c))j||(this.symbolAttr(a),j=!0),m=!0;else if(c==="d"){d=d||[];this.d=d.join(" ");e=d.length;for(m=[];e--;)m[e]=Ub(d[e])?y(d[e]*10)-5:d[e]==="Z"?"x":d[e];d=m.join(" ")||"x";f.path=d;if(l)for(e=l.length;e--;)l[e].path=d;m=!0}else if(c==="zIndex"||c==="visibility"){if(Rb&&c==="visibility"&&h==="DIV")f.gVis=d,this.toggleChildren(f,d),d===Wa&&(d=null);d&&(g[c]=d);m= -!0}else if(c==="width"||c==="height")d=R(0,d),this[c]=d,this.updateClipping?(this[c]=d,this.updateClipping()):g[c]=d,m=!0;else if(/^(x|y)$/.test(c))this[c]=d,f.tagName==="SPAN"?this.updateTransform():g[{x:"left",y:"top"}[c]]=d;else if(c==="class")f.className=d;else if(c==="stroke")d=k.color(d,f,c),c="strokecolor";else if(c==="stroke-width"||c==="strokeWidth")f.stroked=d?!0:!1,c="strokeweight",this[c]=d,Ub(d)&&(d+=Fa);else if(c==="dashstyle")(f.getElementsByTagName("stroke")[0]||Y(k.prepVML([""]), -null,null,f))[c]=d||"solid",this.dashstyle=d,m=!0;else if(c==="fill")h==="SPAN"?g.color=d:(f.filled=d!==La?!0:!1,d=k.color(d,f,c),c="fillcolor");else if(c==="translateX"||c==="translateY"||c==="rotation"||c==="align")c==="align"&&(c="textAlign"),this[c]=d,this.updateTransform(),m=!0;else if(c==="text")this.bBox=null,f.innerHTML=d,m=!0;if(l&&c==="visibility")for(e=l.length;e--;)l[e].style[c]=d;m||(Rb?f[c]=d:P(f,c,d))}return n},clip:function(a){var b=this,c=a.members;c.push(b);b.destroyClip=function(){Ib(c, -b)};return b.css(a.getCSS(b.inverted))},css:function(a){var b=this.element;if(b=a&&b.tagName==="SPAN"&&a.width)delete a.width,this.textWidth=b,this.updateTransform();this.styles=I(this.styles,a);C(this.element,a);return this},safeRemoveChild:function(a){a.parentNode&&Db(a)},destroy:function(){this.destroyClip&&this.destroyClip();return Eb.prototype.destroy.apply(this)},empty:function(){for(var a=this.element.childNodes,b=a.length,c;b--;)c=a[b],c.parentNode.removeChild(c)},getBBox:function(a){var b= -this.element,c=this.bBox;if(!c||a){if(b.nodeName==="text")b.style.position=Gb;c=this.bBox={x:b.offsetLeft,y:b.offsetTop,width:b.offsetWidth,height:b.offsetHeight}}return c},on:function(a,b){this.element["on"+a]=function(){var a=ja.event;a.target=a.srcElement;b(a)};return this},updateTransform:function(){if(this.added){var a=this,b=a.element,c=a.translateX||0,d=a.translateY||0,e=a.x||0,f=a.y||0,g=a.textAlign||"left",h={left:0,center:0.5,right:1}[g],k=g&&g!=="left",i=a.shadows;if(c||d)C(b,{marginLeft:c, -marginTop:d}),i&&n(i,function(a){C(a,{marginLeft:c+1,marginTop:d+1})});a.inverted&&n(b.childNodes,function(c){a.renderer.invertChild(c,b)});if(b.tagName==="SPAN"){var j,l,i=a.rotation,m;j=0;var p=1,H=0,s;m=O(a.textWidth);var t=a.xCorr||0,r=a.yCorr||0,v=[i,g,b.innerHTML,a.textWidth].join(",");if(v!==a.cTT)z(i)&&(j=i*Ec,p=Ma(j),H=pa(j),C(b,{filter:i?["progid:DXImageTransform.Microsoft.Matrix(M11=",p,", M12=",-H,", M21=",H,", M22=",p,", sizingMethod='auto expand')"].join(""):La})),j=q(a.elemWidth,b.offsetWidth), -l=q(a.elemHeight,b.offsetHeight),j>m&&(C(b,{width:m+Fa,display:"block",whiteSpace:"normal"}),j=m),m=y((O(b.style.fontSize)||12)*1.2),t=p<0&&-j,r=H<0&&-l,s=p*H<0,t+=H*m*(s?1-h:h),r-=p*m*(i?s?h:1-h:1),k&&(t-=j*h*(p<0?-1:1),i&&(r-=l*h*(H<0?-1:1)),C(b,{textAlign:g})),a.xCorr=t,a.yCorr=r;C(b,{left:e+t,top:f+r});a.cTT=v}}else this.alignOnAdd=!0},shadow:function(a,b){var c=[],d,e=this.element,f=this.renderer,g,h=e.style,k,i=e.path;i&&typeof i.value!=="string"&&(i="x");if(a){for(d=1;d<=3;d++)k=[''],g=Y(f.prepVML(k),null,{left:O(h.left)+1,top:O(h.top)+1}),k=[''],Y(f.prepVML(k),null,null,g),b?b.element.appendChild(g):e.parentNode.insertBefore(g,e),c.push(g);this.shadows=c}return this}}),Tb=function(){this.init.apply(this,arguments)},Tb.prototype=G(Ob.prototype,{Element:x,isIE8:mb.indexOf("MSIE 8.0")>-1,init:function(a,b,c){var d;this.alignedObjects=[];d=this.createElement(vb); -a.appendChild(d.element);this.box=d.element;this.boxWrapper=d;this.setSize(b,c,!1);if(!T.namespaces.hcv)T.namespaces.add("hcv","urn:schemas-microsoft-com:vml"),T.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "},clipRect:function(a,b,c,d){var e=this.createElement();return I(e,{members:[],left:a,top:b,width:c,height:d,getCSS:function(a){var b=this.top,c=this.left,d=c+this.width,e=b+this.height,b={clip:"rect("+y(a? -c:b)+"px,"+y(a?e:d)+"px,"+y(a?d:e)+"px,"+y(a?b:c)+"px)"};!a&&Rb&&I(b,{width:d+Fa,height:e+Fa});return b},updateClipping:function(){n(e.members,function(a){a.css(e.getCSS(a.inverted))})}})},color:function(a,b,c){var d,e=/^rgba/;if(a&&a.linearGradient){var f,g,h=a.linearGradient,k=h.x1||h[0]||0,i=h.y1||h[1]||0,j=h.x2||h[2]||0,h=h.y2||h[3]||0,l,m,p,q;n(a.stops,function(a,b){e.test(a[1])?(d=$a(a[1]),f=d.get("rgb"),g=d.get("a")):(f=a[1],g=1);b?(p=f,q=g):(l=f,m=g)});a=90-sa.atan((h-i)/(j-k))*180/hb;a=["<", -c,' colors="0% ',l,",100% ",p,'" angle="',a,'" opacity="',q,'" o:opacity2="',m,'" type="gradient" focus="100%" method="any" />'];Y(this.prepVML(a),null,null,b)}else if(e.test(a)&&b.tagName!=="IMG")return d=$a(a),a=["<",c,' opacity="',d.get("a"),'"/>'],Y(this.prepVML(a),null,null,b),d.get("rgb");else{b=b.getElementsByTagName(c);if(b.length)b[0].opacity=1;return a}},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')=== --1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","1&&f.css({left:b,top:c,width:d,height:e});return f},rect:function(a,b,c,d,e,f){if(sb(a))b=a.y,c=a.width,d=a.height,f=a.strokeWidth,a=a.x;var g=this.symbol("rect");g.r=e;return g.attr(g.crisp(f,a,b,R(c,0),R(d,0)))},invertChild:function(a,b){var c=b.style;C(a,{flip:"x",left:O(c.width)-10,top:O(c.height)-10, -rotation:-90})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,c=e.r||c||d,d=Ma(f),h=pa(f),k=Ma(g),i=pa(g),e=e.innerR,j=0.07/c,l=e&&0.1/e||0;if(g-f===0)return["x"];else 2*hb-g+fa+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart.options,c=b.plotOptions,d=a.data; -a.data=null;a=G(c[this.type],c.series,a);a.data=d;this.tooltipOptions=G(b.tooltip,a.tooltip);return a},getColor:function(){var a=this.chart.options.colors,b=this.chart.counters;this.color=this.options.color||a[b.color++]||"#0000ff";b.wrapColor(a.length)},getSymbol:function(){var a=this.options.marker,b=this.chart,c=b.options.symbols,b=b.counters;this.symbol=a.symbol||c[b.symbol++];if(/^url/.test(this.symbol))a.radius=0;b.wrapSymbol(c.length)},addPoint:function(a,b,c,d){var e=this.data,f=this.graph, -g=this.area,h=this.chart,k=this.xData,i=this.yData,j=f&&f.shift||0,l=this.options.data;Kb(d,h);if(f&&c)f.shift=j+1;if(g)g.shift=j+1,g.isArea=!0;b=q(b,!0);d={series:this};this.pointClass.prototype.applyOptions.apply(d,[a]);k.push(d.x);i.push(this.valueCount===4?[d.open,d.high,d.low,d.close]:d.y);l.push(a);c&&(e[0]?e[0].remove(!1):(e.shift(),k.shift(),i.shift(),l.shift()));this.getAttribs();this.isDirtyData=this.isDirty=!0;b&&h.redraw()},setData:function(a,b){var c=this.points,d=this.options,e=this.initialColor, -f=this.chart,g=null;this.xIncrement=null;this.pointRange=this.xAxis&&this.xAxis.categories&&1||d.pointRange;if(z(e))f.counters.color=e;var h=[],k=[],i=a?a.length:[],j=this.valueCount===4;if(i>(d.turboThreshold||1E3)){for(e=0;g===null&&eh||this.forceCrop){h=this.xAxis.getExtremes();var i=h.min,j=h.max;if(a[c-1]j)a=[],b=[];else if(a[0]j){for(h=0;h=i){d=R(0,h-1);break}for(;hj){e=h+1;break}a=a.slice(d,e);b=b.slice(d,e);f=!0}}for(h=a.length-1;h> -0;h--)if(c=a[h]-a[h-1],g===B||c0)g.graphic=c.renderer.symbol(q(g.marker&&g.marker.symbol,this.symbol),d-h,e-h,2*h,2*h).attr(a).add(this.group)},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=q(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=J[a.type].marker?a.options.marker:a.options,c=b.states,d=c[cb],e,f=a.color,g={stroke:f,fill:f},h=a.points, -k=[],i,j=a.pointAttrToOptions,l;a.options.marker?(d.radius=d.radius||b.radius+2,d.lineWidth=d.lineWidth||b.lineWidth+1):d.color=d.color||$a(d.color||f).brighten(d.brightness).get();k[oa]=a.convertAttribs(b,g);n([cb,"select"],function(b){k[b]=a.convertAttribs(c[b],k[oa])});a.pointAttr=k;for(f=h.length;f--;){g=h[f];if((b=g.options&&g.options.marker||g.options)&&b.enabled===!1)b.radius=0;e=!1;if(g.options)for(l in j)z(b[j[l]])&&(e=!0);if(e){i=[];c=b.states||{};e=c[cb]=c[cb]||{};if(!a.options.marker)e.color= -$a(e.color||g.options.color).brighten(e.brightness||d.brightness).get();i[oa]=a.convertAttribs(b,k[oa]);i[cb]=a.convertAttribs(c[cb],k[cb],i[oa]);i.select=a.convertAttribs(c.select,k.select,i[oa])}else i=k;g.pointAttr=i}},destroy:function(){var a=this,b=a.chart,c=a.clipRect,d=/AppleWebKit\/533/.test(mb),e,f,g=a.data||[],h,k,i;U(a,"destroy");ra(a);n(["xAxis","yAxis"],function(b){if(i=a[b])Ib(i.series,a),i.isDirty=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(f=g.length;f--;)(h=g[f])&&h.destroy&& -h.destroy();a.points=null;if(c&&c!==b.clipRect)a.clipRect=c.destroy();n(["area","graph","dataLabelsGroup","group","tracker"],function(b){a[b]&&(e=d&&b==="group"?"hide":"destroy",a[b][e]())});if(b.hoverSeries===a)b.hoverSeries=null;Ib(b.series,a);for(k in a)delete a[k]},drawDataLabels:function(){if(this.options.dataLabels.enabled){var a,b,c=this.points,d=this.options,e=d.dataLabels,f,g=this.dataLabelsGroup,h=this.chart,k=this.xAxis,k=k?k.left:h.plotLeft,i=this.yAxis,i=i?i.top:h.plotTop,j=h.renderer, -l=h.inverted,m=this.type,p=d.stacking,H=m==="column"||m==="bar",s=e.verticalAlign===null,t=e.y===null;H&&(p?(s&&(e=G(e,{verticalAlign:"middle"})),t&&(e=G(e,{y:{top:14,middle:4,bottom:-6}[e.verticalAlign]}))):s&&(e=G(e,{verticalAlign:"top"})));g?g.translate(k,i):g=this.dataLabelsGroup=j.g("data-labels").attr({visibility:this.visible?Wa:Ta,zIndex:6}).translate(k,i).add();k=e.color;k==="auto"&&(k=null);e.style.color=q(k,this.color,"black");n(c,function(c){var i=c.barX,k=i&&i+c.barW/2||c.plotX||-999, -n=q(c.plotY,-999),p=c.dataLabel,s=e.align,y=t?c.y>=0?-6:12:e.y;f=e.formatter.call(c.getLabelConfig());a=(l?h.plotWidth-n:k)+e.x;b=(l?h.plotHeight-k:n)+y;m==="column"&&(a+={left:-1,right:1}[s]*c.barW/2||0);l&&c.y<0&&(s="right",a-=10);if(p)l&&!e.y&&(b=b+O(p.styles.lineHeight)*0.9-p.getBBox().height/2),p.attr({text:f}).animate({x:a,y:b});else if(z(f))p=c.dataLabel=j.text(f,a,b).attr({align:s,rotation:e.rotation,zIndex:1}).css(e.style).add(g),l&&!e.y&&p.attr({y:b+O(p.styles.lineHeight)*0.9-p.getBBox().height/ -2});if(H&&d.stacking&&p)k=c.barY,n=c.barW,c=c.barH,p.align(e,null,{x:l?h.plotWidth-k-c:i,y:l?h.plotHeight-i-n:k,width:l?c:n,height:l?n:c})})}},drawGraph:function(){var a=this,b=a.options,c=a.graph,d=[],e,f=a.area,g=a.group,h=b.lineColor||a.color,k=b.lineWidth,i=b.dashStyle,j,l=a.chart.renderer,m=a.yAxis.getThreshold(b.threshold),p=/^area/.test(a.type),H=[],s=[];n(a.segments,function(c){j=[];n(c,function(d,e){a.getPointSpline?j.push.apply(j,a.getPointSpline(c,d,e)):(j.push(e?ha:ta),e&&b.step&&j.push(d.plotX, -c[e-1].plotY),j.push(d.plotX,d.plotY))});c.length>1?d=d.concat(j):H.push(c[0]);if(p){var e=[],f,g=j.length;for(f=0;f=0;f--)fa&&k>e?(k=R(a,e),j=2*e-k):kg&&j>e?(j=R(g,e),k=2*e-j):ju?h-u:v-(g<=v?u:0)),m=i-3);I(f,{barX:j,barY:i,barW:t,barH:k});f.shapeType="rect";g=I(b.renderer.Element.prototype.crisp.apply({},[e,j,i,t,k]),{r:c.borderRadius});e%2&&(g.y-=1,g.height+=1);f.shapeArgs=g;f.trackerArgs=z(m)&&G(f.shapeArgs,{height:R(6,k+3),y:m})})},getSymbol:function(){}, -drawGraph:function(){},drawPoints:function(){var a=this,b=a.options,c=a.chart.renderer,d,e;n(a.points,function(f){var g=f.plotY;if(g!==B&&!isNaN(g)&&f.y!==null)d=f.graphic,e=f.shapeArgs,d?(Lb(d),d.animate(e)):f.graphic=d=c[f.shapeType](e).attr(f.pointAttr[f.selected?"select":oa]).add(a.group).shadow(b.shadow)})},drawTracker:function(){var a=this,b=a.chart,c=b.renderer,d,e,f=+new Date,g=a.options,h=g.cursor,k=h&&{cursor:h},i;n(a.points,function(h){e=h.tracker;d=h.trackerArgs||h.shapeArgs;delete d.strokeWidth; -if(h.y!==null)e?e.attr(d):h.tracker=c[h.shapeType](d).attr({isTracker:f,fill:Gc,visibility:a.visible?Wa:Ta,zIndex:g.zIndex||1}).on(ua?"touchstart":"mouseover",function(c){i=c.relatedTarget||c.fromElement;if(b.hoverSeries!==a&&P(i,"isTracker")!==f)a.onMouseOver();h.onMouseOver()}).on("mouseout",function(b){if(!g.stickyTracking&&(i=b.relatedTarget||b.toElement,P(i,"isTracker")!==f))a.onMouseOut()}).css(k).add(h.group||b.trackerGroup)})},animate:function(a){var b=this,c=b.points;if(!a)n(c,function(a){var c= -a.graphic,a=a.shapeArgs;c&&(c.attr({height:0,y:b.yAxis.translate(0,0,1)}),c.animate({height:a.height,y:a.y},b.options.animation))}),b.animate=null},remove:function(){var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0});X.prototype.remove.apply(a,arguments)}});aa.column=ec;x=ka(ec,{type:"bar",init:function(){this.inverted=!0;ec.prototype.init.apply(this,arguments)}});aa.bar=x;x=ka(X,{type:"scatter",translate:function(){var a=this;X.prototype.translate.apply(a); -n(a.points,function(b){b.shapeType="circle";b.shapeArgs={x:b.plotX,y:b.plotY,r:a.chart.options.tooltip.snap}})},drawTracker:function(){var a=this,b=a.options.cursor,c=b&&{cursor:b},d;n(a.points,function(b){(d=b.graphic)&&d.attr({isTracker:!0}).on("mouseover",function(){a.onMouseOver();b.onMouseOver()}).on("mouseout",function(){if(!a.options.stickyTracking)a.onMouseOut()}).css(c)})}});aa.scatter=x;x=ka(ib,{init:function(){ib.prototype.init.apply(this,arguments);var a=this,b;I(a,{visible:a.visible!== -!1,name:q(a.name,"Slice")});b=function(){a.slice()};W(a,"select",b);W(a,"unselect",b);return a},setVisible:function(a){var b=this.series.chart,c=this.tracker,d=this.dataLabel,e=this.connector,f=this.shadowGroup,g;g=(this.visible=a=a===B?!this.visible:a)?"show":"hide";this.group[g]();if(c)c[g]();if(d)d[g]();if(e)e[g]();if(f)f[g]();this.legendItem&&b.legend.colorizeItem(this,a)},slice:function(a,b,c){var d=this.series.chart,e=this.slicedTranslation;Kb(c,d);q(b,!0);a=this.sliced=z(a)?a:!this.sliced; -a={translateX:a?e[0]:d.plotLeft,translateY:a?e[1]:d.plotTop};this.group.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}});x=ka(X,{type:"pie",isCartesian:!1,pointClass:x,pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},getColor:function(){this.initialColor=this.chart.counters.color},animate:function(){var a=this;n(a.points,function(b){var c=b.graphic,b=b.shapeArgs,d=-hb/2;c&&(c.attr({r:0,start:d,end:d}),c.animate({r:b.r,start:b.start,end:b.end},a.options.animation))}); -a.animate=null},setData:function(){X.prototype.setData.apply(this,arguments);this.processData();this.generatePoints()},translate:function(){this.generatePoints();var a=0,b=-0.25,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f=c.center.concat([c.size,c.innerSize||0]),g=this.chart,h=g.plotWidth,k=g.plotHeight,i,j,l,m=this.points,p=2*hb,q,s=wa(h,k),t,r,v,u=c.dataLabels.distance,f=nb(f,function(a,b){return(t=/%$/.test(a))?[h,k,s,s][b]*O(a)/100:a});this.getX=function(a,b){l=sa.asin((a-f[1])/(f[2]/ -2+u));return f[0]+(b?-1:1)*Ma(l)*(f[2]/2+u)};this.center=f;n(m,function(b){a+=b.y});n(m,function(c){q=a?c.y/a:0;i=y(b*p*1E3)/1E3;b+=q;j=y(b*p*1E3)/1E3;c.shapeType="arc";c.shapeArgs={x:f[0],y:f[1],r:f[2]/2,innerR:f[3]/2,start:i,end:j};l=(j+i)/2;c.slicedTranslation=nb([Ma(l)*d+g.plotLeft,pa(l)*d+g.plotTop],y);r=Ma(l)*f[2]/2;v=pa(l)*f[2]/2;c.tooltipPos=[f[0]+r*0.7,f[1]+v*0.7];c.labelPos=[f[0]+r+Ma(l)*u,f[1]+v+pa(l)*u,f[0]+r+Ma(l)*e,f[1]+v+pa(l)*e,f[0]+r,f[1]+v,u<0?"center":l

0,p=[[],[]],H,s,t,r,v=2,u;if(d.enabled){X.prototype.drawDataLabels.apply(this); -n(a,function(a){a.dataLabel&&p[a.labelPos[7]t){h=[].concat(E);h.sort(r);for(u=x;u--;)h[u].rank=u;for(u=x;u--;)E[u].rank>=t&&E.splice(u,1);x=E.length}for(u=0;us&&y[w+1]!==null||H=c[1]||t===k;)if(i=c.shift(),j=s(j,m,n,q),j!==B&&(g.push(i),h.push(j)),j=[],m=[],n=[],q=[],t===k)break;if(t===k)break;i=l?b[t]:null;if(d==="ohlc"){i=this.cropStart+t; -var r=e&&e[i]||this.pointClass.prototype.applyOptions.apply({},[f[i]]);i=r.open;var v=r.high,u=r.low,r=r.close;if(typeof i==="number")j.push(i);else if(i===null)j.hasNulls=!0;if(typeof v==="number")m.push(v);else if(v===null)m.hasNulls=!0;if(typeof u==="number")n.push(u);else if(u===null)n.hasNulls=!0;if(typeof r==="number")q.push(r);else if(r===null)q.hasNulls=!0}else if(typeof i==="number")j.push(i);else if(i===null)j.hasNulls=!0}return[g,h]};M.processData=function(){var a=this.options,b=a.dataGrouping, -c=b&&b.enabled,d;this.forceCrop=c;if(Sc.apply(this)!==!1&&c){var c=this.chart,e=this.processedXData,f=this.processedYData,g=c.plotSizeX,h=this.xAxis,k=q(h.groupPixelWidth,b.groupPixelWidth),i=g/k,j=e.length,l=this.groupedData,m=c.series;if(!h.groupPixelWidth){for(c=m.length;c--;)m[c].xAxis===h&&m[c].options.dataGrouping&&(k=R(k,m[c].options.dataGrouping.groupPixelWidth));h.groupPixelWidth=k}n(l||[],function(a,b){a&&(l[b]=a.destroy?a.destroy():null)});if(j>i||b.forced){d=!0;this.points=null;c=h.getExtremes(); -i=c.min;m=c.max;g=k*(m-i)/(h.options.ordinal?g*((m-i)/(j*this.closestPointRange)):g);h=rc(g,i,m,null,b.units||Jc);c=M.groupData.apply(this,[e,f,h,b.approximation]);e=c[0];f=c[1];if(b.smoothed){c=e.length-1;for(e[c]=m;c--&&c>0;)e[c]+=g/2;e[0]=i}this.currentDataGrouping=h.info;if(a.pointRange===null)this.pointRange=h.info.totalRange;this.closestPointRange=h.info.totalRange;this.processedXData=e;this.processedYData=f}else this.currentDataGrouping=null,this.pointRange=a.pointRange;this.hasGroupedData= -d}};M.generatePoints=function(){Tc.apply(this);this.groupedData=this.hasGroupedData?this.points:null};M.tooltipHeaderFormatter=function(a){var b=this.tooltipOptions,c=this.options.dataGrouping,d=b.xDateFormat,e,f=this.xAxis,g,h;if(f&&f.options.type==="datetime"&&c){g=this.currentDataGrouping;c=c.dateTimeLabelFormats;if(g)f=c[g.unitName],g.count===1?d=f[0]:(d=f[1],e=f[2]);else if(!d)for(h in L)if(L[h]>=f.closestPointRange){d=c[h][0];break}d=wb(d,a);e&&(d+=wb(e,a+g.totalRange-1));a=b.headerFormat.replace("{point.key}", -d)}else a=Vc.apply(this,[a]);return a};M.destroy=function(){for(var a=this.groupedData||[],b=a.length;b--;)a[b]&&a[b].destroy();Uc.apply(this)};J.line.dataGrouping=J.spline.dataGrouping=J.area.dataGrouping=J.areaspline.dataGrouping=x;J.column.dataGrouping=G(x,{approximation:"sum",groupPixelWidth:10});J.ohlc=G(J.column,{lineWidth:1,dataGrouping:{approximation:"ohlc",enabled:!0,groupPixelWidth:5},states:{hover:{lineWidth:3}}});var x=ka(ib,{applyOptions:function(a){var b=this.series,c=0;if(typeof a=== -"object"&&typeof a.length!=="number")I(this,a),this.options=a;else if(a.length){if(a.length===5){if(typeof a[0]==="string")this.name=a[0];else if(typeof a[0]==="number")this.x=a[0];c++}this.open=a[c++];this.high=a[c++];this.low=a[c++];this.close=a[c++]}this.y=this.high;if(this.x===B&&b)this.x=b.autoIncrement();return this},tooltipFormatter:function(){var a=this.series;return['',this.name||a.name,"
Open: ",this.open,"
High: ",this.high, -"
Low: ",this.low,"
Close: ",this.close,"
"].join("")}}),oc=ka(aa.column,{type:"ohlc",valueCount:4,pointClass:x,useThreshold:!1,pointAttrToOptions:{stroke:"color","stroke-width":"lineWidth"},translate:function(){var a=this.yAxis;aa.column.prototype.translate.apply(this);n(this.points,function(b){if(b.open!==null)b.plotOpen=a.translate(b.open,0,1,0,1);if(b.close!==null)b.plotClose=a.translate(b.close,0,1,0,1)})},drawPoints:function(){var a=this,b=a.chart,c,d,e,f,g,h,k,i;n(a.points,function(j){if(j.plotY!== -B)k=j.graphic,c=j.pointAttr[j.selected?"selected":""],f=c["stroke-width"]%2/2,i=y(j.plotX)+f,g=y(j.barW/2),h=["M",i,y(j.yBottom),"L",i,y(j.plotY)],j.open!==null&&(d=y(j.plotOpen)+f,h.push("M",i,d,"L",i-g,d)),j.close!==null&&(e=y(j.plotClose)+f,h.push("M",i,e,"L",i+g,e)),k?k.animate({d:h}):j.graphic=b.renderer.path(h).attr(c).add(a.group)})},animate:null});aa.ohlc=oc;J.candlestick=G(J.column,{dataGrouping:{approximation:"ohlc",enabled:!0},lineColor:"black",lineWidth:1,upColor:"white",states:{hover:{lineWidth:2}}}); -x=ka(oc,{type:"candlestick",pointAttrToOptions:{fill:"color",stroke:"lineColor","stroke-width":"lineWidth"},getAttribs:function(){oc.prototype.getAttribs.apply(this,arguments);var a=this.options,b=a.states,a=a.upColor,c=G(this.pointAttr);c[""].fill=a;c.hover.fill=b.hover.upColor||a;c.select.fill=b.select.upColor||a;n(this.points,function(a){if(a.open12?Wa:Ta}));ja=!0}}function e(b){var b=a.tracker.normalizeMouseEvent(b),c=b.chartX,d=b.chartY,e=ua?10:7;if(d>F&&dm+da&&cU&&cU+ia-K?da+wa(10,B):cp&&(c=p-B),c!==da&&a.xAxis[0].setExtremes(w.translate(c,!0),w.translate(c+B,!0),!0,!1));b.preventDefault&&b.preventDefault()}function f(b){b=a.tracker.normalizeMouseEvent(b);b=b.chartX;bU+ia-K&&(b=U+ia-K);v?(I=!0,d(0,0,b-m,va)):u?(I=!0,d(0,0,va,b-m)):z&&(I=!0,bp+E-B&&(b=p+E-B),d(0,0,b-E,b-E+B))}function g(){I&&a.xAxis[0].setExtremes(w.translate(da,!0),w.translate(C,!0),!0,!1);v=u=z=I=E=null;L.cursor=V}function h(){var b= -aa.xAxis,c=b.getExtremes(),e=c.min,f=c.max,g=c.dataMin,c=c.dataMax,h=f-e,i,j,k,l,m;i=x.xData;var n=!!b.setExtremes;j=f>=i[i.length-1];i=e<=g;if(!s)x.options.pointStart=aa.xData[0],x.setData(aa.options.data,!1),m=!0;i&&(l=g,k=l+h);j&&(k=c,i||(l=R(k-h,x.xData[0])));n&&(i||j)?b.setExtremes(l,k,!0,!1):(m&&a.redraw(!1),d(R(e,g),wa(f,c)))}var k=a.renderer,i=a.options,j=i.navigator,l=j.enabled,m,p,x,s,t=i.scrollbar,r=t.enabled,v,u,z,va,E,I,w,ba,da,C,B,L=document.body.style,V,M=j.handles,J=l?j.height:0,$= -j.outlineWidth,K=r?t.height:0,P=J+K,T=t.barBorderRadius,F=j.top||a.chartHeight-J-K-i.chart.spacingBottom,Y=$/2,X,U,ia,ja,i=j.baseSeries,aa=a.series[i]||typeof i==="string"&&a.get(i)||a.series[0],la,ma,na,ka=[],ya,o,oa,qa,pa=[],xa=[];a.resetZoomEnabled=!1;(function(){var b=a.xAxis.length,c=a.yAxis.length;a.extraBottomMargin=P+j.margin;if(l){var d=aa.options,i=d.data,k=j.series;s=k.data;d.data=k.data=null;w=new a.Axis(G({ordinal:aa.xAxis.options.ordinal},j.xAxis,{isX:!0,type:"datetime",index:b,height:J, -top:F,offset:0,offsetLeft:K,offsetRight:-K,startOnTick:!1,endOnTick:!1,minPadding:0,maxPadding:0,zoomEnabled:!1}));ba=new a.Axis(G(j.yAxis,{alignTicks:!1,height:J,top:F,offset:0,index:c,zoomEnabled:!1}));b=G(aa.options,k,{threshold:null,clip:!1,enableMouseTracking:!1,group:"nav",padXAxis:!1,xAxis:b,yAxis:c,name:"Navigator",showInLegend:!1,isInternal:!0,visible:!0});d.data=i;k.data=s;b.data=s||i;x=a.initSeries(b);W(aa,"updatedData",h)}else w={translate:function(b,c){var d=aa.xAxis.getExtremes(),e= -a.plotWidth-2*K,f=d.dataMin,d=d.dataMax-f;return c?b*d/e+f:e*(b-f)/d}};W(a.container,gc,e);W(a.container,Kc,f);W(document,Lc,g)})();return{render:d,destroy:function(){ra(a.container,gc,e);ra(a.container,Kc,f);ra(document,Lc,g);l&&ra(aa,"updatedData",h);n([w,ba,la,ma,na,o,oa,qa,ya],function(a){a&&a.destroy&&a.destroy()});w=ba=la=ma=na=o=oa=qa=ya=null;n([pa,ka,xa],function(a){Cb(a)})}}};I(la,{rangeSelector:{buttonTheme:{width:28,height:16,padding:1,r:0,zIndex:10}}});la.lang=G(la.lang,{rangeSelectorZoom:"Zoom", -rangeSelectorFrom:"From:",rangeSelectorTo:"To:"});Highcharts.RangeSelector=function(a){function b(b,c,d){var e=a.xAxis[0],f=e&&e.getExtremes(),g,h=f&&f.dataMin,i=f&&f.dataMax,k,j=e&&wa(f.max,i),f=new Date(j);g=c.type;var c=c.count,l,m,n={millisecond:1,second:1E3,minute:6E4,hour:36E5,day:864E5,week:6048E5};if(!(h===null||i===null||b===s))n[g]?(l=n[g]*c,k=R(j-l,h)):g==="month"?(f.setMonth(f.getMonth()-c),k=R(f.getTime(),h),l=2592E6*c):g==="ytd"?(f=new Date(0),g=new Date,m=g.getFullYear(),f.setFullYear(m), -String(m)!==wb("%Y",f)&&f.setFullYear(m-1),k=m=R(h||0,f.getTime()),g=g.getTime(),j=wa(i||g,g)):g==="year"?(f.setFullYear(f.getFullYear()-c),k=R(h,f.getTime()),l=31536E6*c):g==="all"&&e&&(k=h,j=i),r[b]&&r[b].setState(2),e?setTimeout(function(){e.setExtremes(k,j,q(d,1),0);s=b},1):(h=a.options.xAxis,h[0]=G(h[0],{range:l,min:m}),s=b)}function c(){j&&j.blur();l&&l.blur()}function d(a,b){var c=a.hasFocus?u.inputEditDateFormat||"%Y-%m-%d":u.inputDateFormat||"%b %e, %Y";if(b)a.HCTime=b;a.value=wb(c,a.HCTime)} -function e(b){var c=b==="min",e;m[b]=Y("span",{innerHTML:k[c?"rangeSelectorFrom":"rangeSelectorTo"]},u.labelStyle,i);e=Y("input",{name:b,className:fb+"range-selector",type:"text"},I({width:"80px",height:"16px",border:"1px solid silver",marginLeft:"5px",marginRight:c?"5px":"0",textAlign:"center"},u.inputStyle),i);e.onfocus=e.onblur=function(a){a=a||window.event;e.hasFocus=a.type==="focus";d(e)};e.onchange=function(){var b=e.value,d=Date.parse(b),f=a.xAxis[0].getExtremes();isNaN(d)&&(d=b.split("-"), -d=Date.UTC(O(d[0]),O(d[1])-1,O(d[2])));if(!isNaN(d)&&(c&&d>f.dataMin&&dj.HCTime))a.xAxis[0].setExtremes(c?d:f.min,c?f.max:d)};return e}var f=a.renderer,g,h=a.container,k=la.lang,i,j,l,m={},p,y,s,t,r=[],v,u,x=[{type:"month",count:1,text:"1m"},{type:"month",count:3,text:"3m"},{type:"month",count:6,text:"6m"},{type:"ytd",text:"YTD"},{type:"year",count:1,text:"1y"},{type:"all",text:"All"}];a.resetZoomEnabled=!1;(function(){a.extraTopMargin=25;u=a.options.rangeSelector;v= -u.buttons||x;var d=u.selected;W(h,gc,c);d!==B&&v[d]&&b(d,v[d],!1);W(a,"load",function(){W(a.xAxis[0],"afterSetExtremes",function(){r[s]&&r[s].setState(0);s=null})})})();return{render:function(c,m){var q=a.options.chart.style,w=u.buttonTheme,x=u.inputEnabled!==!1,z=w&&w.states,B=a.plotLeft,C;g||(t=f.text(k.rangeSelectorZoom,B,a.plotTop-10).css(u.labelStyle).add(),C=B+t.getBBox().width+5,n(v,function(c,d){r[d]=f.button(c.text,C,a.plotTop-25,function(){b(d,c);this.isActive=!0},w,z&&z.hover,z&&z.select).css({textAlign:"center"}).add(); -C+=r[d].width+(u.buttonSpacing||0);s===d&&r[d].setState(2)}),x&&(y=i=Y("div",null,{position:"relative",height:0,fontFamily:q.fontFamily,fontSize:q.fontSize,zIndex:1}),h.parentNode.insertBefore(i,h),p=i=Y("div",null,I({position:"absolute",top:a.plotTop-25+"px",right:a.chartWidth-a.plotLeft-a.plotWidth+"px"},u.inputBoxStyle),i),j=e("min"),l=e("max")));x&&(d(j,c),d(l,m));g=!0},destroy:function(){ra(h,gc,c);n([r],function(a){Cb(a)});t&&(t=t.destroy());if(j)j.onfocus=j.onblur=j.onchange=null;if(l)l.onfocus= -l.onblur=l.onchange=null;n([j,l,m.min,m.max,p,y],function(a){Db(a)});j=l=m=i=p=y=null}}};ac.prototype.callbacks.push(function(a){function b(){f=a.xAxis[0].getExtremes();g.render(R(f.min,f.dataMin),wa(f.max,f.dataMax))}function c(){f=a.xAxis[0].getExtremes();h.render(f.min,f.max)}function d(a){g.render(a.min,a.max)}function e(a){h.render(a.min,a.max)}var f,g=a.scroller,h=a.rangeSelector;g&&(W(a.xAxis[0],"afterSetExtremes",d),W(a,"resize",b),b());h&&(W(a.xAxis[0],"afterSetExtremes",e),W(a,"resize", -c),c());W(a,"destroy",function(){g&&(ra(a,"resize",b),ra(a.xAxis[0],"afterSetExtremes",d));h&&(ra(a,"resize",c),ra(a.xAxis[0],"afterSetExtremes",e))})});Highcharts.StockChart=function(a,b){var c=a.series,d,e={marker:{enabled:!1,states:{hover:{enabled:!0,radius:5}}},gapSize:5,shadow:!1,states:{hover:{lineWidth:2}},dataGrouping:{enabled:!0}};a.xAxis=nb(tb(a.xAxis||{}),function(a){return G({minPadding:0,maxPadding:0,ordinal:!0,title:{text:null},showLastLabel:!0},a,{type:"datetime",categories:null})}); -a.yAxis=nb(tb(a.yAxis||{}),function(a){d=a.opposite;return G({labels:{align:d?"right":"left",x:d?-2:2,y:-2},showLastLabel:!1,title:{text:null}},a)});a.series=null;a=G({chart:{panning:!0},navigator:{enabled:!0},scrollbar:{enabled:!0},rangeSelector:{enabled:!0},title:{text:null},tooltip:{shared:!0,crosshairs:!0},legend:{enabled:!1},plotOptions:{line:e,spline:e,area:e,areaspline:e,column:{shadow:!1,borderWidth:0,dataGrouping:{enabled:!0}}}},a,{chart:{inverted:!1}});a.series=c;return new ac(a,b)};var Wc= -M.init,Xc=M.processData,Yc=ib.prototype.tooltipFormatter;M.init=function(){Wc.apply(this,arguments);var a=this.options.compare;if(a)this.modifyValue=function(b,c){var d=this.compareValue,b=a==="value"?b-d:b=100*(b/d)-100;if(c)c.change=b;return b}};M.processData=function(){Xc.apply(this);if(this.options.compare)for(var a=0,b=this.processedXData,c=this.processedYData,d=c.length,e=this.xAxis.getExtremes().min;a=e){this.compareValue=c[a];break}};ib.prototype.tooltipFormatter= -function(a){a=a.replace("{point.change}",(this.change>0?"+":"")+Wb(this.change,this.series.tooltipOptions.changeDecimals||2));return Yc.apply(this,[a])};(function(){var a=M.init,b=M.getSegments;M.init=function(){var b,d;a.apply(this,arguments);b=this.chart;(d=this.xAxis)&&d.options.ordinal&&W(this,"updatedData",function(){delete d.ordinalIndex});if(d&&d.options.ordinal&&!d.hasOrdinalExtension){d.hasOrdinalExtension=!0;d.beforeSetTickPositions=function(){var a,b=[],c=!1,d,e;if(this.options.ordinal){n(this.series, -function(a,c){if(a.visible!==!1&&(b=b.concat(a.processedXData),c)){b.sort(function(a,b){return a-b});for(c=b.length-1;c--;)b[c]===b[c+1]&&b.splice(c,1)}});a=b.length;if(a>2){d=b[1]-b[0];for(e=a-1;e--&&!c;)b[e+1]-b[e]!==d&&(c=!0)}c?(this.ordinalSlope=(b[a-1]-b[0])/(a-1),this.ordinalOffset=b[0],this.ordinalPositions=b):this.ordinalPositions=this.ordinalSlope=this.ordinalOffset=B}};d.val2lin=function(a,b){var c=this.ordinalPositions;if(c){var d=c.length,e,j;for(e=d;e--;)if(c[e]===a){j=e;break}for(e= -d-1;e--;)if(a>c[e]){c=(a-c[e])/(c[e+1]-c[e]);j=e+c;break}return b?j:this.ordinalSlope*(j||0)+this.ordinalOffset}else return a};d.lin2val=function(a,b){var c=this.ordinalPositions;if(c){var d=this.ordinalSlope,e=this.ordinalOffset,j=c.length-1,l,m;if(b)a<0?a=c[0]:a>j?a=c[j]:(j=Ra(a),m=a-j);else for(;j--;)if(l=d*j+e,a>=l){d=d*(j+1)+e;m=(a-l)/(d-l);break}return m!==B&&c[j]!==B?c[j]+m*(c[j+1]-c[j]):a}else return a};d.getExtendedPositions=function(){var a=d.series[0].currentDataGrouping,e=d.ordinalIndex, -h=a?a.count+a.unitName:"raw",k=d.getExtremes(),i,j;if(!e)e=d.ordinalIndex={};if(!e[h])i={series:[],getExtremes:function(){return{min:k.dataMin,max:k.dataMax}},options:{ordinal:!0}},n(d.series,function(d){j={xAxis:i,xData:d.xData,chart:b};j.options={dataGrouping:a?{enabled:!0,forced:!0,approximation:"open",units:[[a.unitName,[a.count]]]}:{enabled:!1}};d.processData.apply(j);i.series.push(j)}),d.beforeSetTickPositions.apply(i),e[h]=i.ordinalPositions;return e[h]};d.postProcessTickInterval=function(a){var b= -this.ordinalSlope;return b?a/(b/d.closestPointRange):a};W(d,"afterSetTickPositions",function(a){var b=d.options.tickPixelInterval,a=a.tickPositions;if(d.ordinalPositions&&z(b))for(var c=a.length,e,i,j=(e=a.info)?e.higherRanks:[];c--;)e=d.translate(a[c]),i&&i-e1)p&&n(p,function(a){a.setState()}),k<0?(p=s,s=d.ordinalPositions?d:s):p=d.ordinalPositions?d:s,t=s.ordinalPositions,j>t[t.length-1]&&t.push(j),p=q.apply(p,[r.apply(p,[l,!0])+k,!0]),k=q.apply(s,[r.apply(s,[m,!0])+k,!0]),p>wa(i.dataMin,l)&&ka.xAxis.closestPointRange*e&&d.splice(g+1,0,b.splice(h+1,b.length-h))})}})();I(Highcharts,{Chart:ac,dateFormat:wb,pathAnim:Sb,getOptions:function(){return la},hasRtlBug:Rc,numberFormat:Wb,Point:ib,Color:$a,Renderer:Pb,seriesTypes:aa,setOptions:function(a){cc=G(cc,a.xAxis);lc=G(lc,a.yAxis);a.xAxis=a.yAxis=B;la=G(la,a);xc();return la}, -Series:X,addEvent:W,removeEvent:ra,createElement:Y,discardElement:Db,css:C,each:n,extend:I,map:nb,merge:G,pick:q,splat:tb,extendClass:ka,product:"Highstock",version:"1.1.2"})})(); diff --git a/gatling/src/main/resources/assets/js/jquery.min.js b/gatling/src/main/resources/assets/js/jquery.min.js deleted file mode 100644 index ee0233703da..00000000000 --- a/gatling/src/main/resources/assets/js/jquery.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v1.7.1 jquery.com | jquery.org/license */ -(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"":"")+""),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;g=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="

"+""+"
",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="
t
",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="
",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; -f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&i.push({elem:this,matches:d.slice(e)});for(j=0;j0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() -{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/gatling/src/main/resources/assets/js/theme.js b/gatling/src/main/resources/assets/js/theme.js deleted file mode 100644 index 3043c7bbd45..00000000000 --- a/gatling/src/main/resources/assets/js/theme.js +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2011-2012 eBusiness Information, Groupe Excilys (www.excilys.com) - * - * Licensed under the Gatling Highcharts License - */ -Highcharts.theme = { - chart: { - backgroundColor: '#e6e5e0', - borderWidth: 0, - borderRadius: 8, - plotBackgroundColor: null, - plotShadow: false, - plotBorderWidth: 0 - }, - xAxis: { - gridLineWidth: 0, - lineColor: '#666', - tickColor: '#666', - labels: { - style: { - color: '#666', - } - }, - title: { - style: { - color: '#666' - } - } - }, - yAxis: { - alternateGridColor: null, - minorTickInterval: null, - gridLineColor: '#999', - lineWidth: 0, - tickWidth: 0, - labels: { - style: { - color: '#666', - fontWeight: 'bold' - } - }, - title: { - style: { - color: '#666', - font: 'bold 12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif' - } - } - }, - labels: { - style: { - color: '#CCC' - } - }, - - - rangeSelector: { - buttonTheme: { - fill: '#cfc9c6', - stroke: '#000000', - style: { - color: '#34332e', - fontWeight: 'bold', - borderColor: '#b2b2a9' - }, - states: { - hover: { - fill: '#92918C', - stroke: '#000000', - style: { - color: '#34332e', - fontWeight: 'bold', - borderColor: '#8b897d' - }, - }, - select: { - fill: '#E37400', - stroke: '#000000', - style: { - color: '#FFF' - } - } - } - }, - inputStyle: { - backgroundColor: '#333', - color: 'silver' - }, - labelStyle: { - color: '#8b897d' - } - }, - - navigator: { - handles: { - backgroundColor: '#e6e5e0', - borderColor: '#92918C' - }, - outlineColor: '#92918C', - outlineWidth: 1, - maskFill: 'rgba(146, 145, 140, 0.5)', - series: { - color: '#4572A7', - lineColor: '#4572A7' - } - }, - - scrollbar: { - buttonBackgroundColor: '#e6e5e0', - buttonBorderWidth: 1, - buttonBorderColor: '#92918C', - buttonArrowColor: '#92918C', - buttonBorderRadius: 2, - - barBorderWidth: 1, - barBorderRadius: 0, - barBackgroundColor: '#92918C', - barBorderColor: '#92918C', - - rifleColor: '#92918C', - - trackBackgroundColor: '#b0b0a8', - trackBorderWidth: 1, - trackBorderColor: '#b0b0a8' - } -}; - -Highcharts.setOptions(Highcharts.theme); \ No newline at end of file diff --git a/gatling/src/main/resources/assets/style/cible.png b/gatling/src/main/resources/assets/style/cible.png deleted file mode 100644 index 21b01f92fcb4031dcdf2b617a0484a4a86f59642..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1761 zcmV<71|Io|P)50000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU&gGod|RCwCNSlw$>M--pCAATps&6kNr zvl=Zmn3SZDSP%+Y5d05Xd?fHGiQG1W6o@F?b>g~T5E#9L?t*#{ZFWIiD+kN z=m=A3oZ2A=b*yw6^o2$<(2CaQxNKBVRkROjN28wp|ESTtL; zh2KVNTE!C#Q$0-dPidXsiTE*%zmRz;b)s)*>d(ZSi#JYgVCpvQ>B*Z57cQFm?Dj8nLrfea6U8gY;)|;em!@%8oB)X%UW*tA-$nx>{^=E>(zF;d0i15+-~N2pkZcA#vFmpe*n6Q0t*pgSI1wi{%dAuAF;55gYTLH2by^P*47rGG_8;^2|KjA zQai;GNZZoV%21<}%YU26$(y`cBdb+DeONIQz4l+s?5jG+DfgU&V{ zKAchUClnOpf*NcED#_*AytOb7h@yZ$mOmCq)=kAw2Tp3#U0pq-|Ghl&=+OfnrnR*dv$C@6 z^o;N?s@vG8n$^{p&Q?T)7>5B%d|F$3$?KrBwRLzwP=}I~4EWL3xL~%p_`)HV&F&)a zImNxDr3JEyKiwt}?AEOtOh*A?^*j0+SJ<#0M$5ji?G{2q<(I zCgzBmASmnwbf5~pPyc?Ko>*C-cUbmk~ywk{ncUsH}C&dkWtcfEUqJ z0+HM?@Pt}zlj*pB5N%P!R?r7JIANvoI=Z)7ogKY-v*8dy%n>^jx31M{F2H6aBqNw< z3%__VL<-UN`SW?62>1sEhM8V0&hX|s0v8Gsth$kr^Ue!lbK;$FeUv+QZYQI!kz3n& zk-p62K4W-~SGW{^Vr|x}Xvpf9w#lZZZ?#OfSw|0_RDPOS}Sc0mdoP z0NgkHw%;j3JHCK@WY< z@82(YsEB$nhC6GgpAlykqL!)FD&2`zo&f=Pzo9XU4=Q2O>wECBVRcdk(IL^5A zT@l8CO%@0zgt`zz;JVlV-nD;!le+}r=51}AY|K)i(&_2DjF@(3->pNTkjPfyl|Fou zBHGCCHrZ&qyN{aQ-ad9=Y^XN{4e>@NSx=un=9JUV98E)ksH#Ai5*|`9?AA*Ob$(MS z&GXmU*~RZ92qRHBjKJ%R7-9)f^Swo@(>8iX&EY!ui#F;LG&C|v7-fNlg))CACr^eE z7=jcEw-Zm1cuj2|+BD*E^9Om8{ZYS-b@r7k$!DvNo~?UXXhGtDYL!d7xON@LAk(`} z16Rd({Rh9Td(``nJnIQopJdmkm)b7XUSINc5WV<5B0|Y`*TA`4{FwJorElHU)#V+Y zu%3~6ZXK=l1vPK;Pa=`DOZ5MJV#6c+tpDqQZGcAX9SC%YtceF%9g_MWjp00000NkvXXu0mjf DEf!S- diff --git a/gatling/src/main/resources/assets/style/logo.png b/gatling/src/main/resources/assets/style/logo.png deleted file mode 100644 index 3817d1c247023d46ac36cc0138bc9cf1e96a1ae2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28727 zcmV(@K-RyBP)3!zC_y4~8MtxLv*K3=^W6y6T&AfKYx#xWCN}V0Y@oKz|9Xo|gCWCM|hK7b!Xl$%U zE~hSgoT6CJJNP8xi4mC14*H9VNLWQQsv;B~!|GMdSl_q{R*M~)uEJ`z!)&rZr~j6E zNz%D{R*6n7N%FE^%fGc1o)wVkoSF(bISC~`N==c@pJJIK%}%I$|C1XNlBWhzaRc<| z2qc@A&X)tI-K5HG^jS3|XF2`Ow?yqnZ7*9~&~<%Id(p2;wx6!gwDHt;XCE{Bb9yM~ z**MgpLq=05R_K$R$UXcq`c<2GJU)h=-*o+dKq_rQ&Q%S~USiz;EB?|&o9McJ?yhh) zr>1_B#*RTg`9FZIh`KiiDKZGXd=rp~(zwY$KK273n>Mahvm1Kj^+2cxu(~1fvq=iz zD1mPAh_S4fu7l2FLo$iyH{tcyIel@XOG~5F><_h-0|*U3`LLHbCTM0Fb;5 zM2e3Qh>VejP2|t=I)E$D2-ddKLNo!J8wISXEV3`NN8$98y(nCDYU}_CSBt8f~!wv5b_FS1e;pHemphVxz(a zf^2+-nY{j5ZgdIuCZ$4i|EySqK^aee5z_IW0NdUPed{{`cMY{~>}8iihJjrX%;f)q z)G#>V_tTCElwIB@L!<>e*k+icbWOuZbAo((4T5^k=fN@ONeR0rF#60lilP}(tNF=YUs`BBYi+19vr(UYszyJ83 zH#XLPx{wRln4k(YJqo8ghIA$fi`o5}0Ffo6%*u?Z!q#CHdR}4=OxI&T=cAA+w*$p% z=?PV9H0Q?F^$ac4V=!C7G$YM4JC)Z2h%CWErv9ic%RpGd z_}d^SCV)^M^s$qMZA5vUg}fqw!t*=O17+d_G)E~kUZC`IV>e-`V=(UjeNxAi&1PT0 z$*7~Fqvt)v#U3e{Ok#9&9797Rh(uy&ZeBZJwVG-E$a&c!5y(Vou?``pP12hHPqUJ= zv8~9=a4tb)87VV6WNL91LW4?5haq)8K|pc}C|zgRKuqRk4}bmjvafKWy_HZG;8|_N zqMRf5F)x-p59Ukw-*qrCwYGM>qV|!Ai9i$oOsoWKAE~G)!^x8!+4}lbKeyQ&)W9lo zY6PktMm*UHi5QF7?4*UmEavDdX=E+TLY9;=D?k8Ad_xX)KnipM$w}x6u`FgMQug&p z{i?lWAup96%?FY=nst%DHpN138jHDf9&Ap3JWh?F*vw+uwwH@4X*~4xPlhb@jihs;WE@i;rV6dKe1H8xDVGBb7vAR>CXWvWt6V76p+N zdSncfi9Ea?QZkS~(){`yeyzOn@j479Y%kGgfr-R6EQP>+a4)pTAh702z)@!OAKO*b z>}i1tk0qA+yl@s;uzuGVifPN14d=FRBzfAyVlv6}Ha&LuaLXI+yYDfQHKXu&iaK}h z-1vz|q6MjR5XsaSoc3}7Ai%5FlJh&4V_g>U6s_2;%X~^!Q-XyE4}z}abqVttdg(LN zz(io8mNY^#Sq(!tdtJ;X=o6D$Hp%PCL``stvxrTQkjj78XgX@t1b@x0P$#y}GZP}7GlVndNFMm)(>4(8wfdU<;pe{;XzBc# zu30?Pe;GwKPr96yC#x&A-)&NC_baj~Wq5D2R|QPClM5JCSLT(OI)|Qxyi|;WFtS=X zyOpi5Wwf$2>%p!H47of|GuqNp=}dscDkC;F2}ddGt5%q86GHn-uY=0Mbz+g1l7I6n zn*q9>gM3KMdRaD{y^K+ZfE6DU+ISLlz7+vkJ)4&JF78q1|6~th^*+0 z%tEATcjD|D(T(4U9Y|`n8iV`au8$tQQ4jTRM=Df;936A?r;9T1v!{y?9wNzB?3YR! zUWB{q0jYY&{RApwA{p@Y*XfNfvvB26U#2!hoXbq!qLGy9gZWP^Odas z4!!@&W-U4?JRB?3yo)iJ`L!_SuK;HcBJ_+}h8|Tdu(IifbkTBFeeC#0^tK274zY02 z^aP)JpX*mH3q3ksriaIGq+7gEAKZI~Tz}bTr1H)GNi0cy9U;yTSE%1*8h4svJ%feU zC`quG%vo&Q{;viSE<&Vfx$2W0KmV~m*m)`E$*}FCx}pI-Z!`8zDkv|jL2(g}X`^V3 z-R_33xR#ho45R*DjEr|9o(vTA3_kabT8r6!t=F^a z*{X`IUn_D|K5Q{tqOzQRS-^w?;ug`y`icjTmRaQMx?u~+#I|YAeBmJkhN|&e^8V&E zzczUC3OsrH)283M^Ln@|d#1CFUw@sZ(M=;TNfR^S3VjMr*F8LCH+fkAp#9e_nW`@iTS2)T`SqVAr9q##aPl*TXCnb z5!kdutP_96;5=@pma$-E%)#gn4G<%LC?pzoI@Zv7v2zVfP*K#d)Ju%0&xtmm;w)d*3SlzsMBfKib*2oqNAfdj*hMna=9F_2X5$rn(m%~&zep4PnQ(e z{;I07;e95>GIF}$ya0bi*c~Y2V?eS0lw?5Wc0}Ec;FLokz?%%CG z|CO(suKSmFo{P-bMd6wjB7OJ}bWR~KJE2c@{1FX?4^b1w=S+T0bLzd%-ihew59dr2 z9y9Y<6akYS==x(i|N9o4Czq!4Jo7~qulb+p^KhM#n%zLr>Y17)1g84v(XY`l+v&IT zoO5L3R*biP0wvj(3W2T|NS4^midizIdDJMs3z}$VyD=%RE3#CL4MN zp8mtm-Y5Qme>TyeXsX|7ln+S9@*ttlu#b7*G4=Ztz_7i6f&5gdfVey;&&yO)kbsd=#TzLs@(KHa`k!l%jFwifs0a~TO5#mO~^fYJ9HXD zdfdN`dgA;-Hfyc#rA8^xy)brTfgfLi9vy2ccpjS9de5_Kb${#3c{rJpOAByuwJS9V73Fm#XIx08qJ{x!8ZgxS@2aqk@V|y1eU_||bO9^}4|n7Ei9XcU zy0EUP7*?B%@v$_91`-H`h&F0QBePv*VDMDa_kMi)y&rn-SKderD6FrblNL$7(^do` z^c+eioF5d@qKpOUs9Qhu-A~S;@2`EEA+2O-sE6O=-DE-!fw2c9XJnmkMJP^OVmp^g|GKXO|z)YHs% z5;JvhX>m1@={Qt1H^Vw+OPYD$F)ee1!Bxk;16^os9m3YFRj6O%BtS}{^+W{2!%2hP z5<}reb~%eVCWXd|jFz^&=imF&JHC7C+yCxn0uyciBE^xaMt?|)xp-GrjG69No;*b~ zI=OW9GS=q)_A805H=QjT5#D4KE8etX*{85BGrnkkX(LB2MQGqMRBV0MvU8lqgp=k3q2x4BrnqR$nGb5}s5EIW6Yo93wKD17lcBB~ zrtBInhgSd%ON{yU5N#Po!m{PWi9wt?HH@7*>acEIF+$-mP8<)zKar*dL_(G*WTmeW zySLwha{4zM89{6N6X@>Wk0+n~`AuurYR|OYoC&e>!(>mK>*^mQ1W?FvvW_Rl^4u6c6Ok~GAS@! z_BRA%hoJ|CSJrlX{i)KFEKSZPa$BEw%gf!qt}pQ=$;e&jw&8Kbfr?|tNwfb7Xt)w^ z*OKfZKygz5GDKV+UckmBMK+Lo6BaO$IwIlJil0 zHj*?M0+AdIsA=2BEJiXHTxNiT8RFnj2*-{MU}JMFHf^jyBpM-L2@r^+MDiaqoMLYk z{@~`@Njmuui-zE^dr@7v5j(@fIC$(nl0;Ab>F)Ecx!YS*fn+j`L?T7ZR6;DCg2Q2j z#bOcX%6Eboz6u-q=s%U{<|GZ?bUKBhkuKD&YNlh{#84FhB1(J=_P!^euOxzz8OdbF z`qS=D-2f2T?SgIlb+B%HGi>yhY%f!FIBN5B$Kz1LlS`L_tZM|Ww%hk&62#^zZB zWYZb1Q}mxb8bsf@tmSFeJb)26=%wz&%V=KGHqnZa(J+dMf{sq~qoRDZ@H(YM4OO;{DD=Avrff8y zgtru<$a?j3r1`Y^Q8gBG#|3``*9~_-DO&aNn>CXmXz_{VS9%_#34bL$gDi4?#*T^p zl0`8lHb$}rr^yiNgL?RTgb!)4YnvYIgEqJiT9jB@Do7K1GvqZ_h~q>J76KWGliZlr zOXfVVsQKI@JF%b#q3*kjy)9U_DBf%k$?q7ajh&!tZlq&vr$31_BV$G?TaW3K0gEdq zH(d2rwd0wZyuh3pMkKAJ+Vc;9<;rY%!uLh2Rp-(JV$qfgOEG3o;ZfkBPeI}~yz(;u zwUt1J0$!j(`DcZ}L^6&)$2$<7WgLkl*pj{ep<>K%%+$KN8qM25q16E%|?5Kz)dM5$WI2)7+aERSFee7!!)!DVa zz0mwa2CEr91rrEJ&?;92}C@?JED_hl6)J*vCMRG`@aSC_>YWLTv^IjE@cD@ zAc-wNggtdr?aY!o&<%+I?6mDV9F^Vq2o!q8X>j0iZFRNKr!5PCjPBFyH12iUI4cPd z(u3W`s<0fhA~`R@0>(Qa?f)i8=U!;F#OnC|dED#TJmc#Mz=Z$674=tg{j&j*Gp)vM zAfhUd`A>XmWa8*87PHeo<4QJeIGfAlSx0UG3z_TcRrAJG4S_%geSIOEx2*}a)jo`l zkK@GgL17Om@m8aL)egae+Pa>>z5|b-vEdCUE%Oo!af;fn3?c$l^8cojDPk{KIUJ5( z6^|z`PbD)4Y&P?q4!h-N1SnaeiFzzPir&HJaQNi+={(iMOsq6K6LS`w1dY z+%x#}XPvZ)q%vWWHjz1!Ax9LK!9@Vaytt9+V6$2NaW=3H48&lySh02UT5+DX_7Ma_ zAv86fk83Xb5S)%uVVvEz`S)f5OxoV{vMSSYeoRYRK#gM!A&1MLMv!E zUwU!|fb~V)(@}>O9xxGzcnUH;J*O)TSB3S+h$i;6S#cw!Avat}EUDR$2GX=^mv7WH z*V-w7R;QIe413XB7dcC}LLegN-OWbnMi;eo7)tnd&`P@T!E*~ls&^d3I+mVBUhG| zG|=Ss2|GuIXj~*R!Z|U5HN+5y#bTcJ(oC@uy@2nMWkfQmVthP_t(%%qR_evT&@kFN z22fmFj$3Z}Bs{JvghG=-wiFlD;Nm^EArc$M$Y>X0u^`4qD^XHfg378&c)ecO8Bo$G zVkQJAiIgFG2vo9}41GU)UNk!T&9r*-?_#OvQjz%Jx>QQj$H&H`-k!T8x2y8`QtyUu z6?^L+Rut1@VOB!It?GQ(2A+W_Ik>8*+3G^^Z9iEti0B;PCxWG<<=r5(VyZM4 z)2ALn{HZTNOD0wZBJJw8F>P7%POB?7BxvRir^jzks8J*pgZ6f8qCGvOIaZvK`(!=#im(42CeaLK}GLUiyfG(h_sNoJ*T@ zHFLcrDbG*Gq6pxbWsl?E1#pX^+^;F>&azSW_)Fk;fj1yY>`s|5|djI9=K3Sg|{Nqw~Ow*j;>)5 zZY4g}-y$JCHY!2$VFHwBK&)aGi%Aqmm!!pUjXxV7OVM-7*t~JAxZkdBK1U3Dc3muts^Lh0 z`k*AwG7?Cq({MP7v94if-s=NQ`X@0t89I#q`5!O+wh04A-@-0l*;C#o*I)ik zW$l%B)8AtfEfR9&W@!B{En^=MCuN{=EFMFVW20@2@E)BG&kh4jIq<4v>-gZ z6C&mpwgtvnT`iWxHmiA8Hz(^!Ge6UVdoX_DoqG3Ee?kDehlBm*-0SQ)nh5Y=MF@{I z3E*P;&&c7E((s}G)-U|XJ*e2~7h&(q7+*!CuyK78BE!Eg1v}p>h5D~VCTX7Tr}@{W z=av%DV*06>K6;A?rgN9K>Z>mP9;$ZU4V%|LemMfoQV2K_h|}*b zC(9Ox2ue#N9D-dWg>f~7Tuv7OY)+>UrXC@d848Br@py=#RG_q^1jXKB`d!Ar;9m5N zeglcjAbm$hXGe_ArHVaubeb>h=H*wCG;(X(-VJ{L&~G+0?Y^d_YVARdy%Dn?fGBXv zw~g3O8D!GaXR<3EOB(a4EpS}=!6m77?1|gyeFw>j-1MF-E4H8Iy{TutB@oeSp5$;> z3CgVXJ^!)nkssYb)XzMo0#qWM*@q+qAu89%CaVCS?<=rBnBPQySQRy&-Lu=_hIa zy-~A;Mw_MJ&;Ld0d+tNp)<1eL%9?&X&#ZH1mRu?CHchi3Ctpy5H?EQ-15j8RJ8Y#i zfeA=Q+mRyKl1`~8F0~SKnft&jYZ3{PLeVUCZCgcB$&QY$9t1-X*sOK}lw)}OiC#}~|+!E7r@ z`nJQ}_ZT$O8PvdKH}A!N1!<9Eb&`ysAN(Pb2Ooe;Uy;sTncZa&Pc!TvRoe-aEXcOp z_nGXA-~FsuKh7p%HxF>yNc$E&Hu11&!|z=t`!;kdm0O_>9l-D2ixycqZ(-();$ZjN zv}gb02Z%?kXWUNGOgmw`s|G*4?E&2IZ|}j{YrZ$dnDToR%<()){?=Xc-mm`>f#KS- zN)(*}mdIF@{KB2T(j@6a`u0El>Qr8liDNX*w6Rn*n@fxEcjUK?Fc}*^<$;7j#YB_C zPP8%{@sk9UFfpD&MWsy`R%h?y5s!1MiG=dJ)a6ytjtr3maN9BzxE@iv>)(A|9q@$Ap({7DQBq|x3M z&KGv(XHoIy8~z%bH@p#%NKj-K@7(q_G&f#_uD+LWvhC4JyLyjYQ&qO@vGUR?Sght5 zX2KoTbRG1e=Z$LP3v{HksH9^_W2#&O`;K=l%0xom+pkSc2rw;?N8aoZAx(J`x)9uXayh4<+60%B#hD_vIs0PfW!b6E@;H1 z1bBXNyhtXgAsEcS=d;aBM0)NdF%rAYhH_sC(wPhj_!rKA(_f0^{hv{kF*C#I@cO``!@_iJ_mea|gk z{k`BI2Q2HZgk&uoVH|(q8%P}`5OFL7pqFhY3wcF~Ob8dr;v%Y?L}uTA{SE0_rJ${4 z%84*lgkj1~qRkTkO5sVO_C9~D+V$i%m>oxj*Faxp;z~<=M*3c^J}<^)1@!R1nZ!BF z=*Q)Cq&{^odWIx_E@{%9`_fkt3py4B5pCWx%r0D^Eg?Y^5no}Jp83L;^*7#e31o9v z0R2=&HfGKHzxicM4pzW=8rPm5;a{i*Hh)hg?(^&yzM_{kzNkBVgQk+AhBK3$G+9C= zg9m#D_WrTXD>}P%IWdpp0(4Pq&;Sz2Z2YdvBA$frF%-zc<1xcxk*CuWn332BNNiRs zfk6zxa11Je$d*ml;JPb6CJL;xWtzQAJp=o3s^cM&KTlw7{iUd@+d@o+WK<~|R=vm& zpu}eZN{YZMp4v~(-b=tD;rQ`kVms`9&Iuze2hKSiMFdDC^!JR&!7)5NhHT2FGe{B8 zb+-5K)byUmOG=AwCg$`DW-&AJCI@TDE_nK%KfU?QF-Y_bi+N=d&wm38RudY~ri!(M z+19g<4 zKP1L<4EExfe*C^MY1bbYBjGQhPn-0yV}FFez^VlX(J5v!bYe3myZ!{5Z}|!{18K6& z@aT*UTq&LW`Spu35pH%z(V*_z_yZb4$MvcmLHW?#Nxgcv367E?DKfMQso+fr^j$7k zf?NQ|LZemE$zR@v^>6xTlgV^uf}3pwYwO-{Pe;$wANB`&8fdA?Pj*8lXd-@u$GKsF zW1vyZ_UY4ND-`nb6kpw%JG&PKTeap43Th%03j<9>6+GD zhBd2pV|3yqM#uJIX!HfFU2_cqK_x0HE9ktX@Oq2ja&qPR3?k7{L`RN`%55iGdJrOz zVz>0_+C6X)Rqq`*KoY5#q)!iB(*;kF2M&jwu9qTNmBH}95RM)>j2B;ek?zrg=8a9) zI-QPRWHZ@mR-(%(*i;+b(*aZ~us!L9l}afq4Cd+9hmad-hh&>KQLQ$VyyroYxOdi} z&qRgOC+|bx+izLYlOq-;Vxem9v&e-9i}Ylu)JWc&_dI4x(7gQ}mk?ldXv0UK)b2vA z;}J;22DrwEkiP7d+4Pn#s{{MKkvZ_gzg!SmE8^YK7fLqIy&`~3Ujj&6Tb--qrNYqK9(u>3li2KlwDHEzO2rLd(PN`V z1v-b%_^hG_P~dU zu@tslaY$GFg}3Xq7yKJFbWDm2LoeTC&{EDu_EZZuJpJt}$i}ac_ulm#q@%S97$oN( zc0BYx%~SPFlg;X#WvDq#sb;c=H#Oe)#oxa0<*zX-Vc-yviJFlorK-$SGUDf%zw_U8 zA!U%~TmnoO^hCuoN!TaSnKT;ecVKM9Y7;&3`}c>h7Po@y1IBez>7siusuT;h@ir6j4C!H6=1DqAVx5dGtW95y`pK zgeD-BimGWm(Cip*%ZGx`W;Z0eyPw1>@)Ge48uW=y4ok1oN006*n1sUJx8t@?OSOAG zhl!T_mY?cOpqa>x;ne>GKVK5t|M**yca2GZ^pjs-C=EE~Wbb8|IJp^B=baFGJvQo< zMvw0*C>=Q=d-F|S(l^}r8F6iPmWX;K={$nxjQcS1b98<6rH^XXqFd#sKmQ1FN$Z?( z!k`u#t)|1aOnYtg8S*+kxtDuob17_Wdehg3#!g*uyyd}L6+*;mu*d@j|IYvF(_oVS zOx5SG8wL?0V9i)q*ak5f7)4)y4=TzlP)w7W8Aig95OO0SBuk2zOAe*Q%|d$EY+2OS z)(BZLIx>opk#UsyY)GaL2{zKwHh^R*j^DfP!?@`DTQKP#5$ABaO6Z<7VNYQfb0i!V z?qg38Nu?4mhKGg`3WY?Q)Ku4?w5)8M$!w{LMI$48&(j-1Gn%h|Go%CmxuOX!o|CJA z*@_B#S|fuC=Maz8R0_GtE>TY46?~ueS^&`Om?*99m)BhWW%0YKoB(wok1hpH$&sJ_jJU`I8B=)NQ3cX9RAS z7rS>{O)`kBm!&gyl$6NHfke-%5+#hs;^NsSCntq0;}{gT%Z*Dey#%pX>ehjQV>j8X z=3iEnmwkvpBqV@@RuE~`MNr!ABUa)+`@vhNCPjX%#vc7gSl3;(bk(oZaqF(2ObAbG zE!ax~=VZPJk1CABI@`X8PU}`zsU92OqKEpg*Zl3f^=zV)7=bzYvppwXqE{taP_)tG=kw}caN^bO zeqQp_L`7CH0i6KgftTjpqERn~UH|BMj1BlXn*7f~Z`)qTSdQ^yb(riF@jcSe!3t6O zQ7Hf<-ulB*F5M5#sFn>WgYe*7InH)Yd#T8yl88kLK=IZ+MjyYm@U>5_Hc7KeQ}M*% z$WC5iIU1G2+*wi-+V(b?3JPzB$6Y~!E0@@^;kuivE4KXA;o}e89*qUP?6u))-fCX% zsCiJCVk7Fjes#Oe=$SxC zhX!%ujW^<|Yp#N?tQ5t?ULkpWB(z02;f_14wZewyW&VJk7C%0zMp zagfj_fq^FevtVHIZuL37wTSlhy1w?-FSMc2YjWY^@n=`(6#an6ZPj>qc?1fT& zo{`x~tW=+9Ysz&!@eg`<@J2ly^|B{w8X))?lktTffaNh(Tls9Cx-|k5mya&pYAn+@n}4;Vi|>@IJ(V%wXd z4CwMAwzgc#EyZcfRc(jvT~#4$*af{}j!}uFcRuW2=UTSB8M<(f>&A7Gjs$wSE)sdV7DydYKuET6%oB%|};P3?XgCgYEv(MfS zo2d%LC2UxA3AZz|k+HEcoH}(12M!((;L_LEFWAWT^R{8r=8bf04o43k#XEt9{thndLr(aW*DYL zf|*{4mtZD7V*;Gx8XOBdMGWQB%{@ey)#e`g$zCW6_T$3e3AY_q^7!WQBm5tA} zW7$h|bU#xVa=mLBS+2m}4k;a3bc;V-u}+9e_Olls0ud=XWQ>8>D5TCuMRxm4pqI?a zkwH=BU_k*Y`ac!4o0f%X6y7FA&KFG%MMg;mq-f%rvF_sQ3A}vqR4V-~pZ9{N+k2jP zTUXx;?}|i%Hc5DOASNbzJBr-(DDspb673ZRRu?f6pU;j{9i7;|wUO9IC4!+KEW(q+ zmCSSmqX$r4{T@`6H=?(v6C*>zDD#z}x~3W)k6T2ua(H=fBgvq0l$8~WJe{^vZJZ@6T+!S^2M^FSE2>aQ-(efgK>q-G z`}zcHV~JH;Th&v$su~6(F;Lh+3{z}xBduUHW!sjYXeLd}1@gH0dMG93P-Ei-Ne9AYm7_i9$vdEg69Gl8?ZB;};9tVkX*GvK%{vRy5gErFM^q6Uwzc^mmyT z@BX~8FAQEgFD-{xwZgWx1jS+Xdq0XxNIi%do zTTYlbz9VDr=^p%CUw{9*Xm$3P6mz7+admge=JR8H!;jiJ`(8jgO>IT2#A206B3Llg zuhJxsir}>JN-Nf`b)&T zj3l2O7)kP(0n&1Z2sdVKA!B0j;7WL+PD`%9G1@ZlMU2Q>2SkRAc$Nj!lirP&c=Ve5Q{jDdvfk)$*6^}+DaJ0>S5x0kwV>btUi+jP2H zli2fDD@r6{=kZ9dD!mTHr76&NX|>nj^2jjT{5W#NkKq9g1HEZt78ca5wxhb*MQkB} zfuVk^ZKy$S|By)ZV=&<$xZ&}q@Z5_I)Kpx8n%Z@!t6w9$En{P&Xl*?uSVDDmHA>6M z2{@vnO#*=khx5bZ_TkOf{R1ip@pD{si8`l$_y{HgJ=9iP3E->(Jc^5panZ#W(>;}o zs92XXjbthY|HQZ`O|WJ27QRmQ?Qg&7qh)0!?NjynWsLo?JY~X0g?ewH47(&n;hBv@}7g)Ze<&h+gY+W)bmCx7*Cp-`BnObv=Eo5hW=OY% zn`RksDJs|RLnfO>it}Lf49STs`g;7}NQBXmG$zI~@D!Oz<|v{TNqM;!Wu*@Keny12 zGb`ckN5ZKG5Fgl!o`D^x^qo)aVIzUcYOGqV6RQ|PM@I(+iCq+VBVt@k2E)Y2GPv}j zKg5pnu7=+~Omv@J=Q*sZ-Y(uIMM3k%4XCQFgq36s$B)G0iG2MM5SSQsXX@)2?5fAg z%S&!6D)KzypPbnjh?Dp1L&soe1LO*GJJ=?j8|y{#z&*r1K2X3G;)M6MpCa_Vw;^@n zDabagD7S?xtw{vHMYnt%_8ot`==V~GzQ0oXnc*@55`m>29&qLQUc9g%o8ZOF?w(M} znjY74$sXBVJ)*2Se^PdpXR|#|XSLy@la}-UVu%KEj}{*Iv((V3Zx;fiY5368Wh%i- z({T&3yT)Kl`5RM88=wXIg+Y7TJGJO(WTW~$PezV&soW~OaQet0NW~2+C}*(TJU{0z zi@3ba%N`bg;R^q30~y<_o9t$o>{*FfNvJnpOZMz;eOX4rZ68lhBAbheSPYJYotQlJ z=6y&0>p$3F%C-Wpvk3}kJ4zac#t!1>sUL}49G9a4jrCWdykryNky6;5&G2~pU{*4s za$56x3r-#n2~UxUfP&>fD3n1oBGF`4=-(ho6^mdb9OkZDC1MB?(h(jw1BfTO(c1Y4 zj<@c_`n6XZ_KvD@)T~;C$%#p12s}6xo@4bg@UQ86Uxl8`_uOJEgp+h@)}ZL-Z^6=d z_2PjcEx$&(^*1Z6O~Mt_b#?SO?^suYY}B^^&^V~jT^_Jq_K8b$HQla`90voBY4sk+ z#r2}DE5~^d8$s&HJD^SUBp1DQo*l?sT!`eCHql+sJ1&mElV{|`0y#wIczD&#B9izME zLMYsUvB{&NqI)oiJdE!C7tqxE25f15BZ^BZQBzY5v*t!1s0w+)_te+dfrh%BDDt@BbQrE< zN79HPX>V=Ef&Kf@-Q5lUq#y5GRU=%!;(HFGAQHQW{eC}=A3Y|ww6?6Nt*QNVZEe-# z#7v$Qc{lV7x@Q$sTN%vpK_l9ArL1J61Mw&S4X*1xx5#&_aKHIZ*v@+=lKa1d?BD@t zv58rmLFpp*Mk!rK;BgHc7ra-nnMHe+q*U~wzc(2BN^Kncd^B0LP@k-8xxx=8%Rx0V z+{-ju_SVvO2aw)-7l*P#4-LY+{syRY@77&^0(GplIDPQP3yMz*{>#U6-O{=X83abN zJufa8@WPZZc;FB8EpNYzn9p%F5u7{Od6Ezan>a{|#;U4VLR#N}g8nFNVBeqUt1tXM zA;?kTYMrWX##^8CJWXrQIw)(eUao~!mTw|0Ax9VcY$g6_wqemiq&v#`;8}GFvEZ(P zA@v9E{zqz$y~MbBmfXegWo0D1DiGVKt-K7S-e&X;{g!~FUlze=VoMVf0^t*I+14UW zRL~!4H*6%rB`u+8Z39h654}qfP9DW?pFV_J|KKgK@?N-74jY?SqrH7hY%?KT);S~2 zL=hF-C6Vx_(Oaf-%28$ENP7=GR6frABMMZGA zT!O(IKYA2<_w7Z0e?PL>9A`9FDZDq%R4F~}%(OJyC}NM@4s*?RShw7~==aR4&lhi= zBzi72clyx;V8B_)EAor?BinysZXVAH43Kj923n!(;_5_4OwVTKhD@_NJ2t*n9Xaw( zif_X=Wl!}4l*T18hCfkL) z?~6iAgfo02i9zf?`JcRzH!+r+sF=pZi!Qln8!p^)9x~~i$V*vOTaJf+JBa6AID%{5 zaFIw0*tVq^Pd$BtR{ZR&;=;5-6B4n9N6r~Vq1is-a495>BoSz4HMP+n1i>Y8#bAa-C28ZLasZkIC+;F`Bg9&@x#)6_WH!QcoZF?@B#Bwpvf8+yZo!g5el! zxIRwIM#QX`%9~Fq)jOWfwm)_40x85!hGfu{JMrLqVNw1ofLm)A>yX1|oG8d-PvA@jav^V>_7ZiDmo<>9=s%kNdO}*psEPY1? zCjpia18JzQ!s^;p1ORIVqhQThQnVI3cQoLI{V!p7bR0XkZ=&y1V)K?-w4CapK`4vd zC1X=?0|+_Bgbn9*yG)Z@CvZridEG@~&zh{*&y*i?2Em1=iW!Zf5XjUak!eSTuL$j( zy2$tq$J%kYr3ZaOk6~len+X)oLvV6J9L~0q_3PK8v8hRTe@rG5CMPB^F*b%#pHKYG z0K?yNI$hYkd)Jf1whkE;@gby)nL5(yBPyM%!Sq%i96cE7rvV>ba$#YKqs0RV-u-*R zz-n1@>B~FN2;d_3e-QC!zP)lU&GbmI<%k(*>{(L0t5wU^w|^}$^x1Dpi!G*V&P=O( zE&W_Ooe|1l84u6YCs7K!DCLs+A#?K$FN$60%x=hbFRT~*#lPqJUb;q9J(m|s6T>^C zN_r~l)DzK0`n^$)2J;Jyx$*W%{%o4vF9&7ibwwbmqH#CYBK>5BvHe|)$~*K zvgeo2fRW@kxiY0vsjHI7)L&(@+3VRd%%I}1m1Fz*kD_bf=LkkyVRJ;NwG{$#Gu+N% z6uB!%VyuD9w1@sm^b|S6U2XAU>xLUiirj;a_CDPE(=NEUBqG;Zby*gjV+<^rY*uVm zb7z@rF*F6S08TZ$xFkW$y8vGMKbp~17U zc_VyfK3sIk#l%u-5RF7II52?LmR12Ot5;V)5(-5peLmmYwr}5VX>45AbKexZiX;oELv^r5=MNU#h=T zgw&^wKW;FQ(^Ow19mn)UAjd&dQq68ycHDYTw&Sr|vmMW0QV^8p1BEneHC)E$VY~bz zUslSSCq$kJZ=p(K(bRDHW46ma{P(Hf{_DR?7yQ98*ELNs924~DFw~*_Fl~IxrlV?Jgll|#rQ;2xRJRKwbkY(R&*SLgI#D?w;t=7 z*1=-7h?D>>)i^vfg#G*WRu*)IL)JGJQ0BkEZ1264}4Ga&Qf6Sm7g z@E?|4fA;U}vXxBEeEx}H5w@QH-aE8lPbPEX{yX$k+Pv~>8o4aFQx8FDxRO0IpRw$E zM=00&;OF&t#I~phtngjNmas*y%swWvSThSFTXj_wVpNo)-Gyz^A zZ~i2bfhdmt_@9v_%5TCs3Hmk76S!8Q?KfvK*$=9!dck}EnadKg;uyQtht-u=&w(S( z2GvCMvAMf~@r2`jW-Qv3uY;N`p+9ygpc=&;*RH2m zd_LFv{5!N%+BR+DSPJ9y8F%rtg!R=8DOKA)V_JX1cZ7Y36Y!?9&y6>O3xU8Ftoh>9USXY-%UTv`SN(sC_Sp39Hmy^d+Tw`%~GYKmIhG>y3IcZeOrM zO9k&+S;m&Mbcj4-qHiPQjW^BgDLgI7HH-w)@TQV9ucT!HSqpiL@(s(|<|ZehyQ)Ok zys7}7UQiKVv5bnr(wrk36SsvC+5)*4oALMh=DA{rWi0Zb?M|NwsWvN z85|hH@bI{(AtK9lK&low(p~iOqz?z+z?+PCSp~z)>XAHzO3h zkR+BvR7hh3EE{XN9X4!OkCIaF^^=ol)jaXvRLVyyIY` ze`ZE;$NHLnAvrytS>SnCZVp?Fl`3`gVPYo_Lw4HP_$aEQN$eh-Rpxq?6cw7DBeK|Dw!i-m>~^Nrl%tjviC9@K!FHfi=7uXiK@(lkl7ULu>8s3k zKkb&BrRC=O%PMs>LtPuoWDefbWnO<>M$g12WKYeQWOD~NB9${;O*AH~#1_NRII+(W zgkveJuB}F?hd`f;M{!MKu{}+O#($E5^psM&`)`%nJ+c-VDxm>XA=$j8xweN~=8bRO zu7w79mCneeYacgCHmXLA+UO`GXPL;X${6=;3v|PLA=Taujb)sf9G%ad@37U`^M#DKA?9X6py+^1Ok+;Qz)w}wK(pCS@ zMc1}q3Z4Z6%34vi^C@`hX7&c2+GNW^%E%4nhi7-Z~o1Ct1qOG}(6DQ-X{62s8Ygs3oY%voymQ9t~Cj)3)G z&6-*|w*%8fnWY)A!^T%0@I{^)T(t8qg^h(XgB95(CIs7kxan$YDn%h_9|6?5b@c=! zW$^h*goK$|Ik;)l>C6oz0dJsxe?d~{T|G-*Q2{kgpK1Hsy4wIfjmInPu+Ksk@185!V z#p+cx@RgP#lhe^Z7DhZ4LpGDbaIXt%%VgA=Rha2`vo4${)O)I5w|V_C%RZ}{<8dC+ z%og_8K(^Wlgsil1EBQTljf!b1NZ#ruvD>Ga7cc@cRksFIlQqD#Oe~lGKcn6XEn?c( z31TqCbVT+L%IXe@y`HD)^@{y|h=4h~qJvbM7gBs;zfxScU$HsGc;vao(nS|_Q7aad z<=~92gV0`{bYr6d1STWG*1!*6Utf)Ck|c^q>DMs368qd*__K_=swNuS z>vfCb%v`*ASO7|B+N(1$5fnhPapO7>2G7T!fNzqM_f!}$E6jRS0u)V}5yyN#%$lmJ zD}-%E7x64=LAN-+2#DXxa**!fBg9aCJoOyBifFv8DT7JANQ~sEK54o7Q-7Df|HEG` z=#qOay?F69TU7hifA?{cEkS*Hk7$wy3du<`^@Hxh6P+q9IsY=mOk6DQYifIcAed#2zuD zO=q$Q5F6!wI5}y>(XlMNU17K_b`)C`l)4nGbLc425(2QKY=&fM7O|X57{3Dv4<@t{ zRIXl2KtVL3`f@luMd%$I!{PoU8tbBsv#2K6g6}P zyW2%Q9brtLGD*;cGg%#QX+<=4OmMkrWKn;oGC2gpS))WK0fpCTf|Y|8g?5`E4Lmo3 z!LZn#*5@lT4EJm^)ykB zn5IDjlkrxP-6h6WDN}hxoKnCwYDf}GL;H+^&kRVsf%mu=R6`DY!y2gjP`YMsqud3?lkL`RaGXn)PkYFH!kr6)uNSv;r(WK6z zt*r;Uc5NXk;-u9qJ-xZ|e4t#aCasW+s=C1LGXES?ztg!YVzJo01f0I&;^MDPf6wHA z&ins}O~?$OQa0c6HS^fX_1Wj|`oMC&`B&|w8FbQl-QO)uJ5R#W1^wPR!)rPWH{zXy$*pa5g)U&cBUl`3^{@eht0u5&{*sQ5Pl9 zaYMnFN;Wl+L}eRE>|BgR;S!~#J`qnDDGRj1@Ct7Po1I;l@l@gH(z0SyR#jtgs0*zv zUDSyVVaPptd_U~Y9O_qZgV*CjhV=BpYtD{6S!iNEU4};Amm`tLoo2Je?{>R>H05nj z^P7YzZ2u9)!UUA2H-3=FcqY65`yUeqxz|upAkLVV6iIbl{SW_O*?P;r&SoOIWJXpq z%?cHe#cImw4ZPB_(ZEa-DPkk3O_N(nM8!Z?VVY@ANvH8G!z}BF%EgFlN!ZQwJ+o0L zOV9TS2M;o9omPvIvBLmQRKw0@>`2cx6NlNTjWcXo1AAFLT;39>{g1%TZhrcLjo!xj z^Ncs2QOpt3TuRhplIS}Un=9$hIdsVz5RQ0oX(}e3Pct?Mr^P)j>|R!jdCvIY&n;#} zNOy*ZSTcoBifuY(@pmrH#cvWR2xhG$1F|T$YqM;K8Id5$&pfpy%M?&?i*Q&0A&-dM z=I03|RP66gpv##-QxyS9kwQRkCWeyXJ%)|3%*JH~lBsy6<>E!AY-*fd&x?jp;;}>u z^|ZHyIohbzP0%Hp2hl5XyZu z)DezFCilEaUT9KFGXZma?v{@GaiZ-3qRp#O>wB{R<=(zFyma&~RM4Q>z2i-|2 zGtATMOMt9QIRJ{ zjPQeWPs28=iDi}9KjD90e}DgVcDsWO?CiCW3>s}{sJn~K^^^#iNTm>q#hH=3DG&(1-DBbL}+&J%S!Gx_29H%ib@)C};{1T&M!&q1S9s&+8bwU{T{N&$J z>@CB^dv3v|4Hv@*Ek6@>VpOm{)^<06$gjbL!K@~aAl^Jtd9y2r-Ge}A7{B_>og}XY zam(-jjrd%jRa<2-HnI{BStxUib5T1s(ZIiqz~sT{&9mrV#o>V2Y8c6sHCOz9_O1lF zs`6UzbMC#lW9B)KkdOp2kdPn|2(wg*NUd7Lsg>3~3)QaoUi(~a?Q0iRtM%E|*VpH& z7P@@ZzPc2vmKH(Ah=4MOAwmWq3<((^)6I0}|Ly&sbCP>+?r?4rsI0#0x{G-4xo7zQ ze}DVi!@oEGfj)EgJMM`UyPZd0`!{#f;Ro43XEw-LvK&}k67B@e!x+n4X<;}-ikexI zl+~asSn;Mdqx{#5dAfPXkg_CEGl>)`6_HsX0Md?W%H*ZJMq9UTe`j#e7L5S%IzF!% z*U_?*Cu`;x78cCG=d=yt3jyN1$K$f1K;MPO=HTb+@nhY@i4(3;0q@SvPCy}>a_w5@ zDr7d#p+F8=EM`26&D^?W+a`gwt@6ctT*2*G6Yp+iT>bWPp4v+BdRxY#O< zR{M5hqH%IlQ}b#(HgDRr!mp8Y9RUEWDDc#~h|F;^lcr3`Yu~eHe_c{?Vw}_AeAI5Y ze}F)8nlh~sNL*%%nKyVldZc>expR#xa&of&g!ix&0im#}YTy414A|yYR#vPbQ^jB~ z(lydIYJa8wmwhbRBEoIk?=q2PtDTI30Fms}5dNiK?hk1}$BSYQ0ig{m~QKVfM_s zAZtP)+LMkkvkyTCzQ>yv#_sD!BNIOG_Qy~;f??l@UWrLj-<1w=!pt9uDMc@cmedN@ z#eIuBo#$t{ubhd}Oejbas5D%tf;@_cot$R@@_9xk|MEUC%-wOeLz6z;u1P7Z^7J%+ zs7WcP)#WZ8^z^m!GKGx#*U*MY^5pYwh#u}pjE;!`YqyoCcuZ?+YZhwuvq?#bji@o} zN=k}1=H^ZA%E(Bwk@FB=$MKP$NJsM%SwQ-Q3l~#x44%x*%W+Yi-Nd9ssIPAzr65K{ zX?thPD1R9Nsv9*i%9(S?xpU|5sjF{@rxqksK2sDm-1sS(R$P;mlDNfTcNkIFu3Wv^ zalz?u@npim!2#O6osU8{kdcvI*Vfj4e)8n(E%WBhc@6=to_rAp4<05UN$BY4OwP`k z)a-G4>W~>6y=7+kyM+Z)+Gx`!2`D#dyylRY$=bD#zEN6Q{Koml3x$Ud9s3$y=Z^>y zZ}Y7qCL?#Oo^3e4{LGp96%RhRa;3qbfA`q26OfymL%YX6-nw>zVb$QCk4g+Xv8IY=UG%Idoeu@Q`a1^^Qe*bN`|Dp|of*qWuIONr516 zGSaN!yocwZND+2{U%TIS1!>-9Fhpg*h30*7+(hI~Q_2A`8Fg}UPh@OFZJ2wQEHJP{ zTP~5tRwkS1D^cm;kez4vUn;Li&R9XP3UQgc#NPZ}f-wQKnX^ED$1ilO`y#Mj-_niT z)|YcsL`w=44H7(kZO9`o^2OuZ3{CK>zp3F3uKV8qwo8|@h>3BT;IvB#X4Gq#`o@a! zSr9dOp;T>*ml+%uCd&FXrl(MC7VCYhzVhvNHop5i)c$`W-&ak|XH;!}YiwNHJ@~wK z9gYEYJ4{MS;yb2k44(MTS|~?zfv;H<%(m7xICku~12ukhZEf9YGHV6~1}#Y6ZKb8t ze*M4$EBBE0K78niG=BK-$a?NiNK9CYf>=I#cEu|d6*HbdP~c6}XkXNvIkSFQQ**i; z0pmS9pS_{stR4lj4bS;dUS96*wQIjgKD#s9w(WXk*RGGAal1W_^ZGidq0prHFJvU& zlbr1gy1Kdm&krQXit~N;oC@gf=@FF90m_RaE>xxy!D})V51(3)|0$iPv$KmoD+1yP zIwxK`o9YXbY2Mx4GZjt2Is~|83MSC!G{&i^$vbLm8y>^;pNQXgd5L)kzI#T>zY{KL zr$UaW7GgY|!>r_bIO6G@kY4Rx^6ZzHDfud8&uWzgf&+B~A9OBG;)_Vv?iYjzvlRp- zZC*AV1gZRS!{xs~W78+l)7#3Mh|_7QN3bY(c=f(f7uV&f8B%_=BA-p|dyx&ec!Mrt zKz64SjD{$<{kA#0xwx80QLxBN=;MW9t)dU?`0PwAFUUj$s0RT7%F9ZJu7M_o0m15Dkd-zO zi^BVgsjfb;`{R#4`Krt1*)2P3qwpLxD^@JaMFvEsk=Whc{o_wRIrKO(ho==`rAP@e z6pFmi-vlN)9Gmp?G_R~?K`sa`ZJS6=POcfGEY*vbznPYn@@!&aB6p(J)ivBrAd#Ox zxthj-fCyRB0R)Vnc6VDBqRFzyY?heo;lsxtO;1m4KybU<-hORp4DlxxGrVNrr0Bj@1JX!`aD}_%@bS{whl=dkLZ}R%O4C)_Z~Af^V`|#E0;q zU&n!Y8Xt6)>IjCs*-nBVL9UfcY0mx7)mO_im!Q*M_+h_p}2+cE9c&V(*WmEz9S*2TT`=EnDsmJ4a=sUM-R*)CqV zxD-v6=AxpaX4L-Bc)k%XG`cu=Q1f>Zw*P$TH}LGw)N9s? zQ9&)TiMHv}OCNszxff4XRek*GBab|^idYTOemvgBNVD(xXz!Cwr}G|)3?o<=@v{^( zNq&w%P<7=>3)I%uKel)8{+}S2ZJRJ5;}U9nGaf&=yu9>3Dk{pMx%mpdTc&Q_y!Bvo zbo60l5(iLuT3cFLSC^HS)_wWz6;|pdp!guDQl7)RVLZ+QnsXZ7ol-V=`uhjDqMA4{ z9l;^xqvqx%8~*TzH=jW>Zy}m74G0F)o0^*E+fYAU%_dI)!|>WS%1gNax~lZxMk(-;e8aFH6YZ#F7gCifgk)slB88P#Z|w z;3C+Dpi<>>x$eXVejIOpF?Y#@5F*mNS}heDGJ?_6kAp-Dhw7p5yg^>gJ}OG!G4^Ri zIZ#$|3!3zL@NK9jgf(mAlqk^aVxcteaX58uJyGXmI_BB)r`(X z4N}2dSuo}q*O0Gr4msDsW44%|LrS~O`&?AxHKebUd^zNNr4()|ii9SH;3d3#KaEx| zfl^!+h_p|HN&)(`D_~OMlp1!&_h#a3*%xEz&HcpwPwTR2ay0|^vtQ8 zU0HqX_?K|e&+oPN#!s9$;k5+|=f753THHlizv*(*Q0@=@Y7?@w(*pwoo(=0?gLO~; zJJ+P|{`ozs{$Fi08lSDGnAsl_6ASomyl>UL_pV?6+829!do!Y=Emw>t%6Jy8;dy?F zKoBj8(xwu8^-<&<$GJBRSprvxQM#9!o0<;{4BA%Wv1w@T^x?hUoi!=*19FCDXJ?a{ zb$aH^8D+h_eJk7A+R6~{HTn6uuPph(;`N!C6PW@ebdOXquePpXI3z3OMYYeEQTD=t z1BYq|K!~l76Su5v8vlO&{5fl^R_l(wzJdD?C^FDgXV* z+ojHw9&n+IIV?0V^;251j%KH5%y?(Pd z^B_$X)^$vbh0GqgY@+Wa7-t%&ZxK6;RIEmFsPdjGUOa)WN#BKQfk8k@n$2JV%a@*m zsDkC($HFV%1@Kda8W#iwhCgLVgg6U{DDOMHuDR`;uU4c+Lb* z8`lWXe)Sq@=Hn<3$GQ2!H-fr_#kVcmfu_?AYNkd`St=-X>SRp^p8IJj94iii8vUJ? zmWnJPnX?=;sk-$B!6MBn&4pi9z3nH1T z6x2Yo7mQS9lIoJC{0$HJTcC&wI#YP;1usCZ^dyhb$;N;|`dVv6Nq*$t^KNQ7@Skq~)1Q)%nzC z>?+Jn{~0cfSMcB8laBYdTR~2+3VBtj{>zBW+Tr6-P)zZ1x~FtROKmv^ebxxF;k9oM(WZv1pLg3i7w;)im)o=+2#o}Vz zgsG^g@a~7gdkP&ma2Q%zTDU-x@5%ca2@px!Pnwhkb1G*+L;YEvnoajzGd3C^MRHxv03%=QFD7>yiPKca?NX8L6?cl#LzB$4aTW zL;f3`j`3q?7kge_4iBJCe+l_s^`cw2i!Du%l`&2XJSx+EPGM< ze%b@TQp-U2;5z^pPD%=gx9n8k&qu3Xa0pPer*JY?%TrtfV5-x&ifB(88#DrY#L3!_jD21E(qH_BAnf#+Q zk+ToQgh9=F1nKuPs9m?)Y=cgp#V2QB3PHkd%ukc}M10st`4g3b(%#8B{>~nrFr?!( zTqS1X<(TEw<&E5ds=+tt3{v6fF#yO7$*{%ycDbC)d$RNM^Vst*JPrHzeF|H*Y=fqz zW)3I>6do^!E|09tz7>j!3iz=U@}=At0wQJT2u$d-x73(z0Bs2okNC#5`aRz6ZjN9|2tcba=C=%9l10Mj-hZc=+m^!4UJU{{H@T zI7r__#QiR6f&^s%DLUhkThi;J66BRHYYb*r%{W3NLj?8HDAFj5V~VKaP>YJv{ncbL z|I%VH@59B*ByD3G+7F`KE8iEQB4!*osm<5yQJs!P8r6~pZYun%Fc1U~P5dO#L}y3< zLK@2Lf}8NgrzEO=;UUVcXUY6^2_d6O5s@O>gtA+ zlw|M@+0sd=C{<2Q7G!2lfY#PFZhBGbf@a7}WRa~TCe;QeB|$+2DKeABdq?|+i?;ah4N{rVP#L?AbHDd?P`8ej$46+H4M(G9@2MN{Zp!xh=@3{!2-2>c$H10q8>@IkFz0=L)VNG3*){({?e%=BYZfM$rVwKn4&%UPho?kDr|*s!0H%({arw$Pokn z8QHo-aTSMM__Ti^pxx>HGk=HLNWVycAESArODhHq0SY-^6#xlGb8j|wB8O$^zi0&V zx%1}ojc+VS1~37N0xE*PPfCVn2{2p%r;1ogB>S|a-N0s;H*Yprd#ngz$pO#J{A!Yf zS~v9jJMUb|owGZ3e8f#3tu_Mj<$jSQV9S4nQg0q^A|B8oZ<8whRelek;X{CndnMmn z2ss?LeW$Ya?lzk(FXIEgj)L`NMEdXI0L&N@m@OwDIGn&s(LB${B&j--7!3v*D9h$t}6y+rUa6q{dhgJb)baNhtu3~WtDi_Rf=5nC7vX^LttC4wW7XLTbn%aL@{21w=R>}DM#xUe>L~)D& zH(tIw#zWd!Srg#NCm)B(%3C>OCZ|7{*Ysv`BX$0Epb3+E3$V(6^{HAP(aR+VNG9A0 zLh)BXn7jmp)Dph6L!dUXnyBkvQb!R$gF(LyHU9R#zW!3D)A?gGdG4ZZ;A-l8sFF@q z{t<~Wq0Q@{IxGkh&ucWA!?a(6{2N-WPEbr2Wj80K^?RQ_DE(Ct?o}3gs7IEqFGt<_}B`xUrN5g8>Yr+ zUjfaXR;xLIpmHBh^ehCGe@42tg6E?60Yaz=4K#B?m@$x?!WugQ$R-4g7mP-IHE#kg z?S2L{!<4u^^VMAbPO*`zGn5buBQ#;yKH3$rT9L0_8fXCJNI28z?QE=CcL|2l{z=4*pTfG&wWXHa?TB0Lu0Dk*HxP zvQzUQnmiAqX;FxPvd-af&iA<88Qy-2K#tbY`9CxrXr{ca(P&>nmT^wn#mVIS5bY3% zgwg=xLR}-Ra}qXEBRK+ZDliyt5DD@M)&+srPw-izc00?DwTk{LnHOUeb%Xv8x(IS(K+iFP?$l^*7)Ku}rga=T|B9W;3v zC6W#ie4Q+UJVVS&5Gnf<>0}dv)fT+QPW+pEGu=`i0h9d^Jh4vNhY>{1{RBG%UIfm3 z6e0wii0j0P;+Smre*o{Rx_lmBs?9wPK4HpHun6uY0OM6<+Jt^=@rK*1H^4_SEcjH1 zA~se(RNPi!C(=$xd#^^T*{0X(x1m5iAH`Uj!C**py4+bdn|-<{3W;cjM0?yGjh?!Z zOC!b*jXO~*J2ax$FA72v{(S_&sRd7a34x@KA}&-wM!J@_-70VpA+x{>m{=G!7}%@{ z3;;e$augt<$~O}7h?||M2&4R!j0}RDuM<+kbLPS#I5`RFD9oE6%I11Ft|aCZJ`{e_ z0EuKeh@cePmG&Wk&t3)zU`Uvwpx@EYeHLhodQ2A66@$@m1)0h*m&^4RItSmCiIW%? z7blr52nL;9-BKc$PABh{l&g_5shvcyMWojo{6R$U1&cA8D}GlN{MUys(7-6zi13eK z(~Omrgbv&CwW8kc7~XpmDOCvJCkcg~5!_k;AJI+DNXTK%t2W6SswAL*5v*W%prpMl zbUeLHW)mnC#`yo6jPkBY=Hq9PLU&Bwq{qA(PKO3J3hf5TKxL6K)?k1mguxqsr)_}T zk}zyUygrQM^MXxd*qrU3cN5O9A+^DX%<1Z3k()Q8^{xNC}IA*pEn=mGm zfv1uyI~cvn#DxTGDmpXFC?s}@tb&o?lS7FrY>ky65woEvO*jw&!ewG^@|b%6O$4kEg}m=dg(!_4JOU$s#Pn&pr=-^&g*m6N|>Ip z;0#ps6FfG0gbg-a0;qHBjy<2`a|k2~10jLb%8i{B02xo^CzvG!!-_v*xC8h)55yc=1C1E-WnI`99an?GlL)tHBCBXYR;<&LX<_ mSo|b{R~?n>Pb>dVfB^t(ZY0I?=GR*Q00000000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUy>`6pHRCwBAd|b@Hz{mgs|6vRU7?%Mg z!U$smDHb4)RfvU|Tk-$@{|pfIjEo4)VDj&$+rK}87%V_>CLR@$%<~K^91K{&*EcWw zMIqXlfYi@NH$JRQ#IE%3uh;YJet&@Sm>|@bGb_Fw>cUj~aZUWshu1+)0=tR{?u++Z z%YWY3imVi<|MQs@$UbI*P(c3wzYK4e`uu%$9isHtjjcfaP`|HWB6Jx+#Glt+-z{?g^YQw>pGa{sXIrrg`LfMk(+Q!=fyUy1q}@WJ5xhUEB6K{~h!(rQS($R!S{5 cVqXCU0FEX1^@s6HR9gx0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!6G=otRCwBaRk3XYF$@$1uayA|xJs2O zh6iw!${V;%XBun$AzDJ_5hsobnt%D0lR{6AC-TZ=qQYNSE%4fZr(r%*U%{3#@fMV-wZ|U32I`2RVTet9ov9a1><;a zc490LC;}x<)W9HSa|@E2D5Q~wPER)4Hd~) z7a}hi*bPEZ3E##6tEpfWilBDoTvc z!^R~Z;5%h77OwQK2sCp1=!WSe*z2u-)=XU8l4MF00000 LNkvXXu0mjfMhXuu diff --git a/gatling/src/main/resources/assets/style/stat-fond.png b/gatling/src/main/resources/assets/style/stat-fond.png deleted file mode 100644 index ed2bfc0b99e1878f3a210b82b6026d65e0626513..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 305 zcmV-10nYx3P)2 zuHAzle=!hL`292inviD2tR0b+6OFv*h+#cu_+Fni7B08fx?_iSkX#z~XgD&tt_DHV zoT1MVSf=k-MbDoT|Pu&4a zjKu{xFm>-$m+m|1Z>T$$Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipu2 z5E3mCeB9~)00D(bL_t(I%Y~9nOI2YMg`a&=`ce>$uoXjb4Psh}VPCwCNg=)fo<2<}BQ!ER5Jec^aG-qz%O-?R4GXYG%m1}eY| za1}TQw1Bt3T14#93+j~mM%{BiI_kURuc$M1;u~tSBxf^kBQ$MQH2hDmu=Uhfpovp@3kA)cmrgQtYfJQ`ouEzZX!;FtNainSJ00000NkvXX Hu0mjfy)@de diff --git a/gatling/src/main/resources/assets/style/stat-l-temps.png b/gatling/src/main/resources/assets/style/stat-l-temps.png deleted file mode 100644 index 9de94ab3a2e26b31f0ffdfd71985a883c5982155..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 260 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngd!3HEhbh*6;Qj#UE5hcO-X(i=}MX3yqDfvmM z3ZA)%>8U}fi7AzZCsS>Jik5l0IEGZ*niJf}+n~Vnn8p7Ai{AqgzXvS#AAt-O{|_vF zGiI!@Dr6}=DYR*AZ18eJrH&KVVzTl|`g>nr%LtTqWK3z**m5A!L|(c@$;IWcXP8=- z{*|4JCv2F=UQ#K}D57GSBU%<;oaFFmyQ}bnONsh(Dv}*vHdQKcvqsLk(d1buQPlkC zkLK^R8yZoE@0@seZuuvTWBu+%{pX5nOXt - - - - true - - - - - %d{HH:mm:ss.SSS} [%thread{10}][%-5level] %logger{15} - %msg%n%rEx - - - - - - - - - - diff --git a/gatling/src/main/scala/AccountLockoutSimulation.scala b/gatling/src/main/scala/AccountLockoutSimulation.scala deleted file mode 100644 index fd5e3f8c375..00000000000 --- a/gatling/src/main/scala/AccountLockoutSimulation.scala +++ /dev/null @@ -1,29 +0,0 @@ -import com.excilys.ebi.gatling.core.Predef._ -import com.excilys.ebi.gatling.http.Predef._ - -import uaa.Config._ -import uaa.UniqueUsernamePasswordFeeder -import uaa.OAuthComponents._ - -import bootstrap._ -/** - * Simulates logins with an incorrect password until the account is locked out, - * followed by an attempt with the correct password (which should fail) - */ -class AccountLockoutSimulation extends Simulation { - - val lockoutScenario = scenario("Account Lockout") - .feed(UniqueUsernamePasswordFeeder(users)) - .repeat(10)( - exec(cfLoginBadPassword()) - ) - .pause(61*5) // 5 mins 5 secs - .exec((s:Session) => { - s.setAttribute("password", "password") // use the right password - }) - .exec(cfLogin()) - - setUp ( - lockoutScenario.users(5).protocolConfig(uaaHttpConfig) - ) -} diff --git a/gatling/src/main/scala/AuthCodeFlowSimulation.scala b/gatling/src/main/scala/AuthCodeFlowSimulation.scala deleted file mode 100644 index b0edfda97a8..00000000000 --- a/gatling/src/main/scala/AuthCodeFlowSimulation.scala +++ /dev/null @@ -1,19 +0,0 @@ - -import com.excilys.ebi.gatling.core.Predef._ - -import com.excilys.ebi.gatling.http.Predef._ -import uaa.Config._ -import uaa.UsernamePasswordFeeder - -import uaa.OAuthComponents._ - -class AuthCodeFlowSimulation extends Simulation { - - setUp( - scenario("Authorization Code Login") - .feed(UsernamePasswordFeeder()) - .exec( - authorizationCodeLogin(appClient)).users(1).protocolConfig(uaaHttpConfig) - ) - -} diff --git a/gatling/src/main/scala/ScimWorkoutSimulation.scala b/gatling/src/main/scala/ScimWorkoutSimulation.scala deleted file mode 100644 index d60fa8da81b..00000000000 --- a/gatling/src/main/scala/ScimWorkoutSimulation.scala +++ /dev/null @@ -1,59 +0,0 @@ -import com.excilys.ebi.gatling.core.Predef._ -import com.excilys.ebi.gatling.http.Predef._ - -import uaa.Config._ -import uaa.ScimApi._ -import uaa._ -import uaa.SequentialDisplayNameFeeder -import uaa.UsernamePasswordFeeder - -import bootstrap._ -/** - * @author Luke Taylor - * @author Vidya Valmikinathan - */ -class ScimWorkoutSimulation extends Simulation { - val scimWorkout = scenario("SCIM USER workout") - .exec(scimClientLogin()) - .repeat(nUsers) { - bootstrap.feed(UsernamePasswordFeeder()) - .exec(findUserByName("username")) - .exec(getUser) -// .exec(updateUser) - } - - val groupsWorkout = scenario("SCIM GROUP workout") - .exec(scimClientLogin()) - .repeat(nUsers) { // basic lookups - bootstrap.feed(SequentialDisplayNameFeeder()) - .exec(findGroupByName) - .exec(getGroup) - } - .repeat(nUsers - 1) { // repeatedly update the same group and make it grow gradually - feed(RandomGroupMemberFeeder(users, 1)) - .exec(findUserByName("memberName_1", "memberId")) - .exec(addGroupMember("memberId")) - } - .repeat(1) { // prepare base for next part of workout - feed(ConstantFeeder("displayName", "acme")) - .exec(findGroupByName("displayName", "memberId")) - } - .repeat(20) { // nest groups repeatedly - feed(UniqueGroupFeeder(users, groups, 1)) - .exec(findUserByName("displayName", "groupId")) - .exec(getGroup) - .exec(nestGroup) - .exec(findUserByName("memberName_1")) - .exec(getGroup) - .exec(addGroupMember("userId")) - .exec(getUser) - .exec((s: Session) => { -// println("\n\n>>>>>> " + s.getAttribute("scimUser")) - s}) - } - - setUp ( - scimWorkout.users(1).protocolConfig(uaaHttpConfig), - groupsWorkout.users(1).protocolConfig(uaaHttpConfig) - ) -} diff --git a/gatling/src/main/scala/UaaBaseDataCreationSimulation.scala b/gatling/src/main/scala/UaaBaseDataCreationSimulation.scala deleted file mode 100644 index eba66fbbc50..00000000000 --- a/gatling/src/main/scala/UaaBaseDataCreationSimulation.scala +++ /dev/null @@ -1,46 +0,0 @@ - -import com.excilys.ebi.gatling.core.Predef._ -import com.excilys.ebi.gatling.http.Predef._ - -import uaa.Config._ -import uaa.ScimApi._ -import uaa.UaaApi._ -import uaa.OAuthComponents._ -import uaa.{UniqueGroupFeeder, UniqueUsernamePasswordFeeder} -import bootstrap._ - - -object RegisterClients { - val scn = scenario("Register clients") - .exec(adminClientLogin()) - .doIf(haveAccessToken)(exec( - registerClient(scimClient) - ) - .exec( - registerClient(appClient) - ) - ) -} - - -class UaaUserDataCreationSimulation extends Simulation { - def createUsers = scenario("Create users") - .pause(5) - .exec(createScimUsers(UniqueUsernamePasswordFeeder(users))) - - setUp( - RegisterClients.scn.users(1).protocolConfig(uaaHttpConfig) -// , createUsers.users(5).protocolConfig(uaaHttpConfig) - ) -} - -class UaaGroupDataCreationSimulation extends Simulation { - def createGroups = scenario("Create groups") - .pause(5) - .exec(createScimGroups(UniqueGroupFeeder())) - - setUp( - RegisterClients.scn.users(1).protocolConfig(uaaHttpConfig), - createGroups.users(5).protocolConfig(uaaHttpConfig) - ) -} diff --git a/gatling/src/main/scala/UaaSmokeSimulation.scala b/gatling/src/main/scala/UaaSmokeSimulation.scala deleted file mode 100644 index 3714954bd67..00000000000 --- a/gatling/src/main/scala/UaaSmokeSimulation.scala +++ /dev/null @@ -1,91 +0,0 @@ - -import com.excilys.ebi.gatling.core.Predef._ -import com.excilys.ebi.gatling.http.Predef._ - -import uaa.Config._ -import uaa.ScimApi._ -import uaa.UsernamePasswordFeeder -import bootstrap._ - -import uaa.OAuthComponents._ - -class UaaSmokeSimulation extends Simulation { - val Duration = sys.env.getOrElse("GATLING_DURATION", "600").toInt - - val authzCodeLogin = scenario("Authorization Code Login") - .feed(UsernamePasswordFeeder()) - .during(Duration) { - authorizationCodeLogin(appClient) - .pause(0, 2) - } - - val uiLoginLogout = scenario("UI Login/Logout") - .feed(UsernamePasswordFeeder()) - .during(Duration) { - exec(login) - .pause(0, 2) - .exec(logout) - } - - val cfLogins = scenario("CF Logins") - .during(Duration) { - feed(UsernamePasswordFeeder()) -// .exec(cfLoginBadPassword()) - .exec(cfLogin()) - .exec(cfLogin()) -// .exec(cfLoginBadUsername()) - .exec(cfLogin()) - .exec(cfLogin(username="shaun1", password="password")) - .pause(0, 2) - } - - val random = new scala.util.Random() - - val randomUserFeeder = new Feeder[String]() { - def hasNext = true - def next() = Map("username" -> ("randy_" + random.nextLong()), "password" -> "password") - } - - import bootstrap._ - - val scimWorkout = scenario("SCIM workout") - .exec(scimClientLogin()) - .doIf(haveAccessToken) { - during(Duration) { - feed(randomUserFeeder) - .exec(createUser) - .exec(findUserByName("username")) - .exec(getUser) - .pause(0, 2) - } - } - - val passwordScores = scenario("Password score API") - .during(Duration) { - bootstrap.exec( - http("Check complex password") - .post("/password/score") - .param("password", "coRrecth0rseba++ery9.23.2007staple$") - .check(status is 200, jsonPath("//score") is "10")) - .exec( - http("Check simple password") - .post("/password/score") - .param("password", "password1") - .check(status is 200, jsonPath("//score") is "0")) - .exec( - http("Check adjacency password") - .post("/password/score") - .param("password", "sdfghhju") - .check(status is 200, jsonPath("//score") is "1")) - .pause(1,5) - } - - setUp( - uiLoginLogout.users(2).ramp(10).protocolConfig(loginHttpConfig) - , scimWorkout.users(3).ramp(10).protocolConfig(uaaHttpConfig) - , authzCodeLogin.users(5).ramp(10).protocolConfig(loginHttpConfig) - , passwordScores.users(1).ramp(10).protocolConfig(uaaHttpConfig) - , cfLogins.users(10).ramp(1).protocolConfig(uaaHttpConfig) - ) - -} diff --git a/gatling/src/main/scala/VarzSimulation.scala b/gatling/src/main/scala/VarzSimulation.scala deleted file mode 100644 index 982b5dffe48..00000000000 --- a/gatling/src/main/scala/VarzSimulation.scala +++ /dev/null @@ -1,38 +0,0 @@ - -import com.excilys.ebi.gatling.core.Predef._ - -import com.excilys.ebi.gatling.http.Predef._ -import uaa.Config._ -import bootstrap._ - -/** - * Scenarios which hit /varz and /healthz. - * - * Also useful for retrieving remote heap information without any effort - */ -class VarzSimulation extends Simulation { - val Duration = 60 - - def varzScenario(duration: Int = 60) = scenario("Varz") - .during(duration) ( - exec( - http("Varz") - .get("/varz") - .basicAuth(varz_client_id, varz_client_secret) - .asJSON - .check(status is 200, jsonPath("/memory/heap_memory_usage").saveAs("heap")) - ).exec((s:Session) => { - println("Remote heap information: %s".format(s.getAttribute("heap"))); - s - } - ).pause(5) - .exec( - http("Healthz") - .get("/healthz") - .basicAuth(varz_client_id, varz_client_secret) - .check(status is 200) - ) - ) - - setUp( varzScenario(Duration).users(50).ramp(10).protocolConfig(uaaHttpConfig) ) -} diff --git a/gatling/src/main/scala/acm/AcmApi.scala b/gatling/src/main/scala/acm/AcmApi.scala deleted file mode 100644 index 876172cfa01..00000000000 --- a/gatling/src/main/scala/acm/AcmApi.scala +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Cloud Foundry 2012.02.03 Beta - * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - */ -package acm - -import com.excilys.ebi.gatling.core.Predef._ -import com.excilys.ebi.gatling.http.Predef._ -import com.excilys.ebi.gatling.core.session.Session -import com.excilys.ebi.gatling.core.session.EvaluatableString -import java.util.UUID._ -import com.excilys.ebi.gatling.core.action.builder.ActionBuilder - -/** - * Reusable ACM api calls - */ -object AcmApi { - val PermissionSet = "permission_set" - val ObjectId = "acm_object_id" - val GroupId = "acm_group_id" - - val acmUser: EvaluatableString = s => Config.acm_user - val acmPassword: EvaluatableString = s => Config.acm_password - - def createRandomPermissionSet(size: Int) = - http("Create %s Permission Set".format(size)) - .post("/permission_sets") - .basicAuth(acmUser, acmPassword) - .body(genPermissions(size)) - .asJSON - .check(status.is(200), jsonPath("/name").saveAs(PermissionSet)) - - private def genPermissions(n: Int)(s: Session): String = { - val name = "perm_set_%s".format(randomUUID()) - """{"name": "%s", "permissions": [%s]}""".format(name, formatSeq((1 to n) map (_ => "permission_%s".format(randomUUID())))) - } - - def createPermissionSet(name: String, permissions: Seq[String], expectedStatus: Int = 200) = - http("Create Permission Set") - .post("/permission_sets") - .basicAuth(acmUser, acmPassword) - .body("""{"name": "%s", "permissions": [%s]}""".format(name, formatSeq(permissions))) - .asJSON - .check(status.is(expectedStatus)) - - def getPermissionSet(name: String, expectedStatus: Int = 200) = - http("Get Permission Set") - .get("/permission_sets/%s".format(name)) - .basicAuth(acmUser, acmPassword) - .check(status.is(expectedStatus)) - - def updatePermissionSet(name: String, permissions: Seq[String]) = - http("Update Permission Set") - .put("/permission_sets/%s".format(name)) - .basicAuth(acmUser, acmPassword) - .body("""{"name": "%s", "permissions": [%s]}""".format(name, formatSeq(permissions))) - .asJSON - .check(status.is(200)) - - def deletePermissionSet(name: String) = - http("Delete Permission Set") - .delete("/permission_sets/%s".format(name)) - .basicAuth(acmUser, acmPassword) - .check(status.is(200)) - - def createUser(id: String) = - http("Create User") - .post("/users/%s".format(id)) - .basicAuth(acmUser, acmPassword) - .check(status.is(200)) - - def getUser(id: String) = - http("Get User") - .get("/users/%s".format(id)) - .basicAuth(acmUser, acmPassword) - .check(status.is(200)) - - def createObject(permissionSets: Seq[String], acl: Map[String, Seq[String]]) = - http("Create ACM Object") - .post("/objects") - .basicAuth(acmUser, acmPassword) - .body("""{"permission_sets": [%s], "acl": {%s} }""".format(formatSeq(permissionSets), formatAcl(acl))) - .asJSON - .check(status.is(200), jsonPath("/id").saveAs(ObjectId)) - - def getObject(id: String) = - http("Get ACM Object") - .get("/objects/%s".format(id)) - .basicAuth(acmUser, acmPassword) - .check(status.is(200)) - - - def createGroup(id: Option[String], members: Seq[String]): ActionBuilder = - http("Create Group") - .post(id match { - case Some(s) => "/groups/%s".format(s) - case None => "/groups" - }) - .body("""{"members": [%s]}""".format(formatSeq(members))) - .basicAuth(acmUser, acmPassword) - .check(status.is(200), jsonPath("/id").saveAs(GroupId)) - - def getGroup(id: String) = - http("Get Group") - .get("/groups/%s".format(id)) - .basicAuth(acmUser, acmPassword) - .check(status.is(200)) - - - - private def formatSeq(strings: Seq[String]) = strings.mkString("\"", "\",\"", "\"") - - private def formatAcl(acl: Map[String, Seq[String]]) = - acl map { - case (perm, users) => """ "%s": [%s] """.format(perm, formatSeq(users)) - } mkString (",") - -} diff --git a/gatling/src/main/scala/acm/Config.scala b/gatling/src/main/scala/acm/Config.scala deleted file mode 100644 index d09d91eee20..00000000000 --- a/gatling/src/main/scala/acm/Config.scala +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Cloud Foundry 2012.02.03 Beta - * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - */ -package acm - - -import com.excilys.ebi.gatling.http.Predef.httpConfig - -/** - * Basic configuration for the ACM. - * - * Edit `urlBase` or set GATLING_ACM_BASE env variable to run against a different URL - */ -object Config { - val urlBase = sys.env.getOrElse("GATLING_ACM_BASE", "http://localhost:9090") - - val acm_user = "acm_user" - val acm_password = "acm_password" - - def acmHttpConfig = httpConfig.baseURL(urlBase) - -} diff --git a/gatling/src/main/scala/uaa/Config.scala b/gatling/src/main/scala/uaa/Config.scala deleted file mode 100644 index 87c487d3ebe..00000000000 --- a/gatling/src/main/scala/uaa/Config.scala +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Cloud Foundry 2012.02.03 Beta - * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - */ -package uaa - -import com.excilys.ebi.gatling.http.Predef.httpConfig -import java.io.File -import io.Source -import com.excilys.ebi.gatling.http.config.HttpProtocolConfiguration - -/** - */ -object Config { - // Number of base data users to create - val nUsers = 1000 - - // Number of base data groups to create - val nGroups = 50 - - // Average number of members in a group - val avgGroupSize = nUsers/nGroups - - def yetiTarget = for { - userHome <- sys.props.get("user.home") - yetiFile = new File(userHome + "/.bvt/config.yml") - if (yetiFile.exists()) - content = Source.fromFile(yetiFile).getLines().toSeq - if (content.length > 1) - target <- "target: (.*)".r.findPrefixMatchOf(content(1)).map(_.group(1).trim) - } yield { - target - } - - def baseUrl = (sys.env.get("VCAP_BVT_TARGET") orElse yetiTarget) map (_.replace("cc.", "")) - - private def prependHttp(url: String) = if (url.startsWith("http")) url else "http://" + url - - - // The bootstrap admin user - val admin_client_id = sys.env.getOrElse("VCAP_BVT_ADMIN_CLIENT", "admin") - val admin_client_secret = sys.env.getOrElse("VCAP_BVT_ADMIN_SECRET", "adminsecret") - - // "Varz" client - val varz_client_id = sys.env.getOrElse("GATLING_UAA_VARZ_CLIENT", "varz") - val varz_client_secret = sys.env.getOrElse("GATLING_UAA_VARZ_SECRET", "varzclientsecret") - - // Client to mimic a registered application for authorization code flows etc. - val appClient = Client( - id = "gatling_app", - secret= "app_client_secret", - scopes = Seq("cloud_controller.read","cloud_controller.write","openid","password.write","tokens.read","tokens.write"), - redirectUri = Some("http://localhost:8080/app"), - resources = Seq("uaa.none"), - authorities = Seq("cloud_controller.read","cloud_controller.write","openid","password.write","tokens.read","tokens.write"), - grants = Seq("client_credentials", "authorization_code", "refresh_token")) - - // Scim client which is registered by the admin user in order to create users - val scimClient = Client("scim_client", "scim_client_secret", - Seq("uaa.none"), Seq("uaa.none"), Seq("scim.read","scim.write","password.write")) - - // The base user data - val users: Seq[User] = (1 to nUsers).map(i => User("shaun" + i, "password")) - - // The base group data - val groups: Seq[Group] = (1 to nGroups).map(i => Group("acme." + i, Seq(User("shaun" + i, "password")))) - - def uaaHttpConfig = { - val uaaUrl = baseUrl map (prependHttp) map (_.replace("://", "://uaa.")) getOrElse "http://localhost:8080/uaa" - println("**** Targeting UAA at: " + uaaUrl) - httpConfig.baseURL(uaaUrl).disableFollowRedirect.disableAutomaticReferer.warmUp(uaaUrl) - } - - def loginHttpConfig = { - val loginUrl = baseUrl map (prependHttp) map (_.replace("://", "://login.")) getOrElse "http://localhost:8080/uaa" - println("**** Targeting Login server at: " + loginUrl) - httpConfig.baseURL(loginUrl).disableFollowRedirect.disableAutomaticReferer.warmUp(loginUrl) - } - -} diff --git a/gatling/src/main/scala/uaa/OAuthComponents.scala b/gatling/src/main/scala/uaa/OAuthComponents.scala deleted file mode 100644 index e41d1546ecd..00000000000 --- a/gatling/src/main/scala/uaa/OAuthComponents.scala +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Cloud Foundry 2012.02.03 Beta - * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - */ -package uaa - -import java.util.regex.Pattern - -import com.excilys.ebi.gatling.core.Predef._ -import com.excilys.ebi.gatling.core.check.{CheckBuilder, ExtractorFactory, MatcherCheckBuilder} -import com.excilys.ebi.gatling.core.structure.ChainBuilder -import com.excilys.ebi.gatling.http.Predef._ -import com.excilys.ebi.gatling.http.check.HttpCheck -import com.excilys.ebi.gatling.http.check.HttpExtractorCheckBuilder -import com.excilys.ebi.gatling.http.request.HttpPhase - -import OAuthCheckBuilder._ -import com.excilys.ebi.gatling.core.action.builder.ActionBuilder -import com.excilys.ebi.gatling.http.response.ExtendedResponse -import bootstrap._ - -/** - * Checks for the presence of an access token or authorization code in the fragment/parameters of the Location header - * or in the JSON body - */ -object OAuthCheckBuilder { - private val fragmentTokenPattern = Pattern.compile(".*#.*access_token=([^&]+).*") - private val jsonBodyTokenPattern = Pattern.compile(""""access_token":"(.*?)"""") - private val authorizationCodePattern = Pattern.compile(".*code=([^&]+).*") - - def fragmentToken = new FragmentTokenCheckBuilder - - def jsonToken = new JsonTokenCheckBuilder - - // Get round Gatling bug #609 - def locationHeader = new LocationHeaderCheckBuilder - - // Allows saving of the auth code. Should only fail if response is a redirect with no auth code in the location - def authCode = new AuthCodeCheckBuilder - - private[uaa] def fragmentExtractorFactory: ExtractorFactory[ExtendedResponse, String, String] = { (response: ExtendedResponse) => - (expression: String) => - val location = response.getHeader("Location") - if (location != null) { - val matcher = fragmentTokenPattern.matcher(location) - - if (matcher.find()) Some(matcher.group(1)) else None - } else None - } - - private[uaa] def authCodeExtractorFactory: ExtractorFactory[ExtendedResponse, String, String] = { (response: ExtendedResponse) => - (expression: String) => - val location = response.getHeader("Location") - if (location != null) { - val matcher = authorizationCodePattern.matcher(location) - if (matcher.find()) Some(matcher.group(1)) else None - } else Some("NoLocationNoCode") - - } - - private[uaa] def jsonExtractorFactory: ExtractorFactory[ExtendedResponse, String, String] = { (response: ExtendedResponse) => - (expression: String) => - val matcher = jsonBodyTokenPattern.matcher(response.getResponseBody()) - - if (matcher.find()) Some(matcher.group(1)) else None - } - - // Only used for saving the location header (status code can be used to check for a redirect) - private[uaa] def locationHeaderExtractorFactory: ExtractorFactory[ExtendedResponse, String, String] = response => expression => { - if (response.getStatusCode() != 302) - println("Response is not a redirect") - Option(response.getHeader("Location")) orElse(Some("No location header found")) - } -} - -private[uaa] class FragmentTokenCheckBuilder extends HttpExtractorCheckBuilder[String, String](s => "", HttpPhase.HeadersReceived) { - def find = new MatcherCheckBuilder[HttpCheck[String], ExtendedResponse, String, String](httpCheckBuilderFactory, fragmentExtractorFactory) -} - -private[uaa] class JsonTokenCheckBuilder extends HttpExtractorCheckBuilder[String, String](s => "", HttpPhase.CompletePageReceived) { - def find = new MatcherCheckBuilder[HttpCheck[String], ExtendedResponse, String, String](httpCheckBuilderFactory, jsonExtractorFactory) -} - -private[uaa] class AuthCodeCheckBuilder extends HttpExtractorCheckBuilder[String,String](s => "", HttpPhase.HeadersReceived) { - def find = new MatcherCheckBuilder[HttpCheck[String], ExtendedResponse, String, String](httpCheckBuilderFactory, authCodeExtractorFactory) -} - -private[uaa] class LocationHeaderCheckBuilder extends HttpExtractorCheckBuilder[String, String](s => "", HttpPhase.HeadersReceived) { - def find = new MatcherCheckBuilder[HttpCheck[String], ExtendedResponse, String, String](httpCheckBuilderFactory, locationHeaderExtractorFactory) -} - -/** - */ -object OAuthComponents { - private val plainHeaders = Map( - "Accept" -> "application/json", - "Content-Type" -> "application/x-www-form-urlencoded") - - private val jsonHeaders = Map( - "Accept" -> "application/json", - "Content-Type" -> "application/x-www-form-urlencoded") - - def haveAuthCode: (Session => Boolean) = _.isAttributeDefined("code") - - def haveAccessToken : (Session => Boolean) = _.isAttributeDefined("access_token") - - def clearCookies : (Session => Session) = _.removeAttribute("gatling.http.cookies") - - def saveLocation(): CheckBuilder[HttpCheck[String], ExtendedResponse, String] = locationHeader.saveAs("location") - - def statusIs(status:Int) : (Session => Boolean) = _.getAttribute("status").toString.toInt == status - - /** - * Performs an oauth token request as the specific client and saves the returned token - * in the client session under the key "access_token". - */ - def clientCredentialsAccessTokenRequest( - username: String, password: String, client_id: String, scope: String = ""): ActionBuilder = - http("Client Credentials Token Request") - .post("/oauth/token") - .basicAuth(username, password) - .param("client_id", client_id) -// .param("scope", scope) - .param("grant_type", "client_credentials") - .headers(plainHeaders) - .check(status.is(200), jsonToken.saveAs("access_token")) - - /** - * Single cf login action with a specific username/password - */ - def cfLogin(username: String = "${username}", password: String = "${password}"): ActionBuilder = - cfAction("CF login", username, password) - .check(status is 200, jsonToken.saveAs("access_token")) - - def cfLoginBadPassword(username: String = "${username}"): ActionBuilder = - cfAction("CF failed login - bad password", username, "pXssword") - .check(status is 401) - - def cfLoginBadUsername(): ActionBuilder = - cfAction("CF failed login - no user", "idontexist", "password") - .check(status is 401) - - private def cfAction(name: String, username: String, password: String) = - http(name) - .post("/oauth/token") - .param("username", username) - .param("password", password) - .param("grant_type", "password") - .basicAuth("cf", "") - .header("Accept", "application/json") - - def login: ActionBuilder = login("${username}", "${password}") - - def login(username: String, password: String): ActionBuilder = - http("Login") - .post("/login.do") - .param("username", username) - .param("password", password) - .headers(plainHeaders) - .check(status.is(302), saveLocation()) - - def logout = exec( - http("Logout") - .get("/logout.do") - .headers(plainHeaders) - .check(status.is(302), saveLocation()) - ) - .exec( - http("Logged Out") - .get("${location}") - .headers(plainHeaders) - .check(status.in(Seq(200,302)))) - .exec(clearCookies) - - /** - * Action which performs an authorization code token request as a given client. - * - * Requires a username and password in the session. - */ - def authorizationCodeLogin(client:Client): ChainBuilder = authorizationCodeLogin("${username}", "${password}", client) - - def authorizationCodeLogin(username: String, password: String, client:Client): ChainBuilder = { - val redirectUri = client.redirectUri.getOrElse(throw new RuntimeException("Client does not have a redirectUri")) - - exec( - http("Initial Request To Root") - .get("/").check(status.is(302))) - .exec(http("Authorization Endpoint") - .get("/oauth/authorize") - .queryParam("client_id", client.id) -// .queryParam("scope", client.scopes.mkString(" ")) - .queryParam("redirect_uri", redirectUri) - .queryParam("response_type", "code") - .check(status.is(302))) - .exec(login(username, password)) -// .exec((s: Session) => { -// var location = s.getAttribute("location") -// println("Login redirected to " + location) -// s -// }) - .exec( - http("Reload after login") - .get("${location}") - .check(status.saveAs("status"), authCode.saveAs("code"))) - .doIf(statusIs(200))(exec( // Not auto-approved, so we do the approval page - http("Authorization Approval") - .post("/oauth/authorize") - .param("user_oauth_approval", "true") - .check(status.is(302), authCode.saveAs("code")))) - .exec(clearCookies(_)) - .doIf(haveAuthCode) {exec( - http("Access Token Request") - .post("/oauth/token") - .basicAuth(client.id, client.secret) - .param("client_id",client.id) - .param("code", "${code}") - .param("redirect_uri", redirectUri) - .param("grant_type", "authorization_code") - .headers(jsonHeaders) - .check(status.is(200))) - } - - } - -} diff --git a/gatling/src/main/scala/uaa/ScimApi.scala b/gatling/src/main/scala/uaa/ScimApi.scala deleted file mode 100644 index 5559b372b75..00000000000 --- a/gatling/src/main/scala/uaa/ScimApi.scala +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Cloud Foundry 2012.02.03 Beta - * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - */ -package uaa - -import com.excilys.ebi.gatling.core.Predef._ -import com.excilys.ebi.gatling.core.structure.ChainBuilder - -import uaa.OAuthComponents._ - -import uaa.Config._ -import com.excilys.ebi.gatling.http.Predef._ -import com.excilys.ebi.gatling.core.action.builder.ActionBuilder -import bootstrap._ - -/** - * @author Luke Taylor - * @author Vidya Valmikinathan - */ -object ScimApi { - def scimClientLogin() = clientCredentialsAccessTokenRequest( - username = scimClient.id, - password = scimClient.secret, - client_id = scimClient.id) - - /** - * Creates 'n' users by invoking the SCIM API. - * - * Usernames can optionally be prefixed - */ - def createScimUsers(userFeeder: UniqueUsernamePasswordFeeder): ChainBuilder = - exec( - scimClientLogin() - ) - .doIf(haveAccessToken)( - asLongAs(s => {userFeeder.hasNext}) { - feed(userFeeder) - .exec((s: Session) => {println("Creating user: %s" format(s.getAttribute("username"))); s}) - .exec(createUser) - } - ) - - def createScimGroups(groupFeeder: UniqueGroupFeeder): ChainBuilder = - exec( - scimClientLogin() - ) - .doIf(haveAccessToken)( - asLongAs(s => {groupFeeder.hasNext}) { - feed(groupFeeder) - .exec(findUserByName("memberName_1", "memberId_1")) - .exec(findUserByName("memberName_2", "memberId_2")) - .exec(createGroup) - } - ) - - /** - * Finds a user and stores their ID in the session as `userId`. Uses the session attribute "username" for the - * search. - */ - def findUserByName(input: String, output: String) : ActionBuilder = { - http("Find user by name") - .get("/Users") - .queryParam("attributes", "id") - .queryParam("filter","userName eq '${" + input + "}'") - .header("Authorization", "Bearer ${access_token}") - .asJSON - .check(status.is(200), regex(""""id":"(.*?)"""").saveAs(output)) - } - - def findUserByName (input: String) : ActionBuilder = { - findUserByName(input, "userId") - } - - /** - * Find a group by name - * @param input name of the session attribute that has the group name to lookup - * @param output name of the session attribute in which to store the group's id - * @return - */ - def findGroupByName(input: String, output:String) : ActionBuilder = { - http("Find user by name") - .get("/Groups") - .queryParam("attributes", "id") - .queryParam("filter","displayName eq '${" + input + "}'") - .header("Authorization", "Bearer ${access_token}") - .asJSON - .check(status.is(200), regex(""""id":"(.*?)"""").saveAs(output)) - } - - def findGroupByName : ActionBuilder = { - findGroupByName("displayName", "groupId") - } - - /** - * Creates a SCIM user. - * - * A suitable access token must already be available in the session, as well as username and password values. - * - */ - def createUser = - http("Create User") - .post("/Users") - .header("Authorization", "Bearer ${access_token}") - .body("""{"name":{"givenName":"Shaun","familyName":"Sheep","formatted":"Shaun the Sheep"},"password":"${password}","userName":"${username}","emails":[{"value":"${username}@blah.com"}]}""") - .asJSON - .check(status.is(201), regex(""""id":"(.*?)"""").saveAs("__scimUserId")) - - /** - * Create a SCIM group. - */ - def createGroup = - http("Create Group") - .post("/Groups") - .header("Authorization", "Bearer ${access_token}") - .body("""{"displayName":"${displayName}","members":[{"value":"${memberId_1}"},{"value":"${memberId_2}"}]}""") - .asJSON - .check(status.is(201), regex(""""id":"(.*?)"""").saveAs("__scimGroupId")) - - /** - * Pulls a user by `userId` and stores the data under `scimUser` - */ - def getUser (prefix: String) : ActionBuilder = - http("Get User") - .get("/Users/${userId}") - .header("Authorization", "Bearer ${access_token}") - .asJSON - .check(status.is(200), regex(".*").saveAs(prefix + "User"), regex(""""id":"(.*?)"""").saveAs(prefix + "Id")) - - def getUser : ActionBuilder = { - getUser("user") - } - - /** - * Fetch a group using SCIM APIs. - * - * @param prefix prefix for session attributes to which to store the fetched group - * @return - */ - def getGroup (prefix: String) : ActionBuilder = - http("Get Group") - .get("/Groups/${groupId}") - .header("Authorization", "Bearer ${access_token}") - .asJSON - .check(status.is(200), regex(".*").saveAs("scimGroup"), regex("""\[.*?}\]""").saveAs("memberJson"), regex(""""id":"(.*?)"""").saveAs(prefix + "Id")) - - def getGroup : ActionBuilder = { - getGroup("group") - } - - /** - * Performs an update using the data in `scimUser`. - */ - def updateUser = - http("Update user") - .put("/Users/${userId}") - .header("Authorization", "Bearer ${access_token}") - .body("${scimUser}") - .asJSON - .check(status.is(204)) - - /** - * Add a user as a member to a group - * @param member the id of the user to add - * @return - */ - def addGroupMember (member: String) : ActionBuilder = - http("Add member to group") - .put("/Groups/${groupId}") - .header("Authorization", "Bearer ${access_token}") - .header("If-Match", "*") - .body((s: Session) => getUpdatedGroupJson(s.getAttribute("scimGroup").toString, s.getAttribute("memberJson").toString, s.getAttribute(member).toString)) - .asJSON - .check(status.is(200), regex(".*").saveAs("scimGroup"), regex("""\[.*?}\]""").saveAs("memberJson")) - - /** - * Add a group as member to another group. - */ - def nestGroup : ActionBuilder = - http("Add member to group") - .put("/Groups/${groupId}") - .header("Authorization", "Bearer ${access_token}") - .header("If-Match", "*") - .body((s: Session) => getUpdatedGroupJson(s.getAttribute("scimGroup").toString, - s.getAttribute("memberJson").toString, - s.getAttribute("memberId").toString, - "GROUP")) - .asJSON - .check(status.is(200), regex(""""id":"(.*?)"""").saveAs("memberId")) - - /** - * Add a member to a given JSON representation of a group - * @param groupJson current JSON representation of a group - * @param memberJson substring of groupJson that lists current members of the group - * @param newMemberId id of new member to be added - * @param memberType 'USER' or 'GROUP' - * @return JSON representation of group that can be used in a PUT request body - */ - def getUpdatedGroupJson(groupJson: String, memberJson: String, newMemberId: String, memberType: String = "USER") = { - val updatedMembersJson = """[ %s, { "value": "%s", "type": "%s", "authorities": ["READ"] } ]""" format(memberJson.substring(1).dropRight(1), newMemberId, memberType) - groupJson.replace(memberJson, updatedMembersJson) - } - - def changePassword = - http("Change Password") - .put("/Users/${userId}/password") - .header("Authorization", "Bearer ${access_token}") - .body("""{"password":"${password}"}""") - .asJSON - .check(status.is(204)) - -} diff --git a/gatling/src/main/scala/uaa/ScimFeeders.scala b/gatling/src/main/scala/uaa/ScimFeeders.scala deleted file mode 100644 index 77278969107..00000000000 --- a/gatling/src/main/scala/uaa/ScimFeeders.scala +++ /dev/null @@ -1,96 +0,0 @@ -package uaa - -import com.excilys.ebi.gatling.core.feeder.Feeder -import java.util.concurrent.ConcurrentLinkedQueue - -import collection.JavaConversions._ -import util.Random -import collection.{mutable} - -/** - * Various types of feeders for SCIM resources - * @author Vidya Valmikinathan - */ -case class UniqueUsernamePasswordFeeder(usrs: Seq[User], password: Option[String] = None) extends Feeder[String] { - private val users = new ConcurrentLinkedQueue[User](usrs) - - println("%d users".format(users.size())) - - def next = { - val user = users.remove() - val pass = password match { case None => user.password; case Some(p) => p} - Map("username" -> user.username, "password" -> pass) - } - - def hasNext = !users.isEmpty -} - -case class UniqueGroupFeeder(usrs: Seq[User] = Config.users, grps: Seq[Group] = Config.groups, grpSize: Int = Config.avgGroupSize) extends Feeder[String] { - private val nameFeeder = new UniqueDisplayNameFeeder(grps) - private val memberFeeder = new RandomGroupMemberFeeder(usrs, grpSize) - - def hasNext = nameFeeder.hasNext && memberFeeder.hasNext - - def next = { - val map = mutable.HashMap.empty[String, String] - map.putAll(nameFeeder.next) - map.putAll(memberFeeder.next) - println("next group: %s" format(map)) - map.toMap - } -} - -case class UniqueDisplayNameFeeder(grps: Seq[Group]) extends Feeder[String] { - private val groups = new ConcurrentLinkedQueue[Group](grps) - - println("%d groups".format(groups.size())) - - def next = { - Map("displayName" -> groups.remove().displayName) - } - - def hasNext = !groups.isEmpty -} - -case class SequentialDisplayNameFeeder(grps: Seq[Group] = Config.groups, resetAfter: Int = (Config.groups.size-1)) extends Feeder[String] { - private val groups = grps - private var counter = -1 - - def hasNext = !groups.isEmpty - - def next = { - if (counter >= resetAfter) - counter = -1 - counter += 1 - println("next group: " + groups.get(counter).displayName) - Map("displayName" -> groups.get(counter).displayName) - } - -} - -case class RandomGroupMemberFeeder(usrs: Seq[User] = Config.users, n: Int = Config.avgGroupSize) extends Feeder[String] { - private val users = usrs - private val randGen = new Random - private val num = n - - println("picking %d members from %d users" format(num, users.size)) - - def next = { - val members = mutable.HashMap.empty[String, String] - (1 to num).foreach { i => - members += (("memberName_" + i) -> users.get(randGen.nextInt(users.size)).username) - } - println("next member set: %s" format(members)) - members.toMap - } - - def hasNext = !users.isEmpty && num > 0 -} - -case class ConstantFeeder(key: String = "constantKey", value: String = "constantValue") extends Feeder[String] { - def hasNext = true - - def next = { - Map(key -> value) - } -} diff --git a/gatling/src/main/scala/uaa/UsernamePasswordFeeder.scala b/gatling/src/main/scala/uaa/UsernamePasswordFeeder.scala deleted file mode 100644 index 2ec9a7d046e..00000000000 --- a/gatling/src/main/scala/uaa/UsernamePasswordFeeder.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Cloud Foundry 2012.02.03 Beta - * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. - * - * This product is licensed to you under the Apache License, Version 2.0 (the "License"). - * You may not use this product except in compliance with the License. - * - * This product includes a number of subcomponents with - * separate copyright notices and license terms. Your use of these - * subcomponents is subject to the terms and conditions of the - * subcomponent's license, as noted in the LICENSE file. - */ -package uaa - -import com.excilys.ebi.gatling.core.feeder.Feeder - -/** - * Counter-based username generator with fixed password, defaulting to "password". - */ -case class UsernamePasswordFeeder(prefix: String = "shaun", password: String = "password", resetAfter: Int = Config.nUsers) extends Feeder[String] { - var counter = 0 - - def next = { - if (counter == resetAfter) - counter = 1 - counter += 1 - Map("username" -> (prefix + counter), "password" -> password) - } - - def hasNext = true -} - diff --git a/gatling/src/main/scala/uaa/uaaApis.scala b/gatling/src/main/scala/uaa/uaaApis.scala deleted file mode 100644 index 00a3a514fe9..00000000000 --- a/gatling/src/main/scala/uaa/uaaApis.scala +++ /dev/null @@ -1,53 +0,0 @@ -package uaa - -import com.excilys.ebi.gatling.http.Predef._ -import com.excilys.ebi.gatling.core.Predef._ -import uaa.Config._ -import uaa.OAuthComponents._ - -case class User(username: String, password: String) { - override def toString = { - username - } -} - -case class Group(displayName: String, members: Seq[User]) - -case class Client(id: String, secret: String, scopes: Seq[String], resources: Seq[String], authorities: Seq[String], grants: Seq[String] = Seq("client_credentials"), redirectUri: Option[String] = None) { - val toJson = { - val redirectJson = redirectUri match { - case Some(uri) =>"\n\"redirect_uri\" : [\"%s\"],\n".format(uri) - case None => "" - } - """{ - "client_id" : "%s", - "client_secret" : "%s", %s - "scope" : [%s], - "resource_ids" : [%s], - "authorities" : [%s], - "autoapprove": true, - "authorized_grant_types" : [%s] - } - """.format(id, secret, redirectJson, fmt(scopes), fmt(resources), fmt(authorities), fmt(grants)) - } - - private def fmt(seq: Seq[String]) = seq.mkString("\"", "\",\"", "\"") -} - - -object UaaApi { - /** - * Shortcut for getting an access token as the default bootstrap admin client - */ - def adminClientLogin() = - clientCredentialsAccessTokenRequest(admin_client_id, admin_client_secret, admin_client_id) - - - def registerClient(client: Client) = - http("Register Client") - .post("/oauth/clients") - .header("Authorization", "Bearer ${access_token}") - .body(client.toJson) - .asJSON - .check(status is 201) -} diff --git a/gatling/src/test/scala/Engine.scala b/gatling/src/test/scala/Engine.scala deleted file mode 100644 index 4ada44827a2..00000000000 --- a/gatling/src/test/scala/Engine.scala +++ /dev/null @@ -1,13 +0,0 @@ -import com.excilys.ebi.gatling.app.Gatling -import com.excilys.ebi.gatling.core.config.GatlingPropertiesBuilder - -object Engine extends App { - - val props = new GatlingPropertiesBuilder - props.dataDirectory(IDEPathHelper.dataDirectory.toString) - props.resultsDirectory(IDEPathHelper.resultsDirectory.toString) - props.requestBodiesDirectory(IDEPathHelper.requestBodiesDirectory.toString) - props.binariesDirectory(IDEPathHelper.mavenBinariesDirectory.toString) - - Gatling.fromMap(props.build) -} \ No newline at end of file diff --git a/gatling/src/test/scala/IDEPathHelper.scala b/gatling/src/test/scala/IDEPathHelper.scala deleted file mode 100644 index aa6e457a31b..00000000000 --- a/gatling/src/test/scala/IDEPathHelper.scala +++ /dev/null @@ -1,19 +0,0 @@ -import scala.tools.nsc.io.File -import scala.tools.nsc.io.Path.string2path - -object IDEPathHelper { - - val gatlingConfUrl = getClass.getClassLoader.getResource("application.conf").getPath - val projectRootDir = File(gatlingConfUrl).parents(2) - - val mavenSourcesDirectory = projectRootDir / "src" / "test" / "scala" - val mavenResourcesDirectory = projectRootDir / "src" / "test" / "resources" - val mavenTargetDirectory = projectRootDir / "target" - val mavenBinariesDirectory = mavenTargetDirectory / "test-classes" - - val dataDirectory = mavenResourcesDirectory / "data" - val requestBodiesDirectory = mavenResourcesDirectory / "request-bodies" - - val recorderOutputDirectory = mavenSourcesDirectory - val resultsDirectory = mavenTargetDirectory / "results" -} \ No newline at end of file diff --git a/gatling/src/test/scala/Recorder.scala b/gatling/src/test/scala/Recorder.scala deleted file mode 100644 index 670c9e0526b..00000000000 --- a/gatling/src/test/scala/Recorder.scala +++ /dev/null @@ -1,10 +0,0 @@ -import com.excilys.ebi.gatling.recorder.config.RecorderOptions -import com.excilys.ebi.gatling.recorder.controller.RecorderController - -object Recorder extends App { - - RecorderController(new RecorderOptions( - outputFolder = Some(IDEPathHelper.recorderOutputDirectory.toString), - simulationPackage = Some("org.test.gatling"), - requestBodiesFolder = Some(IDEPathHelper.requestBodiesDirectory.toString))) -} \ No newline at end of file diff --git a/login/build.gradle b/login/build.gradle index 20fd849d9f9..a4f462215be 100644 --- a/login/build.gradle +++ b/login/build.gradle @@ -1,6 +1,6 @@ Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } Project identityScim = parent.subprojects.find { it.name.equals('cloudfoundry-identity-scim') } -Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } +Project identityModel = parent.subprojects.find { it.name.equals('cloudfoundry-identity-model') } description = 'CloudFoundry Identity Login' @@ -11,7 +11,7 @@ dependencies { compile(identityScim) { exclude(module: 'jna') } - compile(identityModels) + compile(identityModel) compile group: 'org.springframework', name: 'spring-context-support', version:springVersion provided group: 'javax.servlet', name: 'javax.servlet-api', version:parent.servletVersion diff --git a/models/build.gradle b/model/build.gradle similarity index 94% rename from models/build.gradle rename to model/build.gradle index 7822c55e384..4d1bdb6ca4f 100644 --- a/models/build.gradle +++ b/model/build.gradle @@ -25,7 +25,7 @@ apply from: file('build_properties.gradle') processResources { //maven replaces project.artifactId in the log4j.properties file //https://www.pivotaltracker.com/story/show/74344574 - filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-models') : line } + filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}','cloudfoundry-identity-model') : line } } integrationTest {}.onlyIf { //disable since we don't have any diff --git a/models/build_properties.gradle b/model/build_properties.gradle similarity index 100% rename from models/build_properties.gradle rename to model/build_properties.gradle diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java b/model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java b/model/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateDeserializer.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/impl/JsonDateSerializer.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java b/model/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsRequest.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsResponse.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java b/model/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequest.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/impl/ApprovalsJsonDeserializer.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientConstants.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/SecretChangeRequest.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/ClaimConstants.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java b/model/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChange.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/profile/EmailChangeResponse.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java b/model/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeRequest.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordChangeResponse.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/profile/PasswordResetResponse.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/profile/UserInfoResponse.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/AbstractIdentityProviderDefinition.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/KeystoneIdentityProviderDefinition.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/LdapIdentityProviderDefinition.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/LockoutPolicy.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/PasswordPolicy.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java b/model/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/resources/ActionResult.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java b/model/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResults.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMember.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimMeta.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonDeserializer.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimGroupJsonSerializer.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java b/model/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/util/JsonUtils.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java b/model/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/util/ObjectUtils.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java diff --git a/models/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java similarity index 100% rename from models/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java diff --git a/models/src/main/resources/.gitignore b/model/src/main/resources/.gitignore similarity index 100% rename from models/src/main/resources/.gitignore rename to model/src/main/resources/.gitignore diff --git a/scim/build.gradle b/scim/build.gradle index 2b9080a9c72..8b49270e6cc 100644 --- a/scim/build.gradle +++ b/scim/build.gradle @@ -1,11 +1,11 @@ Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } -Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } +Project identityModel = parent.subprojects.find { it.name.equals('cloudfoundry-identity-model') } description = 'CloudFoundry Identity SCIM' dependencies { compile identityCommon - compile identityModels + compile identityModel compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version:'2.1.2.RELEASE' provided group: 'javax.servlet', name: 'javax.servlet-api', version:parent.servletVersion testCompile identityCommon.configurations.testCompile.dependencies diff --git a/settings.gradle b/settings.gradle index 03b303cdc0e..0f2eebc98a2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ rootProject.name = 'cloudfoundry-identity-parent' -include ':cloudfoundry-identity-models' +include ':cloudfoundry-identity-model' include ':cloudfoundry-identity-client-lib' include ':cloudfoundry-identity-common' include ':cloudfoundry-identity-scim' @@ -9,7 +9,7 @@ include ':cloudfoundry-identity-samples:cloudfoundry-identity-api' include ':cloudfoundry-identity-samples:cloudfoundry-identity-app' include ':cloudfoundry-identity-samples' -project(':cloudfoundry-identity-models').projectDir = "$rootDir/models" as File +project(':cloudfoundry-identity-model').projectDir = "$rootDir/model" as File project(':cloudfoundry-identity-client-lib').projectDir = "$rootDir/client-lib" as File project(':cloudfoundry-identity-common').projectDir = "$rootDir/common" as File project(':cloudfoundry-identity-scim').projectDir = "$rootDir/scim" as File diff --git a/uaa/build.gradle b/uaa/build.gradle index 1058db30627..01b26a38e15 100644 --- a/uaa/build.gradle +++ b/uaa/build.gradle @@ -2,7 +2,7 @@ plugins { id "org.asciidoctor.convert" version "1.5.2" } -Project identityModels = parent.subprojects.find { it.name.equals('cloudfoundry-identity-models') } +Project identityModel = parent.subprojects.find { it.name.equals('cloudfoundry-identity-model') } Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } Project identityScim = parent.subprojects.find { it.name.equals('cloudfoundry-identity-scim') } Project identityLogin = parent.subprojects.find { it.name.equals('cloudfoundry-identity-login') } From 49749bbb85f6ec6f6db5071008541ef11602635b Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Thu, 10 Dec 2015 15:42:44 -0800 Subject: [PATCH 071/103] Add group members endpoints [finishes #109850620] https://www.pivotaltracker.com/story/show/109850620 Signed-off-by: Madhura Bhave --- .../scim/endpoints/ScimGroupEndpoints.java | 34 ++ .../jdbc/JdbcScimGroupMembershipManager.java | 6 +- .../ScimGroupEndpointsMockMvcTests.java | 337 +++++++++++++++--- 3 files changed, 335 insertions(+), 42 deletions(-) diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java index a0fa11596f1..09e9c5b4556 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java @@ -481,6 +481,40 @@ public ScimGroup deleteZoneScope(@PathVariable String userId, return updateGroup(group, group.getId(), String.valueOf(group.getVersion()), httpServletResponse); } + @RequestMapping("/Groups/{groupId}/members/{memberId}") + public ResponseEntity getGroupMembership(@PathVariable String groupId, @PathVariable String memberId) { + ScimGroupMember membership = membershipManager.getMemberById(groupId, memberId); + return new ResponseEntity<>(membership, HttpStatus.OK); + } + + @RequestMapping("/Groups/{groupId}/members") + public ResponseEntity> listGroupMemberships(@PathVariable String groupId) { + dao.retrieve(groupId); + List members = membershipManager.getMembers(groupId); + return new ResponseEntity<>(members, HttpStatus.OK); + } + + @RequestMapping(value = "/Groups/{groupId}/members", method = RequestMethod.PUT) + @ResponseBody + public ScimGroupMember editMemberInGroup(@PathVariable String groupId, @RequestBody ScimGroupMember member) { + return membershipManager.updateMember(groupId, member); + } + + @RequestMapping(value = "/Groups/{groupId}/members", method = RequestMethod.POST) + @ResponseStatus(HttpStatus.CREATED) + @ResponseBody + public ScimGroupMember addMemberToGroup(@PathVariable String groupId, @RequestBody ScimGroupMember member) { + + return membershipManager.addMember(groupId, member); + } + @RequestMapping(value = "/Groups/{groupId}/members/{memberId}", method = RequestMethod.DELETE) + @ResponseBody + @ResponseStatus(HttpStatus.OK) + public ScimGroupMember deleteGroupMembership(@PathVariable String groupId, @PathVariable String memberId) { + ScimGroupMember membership = membershipManager.removeMemberById(groupId, memberId); + return membership; + } + @ExceptionHandler public View handleException(Exception t, HttpServletRequest request) throws ScimException { ScimException e = new ScimException("Unexpected error", t, HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java index 6349a439ece..ae4ce336b95 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java +++ b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java @@ -294,7 +294,7 @@ public ScimGroupMember getMemberById(String groupId, String memberId) throws Sci ScimGroupMember u = jdbcTemplate.queryForObject(GET_MEMBER_SQL, rowMapper, memberId, groupId, IdentityZoneHolder.get().getId()); return u; } catch (EmptyResultDataAccessException e) { - throw new MemberNotFoundException("Member " + memberId + " does not exist in group " + groupId); + throw new MemberNotFoundException("Member " + memberId + " does not exist in group " + groupId); } } @@ -312,6 +312,10 @@ public void setValues(PreparedStatement ps) throws SQLException { } }); + if(updated == 0) { + throw new MemberNotFoundException("Member " + member.getMemberId() + " does not exist in group " + groupId); + } + if (updated != 1) { throw new IncorrectResultSizeDataAccessException("unexpected number of members updated", 1, updated); } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index 49c1e245873..71e614a0a61 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.endpoints; +import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; @@ -34,10 +35,10 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; +import org.hamcrest.Matcher; +import org.junit.After; import org.junit.Assert; -import org.junit.Assume; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -49,26 +50,28 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import javax.sql.DataSource; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -91,6 +94,10 @@ public class ScimGroupEndpointsMockMvcTests extends InjectedMockContextTest { private String clientId; private String clientSecret; private TestClient testClient; + private JdbcTemplate template; + private ScimExternalGroupBootstrap bootstrap; + + private List ephemeralUserIds = new ArrayList<>(); @Before public void setUp() throws Exception { @@ -98,9 +105,16 @@ public void setUp() throws Exception { originalDefaultExternalMembers = (List) getWebApplicationContext().getBean("defaultExternalMembers"); originalDatabaseExternalMembers = getWebApplicationContext().getBean(JdbcScimGroupExternalMembershipManager.class).query(""); } - JdbcTemplate template = getWebApplicationContext().getBean(JdbcTemplate.class); + + if(bootstrap == null){ + bootstrap = getWebApplicationContext().getBean(ScimExternalGroupBootstrap.class); + } + + if(template == null) { + template = getWebApplicationContext().getBean(JdbcTemplate.class); + } + template.update("delete from external_group_mapping"); - ScimExternalGroupBootstrap bootstrap = getWebApplicationContext().getBean(ScimExternalGroupBootstrap.class); bootstrap.afterPropertiesSet(); testClient = new TestClient(getMockMvc()); @@ -121,6 +135,14 @@ public void setUp() throws Exception { identityClientToken = testClient.getClientCredentialsOAuthAccessToken("identity","identitysecret",""); } + @After + public void cleanUp() { + for(String userId : ephemeralUserIds) { + template.update("delete from group_membership where member_id = ? and member_type = 'USER'", userId); + } + ephemeralUserIds.clear(); + } + @Test public void testIdentityClientManagesZoneAdmins() throws Exception { IdentityZone zone = utils().createZoneUsingWebRequest(getMockMvc(), identityClientToken); @@ -236,7 +258,7 @@ private ResultActions[] addAndDeleteMemberstoZoneManagementGroups(String display private ResultActions deleteZoneScope(IdentityZone zone, ScimGroup group) throws Exception { String removeS = String.format("zones.%s.", zone.getId()); String scope = group.getDisplayName().substring(removeS.length()); - MockHttpServletRequestBuilder delete = delete("/Groups/zones/{userId}/{zoneId}/{scope}", scimUser.getId(), zone.getId(),scope) + MockHttpServletRequestBuilder delete = delete("/Groups/zones/{userId}/{zoneId}/{scope}", scimUser.getId(), zone.getId(), scope) .accept(APPLICATION_JSON) .header("Authorization", "Bearer " + identityClientToken); return getMockMvc().perform(delete); @@ -303,27 +325,27 @@ public void testGroupOperations_as_Zone_Admin() throws Exception { ScimGroup.class)); } - @Test - @Ignore //we only create DB once - so can no longer run - public void testDBisDownDuringCreate() throws Exception { - for (String s : getWebApplicationContext().getEnvironment().getActiveProfiles()) { - Assume.assumeFalse("Does not run during MySQL", "mysql".equals(s)); - Assume.assumeFalse("Does not run during PostgreSQL", "postgresql".equals(s)); - } - String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com"; - String displayName ="internal.read"; - DataSource ds = getWebApplicationContext().getBean(DataSource.class); - new JdbcTemplate(ds).execute("SHUTDOWN"); - Method close = ds.getClass().getMethod("close"); - Assert.assertNotNull(close); - close.invoke(ds); - ResultActions result = createGroup(null, displayName, externalGroup); - result.andExpect(status().isServiceUnavailable()); - } +// @Test +// @Ignore //we only create DB once - so can no longer run +// public void testDBisDownDuringCreate() throws Exception { +// for (String s : getWebApplicationContext().getEnvironment().getActiveProfiles()) { +// Assume.assumeFalse("Does not run during MySQL", "mysql".equals(s)); +// Assume.assumeFalse("Does not run during PostgreSQL", "postgresql".equals(s)); +// } +// String externalGroup = "cn=developers,ou=scopes,dc=test,dc=com"; +// String displayName ="internal.read"; +// DataSource ds = getWebApplicationContext().getBean(DataSource.class); +// new JdbcTemplate(ds).execute("SHUTDOWN"); +// Method close = ds.getClass().getMethod("close"); +// Assert.assertNotNull(close); +// close.invoke(ds); +// ResultActions result = createGroup(null, displayName, externalGroup); +// result.andExpect(status().isServiceUnavailable()); +// } @Test public void getGroups_withScimReadTokens_returnsOkWithResults() throws Exception { - MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups") + MockHttpServletRequestBuilder get = get("/Groups") .header("Authorization", "Bearer " + scimReadToken) .param("attributes", "displayName") .param("filter", "displayName co \"scim\"") @@ -336,7 +358,7 @@ public void getGroups_withScimReadTokens_returnsOkWithResults() throws Exception SearchResults searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); assertThat(searchResults.getResources().size(), is(5)); - get = MockMvcRequestBuilders.get("/Groups") + get = get("/Groups") .header("Authorization", "Bearer " + scimReadUserToken) .param("attributes", "displayName") .param("filter", "displayName co \"scim\"") @@ -349,7 +371,7 @@ public void getGroups_withScimReadTokens_returnsOkWithResults() throws Exception searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); assertThat(searchResults.getResources().size(), is(5)); - get = MockMvcRequestBuilders.get("/Groups") + get = get("/Groups") .header("Authorization", "Bearer " + scimReadToken) .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON); @@ -360,7 +382,7 @@ public void getGroups_withScimReadTokens_returnsOkWithResults() throws Exception searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); assertThat(searchResults.getResources().size(), is(greaterThanOrEqualTo(15))); - get = MockMvcRequestBuilders.get("/Groups") + get = get("/Groups") .header("Authorization", "Bearer " + scimReadUserToken) .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON); @@ -399,7 +421,7 @@ subdomain, getMockMvc(), getWebApplicationContext(), bootstrapClient .content(JsonUtils.writeValueAsString(group2))) .andExpect(status().isCreated()); - MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups") + MockHttpServletRequestBuilder get = get("/Groups") .header("Authorization", "Bearer " + result.getZoneAdminToken()) .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) .param("attributes", "displayName") @@ -413,7 +435,7 @@ subdomain, getMockMvc(), getWebApplicationContext(), bootstrapClient SearchResults searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); assertThat(searchResults.getResources().size(), is(1)); - get = MockMvcRequestBuilders.get("/Groups") + get = get("/Groups") .header("Authorization", "Bearer " + result.getZoneAdminToken()) .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) .contentType(MediaType.APPLICATION_JSON) @@ -431,7 +453,7 @@ public void getGroupsInOtherZone_withZoneUserToken_returnsOkWithResults() throws String subdomain = new RandomValueStringGenerator(8).generate(); BaseClientDetails bootstrapClient = null; MockMvcUtils.IdentityZoneCreationResult result = utils().createOtherIdentityZoneAndReturnResult( - subdomain, getMockMvc(), getWebApplicationContext(), bootstrapClient + subdomain, getMockMvc(), getWebApplicationContext(), bootstrapClient ); String zonedClientId = "zonedClientId"; @@ -454,7 +476,7 @@ subdomain, getMockMvc(), getWebApplicationContext(), bootstrapClient TestClient.OAuthToken oauthToken = JsonUtils.readValue(tokenResult.getResponse().getContentAsString(), TestClient.OAuthToken.class); String zoneUserToken = oauthToken.accessToken; - MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups") + MockHttpServletRequestBuilder get = get("/Groups") .with(new SetServerNameRequestPostProcessor(result.getIdentityZone().getSubdomain() + ".localhost")) .header("Authorization", "Bearer " + zoneUserToken) // .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) @@ -469,7 +491,7 @@ subdomain, getMockMvc(), getWebApplicationContext(), bootstrapClient SearchResults searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); assertThat(searchResults.getResources().size(), is(1)); - get = MockMvcRequestBuilders.get("/Groups") + get = get("/Groups") .with(new SetServerNameRequestPostProcessor(result.getIdentityZone().getSubdomain() + ".localhost")) .header("Authorization", "Bearer " + zoneUserToken) // .header(IdentityZoneSwitchingFilter.HEADER, result.getIdentityZone().getId()) @@ -485,7 +507,7 @@ subdomain, getMockMvc(), getWebApplicationContext(), bootstrapClient @Test public void testGetGroupsInvalidFilter() throws Exception { - MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups") + MockHttpServletRequestBuilder get = get("/Groups") .header("Authorization", "Bearer " + scimReadToken) .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON) @@ -494,7 +516,7 @@ public void testGetGroupsInvalidFilter() throws Exception { getMockMvc().perform(get) .andExpect(status().isBadRequest()); - get = MockMvcRequestBuilders.get("/Groups") + get = get("/Groups") .header("Authorization", "Bearer " + scimReadUserToken) .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON) @@ -506,7 +528,7 @@ public void testGetGroupsInvalidFilter() throws Exception { @Test public void testGetGroupsInvalidAttributes() throws Exception { - MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups") + MockHttpServletRequestBuilder get = get("/Groups") .header("Authorization", "Bearer " + scimReadToken) .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON) @@ -515,7 +537,7 @@ public void testGetGroupsInvalidAttributes() throws Exception { getMockMvc().perform(get) .andExpect(status().isBadRequest()); - get = MockMvcRequestBuilders.get("/Groups") + get = get("/Groups") .header("Authorization", "Bearer " + scimReadUserToken) .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON) @@ -764,8 +786,240 @@ public void testGetExternalGroupsFilter() throws Exception { } + @Test + public void get_group_membership() throws Exception { + String groupId = getGroupId("scim.read"); + MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/" + scimUser.getId()) + .header("Authorization", "Bearer " + scimReadToken); + MvcResult mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + ScimGroupMember scimGroupMember = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), ScimGroupMember.class); + Assert.assertNotNull(scimGroupMember); + assertEquals(scimUser.getId(), scimGroupMember.getMemberId()); + } + + @Test + public void get_group_membership_user_not_member_of_group() throws Exception { + String groupId = getGroupId("scim.read"); + MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/id-of-random-user") + .header("Authorization", "Bearer " + scimReadToken); + getMockMvc().perform(get) + .andExpect(status().isNotFound()) + .andReturn(); + } + + @Test + public void get_group_membership_nonexistent_group() throws Exception { + MockHttpServletRequestBuilder get = get("/Groups/nonexistent-group-id/members/" + scimUser.getId()) + .header("Authorization", "Bearer " + scimReadToken); + getMockMvc().perform(get) + .andExpect(status().isNotFound()) + .andReturn(); + } + + @Test + public void get_group_membership_nonexistent_user() throws Exception { + String groupId = getGroupId("scim.read"); + MockHttpServletRequestBuilder get = get("/Groups/" + groupId+ "/members/non-existent-user") + .header("Authorization", "Bearer " + scimReadToken); + getMockMvc().perform(get) + .andExpect(status().isNotFound()) + .andReturn(); + } + + @Test + public void get_all_group_memberships() throws Exception { + String groupId = getGroupId("scim.write"); + ScimUser secondUser = createUserAndAddToGroups(IdentityZone.getUaa(), new HashSet(Arrays.asList("scim.write"))); + + MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/") + .header("Authorization", "Bearer " + scimReadToken); + MvcResult mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + String responseContent = mvcResult.getResponse().getContentAsString(); + Set retrievedMembers = ((List>) JsonUtils.readValue(responseContent, new TypeReference>>() {})) + .stream().map(m -> JsonUtils.writeValueAsString(m)).collect(Collectors.toSet()); + + Matcher> containsExpectedMembers = containsInAnyOrder( + JsonUtils.writeValueAsString(new ScimGroupMember(secondUser.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER))), + JsonUtils.writeValueAsString(new ScimGroupMember(scimUser.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER))) + ); + + Assert.assertThat(retrievedMembers, containsExpectedMembers); + } + + @Test + public void get_group_memberships_for_nonexistent_group() throws Exception { + MockHttpServletRequestBuilder get = get("/Groups/nonexistent-group-id/members/") + .header("Authorization", "Bearer " + scimReadToken); + getMockMvc().perform(get) + .andExpect(status().isNotFound()) + .andReturn(); + } + + @Test + public void add_member_to_group() throws Exception { + ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET); + String groupId = getGroupId("scim.read"); + ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + MockHttpServletRequestBuilder post = post("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember)); + String responseBody = getMockMvc().perform(post) + .andExpect(status().isCreated()) + .andReturn().getResponse().getContentAsString(); + assertEquals(JsonUtils.writeValueAsString(scimGroupMember), responseBody); + } + + @Test + public void add_member_to_group_twice() throws Exception { + ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET); + String groupId = getGroupId("scim.read"); + ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + getMockMvc().perform(post("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember))) + .andExpect(status().isCreated()); + + scimGroupMember.setRoles(Arrays.asList(ScimGroupMember.Role.WRITER)); + getMockMvc().perform(post("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember))) + .andExpect(status().isConflict()); + } + + @Test + public void update_member_in_group() throws Exception { + ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton("scim.read")); + String groupId = getGroupId("scim.read"); + ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + + scimGroupMember.setRoles(Arrays.asList(ScimGroupMember.Role.WRITER)); + String updatedMember = JsonUtils.writeValueAsString(scimGroupMember); + getMockMvc().perform(put("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(updatedMember)) + .andExpect(status().isOk()); + Assert.assertNotNull(updatedMember); + + MockHttpServletRequestBuilder get = get("/Groups/" + groupId + "/members/" + scimGroupMember.getMemberId()) + .header("Authorization", "Bearer " + scimReadToken); + MvcResult mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); + String getResponse = mvcResult.getResponse().getContentAsString(); + assertEquals(updatedMember, getResponse); + } + + @Test + public void update_member_in_nonexistent_group() throws Exception { + ScimGroupMember scimGroupMember = new ScimGroupMember(scimUser.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + + getMockMvc().perform(put("/Groups/nonexistent-group-id/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember))) + .andExpect(status().isNotFound()); + } + + @Test + public void update_member_does_not_exist_in_group() throws Exception { + ScimGroupMember scimGroupMember = new ScimGroupMember(scimUser.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + + String groupId = getGroupId("acme"); + getMockMvc().perform(put("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember))) + .andExpect(status().isNotFound()); + } + + @Test + public void update_nonexistent_user() throws Exception { + ScimGroupMember scimGroupMember = new ScimGroupMember("non-existent-user", ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + + String groupId = getGroupId("scim.read"); + getMockMvc().perform(put("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember))) + .andExpect(status().isNotFound()); + } + + @Test + public void delete_member_from_group() throws Exception { + ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton("scim.read")); + String groupId = getGroupId("scim.read"); + + String deleteResponseBody = getMockMvc().perform(delete("/Groups/" + groupId + "/members/" + user.getId()) + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + + ScimGroupMember deletedMember = JsonUtils.readValue(deleteResponseBody, ScimGroupMember.class); + + assertEquals(user.getId(), deletedMember.getMemberId()); + } + + @Test + public void delete_member_from_nonexistent_group() throws Exception { + ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.singleton("scim.read")); + + getMockMvc().perform(delete("/Groups/nonexistent-group/members/" + user.getId()) + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + public void delete_user_not_member_of_group() throws Exception { + String groupId = getGroupId("acme"); + getMockMvc().perform(delete("/Groups/" + groupId + "/members/" + scimUser.getId()) + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + public void delete_nonexistent_user() throws Exception { + getMockMvc().perform(delete("/Groups/nonexistent-group/members/non-existent-user") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE)) + .andExpect(status().isNotFound()); + } + + @Test + public void add_member_to_nonexistent_group() throws Exception { + ScimUser user = createUserAndAddToGroups(IdentityZone.getUaa(), Collections.EMPTY_SET); + ScimGroupMember scimGroupMember = new ScimGroupMember(user.getId(), ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + MockHttpServletRequestBuilder post = post("/Groups/nonexistent-group-id/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember)); + getMockMvc().perform(post) + .andExpect(status().isNotFound()); + } + + @Test + public void add_nonexistent_user_to_group() throws Exception { + String groupId = getGroupId("scim.read"); + ScimGroupMember scimGroupMember = new ScimGroupMember("random-user-id", ScimGroupMember.Type.USER, Arrays.asList(ScimGroupMember.Role.MEMBER, ScimGroupMember.Role.READER)); + MockHttpServletRequestBuilder post = post("/Groups/" + groupId + "/members") + .header("Authorization", "Bearer " + scimWriteToken) + .header("Content-Type", APPLICATION_JSON_VALUE) + .content(JsonUtils.writeValueAsString(scimGroupMember)); + getMockMvc().perform(post) + .andExpect(status().isNotFound()); + } + protected void checkGetExternalGroupsFilter(String fieldName, String fieldValue) throws Exception { - MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups/External") + MockHttpServletRequestBuilder get = get("/Groups/External") .param("filter", fieldName+" co \""+fieldValue+"\"") .header("Authorization", "Bearer " + scimReadToken) .accept(APPLICATION_JSON); @@ -773,7 +1027,7 @@ protected void checkGetExternalGroupsFilter(String fieldName, String fieldValue) ResultActions result = getMockMvc().perform(get); result.andExpect(status().isOk()); String content = result.andReturn().getResponse().getContentAsString(); - SearchResults members = null; + SearchResults members; Map map = JsonUtils.readValue(content, Map.class); List> resources = (List>)map.get("resources"); @@ -838,7 +1092,7 @@ public void testGetExternalGroupsPagination() throws Exception { } protected void checkGetExternalGroupsPagination(int start, int count) throws Exception { - MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups/External") + MockHttpServletRequestBuilder get = get("/Groups/External") .param("startIndex",String.valueOf(start)) .param("count", String.valueOf(count)) .header("Authorization", "Bearer " + scimReadToken) @@ -888,7 +1142,7 @@ protected void checkGetExternalGroups() throws Exception { checkGetExternalGroups(path); } protected void checkGetExternalGroups(String path) throws Exception { - MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get(path) + MockHttpServletRequestBuilder get = get(path) .header("Authorization", "Bearer " + scimReadToken) .accept(APPLICATION_JSON); @@ -978,6 +1232,7 @@ private ScimUser createUserAndAddToGroups(IdentityZone zone, Set groupNa IdentityZoneHolder.set(zone); } user = usersRepository.createUser(user, "password"); + ephemeralUserIds.add(user.getId()); Collection scimUserGroups = new LinkedList<>(); for (String groupName : groupNames) { From 11474370fcce8b1d5814547737bd92750f4aa797 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Mon, 14 Dec 2015 11:45:17 -0700 Subject: [PATCH 072/103] Align SAML configuration defaults between default zone and newly created zones https://www.pivotaltracker.com/story/show/109996940 [#109996940] --- .../IdentityZoneConfigurationTests.java | 10 ++++-- model/build.gradle | 2 +- .../identity/uaa/zone/SamlConfig.java | 4 +-- .../identity/uaa/zone/SamlConfigTest.java | 33 +++++++++++++++++++ uaa/src/main/resources/login.yml | 2 +- .../webapp/WEB-INF/spring/saml-providers.xml | 2 +- .../identity/uaa/login/BootstrapTests.java | 19 ++++++++--- 7 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 models/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java index e6482e8e2b3..105d8d51c41 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java @@ -55,14 +55,20 @@ public void testDeserialize_Without_SamlConfig() { String s = JsonUtils.writeValueAsString(definition); s = s.replace(",\"samlConfig\":{\"requestSigned\":false,\"wantAssertionSigned\":false}",""); definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); - assertFalse(definition.getSamlConfig().isRequestSigned()); - assertFalse(definition.getSamlConfig().isWantAssertionSigned()); + assertTrue(definition.getSamlConfig().isRequestSigned()); + assertTrue(definition.getSamlConfig().isWantAssertionSigned()); definition.getSamlConfig().setWantAssertionSigned(true); definition.getSamlConfig().setRequestSigned(true); s = JsonUtils.writeValueAsString(definition); definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); assertTrue(definition.getSamlConfig().isRequestSigned()); assertTrue(definition.getSamlConfig().isWantAssertionSigned()); + definition.getSamlConfig().setWantAssertionSigned(false); + definition.getSamlConfig().setRequestSigned(false); + s = JsonUtils.writeValueAsString(definition); + definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); + assertFalse(definition.getSamlConfig().isRequestSigned()); + assertFalse(definition.getSamlConfig().isWantAssertionSigned()); } } diff --git a/model/build.gradle b/model/build.gradle index 4d1bdb6ca4f..deaecf87115 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -17,7 +17,7 @@ dependencies { compile group: 'org.slf4j', name: 'slf4j-log4j12', version:'1.7.7' compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.7' - + testCompile group: 'junit', name: 'junit', version:parent.junitVersion } apply from: file('build_properties.gradle') diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java index 347ae1db043..6549c6dedf3 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java @@ -15,8 +15,8 @@ package org.cloudfoundry.identity.uaa.zone; public class SamlConfig { - private boolean requestSigned = false; - private boolean wantAssertionSigned = false; + private boolean requestSigned = true; + private boolean wantAssertionSigned = true; private String certificate; private String privateKey; diff --git a/models/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java b/models/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java new file mode 100644 index 00000000000..5969f06bd61 --- /dev/null +++ b/models/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java @@ -0,0 +1,33 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.zone; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +public class SamlConfigTest { + + @Test + public void testIsRequestSigned() throws Exception { + assertTrue(new SamlConfig().isRequestSigned()); + + } + + @Test + public void testIsWantAssertionSigned() throws Exception { + assertTrue(new SamlConfig().isWantAssertionSigned()); + } +} \ No newline at end of file diff --git a/uaa/src/main/resources/login.yml b/uaa/src/main/resources/login.yml index d72f66196e9..0c3a66186b2 100644 --- a/uaa/src/main/resources/login.yml +++ b/uaa/src/main/resources/login.yml @@ -115,7 +115,7 @@ login: signMetaData: true #Local/SP metadata - requests signed signRequest: true - #Local/SP metadata - requests signed + #Local/SP metadata - want incoming assertions signed #wantAssertionSigned: true socket: # URL metadata fetch - pool timeout diff --git a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml index 384f996b69a..7edebc61dfc 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml @@ -137,7 +137,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index f408b5aac08..1834406d3ae 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -17,23 +17,24 @@ import org.apache.tomcat.jdbc.pool.DataSource; import org.cloudfoundry.identity.uaa.authentication.login.Prompt; import org.cloudfoundry.identity.uaa.authentication.manager.PeriodLockoutPolicy; -import org.cloudfoundry.identity.uaa.zone.KeyPair; -import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; -import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; -import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.cloudfoundry.identity.uaa.config.YamlServletProfileInitializer; import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.login.saml.ZoneAwareMetadataGenerator; import org.cloudfoundry.identity.uaa.login.util.FakeJavaMailSender; import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices; import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenStore; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.rest.jdbc.SimpleSearchQueryConverter; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneResolvingFilter; +import org.cloudfoundry.identity.uaa.zone.KeyPair; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; @@ -201,6 +202,9 @@ public void testRootContextDefaults() throws Exception { passcode = prompts.get(2); assertEquals("One Time Code ( Get one at http://localhost:8080/uaa/passcode )",passcode.getDetails()[1]); + ZoneAwareMetadataGenerator zoneAwareMetadataGenerator = context.getBean(ZoneAwareMetadataGenerator.class); + assertTrue(zoneAwareMetadataGenerator.isRequestSigned()); + assertTrue(zoneAwareMetadataGenerator.isWantAssertionSigned()); } @Test @@ -660,6 +664,11 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throw public RequestDispatcher getNamedDispatcher(String path) { return new MockRequestDispatcher("/"); } + + @Override + public String getVirtualServerName() { + return "localhost"; + } }; context.setServletContext(servletContext); MockServletConfig servletConfig = new MockServletConfig(servletContext); From 1374e71bb8575830b24815577bd1d40f855538c8 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 15 Dec 2015 07:29:08 -0700 Subject: [PATCH 073/103] Add passphrase to token signing key configuration https://www.pivotaltracker.com/story/show/109998034 [#109998034] --- ...entityZoneConfigurationBootstrapTests.java | 44 ++++++++++++++++++- .../identity/uaa/zone/KeyPair.java | 28 +++++++++--- .../identity/uaa/zone/KeyPairsMap.java | 10 +++-- 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java index 15e5d5d5539..7c5096dda26 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -5,10 +5,15 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.KeyPair; import org.cloudfoundry.identity.uaa.zone.TokenPolicy; -import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + /******************************************************************************* * Cloud Foundry * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. @@ -23,18 +28,53 @@ *******************************************************************************/ public class IdentityZoneConfigurationBootstrapTests extends JdbcTestBase { + public static final String PRIVATE_KEY = + "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXAIBAAKBgQDErZsZY70QAa7WdDD6eOv3RLBA4I5J0zZOiXMzoFB5yh64q0sm\n" + + "ESNtV4payOYE5TnHxWjMo0y7gDsGjI1omAG6wgfyp63I9WcLX7FDLyee43fG5+b9\n" + + "roofosL+OzJSXESSulsT9Y1XxSFFM5RMu4Ie9uM4/izKLCsAKiggMhnAmQIDAQAB\n" + + "AoGAAs2OllALk7zSZxAE2qz6f+2krWgF3xt5fKkM0UGJpBKzWWJnkcVQwfArcpvG\n" + + "W2+A4U347mGtaEatkKxUH5d6/s37jfRI7++HFXcLf6QJPmuE3+FtB2mX0lVJoaJb\n" + + "RLh+tOtt4ZJRAt/u6RjUCVNpDnJB6NZ032bpL3DijfNkRuECQQDkJR+JJPUpQGoI\n" + + "voPqcLl0i1tLX93XE7nu1YuwdQ5SmRaS0IJMozoBLBfFNmCWlSHaQpBORc38+eGC\n" + + "J9xsOrBNAkEA3LD1JoNI+wPSo/o71TED7BoVdwCXLKPqm0TnTr2EybCUPLNoff8r\n" + + "Ngm51jXc8mNvUkBtYiPfMKzpdqqFBWXXfQJAQ7D0E2gAybWQAHouf7/kdrzmYI3Y\n" + + "L3lt4HxBzyBcGIvNk9AD6SNBEZn4j44byHIFMlIvqNmzTY0CqPCUyRP8vQJBALXm\n" + + "ANmygferKfXP7XsFwGbdBO4mBXRc0qURwNkMqiMXMMdrVGftZq9Oiua9VJRQUtPn\n" + + "mIC4cmCLVI5jc+qEC30CQE+eOXomzxNNPxVnIp5k5f+savOWBBu83J2IoT2znnGb\n" + + "wTKZHjWybPHsW2q8Z6Moz5dvE+XMd11c5NtIG2/L97I=\n" + + "-----END RSA PRIVATE KEY-----"; + public static final String PUBLIC_KEY = + "-----BEGIN RSA PUBLIC KEY-----\n" + + "MIGJAoGBAMStmxljvRABrtZ0MPp46/dEsEDgjknTNk6JczOgUHnKHrirSyYRI21X\n" + + "ilrI5gTlOcfFaMyjTLuAOwaMjWiYAbrCB/Knrcj1ZwtfsUMvJ57jd8bn5v2uih+i\n" + + "wv47MlJcRJK6WxP1jVfFIUUzlEy7gh724zj+LMosKwAqKCAyGcCZAgMBAAE=\n" + + "-----END RSA PUBLIC KEY-----"; + + public static final String PASSWORD = "password"; + + public static final String ID = "id"; + + @Test public void tokenPolicy_configured_fromValuesInYaml() throws Exception { IdentityZoneProvisioning provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); IdentityZoneConfigurationBootstrap bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); TokenPolicy tokenPolicy = new TokenPolicy(); + KeyPair key = new KeyPair(PRIVATE_KEY, PUBLIC_KEY, PASSWORD); + Map keys = new HashMap<>(); + keys.put(ID, key); + tokenPolicy.setKeys(keys); tokenPolicy.setAccessTokenValidity(3600); bootstrap.setTokenPolicy(tokenPolicy); bootstrap.afterPropertiesSet(); IdentityZone zone = provisioning.retrieve(IdentityZone.getUaa().getId()); IdentityZoneConfiguration definition = zone.getConfig(); - Assert.assertEquals(3600, definition.getTokenPolicy().getAccessTokenValidity()); + assertEquals(3600, definition.getTokenPolicy().getAccessTokenValidity()); + assertEquals(PASSWORD, definition.getTokenPolicy().getKeys().get(ID).getSigningKeyPassword()); + assertEquals(PUBLIC_KEY, definition.getTokenPolicy().getKeys().get(ID).getVerificationKey()); + assertEquals(PRIVATE_KEY, definition.getTokenPolicy().getKeys().get(ID).getSigningKey()); } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java index 73a9072bfff..67456a9efb6 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java @@ -19,26 +19,36 @@ import java.util.HashMap; import java.util.UUID; -/** - * Created by pivotal on 11/11/15. - */ - public class KeyPair { + public static final String SIGNING_KEY = "signingKey"; + public static final String SIGNING_KEY_PASSWORD = "signingKeyPassword"; + public static final String VERIFICATION_KEY = "verificationKey"; + private UUID id; private String verificationKey = new RandomValueStringGenerator().generate(); private String signingKey = verificationKey; + private String signingKeyPassword; public KeyPair() { } public KeyPair(HashMap keymap) { - this(keymap.get("signingKey"), keymap.get("verificationKey")); + this( + keymap.get(SIGNING_KEY), + keymap.get(VERIFICATION_KEY), + keymap.get(SIGNING_KEY_PASSWORD) + ); } public KeyPair(String signingKey, String verificationKey) { + this(signingKey, verificationKey, null); + } + + public KeyPair(String signingKey, String verificationKey, String signingKeyPassword) { this.signingKey = signingKey; this.verificationKey = verificationKey; + this.signingKeyPassword = signingKeyPassword; } public UUID getId() { return id; } @@ -60,4 +70,12 @@ public String getVerificationKey() { public void setVerificationKey(String verificationKey) { this.verificationKey = verificationKey; } + + public String getSigningKeyPassword() { + return signingKeyPassword; + } + + public void setSigningKeyPassword(String signingKeyPassword) { + this.signingKeyPassword = signingKeyPassword; + } } diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java index 355ae0009ce..91ed7bf365e 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java @@ -17,11 +17,13 @@ import java.util.HashMap; import java.util.Map; -/** - * Created by pivotal on 11/11/15. - */ +import static org.cloudfoundry.identity.uaa.zone.KeyPair.SIGNING_KEY; +import static org.cloudfoundry.identity.uaa.zone.KeyPair.SIGNING_KEY_PASSWORD; +import static org.cloudfoundry.identity.uaa.zone.KeyPair.VERIFICATION_KEY; + public class KeyPairsMap { + private Map keys; public KeyPairsMap(Map> unparsedMap) { @@ -29,7 +31,7 @@ public KeyPairsMap(Map> unparsedMap) { for (String kid : unparsedMap.keySet()) { Map keys = unparsedMap.get(kid); - KeyPair keyPair = new KeyPair(keys.get("signingKey"), keys.get("verificationKey")); + KeyPair keyPair = new KeyPair(keys.get(SIGNING_KEY), keys.get(VERIFICATION_KEY), keys.get(SIGNING_KEY_PASSWORD)); this.keys.put(kid, keyPair); } } From ad176134037744dbfe40255fd504190893d15cb7 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 15 Dec 2015 07:38:30 -0700 Subject: [PATCH 074/103] Add private key password to zone saml configuration https://www.pivotaltracker.com/story/show/109998034 [#109998034] --- .../identity/uaa/zone/SamlConfig.java | 9 ++++ .../identity/uaa/zone/SamlConfigTest.java | 20 +++++++- .../saml/SamlIDPRefreshMockMvcTests.java | 49 +++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java index 6549c6dedf3..acb7765d1e7 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java @@ -19,6 +19,7 @@ public class SamlConfig { private boolean wantAssertionSigned = true; private String certificate; private String privateKey; + private String privateKeyPassword; public boolean isRequestSigned() { return requestSigned; @@ -51,4 +52,12 @@ public String getCertificate() { public String getPrivateKey() { return privateKey; } + + public String getPrivateKeyPassword() { + return privateKeyPassword; + } + + public void setPrivateKeyPassword(String privateKeyPassword) { + this.privateKeyPassword = privateKeyPassword; + } } diff --git a/models/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java b/models/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java index 5969f06bd61..a7c06c5cefd 100644 --- a/models/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java +++ b/models/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java @@ -14,20 +14,36 @@ package org.cloudfoundry.identity.uaa.zone; +import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class SamlConfigTest { + SamlConfig config; + + @Before + public void setUp() { + config = new SamlConfig(); + } + @Test public void testIsRequestSigned() throws Exception { - assertTrue(new SamlConfig().isRequestSigned()); + assertTrue(config.isRequestSigned()); } @Test public void testIsWantAssertionSigned() throws Exception { - assertTrue(new SamlConfig().isWantAssertionSigned()); + assertTrue(config.isWantAssertionSigned()); + } + + @Test + public void testSetPassphrase() { + String passphrase = "password"; + config.setPrivateKeyPassword(passphrase); + assertEquals(passphrase, config.getPrivateKeyPassword()); } } \ No newline at end of file diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java index b6d7ffb3548..4d52ba9b2d1 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java @@ -68,6 +68,47 @@ public class SamlIDPRefreshMockMvcTests extends InjectedMockContextTest { private SamlIdentityProviderConfigurator configurator; + private final String serviceProviderKey = + "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIICXQIBAAKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5\n" + + "L39WqS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vA\n" + + "fpOwznoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQAB\n" + + "AoGAVOj2Yvuigi6wJD99AO2fgF64sYCm/BKkX3dFEw0vxTPIh58kiRP554Xt5ges\n" + + "7ZCqL9QpqrChUikO4kJ+nB8Uq2AvaZHbpCEUmbip06IlgdA440o0r0CPo1mgNxGu\n" + + "lhiWRN43Lruzfh9qKPhleg2dvyFGQxy5Gk6KW/t8IS4x4r0CQQD/dceBA+Ndj3Xp\n" + + "ubHfxqNz4GTOxndc/AXAowPGpge2zpgIc7f50t8OHhG6XhsfJ0wyQEEvodDhZPYX\n" + + "kKBnXNHzAkEAyCA76vAwuxqAd3MObhiebniAU3SnPf2u4fdL1EOm92dyFs1JxyyL\n" + + "gu/DsjPjx6tRtn4YAalxCzmAMXFSb1qHfwJBAM3qx3z0gGKbUEWtPHcP7BNsrnWK\n" + + "vw6By7VC8bk/ffpaP2yYspS66Le9fzbFwoDzMVVUO/dELVZyBnhqSRHoXQcCQQCe\n" + + "A2WL8S5o7Vn19rC0GVgu3ZJlUrwiZEVLQdlrticFPXaFrn3Md82ICww3jmURaKHS\n" + + "N+l4lnMda79eSp3OMmq9AkA0p79BvYsLshUJJnvbk76pCjR28PK4dV1gSDUEqQMB\n" + + "qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/\n" + + "-----END RSA PRIVATE KEY-----"; + + private final String serviceProviderKeyPassword = "password"; + + private final String serviceProviderCertificate = + "-----BEGIN CERTIFICATE-----\n" + + "MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO\n" + + "MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO\n" + + "MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h\n" + + "cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx\n" + + "CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM\n" + + "BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb\n" + + "BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN\n" + + "ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W\n" + + "qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw\n" + + "znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha\n" + + "MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc\n" + + "gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD\n" + + "VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD\n" + + "VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh\n" + + "QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ\n" + + "0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC\n" + + "KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK\n" + + "RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=\n" + + "-----END CERTIFICATE-----\n"; + @Before public void setUpContext() throws Exception { SecurityContextHolder.clearContext(); @@ -368,6 +409,10 @@ public void test_zone_saml_properties() throws Exception { SamlConfig config1 = new SamlConfig(); config1. setWantAssertionSigned(true); config1. setRequestSigned(true); + config1.setPrivateKey(serviceProviderKey); + config1.setPrivateKeyPassword(serviceProviderKeyPassword); + config1.setCertificate(serviceProviderCertificate); + IdentityZoneConfiguration zoneConfig1 = new IdentityZoneConfiguration(null); zoneConfig1.setSamlConfig(config1); @@ -378,6 +423,10 @@ public void test_zone_saml_properties() throws Exception { zone1.setConfig(zoneConfig1); zone1 = zoneProvisioning.create(zone1); + assertEquals(serviceProviderCertificate, zone1.getConfig().getSamlConfig().getCertificate()); + assertEquals(serviceProviderKey, zone1.getConfig().getSamlConfig().getPrivateKey()); + assertEquals(serviceProviderKeyPassword, zone1.getConfig().getSamlConfig().getPrivateKeyPassword()); + SamlConfig config2 = new SamlConfig(); config2. setWantAssertionSigned(false); config2. setRequestSigned(false); From 95f3f938bb575542f5bcbbd6208975c01a97b2a8 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 15 Dec 2015 09:38:35 -0700 Subject: [PATCH 075/103] Upgrade and refresh dependencies https://www.pivotaltracker.com/story/show/110100800 [#110100800] --- common/build.gradle | 26 +++++++++---------- .../test/TestApplicationEventPublisher.java | 7 ++++- ...wareInternalResourceViewResolverTests.java | 23 +++++++++------- model/build.gradle | 4 +-- shared_versions.gradle | 24 ++++++++++++++--- uaa/build.gradle | 20 +++++++------- .../LoginSamlAuthenticationProviderTests.java | 6 +++++ 7 files changed, 70 insertions(+), 40 deletions(-) diff --git a/common/build.gradle b/common/build.gradle index 85f10e8e9e0..57e2fa1bda8 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -4,8 +4,8 @@ description = 'CloudFoundry Identity Common Jar' dependencies { compile identityModel - compile group: 'org.passay', name: 'passay', version:'1.0' - compile group: 'com.google.guava', name: 'guava', version: '18.0' + compile group: 'org.passay', name: 'passay', version:parent.passayVersion + compile group: 'com.google.guava', name: 'guava', version: parent.guavaVersion compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version:parent.bcpkixVersion compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version:parent.bcpkixVersion compile group: 'org.springframework.security', name: 'spring-security-ldap', version:parent.springSecurityVersion @@ -35,23 +35,23 @@ dependencies { compile group: 'org.springframework.security', name: 'spring-security-core', version:parent.springSecurityVersion compile group: 'org.springframework.security', name: 'spring-security-web', version:parent.springSecurityVersion compile group: 'log4j', name: 'log4j', version:'1.2.17' - compile(group: 'org.apache.httpcomponents', name: 'httpclient', version:'4.3.3') { + compile(group: 'org.apache.httpcomponents', name: 'httpclient', version:parent.commonsHttpClientVersion) { exclude(module: 'commons-logging') } - compile(group: 'com.unboundid.product.scim', name: 'scim-sdk', version:'1.6.0') { + compile(group: 'com.unboundid.product.scim', name: 'scim-sdk', version:parent.scimSDKVersion) { exclude(module: 'servlet-api') exclude(module: 'commons-logging') exclude(module: 'httpclient') exclude(module: 'wink-client-apache-httpclient') } - compile group: 'org.hibernate', name: 'hibernate-validator', version:'4.3.1.Final' - compile group: 'org.aspectj', name: 'aspectjrt', version:'1.6.9' + compile group: 'org.hibernate', name: 'hibernate-validator', version:parent.hibernateValidatorVersion + compile group: 'org.aspectj', name: 'aspectjrt', version:parent.aspectJVersion compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version:parent.jacksonVersion compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version:parent.jacksonVersion - compile group: 'org.yaml', name: 'snakeyaml', version:'1.12' - compile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version:'1.1.8' + compile group: 'org.yaml', name: 'snakeyaml', version:parent.snakeYamlVersion + compile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version:parent.mariaDBClientVersion compile group: 'org.flywaydb', name: 'flyway-core', version: parent.flywayVersion - compile group: 'org.hsqldb', name: 'hsqldb', version:'2.3.1' + compile group: 'org.hsqldb', name: 'hsqldb', version:parent.hsqldbVersion compile(group: 'org.springframework.security.extensions', name: 'spring-security-saml2-core', version:parent.springSecuritySamlVersion) { exclude(module: 'bcprov-jdk15') @@ -62,11 +62,11 @@ dependencies { testCompile group: 'org.springframework', name: 'spring-test', version:parent.springVersion testCompile group: 'junit', name: 'junit', version:parent.junitVersion - testCompile group: 'org.hamcrest', name: 'hamcrest-all', version:'1.3' - testCompile group: 'com.jayway.jsonpath', name: 'json-path', version:'0.9.1' - testCompile group: 'com.jayway.jsonpath', name: 'json-path-assert', version:'0.9.1' + testCompile group: 'org.hamcrest', name: 'hamcrest-all', version:parent.hamcrestVersion + testCompile group: 'com.jayway.jsonpath', name: 'json-path', version:parent.jsonPathVersion + testCompile group: 'com.jayway.jsonpath', name: 'json-path-assert', version:parent.jsonPathVersion testCompile group: 'postgresql', name: 'postgresql', version:parent.postgresqlVersion - testCompile group: 'org.mockito', name: 'mockito-all', version:'1.8.5' + testCompile group: 'org.mockito', name: 'mockito-all', version:parent.mockitoVersion testCompile group: 'org.apache.tomcat', name: 'tomcat-jdbc', version:parent.tomcatVersion testCompile group: 'org.springframework.security', name: 'spring-security-test', version:parent.springSecurityVersion } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventPublisher.java b/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventPublisher.java index 7a69b670dd1..7410b89c502 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventPublisher.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventPublisher.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -29,4 +29,9 @@ protected TestApplicationEventPublisher(Class eventType) { public void publishEvent(ApplicationEvent applicationEvent) { handleEvent(applicationEvent); } + + @Override + public void publishEvent(Object event) { + throw new UnsupportedOperationException("not implemented"); + } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/web/ForwardAwareInternalResourceViewResolverTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/web/ForwardAwareInternalResourceViewResolverTests.java index 8f172eae8d1..893dd23badd 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/web/ForwardAwareInternalResourceViewResolverTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/web/ForwardAwareInternalResourceViewResolverTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Cloud Foundry + * Cloud Foundry * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). @@ -13,10 +13,6 @@ package org.cloudfoundry.identity.uaa.web; -import static org.junit.Assert.assertNotNull; - -import java.util.Locale; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -27,9 +23,13 @@ import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.View; +import java.util.Locale; + +import static org.junit.Assert.assertNotNull; + /** * @author Dave Syer - * + * */ public class ForwardAwareInternalResourceViewResolverTests { @@ -37,11 +37,14 @@ public class ForwardAwareInternalResourceViewResolverTests { private MockHttpServletRequest request = new MockHttpServletRequest(); + private GenericApplicationContext context = new GenericApplicationContext(); + @Before public void start() { ServletRequestAttributes attributes = new ServletRequestAttributes(request); LocaleContextHolder.setLocale(request.getLocale()); RequestContextHolder.setRequestAttributes(attributes); + context.refresh(); } @After @@ -51,14 +54,14 @@ public void clean() { @Test public void testResolveNonForward() throws Exception { - resolver.setApplicationContext(new GenericApplicationContext()); + resolver.setApplicationContext(context); View view = resolver.resolveViewName("foo", Locale.US); assertNotNull(view); } @Test public void testResolveRedirect() throws Exception { - resolver.setApplicationContext(new GenericApplicationContext()); + resolver.setApplicationContext(context); View view = resolver.resolveViewName("redirect:foo", Locale.US); assertNotNull(view); } @@ -66,7 +69,7 @@ public void testResolveRedirect() throws Exception { @Test public void testResolveForwardWithAccept() throws Exception { request.addHeader("Accept", "application/json"); - resolver.setApplicationContext(new GenericApplicationContext()); + resolver.setApplicationContext(context); View view = resolver.resolveViewName("forward:foo", Locale.US); assertNotNull(view); } @@ -74,7 +77,7 @@ public void testResolveForwardWithAccept() throws Exception { @Test public void testResolveForwardWithUnparseableAccept() throws Exception { request.addHeader("Accept", "bar"); - resolver.setApplicationContext(new GenericApplicationContext()); + resolver.setApplicationContext(context); View view = resolver.resolveViewName("forward:foo", Locale.US); assertNotNull(view); } diff --git a/model/build.gradle b/model/build.gradle index deaecf87115..df9ca862e42 100644 --- a/model/build.gradle +++ b/model/build.gradle @@ -14,8 +14,8 @@ dependencies { exclude(module: 'spring-security-config') } - compile group: 'org.slf4j', name: 'slf4j-log4j12', version:'1.7.7' - compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.7' + compile group: 'org.slf4j', name: 'slf4j-log4j12', version:parent.slf4jVersion + compile group: 'org.slf4j', name: 'slf4j-api', version:parent.slf4jVersion testCompile group: 'junit', name: 'junit', version:parent.junitVersion } diff --git a/shared_versions.gradle b/shared_versions.gradle index 561e1305618..d607ab0d98f 100644 --- a/shared_versions.gradle +++ b/shared_versions.gradle @@ -1,10 +1,12 @@ ext { servletVersion = '3.1.0' - springVersion = '4.1.6.RELEASE' - springSecurityVersion = '4.0.1.RELEASE' - springSecurityOAuthVersion = '2.0.7.RELEASE' - springSecurityLdapVersion = '2.0.3.RELEASE' + jstlVersion = '1.2' + springVersion = '4.1.8.RELEASE' + springSecurityVersion = '4.0.3.RELEASE' + springSecurityOAuthVersion = '2.0.8.RELEASE' + springSecurityLdapVersion = '2.0.4.RELEASE' springSecuritySamlVersion = '1.0.1.RELEASE' + springRetryVersion = '1.0.2.RELEASE' postgresqlVersion = '9.1-901.jdbc3' tomcatVersion = '7.0.61' springSecurityJwtVersion = '1.0.3.RELEASE' @@ -14,4 +16,18 @@ ext { flywayVersion = '3.2.1' validationAPIVersion = '1.0.0.GA' junitVersion = '4.11' + slf4jVersion = '1.7.7' + passayVersion = '1.0' + guavaVersion = '18.0' + commonsHttpClientVersion = '4.3.3' + scimSDKVersion = '1.6.0' + hibernateValidatorVersion = '4.3.1.Final' + aspectJVersion = '1.6.9' + snakeYamlVersion = '1.12' + mariaDBClientVersion = '1.1.8' + hsqldbVersion = '2.3.1' + hamcrestVersion = '1.3' + jsonPathVersion = '2.1.0' + mockitoVersion = '1.10.19' + cglibVersion = '2.2.2' } diff --git a/uaa/build.gradle b/uaa/build.gradle index 01b26a38e15..264a747fc17 100644 --- a/uaa/build.gradle +++ b/uaa/build.gradle @@ -22,11 +22,11 @@ war { } apply plugin: 'eclipse-wtp' eclipse { - wtp { - component { - contextPath = 'uaa' - } - } + wtp { + component { + contextPath = 'uaa' + } + } } description = 'UAA' @@ -40,13 +40,13 @@ dependencies { compile(identityLogin) { exclude(module: 'jna') } - compile group: 'cglib', name: 'cglib', version:'2.2.2' + compile group: 'cglib', name: 'cglib', version:parent.cglibVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version:parent.springSecurityVersion - runtime group: 'org.springframework.security', name: 'spring-security-jwt', version:'1.0.1.RELEASE' - runtime group: 'org.springframework.retry', name: 'spring-retry', version:'1.0.2.RELEASE' - runtime group: 'org.aspectj', name: 'aspectjweaver', version:'1.6.9' + runtime group: 'org.springframework.security', name: 'spring-security-jwt', version:parent.springSecurityJwtVersion + runtime group: 'org.springframework.retry', name: 'spring-retry', version:parent.springRetryVersion + runtime group: 'org.aspectj', name: 'aspectjweaver', version:parent.aspectJVersion runtime group: 'org.apache.tomcat', name: 'tomcat-jdbc', version:parent.tomcatVersion - runtime group: 'javax.servlet', name: 'jstl', version:'1.2' + runtime group: 'javax.servlet', name: 'jstl', version:parent.jstlVersion runtime group: 'postgresql', name: 'postgresql', version:parent.postgresqlVersion testCompile identityCommon.configurations.testCompile.dependencies diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java index 6d4bd9c2934..17167102bb5 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java @@ -595,6 +595,12 @@ public void publishEvent(ApplicationEvent event) { bootstrap.onApplicationEvent((AuthEvent)event); } } + + @Override + public void publishEvent(Object event) { + throw new UnsupportedOperationException("not implemented"); + } + } public static final String IDP_META_DATA = From a3554653cd4087cc4c0f2ad94116f2a88243fb03 Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Tue, 15 Dec 2015 12:25:58 -0800 Subject: [PATCH 076/103] Remove extraneous alert message in verification code expiry page [#108722168] https://www.pivotaltracker.com/story/show/108722168 --- .../main/resources/templates/web/accounts/link_prompt.html | 4 +--- uaa/src/main/resources/login.yml | 6 +++--- .../identity/uaa/login/AccountsControllerMockMvcTests.java | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/login/src/main/resources/templates/web/accounts/link_prompt.html b/login/src/main/resources/templates/web/accounts/link_prompt.html index a2b811d234b..5e88381da13 100644 --- a/login/src/main/resources/templates/web/accounts/link_prompt.html +++ b/login/src/main/resources/templates/web/accounts/link_prompt.html @@ -13,9 +13,7 @@

Create your
-

Error Message

-

Error Message

-

Please continue here.

+

Your activation link has expired. Please request a new one here.

diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AppApprovalIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AppApprovalIT.java index e41c2e2aa4d..a99b7993890 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AppApprovalIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AppApprovalIT.java @@ -12,12 +12,16 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.integration.feature; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; +import org.hamcrest.Description; import org.hamcrest.Matchers; +import org.hamcrest.TypeSafeMatcher; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -37,8 +41,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.web.client.RestOperations; -import static org.junit.Assert.assertEquals; - @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = DefaultIntegrationTestConfig.class) @OAuth2ContextConfiguration(OAuth2ContextConfiguration.ClientCredentials.class) @@ -138,6 +140,22 @@ public void testApprovingAnApp() throws Exception { Assert.assertThat(webDriver.findElements(By.xpath("//input[@value='app-password.write']")), Matchers.empty()); } + @Test + public void testInvalidAppRedirectDisplaysError() throws Exception { + ScimUser user = createUnapprovedUser(); + + // given we vist the app (specifying an invalid redirect - incorrect protocol https) + webDriver.get(appUrl + "?redirect_uri=https://localhost:8080/app/"); + + // Sign in to login server + webDriver.findElement(By.name("username")).sendKeys(user.getUserName()); + webDriver.findElement(By.name("password")).sendKeys(user.getPassword()); + webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); + + // Authorize the app for some scopes + assertThat(webDriver.findElement(By.className("alert-error")).getText(), RegexMatcher.matchesRegex("^Invalid redirect: (.*) does not match one of the registered values: \\[(.*)\\]")); + } + private ScimUser createUnapprovedUser() throws Exception { String userName = "bob-" + new RandomValueStringGenerator().generate(); String userEmail = userName + "@example.com"; @@ -156,5 +174,29 @@ private ScimUser createUnapprovedUser() throws Exception { return user; } + + public static class RegexMatcher extends TypeSafeMatcher { + + private final String regex; + + public RegexMatcher(final String regex) { + this.regex = regex; + } + + @Override + public void describeTo(final Description description) { + description.appendText("matches regex=`" + regex + "`"); + } + + @Override + public boolean matchesSafely(final String string) { + return string.matches(regex); + } + + + public static RegexMatcher matchesRegex(final String regex) { + return new RegexMatcher(regex); + } + } } From 0cb7cedd06bc9cd72f33585910dacbe00f70da92 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Fri, 8 Jan 2016 11:24:59 -0800 Subject: [PATCH 086/103] Remove no-longer-needed dummy user in the case no user found in Authz Signed-off-by: Jonathan Lo --- .../manager/AuthzAuthenticationManager.java | 111 ++++++------------ 1 file changed, 38 insertions(+), 73 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java index 6302bdb67ca..ac7e8482312 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java @@ -70,13 +70,6 @@ public class AuthzAuthenticationManager implements AuthenticationManager, Applic private String origin; private boolean allowUnverifiedUsers = true; - /** - * Dummy user allows the authentication process for non-existent and locked - * out users to be as close to - * that of normal users as possible to avoid differences in timing. - */ - private final UaaUser dummyUser; - public AuthzAuthenticationManager(UaaUserDatabase cfusers, IdentityProviderProvisioning providerProvisioning) { this(cfusers, new BCryptPasswordEncoder(), providerProvisioning); } @@ -84,7 +77,6 @@ public AuthzAuthenticationManager(UaaUserDatabase cfusers, IdentityProviderProvi public AuthzAuthenticationManager(UaaUserDatabase userDatabase, PasswordEncoder encoder, IdentityProviderProvisioning providerProvisioning) { this.userDatabase = userDatabase; this.encoder = encoder; - this.dummyUser = createDummyUser(); this.providerProvisioning = providerProvisioning; } @@ -98,60 +90,55 @@ public Authentication authenticate(Authentication req) throws AuthenticationExce throw e; } - UaaUser user; - boolean passwordMatches = false; - user = getUaaUser(req); - if (user!=null) { - passwordMatches = - ((CharSequence) req.getCredentials()).length() != 0 && encoder.matches((CharSequence) req.getCredentials(), user.getPassword()); + UaaUser user = getUaaUser(req); + + if (user == null) { + logger.debug("No user named '" + req.getName() + "' was found for origin:"+ origin); + publish(new UserNotFoundEvent(req)); } else { - user = dummyUser; - } + if (!accountLoginPolicy.isAllowed(user, req)) { + logger.warn("Login policy rejected authentication for " + user.getUsername() + ", " + user.getId() + + ". Ignoring login request."); + AuthenticationPolicyRejectionException e = new AuthenticationPolicyRejectionException("Login policy rejected authentication"); + publish(new AuthenticationFailureLockedEvent(req, e)); + throw e; + } - if (!accountLoginPolicy.isAllowed(user, req)) { - logger.warn("Login policy rejected authentication for " + user.getUsername() + ", " + user.getId() - + ". Ignoring login request."); - AuthenticationPolicyRejectionException e = new AuthenticationPolicyRejectionException("Login policy rejected authentication"); - publish(new AuthenticationFailureLockedEvent(req, e)); - throw e; - } + boolean passwordMatches = ((CharSequence) req.getCredentials()).length() != 0 && encoder.matches((CharSequence) req.getCredentials(), user.getPassword()); - if (passwordMatches) { - logger.debug("Password successfully matched for userId["+user.getUsername()+"]:"+user.getId()); + if (!passwordMatches) { + logger.debug("Password did not match for user " + req.getName()); + publish(new UserAuthenticationFailureEvent(user, req)); + } else { + logger.debug("Password successfully matched for userId["+user.getUsername()+"]:"+user.getId()); - if (!allowUnverifiedUsers && !user.isVerified()) { - publish(new UnverifiedUserAuthenticationEvent(user, req)); - logger.debug("Account not verified: " + user.getId()); - throw new AccountNotVerifiedException("Account not verified"); - } + if (!allowUnverifiedUsers && !user.isVerified()) { + publish(new UnverifiedUserAuthenticationEvent(user, req)); + logger.debug("Account not verified: " + user.getId()); + throw new AccountNotVerifiedException("Account not verified"); + } - int expiringPassword = getPasswordExpiresInMonths(); - if (expiringPassword>0) { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(user.getPasswordLastModified().getTime()); - cal.add(Calendar.MONTH, expiringPassword); - if (cal.getTimeInMillis() < System.currentTimeMillis()) { - throw new PasswordExpiredException("Your current password has expired. Please reset your password."); + int expiringPassword = getPasswordExpiresInMonths(); + if (expiringPassword>0) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(user.getPasswordLastModified().getTime()); + cal.add(Calendar.MONTH, expiringPassword); + if (cal.getTimeInMillis() < System.currentTimeMillis()) { + throw new PasswordExpiredException("Your current password has expired. Please reset your password."); + } } - } - Authentication success = new UaaAuthentication( - new UaaPrincipal(user), - user.getAuthorities(), - (UaaAuthenticationDetails) req.getDetails()); + Authentication success = new UaaAuthentication( + new UaaPrincipal(user), + user.getAuthorities(), + (UaaAuthenticationDetails) req.getDetails()); - publish(new UserAuthenticationSuccessEvent(user, success)); + publish(new UserAuthenticationSuccessEvent(user, success)); - return success; + return success; + } } - if (user == dummyUser || user == null) { - logger.debug("No user named '" + req.getName() + "' was found for origin:"+ origin); - publish(new UserNotFoundEvent(req)); - } else { - logger.debug("Password did not match for user " + req.getName()); - publish(new UserAuthenticationFailureEvent(user, req)); - } BadCredentialsException e = new BadCredentialsException("Bad credentials"); publish(new AuthenticationFailureBadCredentialsEvent(req, e)); throw e; @@ -201,28 +188,6 @@ public AccountLoginPolicy getAccountLoginPolicy() { return this.accountLoginPolicy; } - private UaaUser createDummyUser() { - // Create random unguessable password - SecureRandom random = new SecureRandom(); - byte[] passBytes = new byte[16]; - random.nextBytes(passBytes); - String password = encoder.encode(new String(Hex.encode(passBytes))); - // Unique ID which isn't in the database - final String id = UUID.randomUUID().toString(); - - return new UaaUser("dummy_user", password, "dummy_user", "dummy", "dummy") { - @Override - public final String getId() { - return id; - } - - @Override - public final List getAuthorities() { - throw new IllegalStateException(); - } - }; - } - public String getOrigin() { return origin; } From 2b8dd9acc8885eb28f469f891d5b169e76c0c289 Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Fri, 8 Jan 2016 16:19:10 -0800 Subject: [PATCH 087/103] Document redirect URI on auth code token get request [#110532644] https://www.pivotaltracker.com/story/show/110532644 Signed-off-by: Jeremy Coffield --- docs/UAA-APIs.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index b7eac9a3086..41a77ca716b 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -161,6 +161,7 @@ Request Body the authorization code (form encoded) in the case grant_type=authorization_code code=F45jH response_type=token + redirect_uri=http://example-app.com/welcome OR the client credentials (form encoded) in the case of client credentials grant, e.g.:: From 3d769b7e58c127abe700def0c0c1d265258375e2 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 15 Dec 2015 13:42:13 -0700 Subject: [PATCH 088/103] First attempt in a more CORS compliant implementation https://www.pivotaltracker.com/story/show/110165702 [#110165702] TODO: What if Origin header is present and has the same hostname as the Host header. Isn't that a same origin request? References: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS http://www.w3.org/TR/cors https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html https://fetch.spec.whatwg.org/#refsORIGIN --- .../uaa/impl/config/UaaConfiguration.java | 39 +- .../identity/uaa/security/web/CorsFilter.java | 481 +++++++++++++----- .../identity/uaa/util/UaaStringUtils.java | 9 + .../{ => security}/web/CorsFilterTests.java | 147 ++++-- .../main/webapp/WEB-INF/spring-servlet.xml | 87 +++- .../identity/uaa/login/BootstrapTests.java | 74 ++- uaa/src/test/resources/test/bootstrap/uaa.yml | 18 + 7 files changed, 632 insertions(+), 223 deletions(-) rename server/src/test/java/org/cloudfoundry/identity/uaa/{ => security}/web/CorsFilterTests.java (73%) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/UaaConfiguration.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/UaaConfiguration.java index 787039bee68..8da4a823ab4 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/UaaConfiguration.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/UaaConfiguration.java @@ -12,21 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.impl.config; -import java.io.BufferedReader; -import java.io.FileReader; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.validation.ConstraintViolation; -import javax.validation.Valid; -import javax.validation.Validation; -import javax.validation.Validator; -import javax.validation.ValidatorFactory; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; - import org.cloudfoundry.identity.uaa.impl.config.UaaConfiguration.OAuth.Client; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.hibernate.validator.constraints.URL; @@ -36,6 +21,20 @@ import org.yaml.snakeyaml.constructor.Construct; import org.yaml.snakeyaml.nodes.Node; +import javax.validation.ConstraintViolation; +import javax.validation.Valid; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + /** * Java representation of the UAA YAML configuration for validation purposes. * @@ -94,11 +93,7 @@ public class UaaConfiguration { @Valid public OAuth multitenant; @Valid - public String corsXhrAllowedHeaders; - @Valid - public String corsXhrAllowedOrigins; - @Valid - public String corsXhrAllowedUris; + public Map cors; public static class Zones { @Valid @@ -232,10 +227,6 @@ public UaaConfigConstructor() { addPropertyAlias("access-token-validity", OAuthClient.class, "accessTokenValidity"); addPropertyAlias("refresh-token-validity", OAuthClient.class, "refreshTokenValidity"); addPropertyAlias("user.override", Scim.class, "userOverride"); - - addPropertyAlias("cors.xhr.allowed.headers", UaaConfiguration.class, "corsXhrAllowedHeaders"); - addPropertyAlias("cors.xhr.allowed.origins", UaaConfiguration.class, "corsXhrAllowedOrigins"); - addPropertyAlias("cors.xhr.allowed.uris", UaaConfiguration.class, "corsXhrAllowedUris"); } @Override diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java index 2579cd4a56b..5d324f70bcc 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java @@ -13,28 +13,47 @@ package org.cloudfoundry.identity.uaa.security.web; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.annotation.PostConstruct; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import javax.annotation.PostConstruct; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.util.StringUtils; -import org.springframework.web.filter.OncePerRequestFilter; +import static org.cloudfoundry.identity.uaa.util.UaaStringUtils.containsIgnoreCase; +import static org.springframework.http.HttpHeaders.ACCEPT; +import static org.springframework.http.HttpHeaders.ACCEPT_LANGUAGE; +import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS; +import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS; +import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; +import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_MAX_AGE; +import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS; +import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.HttpHeaders.CONTENT_LANGUAGE; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.HttpHeaders.ORIGIN; +import static org.springframework.http.HttpMethod.DELETE; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.OPTIONS; +import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.HttpMethod.PUT; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.FORBIDDEN; +import static org.springframework.http.HttpStatus.METHOD_NOT_ALLOWED; /** * @@ -57,59 +76,136 @@ */ public class CorsFilter extends OncePerRequestFilter { - static final Log LOG = LogFactory.getLog(CorsFilter.class); + static final Log logger = LogFactory.getLog(CorsFilter.class); + public static final String X_REQUESTED_WITH = "X-Requested-With"; + public static final int ACCESS_CONTROL_MAX_AGE_DEFAULT = 1728000; + public static final String WILDCARD = "*"; + + public static class CorsConfiguration { + /** + * A comma delimited list of regular expression patterns that define which + * origins are allowed to use the "X-Requested-With" header in CORS + * requests. + */ + private List allowedOrigins = Arrays.asList(".*"); + private final List allowedOriginPatterns = new ArrayList<>(); + + /** + * A comma delimited list of regular expression patterns that defines which + * UAA URIs allow the "X-Requested-With" header in CORS requests. + */ + private List allowedUris = Arrays.asList(".*"); + private final List allowedUriPatterns = new ArrayList<>(); + + /** + * A comma delimited list of regular expression patterns that define which + * origins are allowed to use the "X-Requested-With" header in CORS + * requests. + */ + private List allowedHeaders = Arrays.asList(ACCEPT, AUTHORIZATION, CONTENT_TYPE); + + private List allowedMethods = Arrays.asList(GET.toString()); + + private boolean allowedCredentials = false; + + private int maxAge = ACCESS_CONTROL_MAX_AGE_DEFAULT; + + public boolean isAllowedCredentials() { + return allowedCredentials; + } - /** - * A comma delimited list of regular expression patterns that defines which - * UAA URIs allow the "X-Requested-With" header in CORS requests. - */ - @Value("#{'${cors.xhr.allowed.uris:^$}'.split(',')}") - private List corsXhrAllowedUris; + public void setAllowedCredentials(boolean allowedCredentials) { + this.allowedCredentials = allowedCredentials; + } - private final List corsXhrAllowedUriPatterns = new ArrayList<>(); + public List getAllowedHeaders() { + return allowedHeaders; + } - /** - * A comma delimited list of regular expression patterns that define which - * origins are allowed to use the "X-Requested-With" header in CORS - * requests. - */ - @Value("#{'${cors.xhr.allowed.origins:^$}'.split(',')}") - private List corsXhrAllowedOrigins; + public void setAllowedHeaders(List allowedHeaders) { + this.allowedHeaders = allowedHeaders; + } - private final List corsXhrAllowedOriginPatterns = new ArrayList<>(); + public List getAllowedMethods() { + return allowedMethods; + } - @Value("#{'${cors.xhr.allowed.headers:Accept,Authorization}'.split(',')}") - private List allowedHeaders; + public void setAllowedMethods(List allowedMethods) { + this.allowedMethods = allowedMethods; + } - @PostConstruct - public void initialize() { + public List getAllowedOriginPatterns() { + return allowedOriginPatterns; + } - if (corsXhrAllowedUris!=null) { - for (String allowedUri : this.corsXhrAllowedUris) { - try { - this.corsXhrAllowedUriPatterns.add(Pattern.compile(allowedUri)); + public List getAllowedOrigins() { + return allowedOrigins; + } - if (LOG.isDebugEnabled()) { - LOG.debug(String - .format("URI '%s' allows 'X-Requested-With' header in CORS requests.", allowedUri)); - } - } catch (PatternSyntaxException patternSyntaxException) { - LOG.error("Invalid regular expression pattern in cors.xhr.allowed.uris: " + allowedUri); - } - } + public void setAllowedOrigins(List allowedOrigins) { + this.allowedOrigins = allowedOrigins; } - if (corsXhrAllowedOrigins!=null) { - for (String allowedOrigin : this.corsXhrAllowedOrigins) { - try { - this.corsXhrAllowedOriginPatterns.add(Pattern.compile(allowedOrigin)); + public List getAllowedUriPatterns() { + return allowedUriPatterns; + } + + public List getAllowedUris() { + return allowedUris; + } + + public void setAllowedUris(List allowedUris) { + this.allowedUris = allowedUris; + } + + public int getMaxAge() { + return maxAge; + } + + public void setMaxAge(int maxAge) { + this.maxAge = maxAge; + } + } + + private CorsConfiguration xhrConfiguration = new CorsConfiguration(); + private CorsConfiguration defaultConfiguration = new CorsConfiguration(); + + public CorsFilter() { + //configure defaults for XHR vs non-XHR requests + xhrConfiguration.setAllowedMethods(Arrays.asList(GET.toString(), OPTIONS.toString())); + defaultConfiguration.setAllowedMethods(Arrays.asList(GET.toString(), OPTIONS.toString(), POST.toString(), PUT.toString(), DELETE.toString())); + + xhrConfiguration.setAllowedHeaders(Arrays.asList(ACCEPT, ACCEPT_LANGUAGE, CONTENT_TYPE, CONTENT_LANGUAGE,AUTHORIZATION, X_REQUESTED_WITH)); + defaultConfiguration.setAllowedHeaders(Arrays.asList(ACCEPT, ACCEPT_LANGUAGE, CONTENT_TYPE, CONTENT_LANGUAGE,AUTHORIZATION)); - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("Origin '%s' allowed 'X-Requested-With' header in CORS requests.", - allowedOrigin)); + xhrConfiguration.setAllowedCredentials(true); + defaultConfiguration.setAllowedCredentials(false); + } + + @PostConstruct + public void initialize() { + for (CorsConfiguration configuration : Arrays.asList(xhrConfiguration, defaultConfiguration)) { + String type = (configuration == xhrConfiguration ? "xhr" : "default"); + configuration.getAllowedUriPatterns().clear(); + configuration.getAllowedOriginPatterns().clear(); + if (configuration.getAllowedUris() != null) { + for (String allowedUri : configuration.getAllowedUris()) { + try { + configuration.getAllowedUriPatterns().add(Pattern.compile(allowedUri)); + logger.debug(String.format("URI '%s' is allowed for a %s CORS requests.", allowedUri, type)); + } catch (PatternSyntaxException patternSyntaxException) { + logger.error("Invalid regular expression pattern in cors."+type+".allowed.uris: " + allowedUri, patternSyntaxException); + } + } + } + if (configuration.getAllowedOrigins() != null) { + for (String allowedOrigin : configuration.getAllowedOrigins()) { + try { + configuration.getAllowedOriginPatterns().add(Pattern.compile(allowedOrigin)); + logger.debug(String.format("Origin '%s' is allowed for a %s CORS requests.", allowedOrigin, type)); + } catch (PatternSyntaxException patternSyntaxException) { + logger.error("Invalid regular expression pattern in cors."+type+".allowed.origins: " + allowedOrigin, patternSyntaxException); } - } catch (PatternSyntaxException patternSyntaxException) { - LOG.error("Invalid regular expression pattern in cors.xhr.allowed.origins: " + allowedOrigin); } } } @@ -121,158 +217,267 @@ protected void doFilterInternal(final HttpServletRequest request, final HttpServ final FilterChain filterChain) throws ServletException, IOException { if (!isCrossOriginRequest(request)) { + //if the Origin header is not present. + //Process as usual filterChain.doFilter(request, response); return; } if (isXhrRequest(request)) { - String method = request.getMethod(); - if (!isCorsXhrAllowedMethod(method)) { - response.setStatus(HttpStatus.METHOD_NOT_ALLOWED.value()); - return; - } - String origin = request.getHeader(HttpHeaders.ORIGIN); - // Validate the origin so we don't reflect back any potentially dangerous content. - URI originURI; - try { - originURI = new URI(origin); - } - catch(URISyntaxException e) { - response.setStatus(HttpStatus.FORBIDDEN.value()); - return; - } - - String requestUri = request.getRequestURI(); - if (!isCorsXhrAllowedRequestUri(requestUri) || !isCorsXhrAllowedOrigin(origin)) { - response.setStatus(HttpStatus.FORBIDDEN.value()); - return; - } - response.addHeader("Access-Control-Allow-Origin", originURI.toString()); - if ("OPTIONS".equals(request.getMethod())) { - buildCorsXhrPreFlightResponse(request, response); - } else { - filterChain.doFilter(request, response); - } + handleRequest(request, response, filterChain, getXhrConfiguration()); + return; + } else { + handleRequest(request, response, filterChain, getDefaultConfiguration()); return; } + } + + protected boolean handleRequest(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain, + CorsConfiguration configuration) throws IOException, ServletException { + + //Validate if this CORS request is allowed for this method + String method = request.getMethod(); + if (!isAllowedMethod(method, configuration)) { + logger.debug(String.format("Request with invalid method was rejected: %s", method)); + response.sendError(METHOD_NOT_ALLOWED.value(), "Illegal method."); + return true; + } + + + // Validate the origin so we don't reflect back any potentially dangerous content. + String origin = request.getHeader(ORIGIN); + // While origin can be a comma delimited list, we don't allow it for CORS + URI originURI; + try { + originURI = new URI(origin); + } catch(URISyntaxException e) { + logger.debug(String.format("Request with invalid origin was rejected: %s", origin)); + response.sendError(FORBIDDEN.value(), "Invalid origin"); + return true; + } + + if (!isAllowedOrigin(origin, configuration)) { + logger.debug(String.format("Request with origin: %s was rejected because it didn't match allowed origins", origin)); + response.sendError(FORBIDDEN.value(), "Illegal origin"); + return true; + } + + String requestUri = request.getRequestURI(); + if (!isAllowedRequestUri(requestUri, configuration)) { + logger.debug(String.format("Request with URI: %s was rejected because it didn't match allowed URIs", requestUri)); + response.sendError(FORBIDDEN.value(), "Illegal request URI"); + return true; + } + + if (configuration.isAllowedCredentials()) { + //if we allow credentials, send back the actual origin + response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, originURI.toString()); + } else { + //send back a wildcard, this will prevent credentials + response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD); + } - response.addHeader("Access-Control-Allow-Origin", "*"); - if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) { - // CORS "pre-flight" request - response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); - response.addHeader("Access-Control-Allow-Headers", "Authorization"); - response.addHeader("Access-Control-Max-Age", "1728000"); + if (OPTIONS.toString().equals(request.getMethod())) { + logger.debug(String.format("Request is a pre-flight request")); + buildCorsPreFlightResponse(request, response, configuration); } else { + logger.debug(String.format("Request cross origin request has passed validation.")); filterChain.doFilter(request, response); } + + return false; } - static boolean isXhrRequest(final HttpServletRequest request) { - String xRequestedWith = request.getHeader("X-Requested-With"); - String accessControlRequestHeaders = request.getHeader("Access-Control-Request-Headers"); - return StringUtils.hasText(xRequestedWith) - || (StringUtils.hasText(accessControlRequestHeaders) && containsHeader( - accessControlRequestHeaders, "X-Requested-With")); + /** + * Returns true if we believe this is an XHR request + * We look for the presence of the X-Requested-With header + * or that the X-Requested-With header is listed as a value + * in the Access-Control-Request-Headers header. + * @param request the HTTP servlet request + * @return true if we believe this is an XHR request + */ + protected boolean isXhrRequest(final HttpServletRequest request) { + if (StringUtils.hasText(request.getHeader(X_REQUESTED_WITH))) { + //the X-Requested-With header is present. This is a XHR request + return true; + } + String accessControlRequestHeaders = request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS); + //One of the requested headers is X-Requested-With so we treat is as XHR request + return StringUtils.hasText(accessControlRequestHeaders) && containsHeader(accessControlRequestHeaders, X_REQUESTED_WITH); + } - private boolean isCrossOriginRequest(final HttpServletRequest request) { - if (StringUtils.isEmpty(request.getHeader(HttpHeaders.ORIGIN))) { - return false; + /** + * Returns true if the `Origin` header is present and has any value + * @param request the HTTP servlet request + * @return true if the `Origin` header is present + */ + protected boolean isCrossOriginRequest(final HttpServletRequest request) { + //TODO what about SAME origin requests that actually have the Origin header present? + //presence of the origin header indicates CORS request + return StringUtils.hasText(request.getHeader(ORIGIN)); + } + + protected String buildCommaDelimitedString(List list) { + StringBuilder builder = new StringBuilder(); + for (String s : list) { + if (builder.length()>0) { + builder.append(", "); + } + builder.append(s); } - else { - return true; + return builder.toString(); + } + + protected List splitCommaDelimitedString(String s) { + String[] list = s.replace(" ", "").split(","); + if (list==null || list.length==0) { + return Collections.EMPTY_LIST; } + return Arrays.asList(list); } - void buildCorsXhrPreFlightResponse(final HttpServletRequest request, final HttpServletResponse response) { - String accessControlRequestMethod = request.getHeader("Access-Control-Request-Method"); + protected void buildCorsPreFlightResponse(final HttpServletRequest request, + final HttpServletResponse response, + final CorsConfiguration configuration) throws IOException { + String accessControlRequestMethod = request.getHeader(ACCESS_CONTROL_REQUEST_METHOD); + + //preflight requires the Access-Control-Request-Method header if (null == accessControlRequestMethod) { - response.setStatus(HttpStatus.BAD_REQUEST.value()); + response.sendError(BAD_REQUEST.value(), "Access-Control-Request-Method header is missing"); return; } - if (!"GET".equalsIgnoreCase(accessControlRequestMethod)) { - response.setStatus(HttpStatus.METHOD_NOT_ALLOWED.value()); + + if (!isAllowedMethod(accessControlRequestMethod, configuration)) { + response.sendError(METHOD_NOT_ALLOWED.value(), "Illegal method requested"); return; } - response.addHeader("Access-Control-Allow-Methods", "GET"); - String accessControlRequestHeaders = request.getHeader("Access-Control-Request-Headers"); + //add all methods that we allow + response.addHeader(ACCESS_CONTROL_ALLOW_METHODS, buildCommaDelimitedString(configuration.getAllowedMethods())); + + //we require Access-Control-Request-Headers header + String accessControlRequestHeaders = request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS); if (null == accessControlRequestHeaders) { - response.setStatus(HttpStatus.BAD_REQUEST.value()); + response.sendError(BAD_REQUEST.value(),"Missing "+ACCESS_CONTROL_REQUEST_HEADERS+" header."); return; } - if (!headersAllowed(accessControlRequestHeaders)) { - response.setStatus(HttpStatus.FORBIDDEN.value()); + if (!headersAllowed(accessControlRequestHeaders, configuration)) { + response.sendError(FORBIDDEN.value(), "Illegal header requested"); return; } - response.addHeader("Access-Control-Allow-Headers", "Authorization, X-Requested-With"); - response.addHeader("Access-Control-Max-Age", "1728000"); + + //echo back what the client requested + response.addHeader(ACCESS_CONTROL_ALLOW_HEADERS, accessControlRequestHeaders); + //send back our configuration value + response.addHeader(ACCESS_CONTROL_MAX_AGE, String.valueOf(configuration.getMaxAge())); } - private static boolean containsHeader(final String accessControlRequestHeaders, final String header) { - List headers = Arrays.asList(accessControlRequestHeaders.replace(" ", "").toLowerCase().split(",")); - return headers.contains(header.toLowerCase()); + protected boolean containsHeader(final String accessControlRequestHeaders, final String header) { + List headers = splitCommaDelimitedString(accessControlRequestHeaders); + return containsIgnoreCase(headers, header); } - private boolean headersAllowed(final String accessControlRequestHeaders) { - List headers = Arrays.asList(accessControlRequestHeaders.replace(" ", "").split(",")); + protected boolean headersAllowed(final String accessControlRequestHeaders, CorsConfiguration configuration) { + List headers = splitCommaDelimitedString(accessControlRequestHeaders); for (String header : headers) { - if (!"X-Requested-With".equalsIgnoreCase(header) && !this.allowedHeaders.contains(header)) { + if (!containsIgnoreCase(configuration.getAllowedHeaders(), header)) { return false; } } return true; } - private static boolean isCorsXhrAllowedMethod(final String method) { - if ("GET".equalsIgnoreCase(method) || "OPTIONS".equalsIgnoreCase(method)) { - return true; - } - return false; + protected boolean isAllowedMethod(final String method, CorsConfiguration configuration) { + return containsIgnoreCase(configuration.getAllowedMethods(), method); } - private boolean isCorsXhrAllowedRequestUri(final String uri) { + protected boolean isAllowedRequestUri(final String uri, CorsConfiguration configuration) { if (StringUtils.isEmpty(uri)) { return false; } - for (Pattern pattern : this.corsXhrAllowedUriPatterns) { + for (Pattern pattern : configuration.getAllowedUriPatterns()) { // Making sure that the pattern matches if (pattern.matcher(uri).find()) { return true; } } - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("The '%s' URI does not allow CORS requests with the 'X-Requested-With' header.", - uri)); - } + logger.debug(String.format("The '%s' URI does not allow CORS requests.", uri)); return false; } - private boolean isCorsXhrAllowedOrigin(final String origin) { - for (Pattern pattern : this.corsXhrAllowedOriginPatterns) { + protected boolean isAllowedOrigin(final String origin, CorsConfiguration configuration) { + for (Pattern pattern : configuration.getAllowedOriginPatterns()) { // Making sure that the pattern matches if (pattern.matcher(origin).find()) { return true; } } - if (LOG.isDebugEnabled()) { - LOG.debug(String.format( - "The '%s' origin is not allowed to make CORS requests with the 'X-Requested-With' header.", - origin)); - } + logger.debug(String.format("The '%s' origin is not allowed to make CORS requests.",origin)); return false; } + //----------------CORS XHR ONLY ---------------------------------------------// public void setCorsXhrAllowedUris(List corsXhrAllowedUris) { - this.corsXhrAllowedUris = corsXhrAllowedUris; + this.xhrConfiguration.setAllowedUris(corsXhrAllowedUris); } public void setCorsXhrAllowedOrigins(List corsXhrAllowedOrigins) { - this.corsXhrAllowedOrigins = corsXhrAllowedOrigins; + this.xhrConfiguration.setAllowedOrigins(corsXhrAllowedOrigins); + } + + public void setCorsXhrAllowedHeaders(List allowedHeaders) { + this.xhrConfiguration.setAllowedHeaders(new ArrayList(allowedHeaders)); + } + + public void setCorsXhrAllowedCredentials(boolean allowedCredentials) { + this.xhrConfiguration.setAllowedCredentials(allowedCredentials); + } + + public void setCorsXhrAllowedMethods(List corsXhrAllowedMethods) { + this.xhrConfiguration.setAllowedMethods(new ArrayList(corsXhrAllowedMethods)); + } + + public void setCorsXhrMaxAge(int age) { + this.xhrConfiguration.setMaxAge(age); + } + + + //----------------CORS NON XHR ONLY ---------------------------------------------// + public void setCorsAllowedUris(List corsAllowedUris) { + this.defaultConfiguration.setAllowedUris(corsAllowedUris); + } + + public void setCorsAllowedOrigins(List corsAllowedOrigins) { + this.defaultConfiguration.setAllowedOrigins(corsAllowedOrigins); + } + + public void setCorsAllowedHeaders(List allowedHeaders) { + this.defaultConfiguration.setAllowedHeaders(new ArrayList(allowedHeaders)); + } + + public void setCorsAllowedCredentials(boolean allowedCredentials) { + this.defaultConfiguration.setAllowedCredentials(allowedCredentials); + } + + public void setCorsAllowedMethods(List corsXhrAllowedMethods) { + this.defaultConfiguration.setAllowedMethods(new ArrayList(corsXhrAllowedMethods)); + } + + public void setCorsMaxAge(int age) { + this.defaultConfiguration.setMaxAge(age); + } + + //----------------CONFIGURATION GETTERS ---------------------------------------------// + + public CorsConfiguration getDefaultConfiguration() { + return defaultConfiguration; } - public void setAllowedHeaders(List allowedHeaders) { - this.allowedHeaders = allowedHeaders; + public CorsConfiguration getXhrConfiguration() { + return xhrConfiguration; } } \ No newline at end of file diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java index af184ade335..b07402aedc7 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java @@ -222,4 +222,13 @@ public static List getAuthoritiesFromStrings(Collect return result; } + public static boolean containsIgnoreCase(List list, String findMe) { + for (String s : list) { + if (findMe.equalsIgnoreCase(s)) { + return true; + } + } + return false; + } + } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/web/CorsFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/CorsFilterTests.java similarity index 73% rename from server/src/test/java/org/cloudfoundry/identity/uaa/web/CorsFilterTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/security/web/CorsFilterTests.java index a46341239af..b933358d964 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/web/CorsFilterTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/CorsFilterTests.java @@ -1,33 +1,51 @@ -package org.cloudfoundry.identity.uaa.web; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.internal.util.reflection.Whitebox.getInternalState; -import static org.mockito.internal.util.reflection.Whitebox.setInternalState; - -import java.io.IOException; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.security.web; import org.apache.log4j.Appender; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import org.apache.log4j.WriterAppender; -import org.cloudfoundry.identity.uaa.security.web.CorsFilter; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.springframework.http.HttpHeaders.ACCEPT; +import static org.springframework.http.HttpHeaders.ACCEPT_LANGUAGE; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.HttpHeaders.CONTENT_LANGUAGE; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; + public class CorsFilterTests { StringWriter writer; @@ -46,6 +64,42 @@ public void clean() { Logger.getRootLogger().removeAppender(this.appender); } + @Test + public void test_XHR_Default_Allowed_Methods() { + CorsFilter filter = new CorsFilter(); + assertThat(filter.getXhrConfiguration().getAllowedMethods(), containsInAnyOrder("GET", "OPTIONS")); + } + + @Test + public void test_NonXHR_Default_Allowed_Methods() { + CorsFilter filter = new CorsFilter(); + assertThat(filter.getDefaultConfiguration().getAllowedMethods(), containsInAnyOrder("GET", "POST", "PUT", "DELETE", "OPTIONS")); + } + + @Test + public void test_XHR_Default_Allowed_Headers() { + CorsFilter filter = new CorsFilter(); + assertThat(filter.getXhrConfiguration().getAllowedHeaders(), containsInAnyOrder(ACCEPT, ACCEPT_LANGUAGE, CONTENT_TYPE, CONTENT_LANGUAGE,AUTHORIZATION, CorsFilter.X_REQUESTED_WITH)); + } + + @Test + public void test_NonXHR_Default_Allowed_Headers() { + CorsFilter filter = new CorsFilter(); + assertThat(filter.getDefaultConfiguration().getAllowedHeaders(), containsInAnyOrder(ACCEPT, ACCEPT_LANGUAGE, CONTENT_TYPE, CONTENT_LANGUAGE,AUTHORIZATION)); + } + + @Test + public void test_XHR_Default_Allowed_Credentials() { + CorsFilter filter = new CorsFilter(); + assertTrue(filter.getXhrConfiguration().isAllowedCredentials()); + } + + @Test + public void test_NonXHR_Default_Allowed_Credentials() { + CorsFilter filter = new CorsFilter(); + assertFalse(filter.getDefaultConfiguration().isAllowedCredentials()); + } + @Test public void testRequestExpectStandardCorsResponse() throws ServletException, IOException { CorsFilter corsFilter = createConfiguredCorsFilter(); @@ -178,7 +232,7 @@ public void testPreFlightExpectStandardCorsResponse() throws ServletException, I corsFilter.doFilter(request, response, filterChain); - assertStandardCorsPreFlightResponse(response); + assertStandardCorsPreFlightResponse(response, "Authorization"); } @Test @@ -312,26 +366,27 @@ public void doInitializeWithNoPropertiesSet() throws ServletException, IOExcepti CorsFilter corsFilter = new CorsFilter(); // We need to set the default value that Spring would otherwise set. - List allowedUris = new ArrayList(Arrays.asList(new String[] { "^$" })); - setInternalState(corsFilter, "corsXhrAllowedUris", allowedUris); + List allowedUris = new ArrayList<>(Arrays.asList(".*")); + corsFilter.getXhrConfiguration().setAllowedUris(allowedUris); + corsFilter.getDefaultConfiguration().setAllowedUris(allowedUris); // We need to set the default value that Spring would otherwise set. - List allowedOrigins = new ArrayList(Arrays.asList(new String[] { "^$" })); - setInternalState(corsFilter, "corsXhrAllowedOrigins", allowedOrigins); + List allowedOrigins = new ArrayList<>(Arrays.asList(".*")); + corsFilter.getDefaultConfiguration().setAllowedOrigins(allowedOrigins); corsFilter.initialize(); @SuppressWarnings("unchecked") - List allowedUriPatterns = (List) getInternalState(corsFilter, "corsXhrAllowedUriPatterns"); + List allowedUriPatterns = corsFilter.getXhrConfiguration().getAllowedUriPatterns(); assertEquals(1, allowedUriPatterns.size()); @SuppressWarnings("unchecked") - List allowedOriginPatterns = - (List) getInternalState(corsFilter, "corsXhrAllowedOriginPatterns"); + List allowedOriginPatterns = corsFilter.getXhrConfiguration().getAllowedOriginPatterns(); assertEquals(1, allowedOriginPatterns.size()); MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/uaa/userinfo"); request.addHeader("Access-Control-Request-Method", "GET"); + request.addHeader("Access-Control-Request-Headers", AUTHORIZATION+", "+ACCEPT+", "+CONTENT_TYPE+", "+ACCEPT_LANGUAGE+", "+CONTENT_LANGUAGE); request.addHeader("Origin", "example.com"); MockHttpServletResponse response = new MockHttpServletResponse(); @@ -340,7 +395,7 @@ public void doInitializeWithNoPropertiesSet() throws ServletException, IOExcepti corsFilter.doFilter(request, response, filterChain); - assertStandardCorsPreFlightResponse(response); + assertStandardCorsPreFlightResponse(response, AUTHORIZATION, ACCEPT, CONTENT_TYPE, ACCEPT_LANGUAGE, CONTENT_LANGUAGE); } @Test @@ -350,10 +405,10 @@ public void doInitializeWithInvalidUriRegex() { List allowedUris = new ArrayList(Arrays.asList(new String[] { "^/uaa/userinfo(", "^/uaa/logout.do$" })); - setInternalState(corsFilter, "corsXhrAllowedUris", allowedUris); + corsFilter.getXhrConfiguration().setAllowedUris(allowedUris); List allowedOrigins = new ArrayList(Arrays.asList(new String[] { "example.com$" })); - setInternalState(corsFilter, "corsXhrAllowedOrigins", allowedOrigins); + corsFilter.getXhrConfiguration().setAllowedOrigins(allowedOrigins); corsFilter.initialize(); @@ -366,12 +421,11 @@ public void doInitializeWithInvalidOriginRegex() { CorsFilter corsFilter = new CorsFilter(); - List allowedUris = - new ArrayList(Arrays.asList(new String[] { "^/uaa/userinfo$", "^/uaa/logout.do$" })); - setInternalState(corsFilter, "corsXhrAllowedUris", allowedUris); + List allowedUris = new ArrayList<>(Arrays.asList("^/uaa/userinfo$", "^/uaa/logout.do$")); + corsFilter.getXhrConfiguration().setAllowedUris(allowedUris); - List allowedOrigins = new ArrayList(Arrays.asList(new String[] { "example.com(" })); - setInternalState(corsFilter, "corsXhrAllowedOrigins", allowedOrigins); + List allowedOrigins = new ArrayList<>(Arrays.asList("example.com(")); + corsFilter.getXhrConfiguration().setAllowedOrigins(allowedOrigins); corsFilter.initialize(); @@ -382,30 +436,31 @@ public void doInitializeWithInvalidOriginRegex() { private static CorsFilter createConfiguredCorsFilter() { CorsFilter corsFilter = new CorsFilter(); - List allowedUris = - new ArrayList(Arrays.asList(new String[] { "^/uaa/userinfo$", "^/uaa/logout\\.do$" })); - setInternalState(corsFilter, "corsXhrAllowedUris", allowedUris); + List allowedUris = new ArrayList<>(Arrays.asList("^/uaa/userinfo$", "^/uaa/logout\\.do$" )); + corsFilter.getXhrConfiguration().setAllowedUris(allowedUris); + corsFilter.getDefaultConfiguration().setAllowedUris(allowedUris); - List allowedOrigins = new ArrayList(Arrays.asList(new String[] { "example.com$" })); - setInternalState(corsFilter, "corsXhrAllowedOrigins", allowedOrigins); + List allowedOrigins = new ArrayList(Arrays.asList("example.com$")); + corsFilter.getXhrConfiguration().setAllowedOrigins(allowedOrigins); + corsFilter.getDefaultConfiguration().setAllowedOrigins(allowedOrigins); - List allowedHeaders = Arrays.asList(new String[] {"Accept", "Authorization"}); - corsFilter.setAllowedHeaders(allowedHeaders); + corsFilter.getXhrConfiguration().setAllowedHeaders(Arrays.asList("Accept", "Authorization","X-Requested-With")); + corsFilter.getDefaultConfiguration().setAllowedHeaders(Arrays.asList("Accept", "Authorization")); corsFilter.initialize(); return corsFilter; } - private static void assertStandardCorsPreFlightResponse(final MockHttpServletResponse response) { + private static void assertStandardCorsPreFlightResponse(final MockHttpServletResponse response, String... allowedHeaders) { assertEquals("*", response.getHeaderValue("Access-Control-Allow-Origin")); - assertEquals("GET, POST, PUT, DELETE", response.getHeaderValue("Access-Control-Allow-Methods")); - assertEquals("Authorization", response.getHeaderValue("Access-Control-Allow-Headers")); + assertEquals("GET, OPTIONS, POST, PUT, DELETE", response.getHeaderValue("Access-Control-Allow-Methods")); + assertThat(new CorsFilter().splitCommaDelimitedString((String)response.getHeaderValue("Access-Control-Allow-Headers")), containsInAnyOrder(allowedHeaders)); assertEquals("1728000", response.getHeaderValue("Access-Control-Max-Age")); } private static void assertXhrCorsPreFlightResponse(final MockHttpServletResponse response) { assertEquals("example.com", response.getHeaderValue("Access-Control-Allow-Origin")); - assertEquals("GET", response.getHeaderValue("Access-Control-Allow-Methods")); + assertEquals("GET, OPTIONS", response.getHeaderValue("Access-Control-Allow-Methods")); assertEquals("Authorization, X-Requested-With", response.getHeaderValue("Access-Control-Allow-Headers")); assertEquals("1728000", response.getHeaderValue("Access-Control-Max-Age")); } diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index b92c8d033eb..04a09a01d13 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -82,7 +82,92 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index f98c48e70ce..87666de5b38 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -15,28 +15,29 @@ import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory; import org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory; import org.apache.tomcat.jdbc.pool.DataSource; +import org.cloudfoundry.identity.uaa.account.ResetPasswordController; import org.cloudfoundry.identity.uaa.authentication.manager.PeriodLockoutPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.impl.config.YamlServletProfileInitializer; import org.cloudfoundry.identity.uaa.message.EmailService; import org.cloudfoundry.identity.uaa.message.NotificationsService; -import org.cloudfoundry.identity.uaa.account.ResetPasswordController; -import org.cloudfoundry.identity.uaa.zone.KeyPair; -import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; -import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; -import org.cloudfoundry.identity.uaa.zone.TokenPolicy; -import org.cloudfoundry.identity.uaa.impl.config.YamlServletProfileInitializer; -import org.cloudfoundry.identity.uaa.constants.OriginKeys; -import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.provider.saml.ZoneAwareMetadataGenerator; import org.cloudfoundry.identity.uaa.message.util.FakeJavaMailSender; -import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; import org.cloudfoundry.identity.uaa.oauth.UaaTokenServices; import org.cloudfoundry.identity.uaa.oauth.UaaTokenStore; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; +import org.cloudfoundry.identity.uaa.provider.saml.ZoneAwareMetadataGenerator; import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; -import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.security.web.CorsFilter; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneResolvingFilter; +import org.cloudfoundry.identity.uaa.zone.KeyPair; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; @@ -87,6 +88,11 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.springframework.http.HttpHeaders.ACCEPT; +import static org.springframework.http.HttpHeaders.ACCEPT_LANGUAGE; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.http.HttpHeaders.CONTENT_LANGUAGE; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; public class BootstrapTests { @@ -207,6 +213,30 @@ public void testRootContextDefaults() throws Exception { ZoneAwareMetadataGenerator zoneAwareMetadataGenerator = context.getBean(ZoneAwareMetadataGenerator.class); assertTrue(zoneAwareMetadataGenerator.isRequestSigned()); assertTrue(zoneAwareMetadataGenerator.isWantAssertionSigned()); + + CorsFilter corFilter = context.getBean(CorsFilter.class); + + assertEquals(1728000, corFilter.getXhrConfiguration().getMaxAge()); + assertEquals(1728000, corFilter.getDefaultConfiguration().getMaxAge()); + + assertEquals(1, corFilter.getXhrConfiguration().getAllowedUris().size()); + assertEquals(".*", corFilter.getXhrConfiguration().getAllowedUris().get(0)); + assertEquals(1, corFilter.getXhrConfiguration().getAllowedUris().size()); + assertEquals(".*", corFilter.getDefaultConfiguration().getAllowedUris().get(0)); + assertEquals(1, corFilter.getXhrConfiguration().getAllowedUriPatterns().size()); + assertEquals(1, corFilter.getDefaultConfiguration().getAllowedUriPatterns().size()); + + assertThat(corFilter.getXhrConfiguration().getAllowedHeaders(), containsInAnyOrder(ACCEPT, ACCEPT_LANGUAGE, CONTENT_TYPE, CONTENT_LANGUAGE,AUTHORIZATION, CorsFilter.X_REQUESTED_WITH)); + assertThat(corFilter.getDefaultConfiguration().getAllowedHeaders(), containsInAnyOrder(ACCEPT, ACCEPT_LANGUAGE, CONTENT_TYPE, CONTENT_LANGUAGE,AUTHORIZATION)); + + assertThat(corFilter.getXhrConfiguration().getAllowedOrigins(), containsInAnyOrder(".*")); + assertThat(corFilter.getDefaultConfiguration().getAllowedOrigins(), containsInAnyOrder(".*")); + + assertThat(corFilter.getXhrConfiguration().getAllowedMethods(), containsInAnyOrder("OPTIONS", "GET")); + assertThat(corFilter.getDefaultConfiguration().getAllowedMethods(), containsInAnyOrder("OPTIONS", "GET", "POST", "PUT", "DELETE")); + + assertTrue(corFilter.getXhrConfiguration().isAllowedCredentials()); + assertFalse(corFilter.getDefaultConfiguration().isAllowedCredentials()); } @Test @@ -260,8 +290,8 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { context = getServletContext(null, "login.yml", "test/hostnames/uaa.yml", "file:./src/main/webapp/WEB-INF/spring-servlet.xml"); IdentityZoneResolvingFilter filter = context.getBean(IdentityZoneResolvingFilter.class); - Set defaultHostnames = new HashSet<>(Arrays.asList(uaa, login, "localhost", "host1.domain.com", "host2", "test3.localhost", "test4.localhost")); - assertEquals(filter.getDefaultZoneHostnames(), defaultHostnames); + + assertThat(filter.getDefaultZoneHostnames(), containsInAnyOrder(uaa, login, "localhost", "host1.domain.com", "host2", "test3.localhost", "test4.localhost")); DataSource ds = context.getBean(DataSource.class); assertEquals(50, ds.getMaxActive()); assertEquals(5, ds.getMaxIdle()); @@ -405,7 +435,7 @@ public void testDefaultInternalHostnamesAndNoDBSettings() throws Exception { } @Test - public void testBootstrappedIdps_and_ExcludedClaims() throws Exception { + public void testBootstrappedIdps_and_ExcludedClaims_and_CorsConfig() throws Exception { //generate login.yml with SAML and uaa.yml with LDAP System.setProperty("database.caseinsensitive", "false"); @@ -428,6 +458,22 @@ public void testBootstrappedIdps_and_ExcludedClaims() throws Exception { } assertNotNull(providerProvisioning.retrieveByOrigin(OriginKeys.LDAP, IdentityZone.getUaa().getId())); + + CorsFilter filter = context.getBean(CorsFilter.class); + + for (CorsFilter.CorsConfiguration configuration : Arrays.asList(filter.getXhrConfiguration(), filter.getDefaultConfiguration())) { + assertEquals(1999999, configuration.getMaxAge()); + assertEquals(1, configuration.getAllowedUris().size()); + assertEquals(".*token$", configuration.getAllowedUris().get(0)); + assertEquals(1, configuration.getAllowedUriPatterns().size()); + assertTrue(configuration.isAllowedCredentials()); + assertThat(configuration.getAllowedHeaders(), containsInAnyOrder("Accept", "Content-Type")); + assertThat(configuration.getAllowedOrigins(), containsInAnyOrder("^example.com.*", "foo.com")); + assertThat(configuration.getAllowedMethods(), containsInAnyOrder("PUT", "POST", "GET")); + } + + + } @Test diff --git a/uaa/src/test/resources/test/bootstrap/uaa.yml b/uaa/src/test/resources/test/bootstrap/uaa.yml index 1bfcca1b08d..303006ef50b 100644 --- a/uaa/src/test/resources/test/bootstrap/uaa.yml +++ b/uaa/src/test/resources/test/bootstrap/uaa.yml @@ -19,3 +19,21 @@ jwt: test-signing-key verificationKey: | test-verification-key +cors: + xhr: &xhr + max_age: 1999999 + allowed: + uris: + .*token$ + credentials: true + headers: + - Accept + - Content-Type + origins: + - ^example.com.* + - foo.com + methods: + - GET + - POST + - PUT + default: *xhr From 4f5435df567af35a78cd89acdd9ae07a0e0ae209 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Sat, 19 Dec 2015 11:29:36 -0700 Subject: [PATCH 089/103] Move to correct place --- .../java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {models => model}/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java (100%) diff --git a/models/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java similarity index 100% rename from models/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java rename to model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java From 54825b572203a254cd4bb8bb8b8cb2060ecdddeb Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Wed, 9 Dec 2015 12:28:50 -0800 Subject: [PATCH 090/103] If verified property is not set in the request to create the user, default to true [#109267688] https://www.pivotaltracker.com/story/show/109267688 Signed-off-by: Madhura Bhave Signed-off-by: Jeremy Coffield --- .../identity/uaa/scim/ScimUser.java | 2 +- .../account/EmailAccountCreationService.java | 1 + .../identity/uaa/scim/ScimUserTests.java | 12 ++++++---- .../ScimUserEndpointsIntegrationTests.java | 22 +++---------------- .../integration/feature/InvitationsIT.java | 1 + .../login/AccountsControllerMockMvcTests.java | 6 +++++ .../mock/audit/AuditCheckMockMvcTests.java | 1 + .../LoginSamlAuthenticationProviderTests.java | 1 + .../ScimUserEndpointsMockMvcTests.java | 1 + 9 files changed, 23 insertions(+), 24 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java index 707728c3d9b..9dfcf8b4021 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java @@ -318,7 +318,7 @@ public void setType(String type) { private boolean active = true; - private boolean verified = false; + private boolean verified = true; private String origin = ""; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java index da709cfc8ff..6ef6f372811 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java @@ -148,6 +148,7 @@ public ScimUser createUser(String username, String password, String origin) { scimUser.setEmails(Arrays.asList(email)); scimUser.setOrigin(origin); scimUser.setPassword(password); + scimUser.setVerified(false); try { ScimUser userResponse = scimUserProvisioning.createUser(scimUser, password); return userResponse; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTests.java index f5ff46084c7..ec768e7e5d1 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTests.java @@ -239,11 +239,9 @@ public void basicNamesAreMappedCorrectly() { roz.setNickName("NickName"); assertEquals("NickName", roz.getNickName()); - assertFalse(roz.isVerified()); - roz.setVerified(true); assertTrue(roz.isVerified()); - - + roz.setVerified(false); + assertFalse(roz.isVerified()); } @Test @@ -430,4 +428,10 @@ public void testPasswordLastModified() throws Exception { assertSame(d, user.getPasswordLastModified()); } + + @Test + public void user_verified_byDefault() throws Exception { + ScimUser user = new ScimUser(); + assertTrue(user.isVerified()); + } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimUserEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimUserEndpointsIntegrationTests.java index 11437e49d55..62736fe6242 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimUserEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimUserEndpointsIntegrationTests.java @@ -139,6 +139,7 @@ public void createUserSucceeds() throws Exception { ScimUser joe2 = client.getForObject(serverRunning.getUrl(userEndpoint + "/{id}"), ScimUser.class, joe1.getId()); assertEquals(joe1.getId(), joe2.getId()); + assertTrue(joe2.isVerified()); } // curl -v -H "Content-Type: application/json" -H "Accept: application/json" @@ -146,8 +147,8 @@ public void createUserSucceeds() throws Exception { // "{\"userName\":\"joe\",\"schemas\":[\"urn:scim:schemas:core:1.0\"]}" // http://localhost:8080/uaa/User @Test - public void createUserSucceedsVerifiedIsFalse() throws Exception { - ResponseEntity response = createUser(JOE, "Joe", "User", "joe@blah.com"); + public void createUserSucceedsWithVerifiedIsFalse() throws Exception { + ResponseEntity response = createUser(JOE, "Joe", "User", "joe@blah.com", false); ScimUser joe1 = response.getBody(); assertEquals(JOE, joe1.getUserName()); @@ -158,23 +159,6 @@ public void createUserSucceedsVerifiedIsFalse() throws Exception { assertFalse(joe2.isVerified()); } - // curl -v -H "Content-Type: application/json" -H "Accept: application/json" - // --data - // "{\"userName\":\"joe\",\"schemas\":[\"urn:scim:schemas:core:1.0\"]}" - // http://localhost:8080/uaa/User - @Test - public void createUserSucceedsWithVerifiedIsTrue() throws Exception { - ResponseEntity response = createUser(JOE, "Joe", "User", "joe@blah.com", true); - ScimUser joe1 = response.getBody(); - assertEquals(JOE, joe1.getUserName()); - - // Check we can GET the user - ScimUser joe2 = client.getForObject(serverRunning.getUrl(userEndpoint + "/{id}"), ScimUser.class, joe1.getId()); - - assertEquals(joe1.getId(), joe2.getId()); - assertTrue(joe2.isVerified()); - } - // curl -v -H "Content-Type: application/json" -H "Accept: application/json" // --data // "{\"userName\":\"joe\",\"schemas\":[\"urn:scim:schemas:core:1.0\"]}" diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java index 606b4372257..3ecaf93f6b4 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/InvitationsIT.java @@ -199,6 +199,7 @@ public static String createInvitation(String baseUrl, String uaaUrl, String user scimUser.setUserName(username); scimUser.setPrimaryEmail(userEmail); scimUser.setOrigin(origin); + scimUser.setVerified(false); String userId = null; try { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java index 24bd1608f40..0b553e6a1d3 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerMockMvcTests.java @@ -11,6 +11,8 @@ import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.PredictableGenerator; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; import org.cloudfoundry.identity.uaa.account.EmailAccountCreationService; import org.cloudfoundry.identity.uaa.test.TestClient; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; @@ -240,6 +242,10 @@ public void testCreatingAnAccount() throws Exception { .andExpect(status().isFound()) .andExpect(redirectedUrl("accounts/email_sent")); + JdbcScimUserProvisioning scimUserProvisioning = getWebApplicationContext().getBean(JdbcScimUserProvisioning.class); + ScimUser scimUser = scimUserProvisioning.query("userName eq '" + userEmail + "' and origin eq '" + OriginKeys.UAA + "'").get(0); + assertFalse(scimUser.isVerified()); + MvcResult mvcResult = getMockMvc().perform(get("/verify_user") .param("code", "test" + generator.counter.get())) .andExpect(status().isFound()) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java index 7e39006407b..581f0708b23 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java @@ -812,6 +812,7 @@ private ScimUser createUser(String adminToken, String username, String firstname user.setName(new ScimUser.Name(firstname, lastname)); user.addEmail(email); user.setPassword(password); + user.setVerified(false); MockHttpServletRequestBuilder userPost = post("/Users") .accept(MediaType.APPLICATION_JSON_VALUE) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java index 4ee96075a0a..aac84c258e9 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java @@ -423,6 +423,7 @@ public void invitedUser_authentication_whenAuthenticatedEmailDoesNotMatchInvited private ScimUser getInvitedUser() { ScimUser invitedUser = new ScimUser(null, "marissa.invited@test.org", "Marissa", "Bloggs"); invitedUser.setPassword("a"); + invitedUser.setVerified(false); invitedUser.setPrimaryEmail("marissa.invited@test.org"); ScimUser scimUser = userProvisioning.create(invitedUser); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java index 016d4b243ae..5f395839f44 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsMockMvcTests.java @@ -559,6 +559,7 @@ private MockHttpServletRequestBuilder setUpVerificationLinkRequest(ScimUser user private ScimUser setUpScimUser() { String email = "joe@"+generator.generate().toLowerCase()+".com"; ScimUser joel = new ScimUser(null, email, "Joel", "D'sa"); + joel.setVerified(false); joel.addEmail(email); joel = usersRepository.createUser(joel, "pas5Word"); return joel; From a3d2be3752153fcc1d1e897d194bf8e81125f578 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Thu, 7 Jan 2016 14:33:04 -0800 Subject: [PATCH 091/103] Respect allowUnverifiedUsers only for users that exist as of this version [#109267688] https://www.pivotaltracker.com/story/show/109267688 Signed-off-by: Jonathan Lo --- .../BadCredentialsListener.java | 4 +- .../UserAuthenticationSuccessListener.java | 38 ++++++++ .../manager/AuthzAuthenticationManager.java | 10 +-- .../uaa/scim/bootstrap/ScimUserBootstrap.java | 1 + .../uaa/user/JdbcUaaUserDatabase.java | 10 ++- .../identity/uaa/user/UaaUser.java | 7 ++ .../identity/uaa/user/UaaUserPrototype.java | 9 ++ ...__Old_Users_For_Verification_Bleedover.sql | 2 + ...__Old_Users_For_Verification_Bleedover.sql | 2 + ...__Old_Users_For_Verification_Bleedover.sql | 2 + ...serAuthenticationSuccessListenerTests.java | 89 +++++++++++++++++++ .../AuthzAuthenticationManagerTests.java | 51 +++++++---- .../uaa/user/JdbcUaaUserDatabaseTests.java | 7 ++ .../main/webapp/WEB-INF/spring-servlet.xml | 4 + uaa/src/main/webapp/WEB-INF/spring/audit.xml | 2 +- 15 files changed, 210 insertions(+), 28 deletions(-) rename server/src/main/java/org/cloudfoundry/identity/uaa/authentication/{event => listener}/BadCredentialsListener.java (90%) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_0_0__Old_Users_For_Verification_Bleedover.sql create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_0__Old_Users_For_Verification_Bleedover.sql create mode 100644 server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_0_0__Old_Users_For_Verification_Bleedover.sql create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/BadCredentialsListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/BadCredentialsListener.java similarity index 90% rename from server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/BadCredentialsListener.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/BadCredentialsListener.java index 07242b3893f..187c2d8a708 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/BadCredentialsListener.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/BadCredentialsListener.java @@ -10,9 +10,11 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.authentication.event; +package org.cloudfoundry.identity.uaa.authentication.listener; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; +import org.cloudfoundry.identity.uaa.authentication.event.PrincipalAuthenticationFailureEvent; +import org.cloudfoundry.identity.uaa.authentication.event.PrincipalNotFoundEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java new file mode 100644 index 00000000000..c69082638bb --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java @@ -0,0 +1,38 @@ +package org.cloudfoundry.identity.uaa.authentication.listener; + +import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.springframework.context.ApplicationListener; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +public class UserAuthenticationSuccessListener implements ApplicationListener { + + private final ScimUserProvisioning scimUserProvisioning; + + public UserAuthenticationSuccessListener(ScimUserProvisioning scimUserProvisioning) { + this.scimUserProvisioning = scimUserProvisioning; + } + + @Override + public void onApplicationEvent(UserAuthenticationSuccessEvent event) { + UaaUser user = event.getUser(); + if(user.isLegacyVerificationBehavior() && !user.isVerified()) { + ScimUser scimUser = scimUserProvisioning.retrieve(user.getId()); + scimUser.setVerified(true); + scimUserProvisioning.update(user.getId(), scimUser); + } + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java index ac7e8482312..815f8001438 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java @@ -112,11 +112,11 @@ public Authentication authenticate(Authentication req) throws AuthenticationExce } else { logger.debug("Password successfully matched for userId["+user.getUsername()+"]:"+user.getId()); - if (!allowUnverifiedUsers && !user.isVerified()) { - publish(new UnverifiedUserAuthenticationEvent(user, req)); - logger.debug("Account not verified: " + user.getId()); - throw new AccountNotVerifiedException("Account not verified"); - } + if ((!allowUnverifiedUsers || !user.isLegacyVerificationBehavior()) && !user.isVerified()) { + publish(new UnverifiedUserAuthenticationEvent(user, req)); + logger.debug("Account not verified: " + user.getId()); + throw new AccountNotVerifiedException("Account not verified"); + } int expiringPassword = getPasswordExpiresInMonths(); if (expiringPassword>0) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java index 769a271e428..a4e2d13f09e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java @@ -14,6 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.authentication.event.UnverifiedUserAuthenticationEvent; import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent; import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java index a602185bed2..e24b632ecb6 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java @@ -40,7 +40,7 @@ */ public class JdbcUaaUserDatabase implements UaaUserDatabase { - public static final String USER_FIELDS = "id,username,password,email,givenName,familyName,created,lastModified,authorities,origin,external_id,verified,identity_zone_id,salt,passwd_lastmodified,phoneNumber "; + public static final String USER_FIELDS = "id,username,password,email,givenName,familyName,created,lastModified,authorities,origin,external_id,verified,identity_zone_id,salt,passwd_lastmodified,phoneNumber,legacy_verification_behavior "; public static final String DEFAULT_USER_BY_USERNAME_QUERY = "select " + USER_FIELDS + "from users " + "where lower(username) = ? and active=? and origin=? and identity_zone_id=?"; @@ -118,18 +118,20 @@ public UaaUser mapRow(ResultSet rs, int rowNum) throws SQLException { .withUsername(rs.getString(2)) .withPassword(rs.getString(3)) .withEmail(rs.getString(4)) - .withAuthorities(getDefaultAuthorities(rs.getString(9))) .withGivenName(rs.getString(5)) .withFamilyName(rs.getString(6)) - .withPhoneNumber(rs.getString(16)) .withCreated(rs.getTimestamp(7)) .withModified(rs.getTimestamp(8)) + .withAuthorities(getDefaultAuthorities(rs.getString(9))) .withOrigin(rs.getString(10)) .withExternalId(rs.getString(11)) .withVerified(rs.getBoolean(12)) .withZoneId(rs.getString(13)) .withSalt(rs.getString(14)) - .withPasswordLastModified(rs.getTimestamp(15)); + .withPasswordLastModified(rs.getTimestamp(15)) + .withPhoneNumber(rs.getString(16)) + .withLegacyVerificationBehavior(rs.getBoolean(17)) + ; if (userAuthoritiesQuery == null) { return new UaaUser(prototype); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java index 8da589efdbe..70e85195a55 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java @@ -65,6 +65,8 @@ public String getZoneId() { private boolean verified = false; + private boolean legacyVerificationBehavior = false; + public UaaUser(String username, String password, String email, String givenName, String familyName) { this("NaN", username, password, email, UaaAuthority.USER_AUTHORITIES, givenName, familyName, new Date(), new Date(), null, null, false, null, null, new Date()); @@ -119,6 +121,7 @@ public UaaUser(UaaUserPrototype prototype) { this.salt = prototype.getSalt(); this.passwordLastModified = prototype.getPasswordLastModified(); this.phoneNumber = prototype.getPhoneNumber(); + this.legacyVerificationBehavior = prototype.isLegacyVerificationBehavior(); } public String getId() { @@ -250,4 +253,8 @@ public void setVerified(boolean verified) { public String getPhoneNumber() { return phoneNumber; } + + public boolean isLegacyVerificationBehavior() { + return legacyVerificationBehavior; + } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java index dba6078a149..ab59dd971c1 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java @@ -51,6 +51,8 @@ public final class UaaUserPrototype { private boolean verified = false; + private boolean legacyVerificationBehavior; + public String getId() { return id; } @@ -194,4 +196,11 @@ public UaaUserPrototype withVerified(boolean verified) { this.verified = verified; return this; } + + public boolean isLegacyVerificationBehavior() { return legacyVerificationBehavior; } + + public UaaUserPrototype withLegacyVerificationBehavior(boolean legacyVerificationBehavior) { + this.legacyVerificationBehavior = legacyVerificationBehavior; + return this; + } } diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_0_0__Old_Users_For_Verification_Bleedover.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_0_0__Old_Users_For_Verification_Bleedover.sql new file mode 100644 index 00000000000..c0689a62cef --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V3_0_0__Old_Users_For_Verification_Bleedover.sql @@ -0,0 +1,2 @@ +ALTER TABLE users ADD COLUMN legacy_verification_behavior BOOLEAN DEFAULT FALSE NOT NULL; +UPDATE users SET legacy_verification_behavior = TRUE; diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_0__Old_Users_For_Verification_Bleedover.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_0__Old_Users_For_Verification_Bleedover.sql new file mode 100644 index 00000000000..c0689a62cef --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V3_0_0__Old_Users_For_Verification_Bleedover.sql @@ -0,0 +1,2 @@ +ALTER TABLE users ADD COLUMN legacy_verification_behavior BOOLEAN DEFAULT FALSE NOT NULL; +UPDATE users SET legacy_verification_behavior = TRUE; diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_0_0__Old_Users_For_Verification_Bleedover.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_0_0__Old_Users_For_Verification_Bleedover.sql new file mode 100644 index 00000000000..c0689a62cef --- /dev/null +++ b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V3_0_0__Old_Users_For_Verification_Bleedover.sql @@ -0,0 +1,2 @@ +ALTER TABLE users ADD COLUMN legacy_verification_behavior BOOLEAN DEFAULT FALSE NOT NULL; +UPDATE users SET legacy_verification_behavior = TRUE; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java new file mode 100644 index 00000000000..d2f3de9d49d --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java @@ -0,0 +1,89 @@ +package org.cloudfoundry.identity.uaa.authentication.listener; + +import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.test.MockAuthentication; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.verification.VerificationMode; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/******************************************************************************* + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + *

+ * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + *

+ * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + *******************************************************************************/ +public class UserAuthenticationSuccessListenerTests { + + UserAuthenticationSuccessListener listener; + ScimUserProvisioning scimUserProvisioning; + + @Before + public void SetUp() + { + scimUserProvisioning = mock(ScimUserProvisioning.class); + listener = new UserAuthenticationSuccessListener(scimUserProvisioning); + } + + private static UserAuthenticationSuccessEvent getEvent(UaaUserPrototype userPrototype) { + return new UserAuthenticationSuccessEvent(new UaaUser(userPrototype), new MockAuthentication()); + } + + private static ScimUser getScimUser(UaaUser user) { + ScimUser scimUser = new ScimUser(user.getId(), user.getUsername(), user.getGivenName(), user.getFamilyName()); + scimUser.setVerified(user.isVerified()); + return scimUser; + } + + @Test + public void unverifiedUserBecomesVerifiedIfTheyHaveLegacyFlag() { + String id = "user-id"; + UserAuthenticationSuccessEvent event = getEvent(new UaaUserPrototype() + .withId(id) + .withUsername("testUser") + .withEmail("test@email.com") + .withVerified(false) + .withLegacyVerificationBehavior(true)); + when(scimUserProvisioning.retrieve(id)).thenReturn(getScimUser(event.getUser())); + + listener.onApplicationEvent(event); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ScimUser.class); + verify(scimUserProvisioning).update(eq(id), captor.capture()); + assertTrue(captor.getValue().isVerified()); + } + + @Test + public void unverifiedUserDoesNotBecomeVerifiedIfTheyHaveNoLegacyFlag() { + String id = "user-id"; + UserAuthenticationSuccessEvent event = getEvent(new UaaUserPrototype() + .withId(id) + .withUsername("testUser") + .withEmail("test@email.com") + .withVerified(false)); + when(scimUserProvisioning.retrieve(id)).thenReturn(getScimUser(event.getUser())); + + listener.onApplicationEvent(event); + + verify(scimUserProvisioning, never()).update(eq(id), anyObject()); + } + +} diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java index eb116fc2681..9ce45292c11 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java @@ -29,6 +29,7 @@ import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.user.UaaUserPrototype; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.Before; @@ -74,22 +75,7 @@ public class AuthzAuthenticationManagerTests { @Before public void setUp() throws Exception { - String id = new RandomValueStringGenerator().generate(); - user = new UaaUser( - id, - "auser", - PASSWORD, - "auser@blah.com", - UaaAuthority.USER_AUTHORITIES, - "A", "User", - new Date(), - new Date(), - OriginKeys.UAA, - null, - true, - IdentityZoneHolder.get().getId(), - id, - new Date()); + user = new UaaUser(getPrototype()); providerProvisioning = mock(IdentityProviderProvisioning.class); db = mock(UaaUserDatabase.class); publisher = mock(ApplicationEventPublisher.class); @@ -98,6 +84,22 @@ public void setUp() throws Exception { mgr.setOrigin(OriginKeys.UAA); } + private UaaUserPrototype getPrototype() { + String id = new RandomValueStringGenerator().generate(); + return new UaaUserPrototype() + .withId(id) + .withUsername("auser") + .withPassword(PASSWORD) + .withEmail("auser@blah.com") + .withAuthorities(UaaAuthority.USER_AUTHORITIES) + .withGivenName("A") + .withFamilyName("User") + .withOrigin(OriginKeys.UAA) + .withZoneId(IdentityZoneHolder.get().getId()) + .withExternalId(id) + .withVerified(true); + } + @Test public void successfulAuthentication() throws Exception { when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); @@ -214,13 +216,28 @@ public void originAuthenticationFail() throws Exception { } @Test - public void unverifiedAuthenticationSucceedsWhenAllowed() throws Exception { + public void unverifiedAuthenticationForOldUserSucceedsWhenAllowed() throws Exception { + mgr.setAllowUnverifiedUsers(true); + user = new UaaUser(getPrototype().withLegacyVerificationBehavior(true)); user.setVerified(false); when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); Authentication result = mgr.authenticate(createAuthRequest("auser", "password")); assertEquals("auser", result.getName()); assertEquals("auser", ((UaaPrincipal) result.getPrincipal()).getName()); + } + + @Test + public void unverifiedAuthenticationForNewUserFailsEvenWhenAllowed() throws Exception { + mgr.setAllowUnverifiedUsers(true); + user.setVerified(false); + when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user); + try { + mgr.authenticate(createAuthRequest("auser", "password")); + fail("Expected AccountNotVerifiedException"); + } catch(AccountNotVerifiedException e) { + verify(publisher).publishEvent(isA(UnverifiedUserAuthenticationEvent.class)); } + } @Test public void unverifiedAuthenticationFailsWhenNotAllowed() throws Exception { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java index d6e04d7ce09..91e8a264e25 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java @@ -25,10 +25,12 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import java.sql.Timestamp; +import java.util.Arrays; import java.util.Collections; import java.util.UUID; 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; @@ -98,6 +100,11 @@ public void clearDb() throws Exception { TestUtils.deleteFrom(dataSource, "users"); } + @Test + public void addedUserHasNoLegacyVerificationBehavior() { + Arrays.asList(JOE_ID, MABEL_ID, ALICE_ID).stream().map(id -> db.retrieveUserById(id)).forEach(user -> assertFalse(user.isLegacyVerificationBehavior())); + } + @Test public void getValidUserSucceeds() { UaaUser joe = db.retrieveUserByName("joe", OriginKeys.UAA); diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index 04a09a01d13..25c762db674 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -409,4 +409,8 @@ + + + + diff --git a/uaa/src/main/webapp/WEB-INF/spring/audit.xml b/uaa/src/main/webapp/WEB-INF/spring/audit.xml index b79e9384cc5..c61c79687b0 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/audit.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/audit.xml @@ -19,7 +19,7 @@ - + From 3095f7242f2671cd039977d5b31d6a35f8d4576d Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Thu, 7 Jan 2016 17:11:22 -0800 Subject: [PATCH 092/103] Invert user verified check for readability [#109267688] https://www.pivotaltracker.com/story/show/109267688 Signed-off-by: Jeremy Coffield --- .../manager/AuthzAuthenticationManager.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java index 815f8001438..8479b8a6741 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java @@ -112,11 +112,11 @@ public Authentication authenticate(Authentication req) throws AuthenticationExce } else { logger.debug("Password successfully matched for userId["+user.getUsername()+"]:"+user.getId()); - if ((!allowUnverifiedUsers || !user.isLegacyVerificationBehavior()) && !user.isVerified()) { - publish(new UnverifiedUserAuthenticationEvent(user, req)); - logger.debug("Account not verified: " + user.getId()); - throw new AccountNotVerifiedException("Account not verified"); - } + if (!(allowUnverifiedUsers && user.isLegacyVerificationBehavior()) && !user.isVerified()) { + publish(new UnverifiedUserAuthenticationEvent(user, req)); + logger.debug("Account not verified: " + user.getId()); + throw new AccountNotVerifiedException("Account not verified"); + } int expiringPassword = getPasswordExpiresInMonths(); if (expiringPassword>0) { From 2d1c201490ec6726f1bff467446fc556900ade90 Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Thu, 7 Jan 2016 17:30:10 -0800 Subject: [PATCH 093/103] Allow user prototypes to be passed to MockUaaUserDatabase [#109267688] https://www.pivotaltracker.com/story/show/109267688 Signed-off-by: Jonathan Lo --- .../UaaAuthenticationTestFactory.java | 2 +- ...ckIdpEnabledAuthenticationManagerTest.java | 2 +- .../ApprovalsAdminEndpointsTests.java | 2 +- .../uaa/user/MockUaaUserDatabase.java | 24 ++++++++----------- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationTestFactory.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationTestFactory.java index 2126d15eec5..3dc27792f32 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationTestFactory.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationTestFactory.java @@ -30,7 +30,7 @@ public class UaaAuthenticationTestFactory { public static UaaPrincipal getPrincipal(String id, String name, String email) { - return new UaaPrincipal(new MockUaaUserDatabase(id, name, email, name, "familyName").retrieveUserById(id)); + return new UaaPrincipal(new MockUaaUserDatabase(u -> u.withId(id).withUsername(name).withEmail(email).withGivenName(name).withFamilyName("familyName")).retrieveUserById(id)); } public static UaaAuthentication getAuthentication(String id, String name, String email) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java index ad688d8ffed..7060e5ba3a2 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java @@ -44,7 +44,7 @@ public class CheckIdpEnabledAuthenticationManagerTest extends JdbcTestBase { @Before public void setupAuthManager() throws Exception { identityProviderProvisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); - MockUaaUserDatabase userDatabase = new MockUaaUserDatabase("id","marissa","test@test.org","first","last"); + MockUaaUserDatabase userDatabase = new MockUaaUserDatabase(u -> u.withId("id").withUsername("marissa").withEmail("test@test.org").withGivenName("first").withFamilyName("last")); PasswordEncoder encoder = mock(PasswordEncoder.class); when(encoder.matches(anyString(),anyString())).thenReturn(true); AuthzAuthenticationManager authzAuthenticationManager = new AuthzAuthenticationManager(userDatabase, encoder, identityProviderProvisioning); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java index ec2fc2e1681..1c2b9a03ddc 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java @@ -67,7 +67,7 @@ public class ApprovalsAdminEndpointsTests extends JdbcTestBase { public void initApprovalsAdminEndpointsTests() { testAccounts = UaaTestAccounts.standard(null); String userId = testAccounts.getUserWithRandomID().getId(); - userDao = new MockUaaUserDatabase(userId, testAccounts.getUserName(), "marissa@test.com", "Marissa", "Bloggs"); + userDao = new MockUaaUserDatabase(u -> u.withId(userId).withUsername(testAccounts.getUserName()).withEmail("marissa@test.com").withGivenName("Marissa").withFamilyName("Bloggs")); jdbcTemplate = new JdbcTemplate(dataSource); marissa = userDao.retrieveUserById(userId); assertNotNull(marissa); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java b/server/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java index 7e568514e1f..651bf498e02 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java @@ -1,6 +1,6 @@ /******************************************************************************* * Cloud Foundry - * Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. + * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. @@ -14,22 +14,18 @@ import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import java.util.Collections; -import java.util.Date; +import java.util.function.Function; -/** - * @author Luke Taylor - */ public class MockUaaUserDatabase extends InMemoryUaaUserDatabase { - - public MockUaaUserDatabase(String id, String name, String email, String givenName, String familyName) { - super(Collections.singleton(createUser(id, name, email, givenName, familyName))); - } - - private static UaaUser createUser(String id, String name, String email, String givenName, String familyName) { - return new UaaUser(id, name, "", email, UaaAuthority.USER_AUTHORITIES, givenName, familyName, - new Date(), new Date(), OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), id, new Date()); + public MockUaaUserDatabase(Function buildPrototype) { + super(Collections.singleton(new UaaUser(buildPrototype.apply( + new UaaUserPrototype() + .withExternalId("externalId") + .withAuthorities(UaaAuthority.USER_AUTHORITIES) + .withOrigin(OriginKeys.UAA) + .withZoneId(IdentityZoneHolder.get().getId()) + )))); } } From 22b88658cce327a4999624d1b0da9493619ae023 Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Fri, 8 Jan 2016 10:11:34 -0800 Subject: [PATCH 094/103] Fix test expectations around user verification [#109267688] https://www.pivotaltracker.com/story/show/109267688 Signed-off-by: Jeremy Coffield --- ...ckIdpEnabledAuthenticationManagerTest.java | 2 +- .../mock/audit/AuditCheckMockMvcTests.java | 68 +++++++++++++------ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java index 7060e5ba3a2..b75f979f5f6 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java @@ -44,7 +44,7 @@ public class CheckIdpEnabledAuthenticationManagerTest extends JdbcTestBase { @Before public void setupAuthManager() throws Exception { identityProviderProvisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); - MockUaaUserDatabase userDatabase = new MockUaaUserDatabase(u -> u.withId("id").withUsername("marissa").withEmail("test@test.org").withGivenName("first").withFamilyName("last")); + MockUaaUserDatabase userDatabase = new MockUaaUserDatabase(u -> u.withId("id").withUsername("marissa").withEmail("test@test.org").withVerified(true)); PasswordEncoder encoder = mock(PasswordEncoder.class); when(encoder.matches(anyString(),anyString())).thenReturn(true); AuthzAuthenticationManager authzAuthenticationManager = new AuthzAuthenticationManager(userDatabase, encoder, identityProviderProvisioning); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java index 581f0708b23..459e2274ba8 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java @@ -56,6 +56,7 @@ import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.http.MediaType; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.ClientDetails; @@ -74,6 +75,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -98,6 +100,7 @@ public class AuditCheckMockMvcTests extends InjectedMockContextTest { private ScimUser testUser; private String testPassword = "secr3T"; ClientDetails originalLoginClient; + private AuthzAuthenticationManager mgr; @Before public void setUp() throws Exception { @@ -119,7 +122,7 @@ public void setUp() throws Exception { testAccounts.getAdminClientId(), testAccounts.getAdminClientSecret(), "uaa.admin,scim.write"); - testUser = createUser(adminToken, "testUser", "Test", "User", "testuser@test.com", testPassword); + testUser = createUser(adminToken, "testUser", "Test", "User", "testuser@test.com", testPassword, true); testListener.clearEvents(); listener2 = listener; @@ -128,6 +131,9 @@ public void setUp() throws Exception { authSuccessListener = mock(new DefaultApplicationListener() {}.getClass()); getWebApplicationContext().addApplicationListener(listener); getWebApplicationContext().addApplicationListener(authSuccessListener); + + this.mgr = getWebApplicationContext().getBean("uaaUserDatabaseAuthenticationManager", AuthzAuthenticationManager.class); + this.mgr.setAllowUnverifiedUsers(false); } @After @@ -201,13 +207,17 @@ public void invalidPasswordLoginFailedTest() throws Exception { } @Test - public void unverifiedUserAuthenticationWhenAllowedTest() throws Exception { + public void unverifiedLegacyUserAuthenticationWhenAllowedTest() throws Exception { + mgr.setAllowUnverifiedUsers(true); + String adminToken = testClient.getClientCredentialsOAuthAccessToken( testAccounts.getAdminClientId(), testAccounts.getAdminClientSecret(), "uaa.admin,scim.write"); - ScimUser molly = createUser(adminToken, "molly", "Molly", "Collywobble", "molly@example.com", "wobblE3"); + ScimUser molly = createUser(adminToken, "molly", "Molly", "Collywobble", "molly@example.com", "wobblE3", false); + + getWebApplicationContext().getBeansOfType(JdbcTemplate.class).values().stream().forEach(jdbc -> jdbc.execute("update users set legacy_verification_behavior = true where origin='uaa' and username = '" + molly.getUserName() + "'")); MockHttpServletRequestBuilder loginPost = post("/authenticate") .accept(MediaType.APPLICATION_JSON_VALUE) @@ -223,18 +233,39 @@ public void unverifiedUserAuthenticationWhenAllowedTest() throws Exception { } @Test - public void unverifiedUserAuthenticationWhenNotAllowedTest() throws Exception { - try { - for (Map.Entry mgr : getWebApplicationContext().getBeansOfType(AuthzAuthenticationManager.class).entrySet()) { - mgr.getValue().setAllowUnverifiedUsers(false); - } + public void unverifiedPostLegacyUserAuthenticationWhenAllowedTest() throws Exception { + mgr.setAllowUnverifiedUsers(true); + + String adminToken = testClient.getClientCredentialsOAuthAccessToken( + testAccounts.getAdminClientId(), + testAccounts.getAdminClientSecret(), + "uaa.admin,scim.write"); + ScimUser molly = createUser(adminToken, "molly", "Molly", "Collywobble", "molly@example.com", "wobblE3", false); + + MockHttpServletRequestBuilder loginPost = post("/authenticate") + .accept(MediaType.APPLICATION_JSON_VALUE) + .param("username", molly.getUserName()) + .param("password", "wobblE3"); + getMockMvc().perform(loginPost) + .andExpect(status().isForbidden()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(AbstractUaaEvent.class); + verify(listener, atLeast(1)).onApplicationEvent(captor.capture()); + + List allValues = captor.getAllValues(); + UnverifiedUserAuthenticationEvent event = (UnverifiedUserAuthenticationEvent) allValues.get(allValues.size() - 1); + assertEquals(molly.getUserName(), event.getUser().getUsername()); + } + + @Test + public void unverifiedUserAuthenticationWhenNotAllowedTest() throws Exception { String adminToken = testClient.getClientCredentialsOAuthAccessToken( testAccounts.getAdminClientId(), testAccounts.getAdminClientSecret(), "uaa.admin,scim.write"); - ScimUser molly = createUser(adminToken, "molly", "Molly", "Collywobble", "molly@example.com", "wobblE3"); + ScimUser molly = createUser(adminToken, "molly", "Molly", "Collywobble", "molly@example.com", "wobblE3", false); MockHttpServletRequestBuilder loginPost = post("/authenticate") .accept(MediaType.APPLICATION_JSON_VALUE) @@ -249,11 +280,6 @@ public void unverifiedUserAuthenticationWhenNotAllowedTest() throws Exception { List allValues = captor.getAllValues(); UnverifiedUserAuthenticationEvent event = (UnverifiedUserAuthenticationEvent) allValues.get(allValues.size() - 1); assertEquals(molly.getUserName(), event.getUser().getUsername()); - } finally { - for (Map.Entry mgr : getWebApplicationContext().getBeansOfType(AuthzAuthenticationManager.class).entrySet()) { - mgr.getValue().setAllowUnverifiedUsers(true); - } - } } @Test @@ -282,7 +308,7 @@ public void findAuditHistory() throws Exception { testAccounts.getAdminClientSecret(), "uaa.admin,scim.write"); - ScimUser jacob = createUser(adminToken, "jacob", "Jacob", "Gyllenhammer", "jacob@gyllenhammer.non", null); + ScimUser jacob = createUser(adminToken, "jacob", "Jacob", "Gyllenhammer", "jacob@gyllenhammer.non", null, true); String jacobId = jacob.getId(); MockHttpServletRequestBuilder loginPost = post("/authenticate") @@ -439,7 +465,7 @@ public void changePassword_ReturnsSuccess_WithValidExpiringCode() throws Excepti .andExpect(status().isOk()); ArgumentCaptor captor = ArgumentCaptor.forClass(AbstractUaaEvent.class); - verify(listener, times(5)).onApplicationEvent(captor.capture()); + verify(listener, atLeastOnce()).onApplicationEvent(captor.capture()); PasswordChangeEvent pce = (PasswordChangeEvent)captor.getValue(); assertEquals(testUser.getUserName(), pce.getUser().getUsername()); assertEquals("Password changed", pce.getMessage()); @@ -716,9 +742,9 @@ public void testGroupEvents() throws Exception { testAccounts.getAdminClientSecret(), "uaa.admin,scim.write"); - ScimUser jacob = createUser(adminToken, "jacob", "Jacob", "Gyllenhammer", "jacob@gyllenhammer.non", null); - ScimUser emily = createUser(adminToken, "emily", "Emily", "Gyllenhammer", "emily@gyllenhammer.non", null); - ScimUser jonas = createUser(adminToken, "jonas", "Jonas", "Gyllenhammer", "jonas@gyllenhammer.non", null); + ScimUser jacob = createUser(adminToken, "jacob", "Jacob", "Gyllenhammer", "jacob@gyllenhammer.non", null, true); + ScimUser emily = createUser(adminToken, "emily", "Emily", "Gyllenhammer", "emily@gyllenhammer.non", null, true); + ScimUser jonas = createUser(adminToken, "jonas", "Jonas", "Gyllenhammer", "jonas@gyllenhammer.non", null, true); ScimGroup group = new ScimGroup(null,"testgroup",IdentityZoneHolder.get().getId()); @@ -805,14 +831,14 @@ public void testGroupEvents() throws Exception { } - private ScimUser createUser(String adminToken, String username, String firstname, String lastname, String email, String password) throws Exception { + private ScimUser createUser(String adminToken, String username, String firstname, String lastname, String email, String password, boolean verified) throws Exception { ScimUser user = new ScimUser(); username+=new RandomValueStringGenerator().generate(); user.setUserName(username); user.setName(new ScimUser.Name(firstname, lastname)); user.addEmail(email); user.setPassword(password); - user.setVerified(false); + user.setVerified(verified); MockHttpServletRequestBuilder userPost = post("/Users") .accept(MediaType.APPLICATION_JSON_VALUE) From bfbb6174d236cb73b1546460a08d3252839beade Mon Sep 17 00:00:00 2001 From: Jeremy Coffield Date: Fri, 8 Jan 2016 10:20:15 -0800 Subject: [PATCH 095/103] User verifyUser method instead of non-atomic update [#109267688] http://www.pivotaltracker.com/story/show/109267688 Signed-off-by: Jonathan Lo --- .../listener/UserAuthenticationSuccessListener.java | 4 +--- .../listener/UserAuthenticationSuccessListenerTests.java | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java index c69082638bb..3a76a87608e 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java @@ -30,9 +30,7 @@ public UserAuthenticationSuccessListener(ScimUserProvisioning scimUserProvisioni public void onApplicationEvent(UserAuthenticationSuccessEvent event) { UaaUser user = event.getUser(); if(user.isLegacyVerificationBehavior() && !user.isVerified()) { - ScimUser scimUser = scimUserProvisioning.retrieve(user.getId()); - scimUser.setVerified(true); - scimUserProvisioning.update(user.getId(), scimUser); + scimUserProvisioning.verifyUser(user.getId(), -1); } } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java index d2f3de9d49d..1777c27fd43 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListenerTests.java @@ -12,7 +12,9 @@ import org.mockito.verification.VerificationMode; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -66,9 +68,7 @@ public void unverifiedUserBecomesVerifiedIfTheyHaveLegacyFlag() { listener.onApplicationEvent(event); - ArgumentCaptor captor = ArgumentCaptor.forClass(ScimUser.class); - verify(scimUserProvisioning).update(eq(id), captor.capture()); - assertTrue(captor.getValue().isVerified()); + verify(scimUserProvisioning).verifyUser(eq(id), eq(-1)); } @Test @@ -83,7 +83,7 @@ public void unverifiedUserDoesNotBecomeVerifiedIfTheyHaveNoLegacyFlag() { listener.onApplicationEvent(event); - verify(scimUserProvisioning, never()).update(eq(id), anyObject()); + verify(scimUserProvisioning, never()).verifyUser(anyString(), anyInt()); } } From 1cb6f61e12b6247660f6f5aa4dc53b1eed7da77b Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 12 Jan 2016 09:07:28 -0700 Subject: [PATCH 096/103] Add scim.write, scim.read and scim.create to zone switching scope list https://www.pivotaltracker.com/story/show/111439796 [#111439796] --- .../uaa/zone/IdentityZoneSwitchingFilter.java | 5 +- .../identity/uaa/mock/util/MockMvcUtils.java | 56 +++++++++++--- ...dentityZoneSwitchingFilterMockMvcTest.java | 77 +++++++++++++++---- .../ScimGroupEndpointsMockMvcTests.java | 4 +- 4 files changed, 112 insertions(+), 30 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java index fb13fd0e01c..f2c43a07111 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java @@ -54,6 +54,9 @@ public IdentityZoneSwitchingFilter(IdentityZoneProvisioning dao) { ZONES_ZONE_ID_PREFIX + ZONE_ID_MATCH + ".clients.admin", ZONES_ZONE_ID_PREFIX + ZONE_ID_MATCH + ".clients.read", ZONES_ZONE_ID_PREFIX + ZONE_ID_MATCH + ".clients.write", + ZONES_ZONE_ID_PREFIX + ZONE_ID_MATCH + ".scim.read", + ZONES_ZONE_ID_PREFIX + ZONE_ID_MATCH + ".scim.write", + ZONES_ZONE_ID_PREFIX + ZONE_ID_MATCH + ".scim.create", ZONES_ZONE_ID_PREFIX + ZONE_ID_MATCH + ".idps.read") ); public static final List zoneScopestoNotStripPrefix = Collections.unmodifiableList( @@ -162,7 +165,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response.sendError(HttpServletResponse.SC_NOT_FOUND, "Identity zone with id/subdomain " + identityZoneIdFromHeader + "/" + identityZoneSubDomain + " does not exist"); return; } - + String identityZoneId = identityZone.getId(); if (!isAuthorizedToSwitchToIdentityZone(identityZoneId)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not authorized to switch to IdentityZone with id "+identityZoneId); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java index 076148d45ab..ad781cb6a67 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/util/MockMvcUtils.java @@ -107,6 +107,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.util.StringUtils.hasText; public class MockMvcUtils { @@ -434,7 +435,7 @@ public IdentityProvider createIdpUsingWebRequest(MockMvc mockMvc, String zoneId, MvcResult result = mockMvc.perform(requestBuilder) .andExpect(resultMatcher) .andReturn(); - if (StringUtils.hasText(result.getResponse().getContentAsString())) { + if (hasText(result.getResponse().getContentAsString())) { try { return JsonUtils.readValue(result.getResponse().getContentAsString(), IdentityProvider.class); } catch (JsonUtils.JsonUtilException e) { @@ -450,13 +451,34 @@ public ScimUser createUser(MockMvc mockMvc, String accessToken, ScimUser user) t } public ScimUser createUserInZone(MockMvc mockMvc, String accessToken, ScimUser user, String subdomain) throws Exception { + return createUserInZone(mockMvc, accessToken, user, subdomain, null); + } + public ScimUser createUserInZone(MockMvc mockMvc, String accessToken, ScimUser user, String subdomain, String zoneId) throws Exception { String requestDomain = subdomain.equals("") ? "localhost" : subdomain + ".localhost"; - MvcResult userResult = mockMvc.perform(post("/Users") - .header("Authorization", "Bearer " + accessToken) - .with(new SetServerNameRequestPostProcessor(requestDomain)) - .contentType(APPLICATION_JSON) - .content(JsonUtils.writeValueAsBytes(user))) - .andExpect(status().isCreated()).andReturn(); + MockHttpServletRequestBuilder post = post("/Users"); + post.header("Authorization", "Bearer " + accessToken) + .with(new SetServerNameRequestPostProcessor(requestDomain)) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsBytes(user)); + if (hasText(zoneId)) { + post.header(IdentityZoneSwitchingFilter.HEADER, zoneId); + } + MvcResult userResult = mockMvc.perform(post) + .andExpect(status().isCreated()).andReturn(); + return JsonUtils.readValue(userResult.getResponse().getContentAsString(), ScimUser.class); + } + + public ScimUser readUserInZone(MockMvc mockMvc, String accessToken, String userId, String subdomain, String zoneId) throws Exception { + String requestDomain = subdomain.equals("") ? "localhost" : subdomain + ".localhost"; + MockHttpServletRequestBuilder get = get("/Users/"+userId); + get.header("Authorization", "Bearer " + accessToken) + .with(new SetServerNameRequestPostProcessor(requestDomain)) + .accept(APPLICATION_JSON); + if (hasText(zoneId)) { + get.header(IdentityZoneSwitchingFilter.HEADER, zoneId); + } + MvcResult userResult = mockMvc.perform(get) + .andExpect(status().isOk()).andReturn(); return JsonUtils.readValue(userResult.getResponse().getContentAsString(), ScimUser.class); } @@ -510,7 +532,7 @@ public ScimGroup createGroup(MockMvc mockMvc, String accessToken, ScimGroup grou .header("Authorization", "Bearer " + accessToken) .contentType(APPLICATION_JSON) .content(JsonUtils.writeValueAsString(group)); - if (StringUtils.hasText(zoneId)) { + if (hasText(zoneId)) { post.header(IdentityZoneSwitchingFilter.HEADER, zoneId); } return JsonUtils.readValue( @@ -605,16 +627,28 @@ public BaseClientDetails getClient(MockMvc mockMvc, String accessToken, String c } public String getZoneAdminToken(MockMvc mockMvc, String adminToken, String zoneId) throws Exception { + String scope = "zones." + zoneId + ".admin"; + return getZoneAdminToken(mockMvc, adminToken, zoneId, scope); + } + + public String getZoneAdminToken(MockMvc mockMvc, String adminToken, String zoneId, String scope) throws Exception { ScimUser user = new ScimUser(); user.setUserName(new RandomValueStringGenerator().generate()); user.setPrimaryEmail(user.getUserName() + "@test.org"); user.setPassword("secr3T"); user = MockMvcUtils.utils().createUser(mockMvc, adminToken, user); - ScimGroup group = new ScimGroup(null, "zones." + zoneId + ".admin", IdentityZone.getUaa().getId()); + ScimGroup group = new ScimGroup(null, scope, IdentityZone.getUaa().getId()); group.setMembers(Arrays.asList(new ScimGroupMember(user.getId()))); MockMvcUtils.utils().createGroup(mockMvc, adminToken, group); - return getUserOAuthAccessTokenAuthCode(mockMvc, "identity", "identitysecret", user.getId(), user.getUserName(), - "secr3T", group.getDisplayName()); + return getUserOAuthAccessTokenAuthCode(mockMvc, + "identity", + "identitysecret", + user.getId(), + user.getUserName(), + "secr3T", + group.getDisplayName() + ); + } public String getUserOAuthAccessToken(MockMvc mockMvc, String clientId, String clientSecret, String username, diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneSwitchingFilterMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneSwitchingFilterMockMvcTest.java index dbf65db442a..dd13e2a45d0 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneSwitchingFilterMockMvcTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/IdentityZoneSwitchingFilterMockMvcTest.java @@ -13,9 +13,7 @@ package org.cloudfoundry.identity.uaa.mock.zones; import org.apache.commons.codec.binary.Base64; -import org.apache.commons.lang.RandomStringUtils; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; -import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -23,9 +21,9 @@ import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.SetServerNameRequestPostProcessor; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.junit.Before; import org.junit.Test; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import org.springframework.test.web.servlet.ResultMatcher; @@ -33,8 +31,10 @@ import java.util.Arrays; import java.util.UUID; +import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; import static org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter.HEADER; import static org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter.SUBDOMAIN_HEADER; +import static org.junit.Assert.assertEquals; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -45,6 +45,7 @@ public class IdentityZoneSwitchingFilterMockMvcTest extends InjectedMockContextT private TestClient testClient; private String identityToken; private String adminToken; + private RandomValueStringGenerator generator; @Before public void setUp() throws Exception { @@ -58,6 +59,8 @@ public void setUp() throws Exception { "admin", "adminsecret", ""); + + generator = new RandomValueStringGenerator(); } @Test @@ -65,7 +68,7 @@ public void testSwitchingZones() throws Exception { IdentityZone identityZone = createZone(identityToken); String zoneId = identityZone.getId(); - String zoneAdminToken = MockMvcUtils.utils().getZoneAdminToken(getMockMvc(),adminToken, zoneId); + String zoneAdminToken = utils().getZoneAdminToken(getMockMvc(),adminToken, zoneId); // Using Identity Client, authenticate in originating Zone // - Create Client using X-Identity-Zone-Id header in new Zone ClientDetails client = createClientInOtherZone(zoneAdminToken, status().isCreated(), HEADER, zoneId); @@ -81,7 +84,7 @@ public void testSwitchingZones() throws Exception { @Test public void testSwitchingZoneWithSubdomain() throws Exception { IdentityZone identityZone = createZone(identityToken); - String zoneAdminToken = MockMvcUtils.utils().getZoneAdminToken(getMockMvc(),adminToken, identityZone.getId()); + String zoneAdminToken = utils().getZoneAdminToken(getMockMvc(),adminToken, identityZone.getId()); ClientDetails client = createClientInOtherZone(zoneAdminToken, status().isCreated(), SUBDOMAIN_HEADER, identityZone.getSubdomain()); getMockMvc().perform(get("/oauth/token?grant_type=client_credentials") @@ -115,7 +118,7 @@ public void testNoSwitching() throws Exception{ @Test public void testSwitchingToInvalidSubDomain() throws Exception{ IdentityZone identityZone = createZone(identityToken); - String zoneAdminToken = MockMvcUtils.utils().getZoneAdminToken(getMockMvc(),adminToken, identityZone.getId()); + String zoneAdminToken = utils().getZoneAdminToken(getMockMvc(),adminToken, identityZone.getId()); createClientInOtherZone(zoneAdminToken, status().isNotFound(), SUBDOMAIN_HEADER, "InvalidSubDomain"); } @@ -123,7 +126,7 @@ public void testSwitchingToInvalidSubDomain() throws Exception{ @Test public void testSwitchingToNonExistentZone() throws Exception { IdentityZone identityZone = createZone(identityToken); - String zoneAdminToken = MockMvcUtils.utils().getZoneAdminToken(getMockMvc(),adminToken, identityZone.getId()); + String zoneAdminToken = utils().getZoneAdminToken(getMockMvc(),adminToken, identityZone.getId()); createClientInOtherZone(zoneAdminToken, status().isNotFound(), HEADER, "i-do-not-exist"); } @@ -140,27 +143,68 @@ public void testSwitchingZonesWithAUser() throws Exception { final String zoneId = createZone(identityToken).getId(); String adminToken = testClient.getClientCredentialsOAuthAccessToken("admin","adminsecret","scim.write"); // Create a User - String username = RandomStringUtils.randomAlphabetic(8) + "@example.com"; + String username = generator.generate() + "@example.com"; + ScimUser user = getScimUser(username); + ScimUser createdUser = utils().createUser(getMockMvc(), adminToken, user); + ScimGroup group = new ScimGroup(null, "zones." + zoneId + ".admin", zoneId); + group.setMembers(Arrays.asList(new ScimGroupMember(createdUser.getId()))); + utils().createGroup(getMockMvc(), adminToken, group); + String userToken = utils().getUserOAuthAccessTokenAuthCode(getMockMvc(),"identity", "identitysecret", createdUser.getId(),createdUser.getUserName(), "secret", null); + createClientInOtherZone(userToken, status().isCreated(), HEADER, zoneId); + } + + protected ScimUser getScimUser(String username) { ScimUser user = new ScimUser(); user.setUserName(username); user.addEmail(username); user.setPassword("secr3T"); user.setVerified(true); user.setZoneId(IdentityZone.getUaa().getId()); - ScimUser createdUser = MockMvcUtils.utils().createUser(getMockMvc(), adminToken, user); - ScimGroup group = new ScimGroup(null, "zones." + zoneId + ".admin", zoneId); - group.setMembers(Arrays.asList(new ScimGroupMember(createdUser.getId()))); - MockMvcUtils.utils().createGroup(getMockMvc(), adminToken, group); - String userToken = MockMvcUtils.utils().getUserOAuthAccessTokenAuthCode(getMockMvc(),"identity", "identitysecret", createdUser.getId(),createdUser.getUserName(), "secret", null); - createClientInOtherZone(userToken, status().isCreated(), HEADER, zoneId); + return user; + } + + @Test + public void test_scim_read_in_another_zone() throws Exception { + final String zoneId = createZone(identityToken).getId(); + ScimUser user = createScimUserUsingZonesScimWrite(zoneId); + String adminToken = testClient.getClientCredentialsOAuthAccessToken("admin","adminsecret","scim.write"); + String scimReadZoneToken = utils().getZoneAdminToken(getMockMvc(), adminToken, zoneId, "zones."+zoneId+".scim.read"); + ScimUser readUser = utils().readUserInZone(getMockMvc(), scimReadZoneToken, user.getId(), "", zoneId); + assertEquals(user.getId(), readUser.getId()); + } + + @Test + public void test_scim_create_in_another_zone() throws Exception { + final String zoneId = createZone(identityToken).getId(); + String adminToken = testClient.getClientCredentialsOAuthAccessToken("admin","adminsecret","scim.write"); + String scimCreateZoneToken = utils().getZoneAdminToken(getMockMvc(), adminToken, zoneId, "zones."+zoneId+".scim.create"); + createUserInAnotherZone(scimCreateZoneToken, zoneId); + } + + @Test + public void test_scim_write_in_another_zone() throws Exception { + final String zoneId = createZone(identityToken).getId(); + createScimUserUsingZonesScimWrite(zoneId); + } + public ScimUser createScimUserUsingZonesScimWrite(String zoneId) throws Exception { + String adminToken = testClient.getClientCredentialsOAuthAccessToken("admin","adminsecret","scim.write"); + String scimWriteZoneToken = utils().getZoneAdminToken(getMockMvc(), adminToken, zoneId, "zones."+zoneId+".scim.write"); + return createUserInAnotherZone(scimWriteZoneToken, zoneId); } private IdentityZone createZone(String accessToken) throws Exception { - return MockMvcUtils.utils().createZoneUsingWebRequest(getMockMvc(), accessToken); + return utils().createZoneUsingWebRequest(getMockMvc(), accessToken); + } + + private ScimUser createUserInAnotherZone(String accessToken, String zoneId) throws Exception { + String username = generator.generate() + "@example.com"; + ScimUser user = getScimUser(username); + ScimUser createdUser = utils().createUserInZone(getMockMvc(), accessToken, user, "", zoneId); + return createdUser; } private ClientDetails createClientInOtherZone(String accessToken, ResultMatcher statusMatcher, String headerKey, String headerValue) throws Exception { - String clientId = UUID.randomUUID().toString(); + String clientId = generator.generate(); BaseClientDetails client = new BaseClientDetails(clientId, null, null, "client_credentials", null); client.setClientSecret("secret"); getMockMvc().perform(post("/oauth/clients") @@ -172,4 +216,5 @@ private ClientDetails createClientInOtherZone(String accessToken, ResultMatcher .andExpect(statusMatcher); return client; } + } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java index 71e614a0a61..3696105a69a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsMockMvcTests.java @@ -348,7 +348,7 @@ public void getGroups_withScimReadTokens_returnsOkWithResults() throws Exception MockHttpServletRequestBuilder get = get("/Groups") .header("Authorization", "Bearer " + scimReadToken) .param("attributes", "displayName") - .param("filter", "displayName co \"scim\"") + .param("filter", "displayName sw \"scim\"") .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON); MvcResult mvcResult = getMockMvc().perform(get) @@ -361,7 +361,7 @@ public void getGroups_withScimReadTokens_returnsOkWithResults() throws Exception get = get("/Groups") .header("Authorization", "Bearer " + scimReadUserToken) .param("attributes", "displayName") - .param("filter", "displayName co \"scim\"") + .param("filter", "displayName sw \"scim\"") .contentType(MediaType.APPLICATION_JSON) .accept(APPLICATION_JSON); mvcResult = getMockMvc().perform(get) From 4e58a12f2223fb44f96d710bbd83b6e4973148fc Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 12 Jan 2016 10:55:36 -0700 Subject: [PATCH 097/103] Cors adjustments - OPTIONS should not have to be whitelisted since that is a known method for preflight requests https://www.pivotaltracker.com/story/show/110165702 [finishes #110165702] --- .../identity/uaa/security/web/CorsFilter.java | 6 ++- .../uaa/security/web/CorsFilterTests.java | 13 ++++--- uaa/src/main/resources/uaa.yml | 38 +++++++++++++++++-- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java index 5d324f70bcc..6b8ca6868bf 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java @@ -237,9 +237,11 @@ protected boolean handleRequest(HttpServletRequest request, FilterChain filterChain, CorsConfiguration configuration) throws IOException, ServletException { + boolean isPreflightRequest = OPTIONS.toString().equals(request.getMethod()); + //Validate if this CORS request is allowed for this method String method = request.getMethod(); - if (!isAllowedMethod(method, configuration)) { + if (!isPreflightRequest && !isAllowedMethod(method, configuration)) { logger.debug(String.format("Request with invalid method was rejected: %s", method)); response.sendError(METHOD_NOT_ALLOWED.value(), "Illegal method."); return true; @@ -279,7 +281,7 @@ protected boolean handleRequest(HttpServletRequest request, response.addHeader(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD); } - if (OPTIONS.toString().equals(request.getMethod())) { + if (isPreflightRequest) { logger.debug(String.format("Request is a pre-flight request")); buildCorsPreFlightResponse(request, response, configuration); } else { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/CorsFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/CorsFilterTests.java index b933358d964..dc146113802 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/CorsFilterTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/CorsFilterTests.java @@ -220,6 +220,7 @@ public void testRequestWithMethodNotAllowed() throws ServletException, IOExcepti @Test public void testPreFlightExpectStandardCorsResponse() throws ServletException, IOException { CorsFilter corsFilter = createConfiguredCorsFilter(); + corsFilter.getDefaultConfiguration().setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/uaa/userinfo"); request.addHeader("Access-Control-Request-Headers", "Authorization"); @@ -232,13 +233,13 @@ public void testPreFlightExpectStandardCorsResponse() throws ServletException, I corsFilter.doFilter(request, response, filterChain); - assertStandardCorsPreFlightResponse(response, "Authorization"); + assertStandardCorsPreFlightResponse(response, "GET, POST, PUT, DELETE", "Authorization"); } @Test public void testPreFlightExpectXhrCorsResponse() throws ServletException, IOException { CorsFilter corsFilter = createConfiguredCorsFilter(); - + corsFilter.getXhrConfiguration().setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/uaa/userinfo"); request.addHeader("Access-Control-Request-Headers", "Authorization, X-Requested-With"); request.addHeader("Access-Control-Request-Method", "GET"); @@ -395,7 +396,7 @@ public void doInitializeWithNoPropertiesSet() throws ServletException, IOExcepti corsFilter.doFilter(request, response, filterChain); - assertStandardCorsPreFlightResponse(response, AUTHORIZATION, ACCEPT, CONTENT_TYPE, ACCEPT_LANGUAGE, CONTENT_LANGUAGE); + assertStandardCorsPreFlightResponse(response, "GET, OPTIONS, POST, PUT, DELETE", AUTHORIZATION, ACCEPT, CONTENT_TYPE, ACCEPT_LANGUAGE, CONTENT_LANGUAGE); } @Test @@ -451,16 +452,16 @@ private static CorsFilter createConfiguredCorsFilter() { return corsFilter; } - private static void assertStandardCorsPreFlightResponse(final MockHttpServletResponse response, String... allowedHeaders) { + private static void assertStandardCorsPreFlightResponse(final MockHttpServletResponse response, String allowedMethods, String... allowedHeaders) { assertEquals("*", response.getHeaderValue("Access-Control-Allow-Origin")); - assertEquals("GET, OPTIONS, POST, PUT, DELETE", response.getHeaderValue("Access-Control-Allow-Methods")); + assertEquals(allowedMethods, response.getHeaderValue("Access-Control-Allow-Methods")); assertThat(new CorsFilter().splitCommaDelimitedString((String)response.getHeaderValue("Access-Control-Allow-Headers")), containsInAnyOrder(allowedHeaders)); assertEquals("1728000", response.getHeaderValue("Access-Control-Max-Age")); } private static void assertXhrCorsPreFlightResponse(final MockHttpServletResponse response) { assertEquals("example.com", response.getHeaderValue("Access-Control-Allow-Origin")); - assertEquals("GET, OPTIONS", response.getHeaderValue("Access-Control-Allow-Methods")); + assertEquals("GET, POST, PUT, DELETE", response.getHeaderValue("Access-Control-Allow-Methods")); assertEquals("Authorization, X-Requested-With", response.getHeaderValue("Access-Control-Allow-Headers")); assertEquals("1728000", response.getHeaderValue("Access-Control-Max-Age")); } diff --git a/uaa/src/main/resources/uaa.yml b/uaa/src/main/resources/uaa.yml index ae8f8f18e43..6e700b842b3 100755 --- a/uaa/src/main/resources/uaa.yml +++ b/uaa/src/main/resources/uaa.yml @@ -259,6 +259,38 @@ oauth: # refreshTokenValiditySeconds: 3600 # Configure whitelist for allowing cross-origin XMLHttpRequest requests. -#cors.xhr.allowed.headers: Accept,Authorization -#cors.xhr.allowed.origins: ^localhost$,^.*\.localhost$ -#cors.xhr.allowed.uris: ^/uaa/userinfo$,^/uaa/logout\.do$ +#cors: +# xhr: +# allowed: +# headers: +# - Accept +# - Authorization +# - Content-Type +# - X-Requested-With +# origin: +# - ^localhost$ +# - ^.*\.localhost$ +# uris: +# - ^/uaa/userinfo$ +# - ^/uaa/logout\.do$ +# methods: +# - GET +# - OPTIONS +# default: +# allowed: +# headers: +# - Accept +# - Authorization +# - Content-Type +# - X-Requested-With +# origin: +# - ^localhost$ +# - ^.*\.localhost$ +# uris: +# - ^/uaa/userinfo$ +# - ^/uaa/logout\.do$ +# methods: +# - GET +# - PUT +# - POST +# - DELETE \ No newline at end of file From 3252c54172f13391c027eebe82aa77c171cbe35b Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 12 Jan 2016 13:29:37 -0700 Subject: [PATCH 098/103] Add logging to CORS filter --- .../identity/uaa/security/web/CorsFilter.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java index 6b8ca6868bf..ac9f1d0d883 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java @@ -223,12 +223,16 @@ protected void doFilterInternal(final HttpServletRequest request, final HttpServ return; } + if (logger.isDebugEnabled()) { + logger.debug("CORS Processing request: "+getRequestInfo(request)); + } if (isXhrRequest(request)) { handleRequest(request, response, filterChain, getXhrConfiguration()); - return; } else { handleRequest(request, response, filterChain, getDefaultConfiguration()); - return; + } + if (logger.isDebugEnabled()) { + logger.debug("CORS processing completed for: "+getRequestInfo(request)+" Status:"+response.getStatus()); } } @@ -421,6 +425,16 @@ protected boolean isAllowedOrigin(final String origin, CorsConfiguration configu logger.debug(String.format("The '%s' origin is not allowed to make CORS requests.",origin)); return false; } + //----------------REQUEST INFO ----------------------------------------------// + public String getRequestInfo(HttpServletRequest request) { + return String.format("URI: %s; Scheme: %s; Host: %s; Port: %s; Origin: %s; Method: %s", + request.getRequestURI(), + request.getScheme(), + request.getServerName(), + request.getServerPort(), + request.getHeader("Origin"), + request.getMethod()); + } //----------------CORS XHR ONLY ---------------------------------------------// public void setCorsXhrAllowedUris(List corsXhrAllowedUris) { From 92121a3112f4a061b9b394eb4f3b1f5bd6dda790 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Tue, 12 Jan 2016 14:36:38 -0700 Subject: [PATCH 099/103] Revert default of wantAssertionSigned for the default zone to false. To avoid a breaking compatibility change for existing SPs https://www.pivotaltracker.com/story/show/109996940 [#109996940] --- .../java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java | 2 +- .../org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java | 3 ++- .../identity/uaa/config/IdentityZoneConfigurationTests.java | 2 +- uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml | 2 +- .../org/cloudfoundry/identity/uaa/login/BootstrapTests.java | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java index acb7765d1e7..a2ce2d388e2 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java @@ -16,7 +16,7 @@ public class SamlConfig { private boolean requestSigned = true; - private boolean wantAssertionSigned = true; + private boolean wantAssertionSigned = false; private String certificate; private String privateKey; private String privateKeyPassword; diff --git a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java index a7c06c5cefd..5704b6cfc77 100644 --- a/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java @@ -18,6 +18,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class SamlConfigTest { @@ -37,7 +38,7 @@ public void testIsRequestSigned() throws Exception { @Test public void testIsWantAssertionSigned() throws Exception { - assertTrue(config.isWantAssertionSigned()); + assertFalse(config.isWantAssertionSigned()); } @Test diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java index 105d8d51c41..ce8ea665a38 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java @@ -56,7 +56,7 @@ public void testDeserialize_Without_SamlConfig() { s = s.replace(",\"samlConfig\":{\"requestSigned\":false,\"wantAssertionSigned\":false}",""); definition = JsonUtils.readValue(s, IdentityZoneConfiguration.class); assertTrue(definition.getSamlConfig().isRequestSigned()); - assertTrue(definition.getSamlConfig().isWantAssertionSigned()); + assertFalse(definition.getSamlConfig().isWantAssertionSigned()); definition.getSamlConfig().setWantAssertionSigned(true); definition.getSamlConfig().setRequestSigned(true); s = JsonUtils.writeValueAsString(definition); diff --git a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml index 560aa53a4ff..f162ab0eebc 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml @@ -137,7 +137,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index 87666de5b38..44a19d03326 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -212,7 +212,7 @@ public void testRootContextDefaults() throws Exception { ZoneAwareMetadataGenerator zoneAwareMetadataGenerator = context.getBean(ZoneAwareMetadataGenerator.class); assertTrue(zoneAwareMetadataGenerator.isRequestSigned()); - assertTrue(zoneAwareMetadataGenerator.isWantAssertionSigned()); + assertFalse(zoneAwareMetadataGenerator.isWantAssertionSigned()); CorsFilter corFilter = context.getBean(CorsFilter.class); From c536952405c25ae75d89ccea6b75a903e806ab62 Mon Sep 17 00:00:00 2001 From: Jonathan Lo Date: Tue, 12 Jan 2016 16:04:57 -0800 Subject: [PATCH 100/103] Remove registered redirect information from error message [#110416348] https://www.pivotaltracker.com/story/show/110416348 --- .../identity/uaa/oauth/UaaAuthorizationEndpoint.java | 8 +++++++- .../identity/uaa/integration/feature/AppApprovalIT.java | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpoint.java index 01388ac42d0..6d3ab1d1891 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpoint.java @@ -156,7 +156,13 @@ public ModelAndView authorize(Map model, @RequestParam Map Date: Wed, 13 Jan 2016 12:25:15 -0800 Subject: [PATCH 101/103] Update docs to reflect new create user verification behavior - removed allowUnverifiedUsers config [#109267688] https://www.pivotaltracker.com/story/show/109267688 Signed-off-by: Jonathan Lo --- docs/UAA-APIs.rst | 2 ++ uaa/src/main/resources/uaa.yml | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 41a77ca716b..a004fe06a68 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -1396,6 +1396,8 @@ See `SCIM - Creating Resources`__ __ http://www.simplecloud.info/specs/draft-scim-api-01.html#create-resource +New users are automatically verified by default. Unverified users can be created by specifying their `verified: false` property in the request body of the `POST` to `/Users`, as shown in the example below. Unverified users must then go through the verification process. Obtaining a verification link (to send to the user) is outlined in the section `Verify User Links: GET /Users/{id}/verify-link`_. Users may then use this link to complete the verification process, at which point they'll be able to login. + ================ ========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================== Request ``POST /Users`` Header Authorization Bearer token diff --git a/uaa/src/main/resources/uaa.yml b/uaa/src/main/resources/uaa.yml index 6e700b842b3..79ac381083e 100755 --- a/uaa/src/main/resources/uaa.yml +++ b/uaa/src/main/resources/uaa.yml @@ -160,10 +160,6 @@ oauth: - roles - user_attributes -# Allow unverified users to log in. Defaults to true -#allowUnverifiedUsers: false - - # Default token signing key. Each installation MUST provide a unique key # in order for tokens to be usable only on that installation. #jwt: From 0c349f44a3ffa384624e971b98554113df900c3b Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 13 Jan 2016 13:16:32 -0700 Subject: [PATCH 102/103] Document and update the internal list of scopes https://www.pivotaltracker.com/story/show/111439796 [#111439796] --- docs/UAA-APIs.rst | 6 ++++++ .../org/cloudfoundry/identity/uaa/client/UaaScopes.java | 3 +++ .../org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 41a77ca716b..ab5e3ff62a5 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -61,6 +61,9 @@ Zone Management Scopes * **zones..clients.admin** - translates into clients.admin after zone switch is complete (used together with the X-Identity-Zone-Id header) * **zones..clients.read** - translates into clients.read after zone switch is complete (used together with the X-Identity-Zone-Id header) * **zones..clients.write** - translates into clients.write after zone switch is complete (used together with the X-Identity-Zone-Id header) +* **zones..scim.read** - translates into scim.read after zone switch is complete (used together with the X-Identity-Zone-Id header) +* **zones..scim.create** - translates into scim.create after zone switch is complete (used together with the X-Identity-Zone-Id header) +* **zones..scim.write** - translates into scim.write after zone switch is complete (used together with the X-Identity-Zone-Id header) * **zones..idps.read** - translates into idps.read after zone switch is complete (used together with the X-Identity-Zone-Id header) A Note on Filtering @@ -2163,6 +2166,9 @@ The following zone management scopes are supported: - zones.{zone id}.clients.admin - zones.{zone id}.clients.write - zones.{zone id}.clients.read +- zones.{zone id}.scim.read +- zones.{zone id}.scim.write +- zones.{zone id}.scim.create * Response Body:: diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/client/UaaScopes.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/UaaScopes.java index 3951cb49a7d..a3caebc26ca 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/client/UaaScopes.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/UaaScopes.java @@ -38,6 +38,9 @@ public class UaaScopes { "zones.*.clients.admin", "zones.*.clients.read", "zones.*.clients.write", + "zones.*.scim.create", + "zones.*.scim.read", + "zones.*.scim.write", "zones.*.idps.read", "idps.read", "idps.write", diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java index 0b516c50216..de3ed87d13e 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java @@ -32,8 +32,8 @@ public class UaaScopesTests { @Test public void testGetUaaScopes() throws Exception { - assertEquals(23, uaaScopes.getUaaScopes().size()); - assertEquals(23, uaaScopes.getUaaAuthorities().size()); + assertEquals(26, uaaScopes.getUaaScopes().size()); + assertEquals(26, uaaScopes.getUaaAuthorities().size()); } @Test From acd3dc9ef0c4d0dca6417c93dfa139d00d958afa Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 13 Jan 2016 16:41:10 -0700 Subject: [PATCH 103/103] Bump release version to 3.0.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cd92d6b0851..4950f0ded39 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=3.0.0-SNAPSHOT +version=3.0.0