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 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/.travis.yml b/.travis.yml index 34d027c5a15..44b4ac2f666 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 @@ -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/README.md b/README.md index 2fd2c3a507c..a6856510167 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 @@ -30,17 +33,28 @@ If this works you are in business: $ ./gradlew run The apps all work together with the apps running on the same port -(8080) as [`/uaa`](http://localhost:8080/uaa), [`/app`](http://localhost:8080/app) and [`/api`](http://localhost:8080/api). +(8080) as [`/uaa`](http://localhost:8080/uaa), [`/app`](http://localhost:8080/app) and [`/api`](http://localhost:8080/api). + +UAA will log to a file called `uaa.log` which can be found using the following command:- + + $ sudo find / -name uaa.log + +which you should find under something like:- + + /private/var/folders/7v/518b18d97_3f4c8fzxphy6f8zcm51c/T/cargo/conf/logs/ ### 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: @@ -48,7 +62,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 -* We have not tested our system on Apache Tomcat 8 and Java 8, so we pick a build pack that produces lower versions +* You may also provide a configuration manifest where the environment variable UAA_CONFIG_YAML contains full configuration yaml. ### Demo of command line usage on local server @@ -194,13 +208,60 @@ 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: 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_CONFIG_YAML: | + 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://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 @@ -230,20 +291,19 @@ The defaults are ## Inventory -There are actually several projects here, the main `uaa` server application and some samples: +There are actually several projects here, the main `uaa` server application, a client library and some samples: -0. `common` is a module containing a JAR with all the business logic. It is used in -the webapps below. +1. `uaa` a WAR project for easy deployment -1. `uaa` is the actual UAA server - compiles as a WAR file for easy deployment +2. `server` a JAR project containing the implementation of UAA's REST API (including [SCIM](http://www.simplecloud.info/)) and UI -2. `api` (sample) is an OAuth2 resource service which returns a mock list of deployed apps +3. `model` a JAR project used by both the client library and server -3. `app` (sample) is a user application that uses both of the above +4. `client-lib` a JAR project that provides a Java client API -4. `scim` [SCIM](http://www.simplecloud.info/) user management module used by UAA +5. `api` (sample) is an OAuth2 resource service which returns a mock list of deployed apps -5. `login` This module represents the UI of the UAA. It is the code that was merged in from the former login-server project. +6. `app` (sample) is a user application that uses both of the above In CloudFoundry terms @@ -256,6 +316,11 @@ In CloudFoundry terms * `app` is a webapp that needs single sign on and access to the `api` service on behalf of users. +### Organization of Code + +The projects are organized into horizontal layers; client, model, server, etc. Within all of these projects the java packages are organized vertically around our internal services; zones, providers, clients, etc. + + ## UAA Server The authentication service is `uaa`. It's a plain Spring MVC webapp. @@ -409,4 +474,4 @@ Here are some ways for you to get involved in the community: YourKit, LLC is the creator of YourKit Java Profiler and YourKit .NET Profiler, innovative and intelligent tools for profiling Java and .NET applications. - [![](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com/java/profiler/index.jsp) \ No newline at end of file + [![](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com/java/profiler/index.jsp) diff --git a/build.gradle b/build.gradle index b4471cf074c..9cf22898dcc 100644 --- a/build.gradle +++ b/build.gradle @@ -7,20 +7,25 @@ buildscript { maven { url 'http://repo.spring.io/plugins-release' } + maven { + url 'https://plugins.gradle.org/m2/' + } } 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 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(',') @@ -44,7 +49,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,38 +60,68 @@ allprojects { repositories { mavenCentral() } +} - ext { - runningWithCoverage = { - allprojects.collect { it.tasks.findByName('instrument').enabled }.find() - } +apply plugin: 'org.flywaydb.flyway' - 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 - } - } +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 { + logging.captureStandardError LogLevel.INFO + logging.captureStandardOutput LogLevel.INFO // suppress "## warnings" message + } + task javadocJar(type: Jar, dependsOn: javadoc) { classifier 'javadoc' from javadoc.destinationDir @@ -151,100 +186,23 @@ 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 { - 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") - } -} -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 + 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 + } } } -flywayClean.enabled = Boolean.valueOf(System.getProperty("flyway.clean", "true")) - -task prepareDatabase { - dependsOn { databaseType().equals('hsqldb') ? null : flywayClean } -} - -task resetCoverage(type: Delete) { - delete integrationCoverageFile - onlyIf { runningWithCoverage() } -} - -apply plugin: 'cargo' - -task cleanCargoConfDir { - delete file(System.getenv('TMPDIR') + '/cargo/conf') -} - -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) +def jacocoJarPath = project.zipTree(configurations.jacocoAgent.singleFile).filter({ it.name == 'jacocoagent.jar' }).asPath +def integrationTestCoverageExecutionData = "${buildDir}/integrationTestCoverageReport.exec" cargo { containerId = 'tomcat7x' @@ -261,8 +219,9 @@ cargo { } local { + jvmArgs = "-javaagent:${jacocoJarPath}=output=file,dumponexit=true,append=false,destfile=${integrationTestCoverageExecutionData}" + systemProperties { - property 'net.sourceforge.cobertura.datafile', integrationCoverageFile property 'spring.profiles.active', System.getProperty('spring.profiles.active', 'default') } @@ -277,21 +236,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,35 +257,58 @@ project.gradle.taskGraph.whenReady { TaskExecutionGraph graph -> } } -apply plugin: 'coveralls' +apply plugin: 'com.github.kt3k.coveralls' -Project identityPayload = subprojects.find { it.name.equals('cloudfoundry-identity-payload') } +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') } Project identityUaa = subprojects.find { it.name.equals('cloudfoundry-identity-uaa') } -cobertura { - coverageFormats = ['xml', 'html'] - coverageSourceDirs = [ - identityPayload.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 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 = [integrationTest, test] + additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs) + sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs) + classDirectories = files(subprojects.sourceSets.main.output) + executionData = files(integrationTestCoverageExecutionData, 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' +} + // 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 a922f39ca78..7c759f6daa9 100644 --- a/client-lib/build.gradle +++ b/client-lib/build.gradle @@ -1,13 +1,27 @@ -Project identityPayload = parent.subprojects.find { it.name.equals('cloudfoundry-identity-payload') } +Project identityModel = parent.subprojects.find { it.name.equals('cloudfoundry-identity-model') } -description = 'CloudFoundry Identity Common Jar' +description = 'CloudFoundry Identity Client Library Jar' dependencies { - compile identityPayload + compile identityModel + testCompile group: 'junit', name: 'junit', version: parent.junitVersion + testCompile identityModel.configurations.testCompile.dependencies + testCompile identityModel.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-common') : line } + filter { line -> line.contains('${project.artifactId}') ? line.replace('${project.artifactId}', 'cloudfoundry-identity-clientlib') : line } } + +test { + exclude 'org/cloudfoundry/identity/client/integration/*.class' +} + +integrationTest { + 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..b53e75d8c81 --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContext.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.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 { + + /** + * 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 new file mode 100644 index 00000000000..d320037d535 --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextFactory.java @@ -0,0 +1,302 @@ +/* + * ***************************************************************************** + * 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.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.BaseOAuth2ProtectedResourceDetails; +import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; +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; +import org.springframework.web.util.UriComponentsBuilder; + +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; +import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD_WITH_PASSCODE; +import static org.springframework.security.oauth2.common.AuthenticationScheme.header; + +public class UaaContextFactory { + + /** + * 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.path(tokenPath); + UriComponentsBuilder authorizationURI = UriComponentsBuilder.newInstance(); + 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 + * 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 + */ + 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: + 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()); + } + } + + /** + * Not yet implemented + * @param tokenRequest - a configured TokenRequest + * @return an authenticated {@link UaaContext} + */ + 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 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 + protected ResponseExtractor getResponseExtractor() { + getRestTemplate(); // force initialization + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + return new HttpMessageConverterExtractor(CompositeAccessToken.class, Arrays.asList(converter)); + } + }; + enhanceRequestParameters(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} + * @param tokenRequest - a configured TokenRequest + * @return an authenticated {@link UaaContext} + */ + protected UaaContext authenticatePassword(final TokenRequest tokenRequest) { + ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider() { + @Override + protected ResponseExtractor getResponseExtractor() { + getRestTemplate(); // force initialization + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + return new HttpMessageConverterExtractor(CompositeAccessToken.class, Arrays.asList(converter)); + } + }; + enhanceRequestParameters(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); + } + + /** + * 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, + MultiValueMap form, + HttpHeaders headers) -> { + 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())); + } + } + ); + } + + /** + * 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(); + 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); + } + + /** + * 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 new file mode 100644 index 00000000000..8d17f9e440e --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/UaaContextImpl.java @@ -0,0 +1,81 @@ +/* + * ***************************************************************************** + * 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; + } + + /** + * {@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 new file mode 100644 index 00000000000..20a014dd554 --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/GrantType.java @@ -0,0 +1,28 @@ +/* + * ***************************************************************************** + * 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; + +/** + * Represent the standard Oauth 2 grant types + */ +public enum GrantType { + CLIENT_CREDENTIALS, + PASSWORD, + PASSWORD_WITH_PASSCODE, + 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 new file mode 100644 index 00000000000..3c0a3d1f9c9 --- /dev/null +++ b/client-lib/src/main/java/org/cloudfoundry/identity/client/token/TokenRequest.java @@ -0,0 +1,352 @@ +/* + * ***************************************************************************** + * 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; +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. + * the {@link #isValid()} method validates the token request object + * for each {@link GrantType} + * + */ +public class TokenRequest { + + private GrantType grantType; + private String clientId; + private String clientSecret; + private String username; + private String password; + private String passcode; + private Set scopes; + private URI tokenEndpoint; + private URI authorizationEndpoint; + private boolean idToken = false; + private URI redirectUri; + private String authCodeAPIToken; + + /** + * 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 !hasAnyNullValues( + Arrays.asList( + tokenEndpoint, + clientId, + clientSecret + ) + ); + case PASSWORD: + return !hasAnyNullValues( + Arrays.asList( + tokenEndpoint, + clientId, + clientSecret, + username, + password + ) + ); + case PASSWORD_WITH_PASSCODE: + return !hasAnyNullValues( + Arrays.asList( + tokenEndpoint, + clientId, + clientSecret, + username, + passcode + ) + ); + case AUTHORIZATION_CODE: + return !hasAnyNullValues( + Arrays.asList( + tokenEndpoint, + authorizationEndpoint, + clientId, + clientSecret, + username, + password, + redirectUri + ) + ); + case AUTHORIZATION_CODE_WITH_TOKEN: + return !hasAnyNullValues( + Arrays.asList( + tokenEndpoint, + authorizationEndpoint, + clientId, + clientSecret, + username, + password, + redirectUri, + authCodeAPIToken + ) + ); + 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; + } + + /** + * 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 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 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 + * @return true if the list or any item in the list is null + */ + protected boolean hasAnyNullValues(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/ClientAPITokenIntegrationTest.java b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java new file mode 100644 index 00000000000..a855abf20e2 --- /dev/null +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/integration/ClientAPITokenIntegrationTest.java @@ -0,0 +1,207 @@ +/* + * ***************************************************************************** + * 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.Before; +import org.junit.Ignore; +import org.junit.Test; + +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; + +public class ClientAPITokenIntegrationTest { + + public static String uaaURI = "http://localhost:8080/uaa"; + + private UaaContextFactory factory; + + @Before + public void setUp() throws Exception { + factory = + UaaContextFactory.factory(new URI(uaaURI)) + .authorizePath("/oauth/authorize") + .tokenPath("/oauth/token"); + } + + @Test + public void test_admin_client_token() throws Exception { + TokenRequest clientCredentials = factory.tokenRequest() + .setClientId("admin") + .setClientSecret("adminsecret") + .setGrantType(GrantType.CLIENT_CREDENTIALS); + + UaaContext context = factory.authenticate(clientCredentials); + assertNotNull(context); + assertTrue(context.hasAccessToken()); + assertFalse(context.hasIdToken()); + assertFalse(context.hasRefreshToken()); + assertTrue(context.getToken().getScope().contains("uaa.admin")); + } + + @Test + public void test_password_token_without_id_token() throws Exception { + UaaContext context = retrievePasswordToken(null); + assertTrue(context.getToken().getScope().contains("openid")); + } + + protected UaaContext retrievePasswordToken(Collection scopes) { + TokenRequest passwordGrant = factory.tokenRequest() + .setClientId("cf") + .setClientSecret("") + .setGrantType(PASSWORD) + .setUsername("marissa") + .setPassword("koala") + .setScopes(scopes); + UaaContext context = factory.authenticate(passwordGrant); + assertNotNull(context); + assertTrue(context.hasAccessToken()); + assertFalse(context.hasIdToken()); + assertTrue(context.hasRefreshToken()); + return context; + } + + @Test + public void test_password_token_with_id_token() throws Exception { + TokenRequest passwordGrant = factory.tokenRequest() + .setClientId("cf") + .setClientSecret("") + .setGrantType(PASSWORD) + .setUsername("marissa") + .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); + assertTrue(context.hasAccessToken()); + assertTrue(context.hasIdToken()); + assertTrue(context.hasRefreshToken()); + 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 { + 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")); + } + + @Test + public void test_auth_code_token_using_api() throws Exception { + UaaContext passwordContext = retrievePasswordToken(Arrays.asList("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/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 new file mode 100644 index 00000000000..d1cfdec0089 --- /dev/null +++ b/client-lib/src/test/java/org/cloudfoundry/identity/client/token/TokenRequestTest.java @@ -0,0 +1,103 @@ +/* + * ***************************************************************************** + * 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; + +import java.net.URI; +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.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; + + +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 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_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()); + 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_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() { + 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 diff --git a/common/.gitignore b/common/.gitignore deleted file mode 100644 index 84a4913a97d..00000000000 --- a/common/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -/bin \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle deleted file mode 100644 index 32a361ccee9..00000000000 --- a/common/build.gradle +++ /dev/null @@ -1,80 +0,0 @@ -Project identityPayload = parent.subprojects.find { it.name.equals('cloudfoundry-identity-payload') } - -description = 'CloudFoundry Identity Common Jar' - -dependencies { - compile identityPayload - 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 - compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version:parent.bcpkixVersion - compile group: 'org.springframework.security', name: 'spring-security-ldap', version:parent.springSecurityVersion - compile group: 'org.springframework.ldap', name: 'spring-ldap-core', version:parent.springSecurityLdapVersion - compile group: 'org.springframework.ldap', name: 'spring-ldap-core-tiger', version:parent.springSecurityLdapVersion - compile(group: 'org.apache.directory.api', name: 'api-ldap-model', version:parent.apacheLdapApiVersion) { - exclude(module: 'slf4j-api') - } - compile group: 'org.springframework.security', name: 'spring-security-jwt', version:parent.springSecurityJwtVersion - compile(group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version:parent.springSecurityOAuthVersion) { - exclude(module: 'commons-codec') - exclude(module: 'jackson-mapper-asl') - } - compile group: 'org.springframework.security', name: 'spring-security-config', version:parent.springSecurityVersion - compile (group: 'org.springframework.security.oauth', name: 'spring-security-oauth', version:parent.springSecurityOAuthVersion) { - exclude(module: 'spring-security-config') - } - compile group: 'org.springframework', name: 'spring-core', version:parent.springVersion - compile group: 'org.springframework', name: 'spring-expression', version:parent.springVersion - compile group: 'org.springframework', name: 'spring-beans', version:parent.springVersion - compile group: 'org.springframework', name: 'spring-context', version:parent.springVersion - compile group: 'org.springframework', name: 'spring-aop', version:parent.springVersion - compile group: 'org.springframework', name: 'spring-web', version:parent.springVersion - compile group: 'org.springframework', name: 'spring-jdbc', version:parent.springVersion - compile group: 'org.springframework', name: 'spring-webmvc', version:parent.springVersion - 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: 'org.apache.httpcomponents', name: 'httpclient', version:'4.3.3') { - exclude(module: 'commons-logging') - } - compile(group: 'com.unboundid.product.scim', name: 'scim-sdk', version:'1.6.0') { - exclude(module: 'servlet-api') - exclude(module: 'commons-logging') - 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 - 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.flywaydb', name: 'flyway-core', version: parent.flywayVersion - compile group: 'org.hsqldb', name: 'hsqldb', version:'2.3.1' - - compile(group: 'org.springframework.security.extensions', name: 'spring-security-saml2-core', version:parent.springSecuritySamlVersion) { - exclude(module: 'bcprov-jdk15') - } - - - 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: '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: 'postgresql', name: 'postgresql', version:parent.postgresqlVersion - testCompile group: 'org.mockito', name: 'mockito-all', version:'1.8.5' - testCompile group: 'org.apache.tomcat', name: 'tomcat-jdbc', version:parent.tomcatVersion - testCompile group: 'org.springframework.security', name: 'spring-security-test', version:parent.springSecurityVersion -} - -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 } -} diff --git a/common/src/META-INF/MANIFEST.MF b/common/src/META-INF/MANIFEST.MF deleted file mode 100644 index 5e9495128c0..00000000000 --- a/common/src/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - 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/common/src/main/java/org/cloudfoundry/identity/uaa/error/JsonAwareAccessDeniedHandler.java b/common/src/main/java/org/cloudfoundry/identity/uaa/error/JsonAwareAccessDeniedHandler.java deleted file mode 100644 index e9a04a7543c..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/error/JsonAwareAccessDeniedHandler.java +++ /dev/null @@ -1,58 +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.error; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.http.MediaType; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.util.StringUtils; - -/** - * If access is denied and the caller has asked for a JSON response this can - * send one, along with a standard 403 - * status. - * - * @author Dave Syer - * - */ -public class JsonAwareAccessDeniedHandler implements AccessDeniedHandler { - - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, - AccessDeniedException authException) throws IOException, ServletException { - String accept = request.getHeader("Accept"); - boolean json = false; - if (StringUtils.hasText(accept)) { - for (MediaType mediaType : MediaType.parseMediaTypes(accept)) { - if (mediaType.includes(MediaType.APPLICATION_JSON)) { - json = true; - break; - } - } - } - if (json) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.getWriter().append(String.format("{\"error\":\"%s\"}", authException.getMessage())); - } else { - response.sendError(HttpServletResponse.SC_FORBIDDEN, authException.getMessage()); - } - } - -} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/error/JsonAwareAuthenticationEntryPoint.java b/common/src/main/java/org/cloudfoundry/identity/uaa/error/JsonAwareAuthenticationEntryPoint.java deleted file mode 100644 index dd3d0977470..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/error/JsonAwareAuthenticationEntryPoint.java +++ /dev/null @@ -1,77 +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.error; - -import java.io.IOException; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.http.MediaType; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * If authentication fails and the caller has asked for a JSON response this can - * send one, along with a standard 401 - * status. - * - * @author Dave Syer - * - */ -public class JsonAwareAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean { - - private String realmName; - - private String typeName = "Basic"; - - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(StringUtils.hasText(realmName), "realmName must be specified"); - } - - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) - throws IOException, ServletException { - response.addHeader("WWW-Authenticate", String.format("%s realm=\"%s\"", typeName, realmName)); - String accept = request.getHeader("Accept"); - boolean json = false; - if (StringUtils.hasText(accept)) { - for (MediaType mediaType : MediaType.parseMediaTypes(accept)) { - if (mediaType.includes(MediaType.APPLICATION_JSON)) { - json = true; - break; - } - } - } - if (json) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.getWriter().append(String.format("{\"error\":\"%s\"}", authException.getMessage())); - } else { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); - } - } - - public void setRealmName(String realmName) { - this.realmName = realmName; - } - - public void setTypeName(String typeName) { - this.typeName = typeName; - } -} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/util/FileLocator.java b/common/src/main/java/org/cloudfoundry/identity/uaa/login/util/FileLocator.java deleted file mode 100644 index 360efc7b3d2..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/util/FileLocator.java +++ /dev/null @@ -1,35 +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.login.util; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.URL; - -public class FileLocator { - - public static File locate(String filename) throws IOException { - File f = new File(filename); - //try file system - if (f.exists()) { - return f; - } - //try classloader - URL url = Thread.currentThread().getContextClassLoader().getResource( filename ); - if ( url != null && url.getFile() != null && (new File(url.getFile()).exists()) ) { - return new File(url.getFile()); - } - throw new FileNotFoundException( "Cannot find resource on file system or classpath: '" + filename + "'" ); - } -} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/JitClientDetailsService.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/JitClientDetailsService.java deleted file mode 100644 index 7095739be41..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/JitClientDetailsService.java +++ /dev/null @@ -1,48 +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.oauth; - -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; -import org.cloudfoundry.identity.uaa.user.UaaAuthority; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; -import org.springframework.security.oauth2.provider.client.BaseClientDetails; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; - -/** - * @author Dave Syer - * - */ -public class JitClientDetailsService extends JdbcQueryableClientDetailsService { - - public JitClientDetailsService(JdbcClientDetailsService delegate, JdbcTemplate jdbcTemplate, - JdbcPagingListFactory pagingListFactory) { - super(delegate, jdbcTemplate, pagingListFactory); - } - - @Override - public ClientDetails retrieve(String clientId) throws OAuth2Exception { - ClientDetails result; - try { - result = super.retrieve(clientId); - } catch (OAuth2Exception e) { - BaseClientDetails details = new BaseClientDetails(clientId, "openid", "openid", "authorization_code", - UaaAuthority.UAA_NONE.toString()); - result = details; - } - return result; - } - -} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/NoSuchTokenException.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/NoSuchTokenException.java deleted file mode 100644 index 34664a74d67..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/NoSuchTokenException.java +++ /dev/null @@ -1,29 +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.oauth; - -/** - * Exception for token admin when attempting to use a value that could not be - * found. - * - * @author Dave Syer - * - */ -public class NoSuchTokenException extends RuntimeException { - - public NoSuchTokenException(String message) { - super(message); - } - -} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthenticationKeyGenerator.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthenticationKeyGenerator.java deleted file mode 100644 index 2a13976874d..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthenticationKeyGenerator.java +++ /dev/null @@ -1,90 +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.oauth; - -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.springframework.security.oauth2.common.util.OAuth2Utils; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; -import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator; - -/** - * @author Dave Syer - * - */ -public class UaaAuthenticationKeyGenerator implements AuthenticationKeyGenerator { - - private static final String CLIENT_ID = "client_id"; - - private static final String SCOPE = "scope"; - - private static final String ACCESS_TOKEN_VALIDITY = "access_token_validity"; - - private static final String REFRESH_TOKEN_VALIDITY = "refresh_token_validity"; - - private UserTokenConverter userTokenConverter = new UaaUserTokenConverter(); - - private ClientDetailsService clientDetailsService; - - /** - * @param clientDetailsService the clientDetailsService to set - */ - public void setClientDetailsService(ClientDetailsService clientDetailsService) { - this.clientDetailsService = clientDetailsService; - } - - @Override - public String extractKey(OAuth2Authentication authentication) { - Map values = new LinkedHashMap(); - OAuth2Request authorizationRequest = authentication.getOAuth2Request(); - if (!authentication.isClientOnly()) { - values.putAll(userTokenConverter.convertUserAuthentication(authentication.getUserAuthentication())); - } - ClientDetails client = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId()); - values.put(CLIENT_ID, client.getClientId()); - if (authorizationRequest.getScope() != null) { - values.put(SCOPE, OAuth2Utils.formatParameterList(authorizationRequest.getScope())); - } - Integer validity = client.getAccessTokenValiditySeconds(); - if (validity != null) { - values.put(ACCESS_TOKEN_VALIDITY, validity); - } - validity = client.getRefreshTokenValiditySeconds(); - if (validity != null && client.getAuthorizedGrantTypes().contains("refresh_token")) { - values.put(REFRESH_TOKEN_VALIDITY, validity); - } - MessageDigest digest; - try { - digest = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK)."); - } - - try { - byte[] bytes = digest.digest(values.toString().getBytes("UTF-8")); - return String.format("%032x", new BigInteger(1, bytes)); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK)."); - } - } - -} 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 deleted file mode 100644 index a694c201a0e..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserTokenConverter.java +++ /dev/null @@ -1,44 +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.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 java.util.LinkedHashMap; -import java.util.Map; - -import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.springframework.security.core.Authentication; - -/** - * @author Dave Syer - * - */ -public class UaaUserTokenConverter implements UserTokenConverter { - - @Override - public Map convertUserAuthentication(Authentication authentication) { - Map response = new LinkedHashMap(); - if (authentication.getPrincipal() instanceof UaaPrincipal) { - UaaPrincipal principal = (UaaPrincipal) authentication.getPrincipal(); - response.put(USER_ID, principal.getId()); - response.put(USER_NAME, principal.getName()); - response.put(EMAIL, principal.getEmail()); - } - return response; - } - -} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserTokenConverter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserTokenConverter.java deleted file mode 100644 index 968115caa10..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserTokenConverter.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.oauth; - -import java.util.Map; - -import org.springframework.security.core.Authentication; - -/** - * @author Dave Syer - * - */ -public interface UserTokenConverter { - - /** - * Extract information about the user to be used in an access token (i.e. - * for resource servers). - * - * @param userAuthentication an authentication representing a user - * @return a map of key values representing the unique information about the - * user - */ - Map convertUserAuthentication(Authentication userAuthentication); - -} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OpenIdToken.java b/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OpenIdToken.java deleted file mode 100644 index a08691a6b7c..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/OpenIdToken.java +++ /dev/null @@ -1,164 +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.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; -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; - -@JsonSerialize(using = OpenIdToken.OpenIdTokenJackson1Serializer.class) -@JsonDeserialize(using = OpenIdToken.OpenIdTokenJackson1Deserializer.class) -public class OpenIdToken extends DefaultOAuth2AccessToken { - - public static String ID_TOKEN = "id_token"; - - public String getIdTokenValue() { - return idTokenValue; - } - - public void setIdTokenValue(String idTokenValue) { - this.idTokenValue = idTokenValue; - } - - private String idTokenValue; - - public OpenIdToken(String accessTokenValue) { - super(accessTokenValue); - } - - public OpenIdToken(OAuth2AccessToken accessToken) { - super(accessToken); - } - - public static final class OpenIdTokenJackson1Serializer extends StdSerializer { - - public OpenIdTokenJackson1Serializer() { - 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 OpenIdToken && ((OpenIdToken)token).getIdTokenValue()!=null) { - jgen.writeStringField(ID_TOKEN, ((OpenIdToken) 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 OpenIdTokenJackson1Deserializer extends StdDeserializer { - - public OpenIdTokenJackson1Deserializer() { - 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? - - OpenIdToken accessToken = new OpenIdToken(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/common/src/main/java/org/cloudfoundry/identity/uaa/web/CorsFilter.java b/common/src/main/java/org/cloudfoundry/identity/uaa/web/CorsFilter.java deleted file mode 100644 index 53c7b680869..00000000000 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/web/CorsFilter.java +++ /dev/null @@ -1,278 +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.web; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -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; - -/** - * - * Modern browser include the X-Requested-With header when making calls through - * the XMLHttpRequest API which allows the server CORS filtering to mitigate - * against CSRF attacks performed by XHR requests. However, in some situations - * XHR requests are useful. For example, when a single page JavaScript apps that - * implements login using implicit grant wants to: 1) log the user out by - * calling the /logout.do URI 2) get user information by calling the /userinfo - * URI. - * - * To enable the scenarios described above, this filter allows CORS requests to - * include the "X-Requested-With" header for a whitelist of URIs and origins and - * only for the HTTP GET method. - * - * The implementation is based on guidance from: - * http://www.w3.org/TR/cors/ - * https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS - * - */ -public class CorsFilter extends OncePerRequestFilter { - - static final Log LOG = LogFactory.getLog(CorsFilter.class); - - /** - * 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; - - private final List corsXhrAllowedUriPatterns = 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. - */ - @Value("#{'${cors.xhr.allowed.origins:^$}'.split(',')}") - private List corsXhrAllowedOrigins; - - private final List corsXhrAllowedOriginPatterns = new ArrayList<>(); - - @Value("#{'${cors.xhr.allowed.headers:Accept,Authorization}'.split(',')}") - private List allowedHeaders; - - @PostConstruct - public void initialize() { - - if (corsXhrAllowedUris!=null) { - for (String allowedUri : this.corsXhrAllowedUris) { - try { - this.corsXhrAllowedUriPatterns.add(Pattern.compile(allowedUri)); - - 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); - } - } - } - - if (corsXhrAllowedOrigins!=null) { - for (String allowedOrigin : this.corsXhrAllowedOrigins) { - try { - this.corsXhrAllowedOriginPatterns.add(Pattern.compile(allowedOrigin)); - - if (LOG.isDebugEnabled()) { - LOG.debug(String.format("Origin '%s' allowed 'X-Requested-With' header in CORS requests.", - allowedOrigin)); - } - } catch (PatternSyntaxException patternSyntaxException) { - LOG.error("Invalid regular expression pattern in cors.xhr.allowed.origins: " + allowedOrigin); - } - } - } - } - - - @Override - protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, - final FilterChain filterChain) throws ServletException, IOException { - - if (!isCrossOriginRequest(request)) { - 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); - } - return; - } - - 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"); - } else { - filterChain.doFilter(request, response); - } - } - - 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")); - } - - private boolean isCrossOriginRequest(final HttpServletRequest request) { - if (StringUtils.isEmpty(request.getHeader(HttpHeaders.ORIGIN))) { - return false; - } - else { - return true; - } - } - - void buildCorsXhrPreFlightResponse(final HttpServletRequest request, final HttpServletResponse response) { - String accessControlRequestMethod = request.getHeader("Access-Control-Request-Method"); - if (null == accessControlRequestMethod) { - response.setStatus(HttpStatus.BAD_REQUEST.value()); - return; - } - if (!"GET".equalsIgnoreCase(accessControlRequestMethod)) { - response.setStatus(HttpStatus.METHOD_NOT_ALLOWED.value()); - return; - } - response.addHeader("Access-Control-Allow-Methods", "GET"); - - String accessControlRequestHeaders = request.getHeader("Access-Control-Request-Headers"); - if (null == accessControlRequestHeaders) { - response.setStatus(HttpStatus.BAD_REQUEST.value()); - return; - } - if (!headersAllowed(accessControlRequestHeaders)) { - response.setStatus(HttpStatus.FORBIDDEN.value()); - return; - } - response.addHeader("Access-Control-Allow-Headers", "Authorization, X-Requested-With"); - response.addHeader("Access-Control-Max-Age", "1728000"); - } - - private static boolean containsHeader(final String accessControlRequestHeaders, final String header) { - List headers = Arrays.asList(accessControlRequestHeaders.replace(" ", "").toLowerCase().split(",")); - return headers.contains(header.toLowerCase()); - } - - private boolean headersAllowed(final String accessControlRequestHeaders) { - List headers = Arrays.asList(accessControlRequestHeaders.replace(" ", "").split(",")); - for (String header : headers) { - if (!"X-Requested-With".equalsIgnoreCase(header) && !this.allowedHeaders.contains(header)) { - return false; - } - } - return true; - } - - private static boolean isCorsXhrAllowedMethod(final String method) { - if ("GET".equalsIgnoreCase(method) || "OPTIONS".equalsIgnoreCase(method)) { - return true; - } - return false; - } - - private boolean isCorsXhrAllowedRequestUri(final String uri) { - if (StringUtils.isEmpty(uri)) { - return false; - } - - for (Pattern pattern : this.corsXhrAllowedUriPatterns) { - // 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)); - } - return false; - } - - private boolean isCorsXhrAllowedOrigin(final String origin) { - for (Pattern pattern : this.corsXhrAllowedOriginPatterns) { - // 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)); - } - return false; - } - - public void setCorsXhrAllowedUris(List corsXhrAllowedUris) { - this.corsXhrAllowedUris = corsXhrAllowedUris; - } - - public void setCorsXhrAllowedOrigins(List corsXhrAllowedOrigins) { - this.corsXhrAllowedOrigins = corsXhrAllowedOrigins; - } - - public void setAllowedHeaders(List allowedHeaders) { - this.allowedHeaders = allowedHeaders; - } -} \ No newline at end of file diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/PromptEditorTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/PromptEditorTests.java deleted file mode 100644 index 952830c11af..00000000000 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/PromptEditorTests.java +++ /dev/null @@ -1,43 +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.authentication.login; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -/** - * @author Dave Syer - * - */ -public class PromptEditorTests { - - private PromptEditor editor = new PromptEditor(); - - @Test - public void testDeserialization() throws Exception { - editor.setAsText("username:[text,Username]"); - Prompt prompt = (Prompt) editor.getValue(); - String[] values = prompt.getDetails(); - assertEquals("text", values[0]); - assertEquals("Username", values[1]); - } - - @Test - public void testSerialization() throws Exception { - Prompt prompt = new Prompt("username", "text", "Username"); - editor.setValue(prompt); - assertEquals("\"username\":[\"text\",\"Username\"]", editor.getAsText()); - } - -} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/PromptTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/PromptTests.java deleted file mode 100644 index 61ee5245c36..00000000000 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/PromptTests.java +++ /dev/null @@ -1,40 +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.authentication.login; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -/** - * @author Dave Syer - * - */ -public class PromptTests { - - @Test - public void testSerialization() throws Exception { - Prompt prompt = new Prompt("username", "text", "Username"); - String[] values = prompt.getDetails(); - assertEquals("text", values[0]); - assertEquals("Username", values[1]); - } - - @Test - public void testDeserialization() throws Exception { - Prompt prompt = new Prompt("username", "text", "Username"); - Prompt value = Prompt.valueOf("username:[text,Username]"); - assertEquals(prompt, value); - } - -} 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 deleted file mode 100644 index b9e8101fe3b..00000000000 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.cloudfoundry.identity.uaa.config; - -import org.cloudfoundry.identity.uaa.test.JdbcTestBase; -import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; -import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; -import org.junit.Assert; -import org.junit.Test; - -/******************************************************************************* - * 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 IdentityZoneConfigurationBootstrapTests extends JdbcTestBase { - - - @Test - public void tokenPolicy_configured_fromValuesInYaml() throws Exception { - IdentityZoneProvisioning provisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); - IdentityZoneConfigurationBootstrap bootstrap = new IdentityZoneConfigurationBootstrap(provisioning); - TokenPolicy tokenPolicy = new TokenPolicy(); - 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()); - } -} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/error/JsonAwareAccessDeniedHandlerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/error/JsonAwareAccessDeniedHandlerTests.java deleted file mode 100644 index f89eaa9794b..00000000000 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/error/JsonAwareAccessDeniedHandlerTests.java +++ /dev/null @@ -1,69 +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.error; - -import static org.junit.Assert.assertEquals; - -import javax.servlet.http.HttpServletResponse; - -import org.junit.Test; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.AccessDeniedException; - -/** - * @author Dave Syer - * - */ -public class JsonAwareAccessDeniedHandlerTests { - - private JsonAwareAccessDeniedHandler entryPoint = new JsonAwareAccessDeniedHandler(); - - private MockHttpServletRequest request = new MockHttpServletRequest(); - - private MockHttpServletResponse response = new MockHttpServletResponse(); - - @Test - public void testCommenceWithJson() throws Exception { - request.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE); - entryPoint.handle(request, response, new AccessDeniedException("Bad")); - assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); - assertEquals("{\"error\":\"Bad\"}", response.getContentAsString()); - assertEquals(null, response.getErrorMessage()); - } - - @Test - public void testCommenceWithEmptyAccept() throws Exception { - entryPoint.handle(request, response, new AccessDeniedException("Bad")); - assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); - assertEquals("Bad", response.getErrorMessage()); - } - - @Test - public void testCommenceWithHtmlAccept() throws Exception { - request.addHeader("Accept", MediaType.TEXT_HTML_VALUE); - entryPoint.handle(request, response, new AccessDeniedException("Bad")); - assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); - assertEquals("Bad", response.getErrorMessage()); - } - - @Test - public void testCommenceWithHtmlAndJsonAccept() throws Exception { - request.addHeader("Accept", String.format("%s,%s", MediaType.TEXT_HTML_VALUE, MediaType.APPLICATION_JSON)); - entryPoint.handle(request, response, new AccessDeniedException("Bad")); - assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); - assertEquals(null, response.getErrorMessage()); - } - -} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/error/JsonAwareAuthenticationEntryPointTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/error/JsonAwareAuthenticationEntryPointTests.java deleted file mode 100644 index def18a68b26..00000000000 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/error/JsonAwareAuthenticationEntryPointTests.java +++ /dev/null @@ -1,86 +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.error; - -import static org.junit.Assert.assertEquals; - -import javax.servlet.http.HttpServletResponse; - -import org.junit.Test; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.BadCredentialsException; - -/** - * @author Dave Syer - * - */ -public class JsonAwareAuthenticationEntryPointTests { - - private JsonAwareAuthenticationEntryPoint entryPoint = new JsonAwareAuthenticationEntryPoint(); - - private MockHttpServletRequest request = new MockHttpServletRequest(); - - private MockHttpServletResponse response = new MockHttpServletResponse(); - - { - entryPoint.setRealmName("UAA"); - } - - @Test(expected = IllegalStateException.class) - public void testAfterPropertiesSet() throws Exception { - entryPoint = new JsonAwareAuthenticationEntryPoint(); - entryPoint.afterPropertiesSet(); - } - - @Test - public void testCommenceWithJson() throws Exception { - request.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE); - entryPoint.commence(request, response, new BadCredentialsException("Bad")); - assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus()); - assertEquals("{\"error\":\"Bad\"}", response.getContentAsString()); - assertEquals(null, response.getErrorMessage()); - } - - @Test - public void testTypeName() throws Exception { - entryPoint.setTypeName("Foo"); - entryPoint.commence(request, response, new BadCredentialsException("Bad")); - assertEquals("Foo realm=\"UAA\"", response.getHeader("WWW-Authenticate")); - } - - @Test - public void testCommenceWithEmptyAccept() throws Exception { - entryPoint.commence(request, response, new BadCredentialsException("Bad")); - assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus()); - assertEquals("Bad", response.getErrorMessage()); - } - - @Test - public void testCommenceWithHtmlAccept() throws Exception { - request.addHeader("Accept", MediaType.TEXT_HTML_VALUE); - entryPoint.commence(request, response, new BadCredentialsException("Bad")); - assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus()); - assertEquals("Bad", response.getErrorMessage()); - } - - @Test - public void testCommenceWithHtmlAndJsonAccept() throws Exception { - request.addHeader("Accept", String.format("%s,%s", MediaType.TEXT_HTML_VALUE, MediaType.APPLICATION_JSON)); - entryPoint.commence(request, response, new BadCredentialsException("Bad")); - assertEquals(HttpServletResponse.SC_UNAUTHORIZED, response.getStatus()); - assertEquals(null, response.getErrorMessage()); - } - -} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthenticationKeyGeneratorTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthenticationKeyGeneratorTests.java deleted file mode 100644 index ad9130372d6..00000000000 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthenticationKeyGeneratorTests.java +++ /dev/null @@ -1,62 +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.oauth; - -import static org.junit.Assert.assertNotSame; - -import java.util.Arrays; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; -import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.security.oauth2.provider.AuthorizationRequest; -import org.springframework.security.oauth2.provider.client.BaseClientDetails; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.ClientDetailsService; -import org.springframework.security.oauth2.provider.OAuth2Authentication; - -/** - * @author Dave Syer - * - */ -public class UaaAuthenticationKeyGeneratorTests { - - private UaaAuthenticationKeyGenerator generator = new UaaAuthenticationKeyGenerator(); - - private ClientDetailsService clientDetailsService = Mockito.mock(ClientDetailsService.class); - - private AuthorizationRequest authorizationRequest = new AuthorizationRequest("client", Arrays.asList("read", - "write")); - - private UaaAuthentication userAuthentication = UaaAuthenticationTestFactory.getAuthentication("FOO", "foo", - "foo@test.org"); - - @Before - public void init() { - ClientDetails client = new BaseClientDetails("client", "none", "read,write", "authorization_code", "uaa.none"); - Mockito.when(clientDetailsService.loadClientByClientId("client")).thenReturn(client); - generator.setClientDetailsService(clientDetailsService); - } - - @Test - public void testEmailChanges() { - String key1 = generator.extractKey(new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication)); - userAuthentication = UaaAuthenticationTestFactory.getAuthentication("FOO", "foo", "foo@none.org"); - String key2 = generator.extractKey(new OAuth2Authentication(authorizationRequest.createOAuth2Request(), userAuthentication)); - assertNotSame(key1, key2); - } - -} 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 deleted file mode 100644 index bf8dcc61887..00000000000 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaUserTokenConverterTests.java +++ /dev/null @@ -1,43 +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.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.junit.Assert.assertEquals; - -import java.util.Map; - -import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; -import org.junit.Test; - -/** - * @author Dave Syer - * - */ -public class UaaUserTokenConverterTests { - - private UaaUserTokenConverter converter = new UaaUserTokenConverter(); - - @Test - public void test() { - Map map = converter.convertUserAuthentication(UaaAuthenticationTestFactory.getAuthentication("FOO", - "foo", "foo@test.org")); - assertEquals("FOO", map.get(USER_ID)); - assertEquals("foo@test.org", map.get(EMAIL)); - assertEquals("foo", map.get(USER_NAME)); - } - -} 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 deleted file mode 100644 index 4dd90f21b94..00000000000 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalTests.java +++ /dev/null @@ -1,68 +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.oauth.approval; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; -import java.util.List; - -import org.junit.Test; - -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()); - } - - @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))); - - 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) - ); - assertTrue(approvals.contains(new Approval("u1", "c1", "s1", 100, Approval.ApprovalStatus.APPROVED))); - assertFalse(approvals.contains(new Approval("u1", "c1", "s1", 100, 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()); - } -} 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 deleted file mode 100644 index 607119cdfca..00000000000 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java +++ /dev/null @@ -1,66 +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.user; - -import java.util.Date; - -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.springframework.security.core.userdetails.UsernameNotFoundException; - -/** - * @author Luke Taylor - */ -public class MockUaaUserDatabase implements UaaUserDatabase { - UaaUser user; - - 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()); - } - - 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()); - } - - @Override - public UaaUser retrieveUserByName(String username, String origin) throws UsernameNotFoundException { - if (user.getUsername().equals(username) && user.getOrigin().equals(origin)) { - return user; - } - else { - throw new UsernameNotFoundException(username); - } - } - - @Override - public UaaUser retrieveUserById(String id) throws UsernameNotFoundException { - if (user.getId().equals(id)) { - return user; - } - else { - throw new UsernameNotFoundException(id); - } - } - - public UaaUser updateUser(String userId, UaaUser user) throws UsernameNotFoundException { - if (user.getId().equals(userId)) { - this.user = user; - return user; - } else { - throw new UsernameNotFoundException(userId); - } - } -} 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/docs/Sysadmin-Guide.rst b/docs/Sysadmin-Guide.rst index b94f3bbb4ac..73ba159e1d8 100644 --- a/docs/Sysadmin-Guide.rst +++ b/docs/Sysadmin-Guide.rst @@ -21,7 +21,7 @@ source .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 @@ -144,6 +147,74 @@ 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 + redirect_uri=http://example-app.com/welcome + + 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 +360,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 +429,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 +656,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`` -------------------------------------------- @@ -746,22 +806,25 @@ 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) - ===================== ==================== ======== ======================================================================================================================================================================== - 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``. + 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. - ===================== ==================== ======== ======================================================================================================================================================================== + 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 ``true``. + wantAssertionSigned Boolean Optional Exposed SAML metadata property. If ``true``, all assertions received by the SAML provider must be signed. Defaults to ``true``. + certificate String Optional Exposed SAML metadata property. The certificate used to sign all communications. Reserved for future use. + privateKey String Optional Exposed SAML metadata property. The SAML provider's private key. Reserved for future use. + privateKeyPassword String Optional Exposed SAML metadata property. The SAML provider's private key password. Reserved for future use. + + ===================== ==================== ======== ======================================================================================================================================================================== Curl Example POST (Token contains ``zones.write`` scope) :: @@ -811,7 +874,7 @@ All operations after this, are exactly the same as against the default zone. List Identity Zones: ``GET /identity-zones`` ------------------------------------- +-------------------------------------------- ============== =========================================================================== Request ``GET /identity-zones`` @@ -819,33 +882,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}`` @@ -853,17 +916,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 + } ============== =========================================================================== @@ -1129,7 +1215,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+ @@ -1144,7 +1230,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 @@ -1159,7 +1245,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) @@ -1269,6 +1355,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 ============================ @@ -1283,6 +1399,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 @@ -1557,7 +1675,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 @@ -1566,19 +1684,27 @@ See `SCIM - Changing Password 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 21b01f92fcb..00000000000 Binary files a/gatling/src/main/resources/assets/style/cible.png and /dev/null differ 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 3817d1c2470..00000000000 Binary files a/gatling/src/main/resources/assets/style/logo.png and /dev/null differ diff --git a/gatling/src/main/resources/assets/style/sou-menu-fleche-ouvert.png b/gatling/src/main/resources/assets/style/sou-menu-fleche-ouvert.png deleted file mode 100644 index 5e68affdbcb..00000000000 Binary files a/gatling/src/main/resources/assets/style/sou-menu-fleche-ouvert.png and /dev/null differ diff --git a/gatling/src/main/resources/assets/style/sous-menu-fleche.png b/gatling/src/main/resources/assets/style/sous-menu-fleche.png deleted file mode 100644 index 64db5abc559..00000000000 Binary files a/gatling/src/main/resources/assets/style/sous-menu-fleche.png and /dev/null differ diff --git a/gatling/src/main/resources/assets/style/stat-fleche-bas.png b/gatling/src/main/resources/assets/style/stat-fleche-bas.png deleted file mode 100644 index 8e0b501a37f..00000000000 Binary files a/gatling/src/main/resources/assets/style/stat-fleche-bas.png and /dev/null differ 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 ed2bfc0b99e..00000000000 Binary files a/gatling/src/main/resources/assets/style/stat-fond.png and /dev/null differ diff --git a/gatling/src/main/resources/assets/style/stat-l-roue.png b/gatling/src/main/resources/assets/style/stat-l-roue.png deleted file mode 100644 index 8ef362fec14..00000000000 Binary files a/gatling/src/main/resources/assets/style/stat-l-roue.png and /dev/null differ 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 9de94ab3a2e..00000000000 Binary files a/gatling/src/main/resources/assets/style/stat-l-temps.png and /dev/null differ diff --git a/gatling/src/main/resources/assets/style/style.css b/gatling/src/main/resources/assets/style/style.css deleted file mode 100644 index 4cd7bc15804..00000000000 --- a/gatling/src/main/resources/assets/style/style.css +++ /dev/null @@ -1,133 +0,0 @@ -body{font-family:arial;margin:0;padding:0;background:#DCDBD4;color:#000;font-size:12px;} -.container{width:1200px;margin:0 auto;position:relative;overflow:visible;} -.frise{background:url('fond-degrade.gif') repeat-x;height:530px;width: 100%;position:absolute;top:60px;z-index:-1;} -.details{height:1250px;} -.global{height:650px} - -a{text-decoration:none;color:#E37400;} -a:hover{color:#D1D1CE;} -p{margin:10px 0;padding:0;} -img{border:0;} -h1{font-size:18px;color:#E37400;margin: 10px 0;} -h1 span{color:#D1D1CE;} - -.main{width:100%} - -.head{height:100px;width:600px;padding:10px 0 0 160px;background:url('fond-lueur.gif') no-repeat;background-position:70px 0;position:absolute;top:0;left:0;} - - -.foot{background:#92918C;width:100%;color:#FFF;} -.foot a{width:85px;height:27px;margin:3px auto;display:block} - - -.cadre{width:1050px;padding:38px 0 0 100px; -position:absolute; -top:70px; -} - -.onglet{ - background:#FFF; - padding:7px 80px; - margin:5px 5px 0 5px; - color:#BCBCB5; - font-size:18px; - font-weight:bold; - position:absolute; - right:0; - top:-9px; - z-index:8; - height:28px; - box-shadow:0 0 0 black,4px -4px 3px #D1D1CE,-4px -4px 3px #D1D1CE; - -moz-box-shadow:0 0 0 black,4px -4px 3px #D1D1CE,-4px -4px 3px #D1D1CE; - -webkit-box-shadow:0 0 0 black,4px -4px 3px #D1D1CE,-4px -4px 3px #D1D1CE; - -webkit-border-top-right-radius: 8px; - -webkit-border-top-left-radius: 8px; - border-color: #D1D1CE} -.onglet img{position:absolute;top:7px;left:7px;} -.onglet span{color:#E37400;margin-right:10px;} -.onglet p{margin:7px 0 10px 0; font-size:22px} - -.content{ - margin-right: 5px; - padding:0 10px 40px 1px; - background:#FFF; - box-shadow:-4px -4px 3px #D1D1CE, 4px 4px 3px #D1D1CE, -4px 4px 3px #D1D1CE, 4px -4px 3px #D1D1CE; - -moz-box-shadow:-4px -4px 3px #D1D1CE, 4px 4px 3px #D1D1CE, -4px 4px 3px #D1D1CE, 4px -4px 3px #D1D1CE; - -webkit-box-shadow:-4px -4px 3px #D1D1CE, 4px 4px 3px #D1D1CE, -4px 4px 3px #D1D1CE, 4px -4px 3px #D1D1CE; - - border-top-left-radius: 8px; - border-bottom-left-radius: 8px; - border-bottom-right-radius: 8px; - -moz-border-top-left-radius: 8px; - -moz-border-bottom-left-radius: 8px; - -moz-border-bottom-right-radius: 8px; - -webkit-border-top-left-radius: 8px; - -webkit-border-bottom-left-radius: 8px; - -webkit-border-bottom-right-radius: 8px; - - border-color: #D1D1CE - } -.content-in{margin:30px 30px 30px 120px;} - - -.sous-menu{border-bottom:2px #E37400 solid;padding:10px 0 5px 0;margin:0 5px;z-index:-1;} -.sous-menu a{color:#FFF;font-size:14px;font-weight:bold;} -.sous-menu .item{background:#D1D1CE url('sous-menu-fleche.png') 12px 6px no-repeat;display:inline;margin:0 10px 0 0;padding:5px 15px 5px 25px; -border-top-right-radius:8px; -border-top-left-radius:8px} -.sous-menu .ouvert{background:#E37400 url('sou-menu-fleche-ouvert.png') 10px 7px no-repeat;} - -.nav{top:180px;width:200px;background:#C5C3BC;position:absolute;z-index: 9;border-radius:8px;border:1px solid #C5C3BC} -.nav ul{padding:0;margin:3px} -.nav li{margin:0 auto;padding:3px 0;list-style:none;width:190px;border-bottom:1px #acaba6 solid;border-top:1px #d4d2cc solid;} -.nav li:first-child{border-top:none;} -.nav li:last-child{border-bottom:none;} -.nav a{color:#000;padding:5px 10px;display:block;width:170px;font-size:14px;font-weight:bold;margin: 0 auto} -.nav a:hover{background-color:#92918C;border-radius:8px} -.nav .on a{color:#FFF;background-color:#E37400;border-radius:8px} - -.article{position:relative;} -.infos{position:absolute;top:0;right:-63px;width:250px;color:#FFF;} -.infos-in{padding:0 0 25px 0;position:relative;} -.info{margin:0;height:100%;background:#CF6900 url('stat-fond.png') repeat-x;} -.repli{position:absolute;bottom:0;right:0;background:url('stat-fleche-bas.png') no-repeat top left;height:25px;width:22px;} -.decor{background:url('stat-forme.jpg') no-repeat 170px 0;width:100%;padding:15px 0px 15px 0;} - -.infos p{margin:3px 0px 3px 40px} -.infos p strong{font-weight:bold;margin-left:10px;} -.titre{background:#ff9916;text-align:center;width:120px;height:15px;font-weight:bold; - border-top-left-radius: 8px; - border-top-right-radius: 8px; - -moz-border-top-left-radius: 8px; - -moz-border-top-right-radius: 8px; - -webkit-border-top-left-radius: 8px; - -webkit-border-top-right-radius: 8px; - padding-top:5px; -} - -.infos h2{font-size:13px;font-weight:bold;margin:0;padding:3px 0 0 25px;color:#FFF;height:19px;width:165px; -border-top-right-radius:8px; -border-bottom-right-radius:8px; --moz-border-top-right-radius:8px; --moz-border-bottom-right-radius:8px; --webkit-border-top-right-radius:8px; --webkit-border-bottom-right-radius:8px; -} - -.infos .first { - background: url('stat-l-roue.png') no-repeat 3px 3px #d16b00; -} -.infos .second { - background: url('stat-l-temps.png') no-repeat 3px 3px #d16b00; -} - -.schema{background:#EAEAEA;border-radius:8px;border:1px solid #EAEAEA;margin-bottom:20px} -.demi{width:670px;height:205px;} -.geant{width:900px;height:350px;} -.chart_title{ - background:#b0b0a8; - padding:2px 10px; - color:#FFF; - font-weight:bold; - border-radius:8px; -} \ No newline at end of file diff --git a/gatling/src/main/resources/gatling.conf b/gatling/src/main/resources/gatling.conf deleted file mode 100644 index 84bffe38b88..00000000000 --- a/gatling/src/main/resources/gatling.conf +++ /dev/null @@ -1,27 +0,0 @@ -######################### -# Gatling Configuration # -######################### - -# This file contains all the settings configurable for Gatling with their default values - -gatling { - encoding = "utf-8" # encoding for every file manipulation made in gatling - - simulation { - timeout = 86400 # max duration of a simulation in seconds - scalaPackage = "" - } - charting { - indicators { - lowerBound = 200 # in ms - higherBound = 1500 # in ms - } - } - http { - provider = "Netty" # Choose between 'Netty', 'JDK', 'Apache' or 'Grizzly' - compressionEnabled = true # Set if compression should be supported or not - connectionTimeout = 60000 # Timeout of the connection to the server (ms) - requestTimeout = 60000 # Timeout of the requests (ms) - maxRetry = 5 # number of times that a request should be tried again - } -} \ No newline at end of file diff --git a/gatling/src/main/resources/logback.xml b/gatling/src/main/resources/logback.xml deleted file mode 100644 index eb03b25d6d3..00000000000 --- a/gatling/src/main/resources/logback.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - 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/gradle.properties b/gradle.properties index 42d447f3076..4950f0ded39 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=2.7.3 +version=3.0.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index b7612167031..941144813d2 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 88a068a11d7..ecee8caddd2 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 +#Mon Dec 14 13:52:13 PST 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-all.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` diff --git a/login/build.gradle b/login/build.gradle deleted file mode 100644 index 47aa40c7135..00000000000 --- a/login/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } -Project identityScim = parent.subprojects.find { it.name.equals('cloudfoundry-identity-scim') } - -description = 'CloudFoundry Identity Login' - -dependencies { - compile(identityCommon) { - exclude(module: 'jna') - } - compile(identityScim) { - exclude(module: 'jna') - } - - compile group: 'org.springframework', name: 'spring-context-support', version:springVersion - provided group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' - - testCompile identityCommon.configurations.testCompile.dependencies - testCompile identityCommon.sourceSets.test.output - - compile group: 'org.springframework.security', name: 'spring-security-openid', version:springSecurityVersion - compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version:'2.1.2.RELEASE' - compile group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version:'1.2.3' - compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity3', version:'2.1.1.RELEASE' - compile group: 'javax.mail', name: 'mail', version:'1.4.7' - -} - -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)) - } - } -} \ No newline at end of file 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 deleted file mode 100644 index 35f33ca51a1..00000000000 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AbstractControllerInfo.java +++ /dev/null @@ -1,162 +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.login; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.util.UaaStringUtils; -import org.springframework.core.io.support.PropertiesLoaderUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.ui.Model; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLDecoder; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import javax.servlet.http.HttpServletRequest; - -/** - * Contains basic information used by the - * login controllers - * @author fhanik - * - */ -public abstract class AbstractControllerInfo { - private final Log logger = LogFactory.getLog(getClass()); - private Map links = new HashMap(); - private static String DEFAULT_BASE_UAA_URL = "https://uaa.cloudfoundry.com"; - protected static final String HOST = "Host"; - protected static final String AUTHORIZATON = "Authorization"; - - private Properties gitProperties = new Properties(); - - private Properties buildProperties = new Properties(); - - private String baseUrl; - - private String uaaHost; - - /** - * @param links the links to set - */ - public void setLinks(Map links) { - this.links = links; - } - - public Map getLinks() { - return links; - } - - protected void initProperties() { - setUaaBaseUrl(DEFAULT_BASE_UAA_URL); - try { - gitProperties = PropertiesLoaderUtils.loadAllProperties("git.properties"); - } catch (IOException e) { - // Ignore - } - try { - buildProperties = PropertiesLoaderUtils.loadAllProperties("build.properties"); - } catch (IOException e) { - // Ignore - } - - } - - /** - * @param baseUrl the base uaa url - */ - public void setUaaBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - try { - URI uri = new URI(baseUrl); - this.uaaHost = uri.getHost(); - if (uri.getPort()!=443 && uri.getPort()!=80 && uri.getPort()>0) { - //append non standard ports to the hostname - this.uaaHost += ":"+uri.getPort(); - } - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Could not extract host from URI: " + baseUrl); - } - } - - protected String getUaaBaseUrl() { - return baseUrl; - } - - protected String getUaaHost() { - return uaaHost; - } - - protected Map getLinksInfo() { - Map model = new HashMap(); - model.put(Origin.UAA, getUaaBaseUrl()); - model.put("login", getUaaBaseUrl().replaceAll(Origin.UAA, "login")); - model.putAll(getLinks()); - return model; - } - - protected HttpHeaders getRequestHeaders(HttpHeaders headers) { - // Some of the headers coming back are poisonous apparently - // (content-length?)... - HttpHeaders outgoingHeaders = new HttpHeaders(); - outgoingHeaders.putAll(headers); - outgoingHeaders.remove(HOST); - outgoingHeaders.remove(HOST.toLowerCase()); - outgoingHeaders.set(HOST, getUaaHost()); - logger.debug("Outgoing headers: " + outgoingHeaders); - return outgoingHeaders; - } - - protected String extractPath(HttpServletRequest request) { - String query = request.getQueryString(); - try { - query = query == null ? "" : "?" + URLDecoder.decode(query, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("Cannot decode query string: " + query); - } - String path = request.getRequestURI() + query; - String context = request.getContextPath(); - path = path.substring(context.length()); - if (path.startsWith("/")) { - // In the root context we have to remove this as well - path = path.substring(1); - } - logger.debug("Path: " + path); - return path; - } - - protected void populateBuildAndLinkInfo(Model model) { - Map attributes = new HashMap(); - attributes.put("links", getLinksInfo()); - model.addAllAttributes(attributes); - model.addAttribute("links", getLinks()); - } - - protected void setCommitInfo(Map model) { - model.put("commit_id", gitProperties.getProperty("git.commit.id.abbrev", "UNKNOWN")); - model.put( - "timestamp", - gitProperties.getProperty("git.commit.time", - new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()))); - model.put("app", UaaStringUtils.getMapFromProperties(buildProperties, "build.")); - - } -} diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AnalyticsInterceptor.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/AnalyticsInterceptor.java deleted file mode 100644 index 1ccdc2c08e3..00000000000 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AnalyticsInterceptor.java +++ /dev/null @@ -1,65 +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.login; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; - -/** - * @author Dave Syer - * - */ -public class AnalyticsInterceptor extends HandlerInterceptorAdapter { - - private Analytics analytics; - - /** - * @param analytics the analytics to set - */ - public void setAnalytics(Analytics analytics) { - this.analytics = analytics; - } - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, - ModelAndView modelAndView) throws Exception { - if (modelAndView != null && modelAndView.hasView() && analytics != null) { - modelAndView.addObject("analytics", analytics); - } - } - - public static class Analytics { - private String code; - private String domain; - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - public String getDomain() { - return domain; - } - - public void setDomain(String domain) { - this.domain = domain; - } - } -} diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ClientInfoAuthenticationFilter.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/ClientInfoAuthenticationFilter.java deleted file mode 100644 index 32636975c9e..00000000000 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ClientInfoAuthenticationFilter.java +++ /dev/null @@ -1,180 +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.login; - -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -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 org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.provider.client.BaseClientDetails; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; - -/** - * Authentication filter accepting basic authorization header and using it to - * relay to a remote /clientinfo endpoint. Allows rejecting of - * clients based on id or authorized grant type. - * - * @author Dave Syer - * - */ -public class ClientInfoAuthenticationFilter implements Filter { - - protected final Log logger = LogFactory.getLog(getClass()); - - private Set allowedClients = Collections.singleton(".*"); - - private Set allowedGrantTypes = Collections.singleton(".*"); - - private RestOperations restTemplate = new RestTemplate(); - - private String clientInfoUrl; - - private AuthenticationEntryPoint authenticationEntryPoint = new BasicAuthenticationEntryPoint(); - - public void setRestTemplate(RestOperations restTemplate) { - this.restTemplate = restTemplate; - } - - public void setClientInfoUrl(String clientInfoUrl) { - this.clientInfoUrl = clientInfoUrl; - } - - /** - * @param allowedClients the allowedClients to set - */ - public void setAllowedClients(Set allowedClients) { - this.allowedClients = new HashSet(allowedClients); - } - - /** - * @param allowedGrantTypes the allowedGrantTypes to set - */ - public void setAllowedGrantTypes(Set allowedGrantTypes) { - this.allowedGrantTypes = allowedGrantTypes; - } - - /** - * @param authenticationEntryPoint the authenticationEntryPoint to set - */ - public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { - this.authenticationEntryPoint = authenticationEntryPoint; - } - - /** - * Populates the Spring Security context with a - * {@link UsernamePasswordAuthenticationToken} referring to the client - * that authenticates using the basic authorization header. - */ - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, - ServletException { - - HttpServletRequest servletRequest = (HttpServletRequest) request; - String header = servletRequest.getHeader("Authorization"); - if (header == null || !header.startsWith("Basic ")) { - chain.doFilter(request, response); - return; - } - - HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", header); - - try { - - ResponseEntity result = restTemplate.exchange(clientInfoUrl, HttpMethod.GET, - new HttpEntity(headers), BaseClientDetails.class); - - ClientDetails client = result.getBody(); - String clientId = client.getClientId(); - validateClient(client); - - Authentication authResult = new UsernamePasswordAuthenticationToken(clientId, "", - client.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authResult); - - } catch (RuntimeException e) { - logger.debug("Authentication failed"); - authenticationEntryPoint.commence(servletRequest, (HttpServletResponse) response, - new BadCredentialsException("Could not authenticate", e)); - return; - } - - chain.doFilter(request, response); - - } - - protected void validateClient(ClientDetails client) { - String clientId = client.getClientId(); - for (String pattern : allowedClients) { - if (!clientId.matches(pattern)) { - throw new BadCredentialsException("Client not permitted: " + clientId); - } - } - Set grantTypes = client.getAuthorizedGrantTypes(); - boolean matched = false; - for (String pattern : allowedGrantTypes) { - for (String grantType : grantTypes) { - if (grantType.matches(pattern)) { - matched = true; - } - } - } - if (!matched) { - throw new BadCredentialsException("Client not permitted (wrong grant type): " + clientId); - } - } - - protected List getAuthorities(Collection authorities) { - return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils - .collectionToCommaDelimitedString(authorities)); - } - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - } - - @Override - public void destroy() { - } - -} diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/LinkedMaskingMultiValueMap.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/LinkedMaskingMultiValueMap.java deleted file mode 100644 index 4108dd6c9dd..00000000000 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/LinkedMaskingMultiValueMap.java +++ /dev/null @@ -1,269 +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.login; - -import java.io.Serializable; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.springframework.util.MultiValueMap; - -/** - * Simple implementation of {@link MultiValueMap} that wraps a - * {@link LinkedHashMap}, - * storing multiple values in a {@link LinkedList}. - * - *

- * This Map implementation is generally not thread-safe. It is primarily - * designed for data structures exposed from request objects, for use in a - * single thread only. - * - * Enhancements from Spring Core is that we can mask values from sensitive - * attributes such as passwords and other credentials. It also supports cyclic - * references in the toString and hashCode methods - * - * @author Arjen Poutsma - * @author Juergen Hoeller - * @author fhanik - * @since 3.0 - */ -public class LinkedMaskingMultiValueMap implements MultiValueMap, Serializable { - - private static final long serialVersionUID = 3801124242820219132L; - - private final Map> targetMap; - - private final Set maskedAttributeSet = new HashSet(); - - /** - * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}. - */ - public LinkedMaskingMultiValueMap() { - this.targetMap = new LinkedHashMap>(); - } - - public LinkedMaskingMultiValueMap(K maskedAttribute) { - this.targetMap = new LinkedHashMap>(); - this.maskedAttributeSet.add(maskedAttribute); - } - - /** - * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}. - */ - public LinkedMaskingMultiValueMap(Set maskedAttributes) { - this.targetMap = new LinkedHashMap>(); - this.maskedAttributeSet.addAll(maskedAttributes); - } - - /** - * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap} with - * the given initial capacity. - * - * @param initialCapacity the initial capacity - */ - public LinkedMaskingMultiValueMap(int initialCapacity) { - this.targetMap = new LinkedHashMap>(initialCapacity); - } - - /** - * Copy constructor: Create a new LinkedMultiValueMap with the same mappings - * as the specified Map. - * - * @param otherMap the Map whose mappings are to be placed in this Map - */ - public LinkedMaskingMultiValueMap(Map> otherMap) { - this.targetMap = new LinkedHashMap>(otherMap); - } - - // masked attributes - - // MultiValueMap implementation - - @Override - public void add(K key, V value) { - List values = this.targetMap.get(key); - if (values == null) { - values = new LinkedList(); - this.targetMap.put(key, values); - } - values.add(value); - } - - @Override - public V getFirst(K key) { - List values = this.targetMap.get(key); - return (values != null ? values.get(0) : null); - } - - @Override - public void set(K key, V value) { - List values = new LinkedList(); - values.add(value); - this.targetMap.put(key, values); - } - - @Override - public void setAll(Map values) { - for (Entry entry : values.entrySet()) { - set(entry.getKey(), entry.getValue()); - } - } - - @Override - public Map toSingleValueMap() { - LinkedHashMap singleValueMap = new LinkedHashMap(this.targetMap.size()); - for (Entry> entry : targetMap.entrySet()) { - singleValueMap.put(entry.getKey(), entry.getValue().get(0)); - } - return singleValueMap; - } - - // Map implementation - - @Override - public int size() { - return this.targetMap.size(); - } - - @Override - public boolean isEmpty() { - return this.targetMap.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return this.targetMap.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return this.targetMap.containsValue(value); - } - - @Override - public List get(Object key) { - return this.targetMap.get(key); - } - - @Override - public List put(K key, List value) { - return this.targetMap.put(key, value); - } - - @Override - public List remove(Object key) { - return this.targetMap.remove(key); - } - - @Override - public void putAll(Map> m) { - this.targetMap.putAll(m); - } - - @Override - public void clear() { - this.targetMap.clear(); - } - - @Override - public Set keySet() { - return this.targetMap.keySet(); - } - - @Override - public Collection> values() { - return this.targetMap.values(); - } - - @Override - public Set>> entrySet() { - return this.targetMap.entrySet(); - } - - @Override - public boolean equals(Object obj) { - return this.targetMap.equals(obj); - } - - @Override - public int hashCode() { - int h = 0; - Iterator>> i = entrySet().iterator(); - while (i.hasNext()) { - int keyHash = 1; - Entry> entry = i.next(); - if (entry.getKey() == null || entry.getKey() == this) { - // no op - don't modify the hash - } else { - keyHash += entry.getKey().hashCode(); - } - List value = entry.getValue(); - int valueHash = 1; - for (V v : value) { - valueHash = 31 * valueHash + (v == null ? 0 : v == this ? 0 : v.hashCode()); - } - - h += (keyHash ^ valueHash); - } - return h; - } - - @Override - public String toString() { - Iterator>> i = targetMap.entrySet().iterator(); - if (!i.hasNext()) - return "{}"; - - StringBuilder sb = new StringBuilder(); - sb.append('{'); - - while (i.hasNext()) { - - Entry> e = i.next(); - List value = e.getValue(); - - K key = e.getKey(); - sb.append(key == this ? "(this map)" : key); - sb.append('='); - - if (maskedAttributeSet.contains(key)) { - sb.append("[PROTECTED]"); - } else if (value == null) { - sb.append("[]"); - } else { - Iterator it = value.iterator(); - sb.append('['); - while (it.hasNext()) { - V v = it.next(); - sb.append(v == this ? "(this map)" : v); - if (it.hasNext()) { - sb.append(',').append(' '); - } - } - sb.append(']'); - } - - if (i.hasNext()) { - sb.append(',').append(' '); - } - } - return sb.toString(); - } - -} diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/util/IndirectBeanCreator.java b/login/src/main/java/org/cloudfoundry/identity/uaa/login/util/IndirectBeanCreator.java deleted file mode 100644 index b926dc1fa78..00000000000 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/util/IndirectBeanCreator.java +++ /dev/null @@ -1,22 +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.login.util; - -/** - * @see http://forum.spring.io/forum/spring-projects/container/74457-move-bean-s-class-attribute-value-to-external-properties - */ -public class IndirectBeanCreator { - public static T getBean(final Class clazz) throws IllegalAccessException, InstantiationException { - return clazz.newInstance(); - } -} diff --git a/login/src/main/java/org/cloudfoundry/identity/web/Prompt.java b/login/src/main/java/org/cloudfoundry/identity/web/Prompt.java deleted file mode 100644 index 21956cf5eb4..00000000000 --- a/login/src/main/java/org/cloudfoundry/identity/web/Prompt.java +++ /dev/null @@ -1,38 +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.web; - -/** - * @author Dave Syer - * - */ -public class Prompt { - - private final String name; - private final String text; - private final String type; - - public Prompt(String name, String type, String text) { - this.name = name; - this.type = type; - this.text = text; - } - - public String getName() { - return name; - } - - public String[] getDetails() { - return new String[] { type, text }; - } -} diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/LinkedMaskingMultiValueMapTests.java b/login/src/test/java/org/cloudfoundry/identity/uaa/login/LinkedMaskingMultiValueMapTests.java deleted file mode 100644 index 16ec5d1ff61..00000000000 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/LinkedMaskingMultiValueMapTests.java +++ /dev/null @@ -1,150 +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.login; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.util.MultiValueMap; - -/** - * @author Arjen Poutsma - * @author fhanik - */ -public class LinkedMaskingMultiValueMapTests { - - private LinkedMaskingMultiValueMap map; - private LinkedMaskingMultiValueMap objectMap; - - @Before - public void setUp() { - map = new LinkedMaskingMultiValueMap("password"); - objectMap = new LinkedMaskingMultiValueMap("password"); - } - - @Test - public void add() { - map.add("key", "value1"); - map.add("key", "value2"); - assertEquals(1, map.size()); - List expected = new ArrayList(2); - expected.add("value1"); - expected.add("value2"); - assertEquals(expected, map.get("key")); - } - - @Test - public void getFirst() { - List values = new ArrayList(2); - values.add("value1"); - values.add("value2"); - map.put("key", values); - assertEquals("value1", map.getFirst("key")); - assertNull(map.getFirst("other")); - } - - @Test - public void set() { - map.set("key", "value1"); - map.set("key", "value2"); - assertEquals(1, map.size()); - assertEquals(Collections.singletonList("value2"), map.get("key")); - } - - @Test - public void equals() { - map.set("key1", "value1"); - assertEquals(map, map); - MultiValueMap o1 = new LinkedMaskingMultiValueMap(); - o1.set("key1", "value1"); - assertEquals(map, o1); - assertEquals(o1, map); - Map> o2 = new HashMap>(); - o2.put("key1", Collections.singletonList("value1")); - assertEquals(map, o2); - assertEquals(o2, map); - } - - @Test - public void testSelfReferenceKey() { - objectMap.add(objectMap, "value1"); - String s = objectMap.toString(); - assertTrue(s.indexOf("this map") >= 0); - } - - @Test - public void testSelfReferenceValue() { - objectMap.add("key1", objectMap); - String s = objectMap.toString(); - assertTrue(s.indexOf("this map") >= 0); - } - - @Test - public void doNotPrintPassword() { - map.add("password", "password-value"); - String s = map.toString(); - assertTrue(s.indexOf("password") >= 0); - assertFalse(s.indexOf("password-value") >= 0); - assertTrue(s.indexOf("PROTECTED") >= 0); - } - - @Test - public void testHash() { - map.add("key1", "value1"); - map.add("key1", "value2"); - objectMap.add("key1", "value1"); - objectMap.add("key1", "value2"); - int hash1 = map.hashCode(); - int hash2 = objectMap.hashCode(); - assertEquals(hash1, hash2); - } - - @Test - public void testCyclicKeyHash() { - objectMap.add(objectMap, "value1"); - objectMap.add(objectMap, "value2"); - LinkedMaskingMultiValueMap objectMap2 = new LinkedMaskingMultiValueMap( - "password"); - objectMap2.add(objectMap2, "value1"); - objectMap2.add(objectMap2, "value2"); - int hash1 = objectMap.hashCode(); - int hash2 = objectMap2.hashCode(); - assertEquals(hash1, hash2); - } - - @Test - public void testCyclicValueHash() { - objectMap.add("key1", "value1"); - objectMap.add("key1", objectMap); - - LinkedMaskingMultiValueMap objectMap2 = new LinkedMaskingMultiValueMap( - "password"); - objectMap2.add("key1", "value1"); - objectMap2.add("key1", objectMap2); - - int hash1 = objectMap.hashCode(); - int hash2 = objectMap2.hashCode(); - assertEquals(hash1, hash2); - } - -} \ No newline at end of file diff --git a/login/src/test/java/org/cloudfoundry/identity/web/PromptTest.java b/login/src/test/java/org/cloudfoundry/identity/web/PromptTest.java deleted file mode 100644 index 87131651754..00000000000 --- a/login/src/test/java/org/cloudfoundry/identity/web/PromptTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.cloudfoundry.identity.web; - -import static org.hamcrest.Matchers.arrayContaining; -import static org.hamcrest.Matchers.is; - -import org.junit.Assert; -import org.junit.Test; - -public class PromptTest { - - @Test - public void testPrompt() throws Exception { - Prompt prompt = new Prompt("username", "text", "Username"); - Assert.assertThat(prompt.getName(), is("username")); - Assert.assertThat(prompt.getDetails(), arrayContaining("text", "Username")); - } -} \ No newline at end of file diff --git a/model/build.gradle b/model/build.gradle new file mode 100644 index 00000000000..c39581b0e95 --- /dev/null +++ b/model/build.gradle @@ -0,0 +1,35 @@ +description = 'CloudFoundry Identity Model 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', name: 'spring-web', version: parent.springVersion + compile group: 'org.springframework', name: 'spring-webmvc', version: parent.springVersion + compile group: 'org.springframework.security', name: 'spring-security-config', version: parent.springSecurityVersion + compile (group: 'org.springframework.security.oauth', name: 'spring-security-oauth', version: parent.springSecurityOAuthVersion) { + exclude(module: 'spring-security-config') + } + compile (group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: parent.springSecurityOAuthVersion) { + exclude(module: 'jackson-mapper-asl') + } + + 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 +} + +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-model') : line } +} + +integrationTest {}.onlyIf { //disable since we don't have any + false +} + diff --git a/payload/build_properties.gradle b/model/build_properties.gradle similarity index 100% rename from payload/build_properties.gradle rename to model/build_properties.gradle diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChange.java b/model/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChange.java new file mode 100644 index 00000000000..ad91f519076 --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/account/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.account; + +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/model/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeResponse.java new file mode 100644 index 00000000000..06a7d619957 --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/account/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.account; + +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/login/ForgotPasswordInfo.java b/model/src/main/java/org/cloudfoundry/identity/uaa/account/ForgotPasswordInfo.java similarity index 91% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/login/ForgotPasswordInfo.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/account/ForgotPasswordInfo.java index 84d830aad84..a98fed29d1b 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/ForgotPasswordInfo.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/account/ForgotPasswordInfo.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.account; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/account/LostPasswordChangeRequest.java b/model/src/main/java/org/cloudfoundry/identity/uaa/account/LostPasswordChangeRequest.java new file mode 100644 index 00000000000..b3432b78daa --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/account/LostPasswordChangeRequest.java @@ -0,0 +1,39 @@ +package org.cloudfoundry.identity.uaa.account; + +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 LostPasswordChangeRequest { + + @JsonProperty("code") + private String changeCode; + + @JsonProperty("new_password") + private String newPassword; + + public LostPasswordChangeRequest() { } + + public LostPasswordChangeRequest(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/model/src/main/java/org/cloudfoundry/identity/uaa/account/LostPasswordChangeResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/account/LostPasswordChangeResponse.java new file mode 100644 index 00000000000..d076798480f --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/account/LostPasswordChangeResponse.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.account; + +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 LostPasswordChangeResponse { + + @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/common/src/main/java/org/cloudfoundry/identity/uaa/message/PasswordChangeRequest.java b/model/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordChangeRequest.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/message/PasswordChangeRequest.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordChangeRequest.java index 3aed2647ec8..3cb130436bf 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/message/PasswordChangeRequest.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordChangeRequest.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.message; +package org.cloudfoundry.identity.uaa.account; import com.fasterxml.jackson.databind.annotation.JsonSerialize; diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordResetResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordResetResponse.java new file mode 100644 index 00000000000..d8c50ddcc4c --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/account/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.account; + +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/model/src/main/java/org/cloudfoundry/identity/uaa/account/UserInfoResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/account/UserInfoResponse.java new file mode 100644 index 00000000000..084166d0306 --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/account/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.account; + +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/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java b/model/src/main/java/org/cloudfoundry/identity/uaa/approval/Approval.java similarity index 63% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/approval/Approval.java index a100db7076c..431b10899fb 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/Approval.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/approval/Approval.java @@ -10,90 +10,109 @@ * 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.approval; +package org.cloudfoundry.identity.uaa.approval; import java.util.Calendar; import java.util.Date; 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.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.approval.impl.ApprovalsJsonDeserializer; @JsonInclude(JsonInclude.Include.NON_NULL) @JsonDeserialize(using = ApprovalsJsonDeserializer.class) public class Approval { - private String userId; - - private String clientId; - - private String scope; + public Approval() { + } public enum ApprovalStatus { APPROVED, - DENIED; + DENIED } - private ApprovalStatus status; + private String userId = ""; - public ApprovalStatus getStatus() { - return status; - } + private String clientId = ""; + + private String scope = ""; + + private ApprovalStatus status; private Date expiresAt; - private Date lastUpdatedAt; + private Date lastUpdatedAt = new Date(); + + 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() { + return status; } 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 @@ -101,40 +120,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; @@ -162,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/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsJsonDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java similarity index 92% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsJsonDeserializer.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java index ae2d0489e02..a3a70a89e70 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsJsonDeserializer.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/approval/impl/ApprovalsJsonDeserializer.java @@ -1,11 +1,11 @@ -package org.cloudfoundry.identity.uaa.oauth.approval; +package org.cloudfoundry.identity.uaa.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.ApprovalStatus; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; import java.io.IOException; import java.util.Date; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java b/model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java similarity index 90% rename from common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java index e4afea35e03..c43baf47385 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCode.java +++ b/model/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/model/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java b/model/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java new file mode 100644 index 00000000000..fb89ab7b66d --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/constants/OriginKeys.java @@ -0,0 +1,29 @@ +/* + * ****************************************************************************** + * 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; + +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/util/json/JsonDateDeserializer.java b/model/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 model/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/model/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/model/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 model/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/model/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/login/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 login/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/login/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 login/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/model/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/login/AuthenticationResponse.java new file mode 100644 index 00000000000..8e23bd6ab5f --- /dev/null +++ b/model/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/model/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 model/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/model/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 model/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinResponse.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/client/ClientConstants.java b/model/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 model/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/model/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/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java similarity index 84% rename from common/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 index 460be9a88bf..74152431c38 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModification.java @@ -4,10 +4,15 @@ 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.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 { @@ -25,14 +30,6 @@ 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 ClientDetailsModification(ClientDetails prototype) { super(prototype); if (prototype instanceof BaseClientDetails) { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/SecretChangeRequest.java b/model/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 model/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/model/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/oauth/Claims.java b/model/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 model/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/model/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/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java new file mode 100644 index 00000000000..6daf3fbb126 --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/Claims.java @@ -0,0 +1,303 @@ +/* + * ****************************************************************************** + * 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.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_NULL) +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/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java new file mode 100644 index 00000000000..4b2b56c0383 --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessToken.java @@ -0,0 +1,45 @@ +/* + * ***************************************************************************** + * 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.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; + +@JsonSerialize(using = CompositeAccessTokenSerializer.class) +@JsonDeserialize(using = CompositeAccessTokenDeserializer.class) +public class CompositeAccessToken extends DefaultOAuth2AccessToken { + + public static String ID_TOKEN = "id_token"; + + public String getIdTokenValue() { + return idTokenValue; + } + + public void setIdTokenValue(String idTokenValue) { + this.idTokenValue = idTokenValue; + } + + private String idTokenValue; + + public CompositeAccessToken(String accessTokenValue) { + super(accessTokenValue); + } + + public CompositeAccessToken(OAuth2AccessToken accessToken) { + super(accessToken); + } + +} diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenDeserializer.java new file mode 100644 index 00000000000..e9bfc9ddc22 --- /dev/null +++ b/model/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/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/CompositeAccessTokenSerializer.java new file mode 100644 index 00000000000..5d0e9c90b24 --- /dev/null +++ b/model/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(); + } +} diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeyResponse.java new file mode 100644 index 00000000000..0ef25a4d681 --- /dev/null +++ b/model/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("value") + 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/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java b/model/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/VerificationKeysListResponse.java new file mode 100644 index 00000000000..15ac2e31ab5 --- /dev/null +++ b/model/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/common/src/main/java/org/cloudfoundry/identity/uaa/AbstractIdentityProviderDefinition.java b/model/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 model/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/model/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/model/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java similarity index 89% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ExternalIdentityProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/ExternalIdentityProviderDefinition.java index 498f61090f2..fe6ab8947d8 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ExternalIdentityProviderDefinition.java +++ b/model/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; @@ -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/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java similarity index 92% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java index cbaa36e45a1..77b26a0096f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProvider.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java @@ -10,10 +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.zone; +package org.cloudfoundry.identity.uaa.provider; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; + import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; @@ -23,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; @@ -37,11 +32,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) @@ -291,11 +286,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/common/src/main/java/org/cloudfoundry/identity/uaa/KeystoneIdentityProviderDefinition.java b/model/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 model/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/model/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/model/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 model/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/model/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/model/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 model/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/model/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/model/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 model/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/model/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/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java similarity index 60% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java index 4508117e5db..1fb7bf073ea 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/SamlIdentityProviderDefinition.java @@ -10,11 +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.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.springframework.util.StringUtils; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -40,7 +39,7 @@ public enum MetadataLocation { URL, DATA, UNKNOWN - }; + } private String metaDataLocation; private String idpEntityAlias; @@ -54,70 +53,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 @@ -126,11 +82,8 @@ public MetadataLocation getType() { if (trimmedLocation.startsWith(" 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/common/src/main/java/org/cloudfoundry/identity/uaa/zone/UaaIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java similarity index 89% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/UaaIdentityProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java index 54b9705e3a1..6d6908c3afe 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/UaaIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/UaaIdentityProviderDefinition.java @@ -10,11 +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.zone; +package org.cloudfoundry.identity.uaa.provider; -import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.config.LockoutPolicy; -import org.cloudfoundry.identity.uaa.config.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/message/SimpleMessage.java b/model/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 model/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/model/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/model/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 model/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/model/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/scim/ScimCore.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java similarity index 96% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java index 1fdd6a8bd5e..5d87fd06f23 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimCore.java +++ b/model/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/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java similarity index 84% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java index 53ad93218a2..7a7fc5eba92 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroup.java +++ b/model/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; @@ -31,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/scim/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 scim/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/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java similarity index 97% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMember.java rename to model/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/model/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/ScimMeta.java b/model/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 model/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/model/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/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java similarity index 98% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java index 23537b2e13f..9dfcf8b4021 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUser.java @@ -12,23 +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.util.json.JsonDateSerializer; +import org.cloudfoundry.identity.uaa.impl.JsonDateSerializer; +import org.cloudfoundry.identity.uaa.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 * @@ -317,7 +318,7 @@ public void setType(String type) { private boolean active = true; - private boolean verified = false; + private boolean verified = true; private String origin = ""; @@ -500,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/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupJsonDeserializer.java b/model/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 model/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/model/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/model/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 model/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/model/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/model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java similarity index 93% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserJsonDeserializer.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/scim/impl/ScimUserJsonDeserializer.java index 19d43796fc1..8d32ffd65a0 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserJsonDeserializer.java +++ b/model/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; @@ -23,9 +23,11 @@ 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.oauth.approval.Approval; -import org.cloudfoundry.identity.uaa.util.json.JsonDateDeserializer; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.approval.Approval; +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 @@ -76,7 +78,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/common/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 common/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/common/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 common/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/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java similarity index 91% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java index 23593907640..ef2a430291c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZone.java @@ -13,18 +13,12 @@ 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 +28,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/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneConfiguration.java index 387b494f0c2..3dca8b403d8 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfiguration.java +++ b/model/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/common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java similarity index 61% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java index 9484baede28..67456a9efb6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPair.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPair.java @@ -12,32 +12,49 @@ * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.zone; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import java.util.HashMap; - -/** - * Created by pivotal on 11/11/15. - */ +import java.util.UUID; 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; } + + public void setId(UUID id) { this.id = id; } + public String getSigningKey() { return signingKey; } @@ -53,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/common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java similarity index 74% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java index d178953b068..91ed7bf365e 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/KeyPairsMap.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/KeyPairsMap.java @@ -12,16 +12,18 @@ * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.zone; 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); } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java similarity index 82% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java index a3dd7da44c2..a2ce2d388e2 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/SamlConfig.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/SamlConfig.java @@ -12,13 +12,14 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.zone; public class SamlConfig { - private boolean requestSigned = false; + private boolean requestSigned = true; private boolean wantAssertionSigned = false; 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/common/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java b/model/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/zone/TokenPolicy.java index 18fff7785a6..68b0fd23474 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/TokenPolicy.java +++ b/model/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/payload/src/main/resources/.gitignore b/model/src/main/resources/.gitignore similarity index 100% rename from payload/src/main/resources/.gitignore rename to model/src/main/resources/.gitignore 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 new file mode 100644 index 00000000000..5704b6cfc77 --- /dev/null +++ b/model/src/test/java/org/cloudfoundry/identity/uaa/zone/SamlConfigTest.java @@ -0,0 +1,50 @@ +/* + * ***************************************************************************** + * 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.Before; +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 { + + SamlConfig config; + + @Before + public void setUp() { + config = new SamlConfig(); + } + + @Test + public void testIsRequestSigned() throws Exception { + assertTrue(config.isRequestSigned()); + + } + + @Test + public void testIsWantAssertionSigned() throws Exception { + assertFalse(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/payload/build.gradle b/payload/build.gradle deleted file mode 100644 index 1db3575d86e..00000000000 --- a/payload/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -description = 'CloudFoundry Identity Payload Data Objects JAR' - -dependencies { - compile(group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version:parent.springSecurityOAuthVersion) { - exclude(module: 'commons-codec') - exclude(module: 'jackson-mapper-asl') - } - compile group: 'org.springframework.security', name: 'spring-security-config', version:parent.springSecurityVersion - compile (group: 'org.springframework.security.oauth', name: 'spring-security-oauth', version:parent.springSecurityOAuthVersion) { - exclude(module: 'spring-security-config') - } - -} - -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 } -} diff --git a/samples/api/build.gradle b/samples/api/build.gradle index 78617b25f46..62bc65d6d3b 100644 --- a/samples/api/build.gradle +++ b/samples/api/build.gradle @@ -11,33 +11,29 @@ eclipse { description = 'Sample resource server for Cloudfoundry Identity Services' dependencies { Project identityParent = parent.parent - Project identityCommon = identityParent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } - Project identityScim = identityParent.subprojects.find { it.name.equals('cloudfoundry-identity-scim') } + Project identityServer = identityParent.subprojects.find { it.name.equals('cloudfoundry-identity-server') } - compile identityCommon + compile identityServer + + providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version:parent.servletVersion compile(group: 'org.springframework.security', name: 'spring-security-taglibs', version:identityParent.springSecurityVersion) { exclude(module: 'spring-jdbc') exclude(module: 'spring-tx') } compile group: 'org.springframework.security', name: 'spring-security-config', version:identityParent.springSecurityVersion - testCompile identityCommon.configurations.testCompile.dependencies - testCompile identityCommon.sourceSets.test.output - testCompile identityScim + + testCompile identityServer.configurations.testCompile.dependencies + testCompile identityServer.sourceSets.test.output 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' } test { exclude 'org/cloudfoundry/identity/api/web/*IntegrationTests.class' } -task integrationTest(type: Test) { - dependsOn parent.parent.cargoStartLocal, parent.parent.resetCoverage - - finalizedBy parent.parent.flushCoverageData - +integrationTest { filter { includeTestsMatching "org.cloudfoundry.identity.api.web.*IntegrationTests" } diff --git a/samples/api/src/main/webapp/WEB-INF/spring-servlet.xml b/samples/api/src/main/webapp/WEB-INF/spring-servlet.xml index 1d73aa23d70..f3639d1fadf 100755 --- a/samples/api/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/samples/api/src/main/webapp/WEB-INF/spring-servlet.xml @@ -63,12 +63,12 @@ - - - - - - + + + + + + @@ -80,13 +80,13 @@ - - - - - - - + + + + + + + diff --git a/samples/api/src/main/webapp/WEB-INF/web.xml b/samples/api/src/main/webapp/WEB-INF/web.xml index e730e100e36..821752a69fe 100755 --- a/samples/api/src/main/webapp/WEB-INF/web.xml +++ b/samples/api/src/main/webapp/WEB-INF/web.xml @@ -17,7 +17,7 @@ cors - org.cloudfoundry.identity.uaa.web.CorsFilter + org.cloudfoundry.identity.uaa.security.web.CorsFilter diff --git a/samples/app/build.gradle b/samples/app/build.gradle index 9d3bdd14928..3a44a3dde48 100644 --- a/samples/app/build.gradle +++ b/samples/app/build.gradle @@ -11,29 +11,25 @@ eclipse { description = 'Sample user webapp for Cloudfoundry Identity Services' dependencies { Project identityParent = parent.parent - Project identityCommon = identityParent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } - Project identityScim = identityParent.subprojects.find { it.name.equals('cloudfoundry-identity-scim') } + Project identityServer = identityParent.subprojects.find { it.name.equals('cloudfoundry-identity-server') } - compile identityCommon + compile identityServer + + providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version:parent.servletVersion runtime group: 'javax.servlet', name: 'jstl', version:'1.2' runtime(group: 'org.springframework.security', name: 'spring-security-config', version:identityParent.springSecurityVersion) { exclude(module: 'spring-aop') } - testCompile identityCommon.configurations.testCompile.dependencies - testCompile identityCommon.sourceSets.test.output - testCompile identityScim - providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version:'3.0.1' + + testCompile identityServer.configurations.testCompile.dependencies + testCompile identityServer.sourceSets.test.output } test { exclude 'org/cloudfoundry/identity/app/integration/*.class' } -task integrationTest(type: Test) { - dependsOn parent.parent.cargoStartLocal, parent.parent.resetCoverage - - finalizedBy parent.parent.flushCoverageData - +integrationTest { filter { includeTestsMatching "org.cloudfoundry.identity.app.integration.*" } diff --git a/samples/app/src/main/webapp/WEB-INF/spring-servlet.xml b/samples/app/src/main/webapp/WEB-INF/spring-servlet.xml index 05503183abe..b381c5dc28b 100755 --- a/samples/app/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/samples/app/src/main/webapp/WEB-INF/spring-servlet.xml @@ -89,11 +89,11 @@ - - - - - + + + + + diff --git a/samples/app/src/test/java/org/cloudfoundry/identity/app/integration/AuthenticationIntegrationTests.java b/samples/app/src/test/java/org/cloudfoundry/identity/app/integration/AuthenticationIntegrationTests.java index 41c6358df52..3fb526f9d73 100644 --- a/samples/app/src/test/java/org/cloudfoundry/identity/app/integration/AuthenticationIntegrationTests.java +++ b/samples/app/src/test/java/org/cloudfoundry/identity/app/integration/AuthenticationIntegrationTests.java @@ -20,10 +20,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.cloudfoundry.identity.uaa.test.IntegrationTestContextLoader; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; -import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.junit.Rule; import org.junit.Test; import org.springframework.http.HttpHeaders; diff --git a/scim/build.gradle b/scim/build.gradle deleted file mode 100644 index a575cb275a1..00000000000 --- a/scim/build.gradle +++ /dev/null @@ -1,28 +0,0 @@ -Project identityCommon = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } - -description = 'CloudFoundry Identity SCIM' - -dependencies { - compile identityCommon - 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 - 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/scim/src/META-INF/MANIFEST.MF b/scim/src/META-INF/MANIFEST.MF deleted file mode 100644 index 5e9495128c0..00000000000 --- a/scim/src/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordReset.java b/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordReset.java deleted file mode 100644 index 43f8122834b..00000000000 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordReset.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cloudfoundry.identity.uaa.scim.endpoints; - -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/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 83dea4300ce..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; - } -} \ No newline at end of file diff --git a/server/build.gradle b/server/build.gradle new file mode 100644 index 00000000000..ecf97833b9a --- /dev/null +++ b/server/build.gradle @@ -0,0 +1,87 @@ +description = 'CloudFoundry Identity Server JAR' + +Project identityModel = parent.subprojects.find { it.name.equals('cloudfoundry-identity-model') } + +dependencies { + + compile(identityModel) + + provided group: 'javax.servlet', name: 'javax.servlet-api', version: parent.servletVersion + compile group: 'javax.mail', name: 'mail', version: parent.javamailVersion + + compile group: 'commons-logging', name: 'commons-logging', version: parent.commonsLoggingVersion + + compile group: 'org.springframework', name: 'spring-beans', version: parent.springVersion + compile group: 'org.springframework', name: 'spring-context', version: parent.springVersion + compile group: 'org.springframework', name: 'spring-tx', version: parent.springVersion + compile group: 'org.springframework', name: 'spring-jdbc', version: parent.springVersion + compile group: 'org.springframework', name: 'spring-web', version: parent.springVersion + compile group: 'org.springframework', name: 'spring-context-support', version:springVersion + compile group: 'org.springframework.security', name: 'spring-security-core', version: parent.springSecurityVersion + compile group: 'org.springframework.security', name: 'spring-security-jwt', version: parent.springSecurityJwtVersion + compile group: 'org.springframework.security', name: 'spring-security-openid', version:springSecurityVersion + compile(group: 'org.springframework.security.extensions', name: 'spring-security-saml2-core', version:parent.springSecuritySamlVersion) { + exclude(module: 'bcprov-jdk15') + } + compile(group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version: parent.springSecurityOAuthVersion) { + exclude(module: 'commons-codec') + exclude(module: 'jackson-mapper-asl') + } + + compile group: 'org.aspectj', name: 'aspectjrt', version: parent.aspectJVersion + + compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version: parent.thymeleafVersion + compile group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version: parent.thymeleafDialectVersion + compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-springsecurity3', version: parent.thymeleafExtrasVersion + + 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: parent.hibernateValidatorVersion + compile group: 'org.flywaydb', name: 'flyway-core', version: parent.flywayVersion + compile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: parent.mariaDBClientVersion + compile group: 'org.hsqldb', name: 'hsqldb', version: parent.hsqldbVersion + + compile group: 'org.yaml', name: 'snakeyaml', version: parent.snakeYamlVersion + + compile group: 'org.springframework.security', name: 'spring-security-ldap', version:parent.springSecurityVersion + compile group: 'org.springframework.ldap', name: 'spring-ldap-core', version:parent.springSecurityLdapVersion + compile group: 'org.springframework.ldap', name: 'spring-ldap-core-tiger', version:parent.springSecurityLdapVersion + compile(group: 'org.apache.directory.api', name: 'api-ldap-model', version:parent.apacheLdapApiVersion) { + exclude(module: 'slf4j-api') + } + + compile group: 'org.passay', name: 'passay', version: parent.passayVersion + + testCompile group: 'org.springframework', name: 'spring-test', version: parent.springVersion + testCompile (group: 'junit', name: 'junit', version: parent.junitVersion) { + exclude(module: 'hamcrest-core') + } + testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: parent.hamcrestVersion + testCompile (group: 'org.mockito', name: 'mockito-all', version: parent.mockitoVersion) { + exclude(module: 'hamcrest-core') + } + + testCompile group: 'org.flywaydb', name: 'flyway-core', version: parent.flywayVersion + testCompile group: 'postgresql', name: 'postgresql', version: parent.postgresqlVersion + + testCompile group: 'org.apache.tomcat', name: 'tomcat-jdbc', version:parent.tomcatVersion + + testCompile group: 'com.jayway.jsonpath', name: 'json-path', version: parent.jsonPathVersion + testCompile group: 'com.jayway.jsonpath', name: 'json-path-assert', version: parent.jsonPathVersion + +} + +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-server') : line } +} + +integrationTest {}.onlyIf { //disable since we don't have any + true == false +} diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountCreationService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/AccountCreationService.java similarity index 98% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountCreationService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/AccountCreationService.java index e786b046fc8..c9d94169ecd 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountCreationService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/AccountCreationService.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.account; import com.fasterxml.jackson.annotation.JsonProperty; import org.cloudfoundry.identity.uaa.scim.ScimUser; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/AccountsController.java similarity index 96% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/AccountsController.java index e58044a2902..c1665825361 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AccountsController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/AccountsController.java @@ -10,10 +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.login; +package org.cloudfoundry.identity.uaa.account; -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; @@ -97,12 +97,13 @@ 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(), 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/ChangeEmailController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangeEmailController.java similarity index 97% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangeEmailController.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangeEmailController.java index 87475cf9d61..dca47d00ca9 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangeEmailController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangeEmailController.java @@ -1,9 +1,9 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.account; -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/ChangeEmailService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangeEmailService.java similarity index 83% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangeEmailService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangeEmailService.java index 7eb7c42c4dd..ecc7822637f 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangeEmailService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangeEmailService.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.account; import java.util.Map; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangePasswordController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangePasswordController.java similarity index 98% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangePasswordController.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangePasswordController.java index 340d5c35b9e..bf01861094d 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangePasswordController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangePasswordController.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.login; +package org.cloudfoundry.identity.uaa.account; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; @@ -28,7 +28,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.util.Arrays; import java.util.LinkedList; import static org.springframework.web.bind.annotation.RequestMethod.GET; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangePasswordService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangePasswordService.java similarity index 94% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangePasswordService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangePasswordService.java index 8a1aeffac82..3147e121cd4 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ChangePasswordService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ChangePasswordService.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.login; +package org.cloudfoundry.identity.uaa.account; public interface ChangePasswordService { void changePassword(String username, String currentPassword, String newPassword); diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/ConflictException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ConflictException.java similarity index 85% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/login/ConflictException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/ConflictException.java index e9f3e50ac2e..ce6904ef644 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/ConflictException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ConflictException.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.account; public class ConflictException extends RuntimeException { String userId; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java similarity index 95% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java index 49de83774bd..6ef6f372811 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailAccountCreationService.java @@ -1,12 +1,14 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.account; 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.message.MessageService; +import org.cloudfoundry.identity.uaa.message.MessageType; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceAlreadyExistsException; @@ -71,10 +73,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()) { @@ -104,7 +106,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); } @@ -147,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/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.java similarity index 95% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.java index abd6217c947..bd856045bda 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/EmailChangeEmailService.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.login; +package org.cloudfoundry.identity.uaa.account; import java.sql.Timestamp; import java.util.Collections; @@ -21,10 +21,12 @@ 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.message.MessageService; +import org.cloudfoundry.identity.uaa.message.MessageType; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -63,7 +65,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()) { @@ -87,7 +89,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/scim/src/main/java/org/cloudfoundry/identity/uaa/login/NotFoundException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/NotFoundException.java similarity index 57% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/login/NotFoundException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/NotFoundException.java index c9bd110d361..24b56fbb4a9 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/NotFoundException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/NotFoundException.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.account; public class NotFoundException extends RuntimeException { diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/openid2/OpenIdUserDetailsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/OpenIdUserDetailsService.java similarity index 98% rename from login/src/main/java/org/cloudfoundry/identity/uaa/openid2/OpenIdUserDetailsService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/OpenIdUserDetailsService.java index e688f002ea4..177807b5328 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/openid2/OpenIdUserDetailsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/OpenIdUserDetailsService.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.openid2; +package org.cloudfoundry.identity.uaa.account; import java.util.List; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordChangeEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordChangeEndpoint.java similarity index 93% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordChangeEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordChangeEndpoint.java index 8b5d9541e87..1b046f31b83 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordChangeEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordChangeEndpoint.java @@ -10,14 +10,13 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.password; +package org.cloudfoundry.identity.uaa.account; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -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.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; +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 +80,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/password/PasswordCheckEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordCheckEndpoint.java similarity index 97% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordCheckEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordCheckEndpoint.java index e5c1f8ae427..cfd0b781622 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/PasswordCheckEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordCheckEndpoint.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.password; +package org.cloudfoundry.identity.uaa.account; import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordScore; import org.springframework.stereotype.Controller; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/PasswordConfirmationValidation.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordConfirmationValidation.java similarity index 96% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/PasswordConfirmationValidation.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordConfirmationValidation.java index 2eb90c995bb..6cadb942e98 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/PasswordConfirmationValidation.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordConfirmationValidation.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.login; +package org.cloudfoundry.identity.uaa.account; import org.springframework.util.StringUtils; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpoint.java similarity index 73% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpoint.java index 69ce09413a4..62b234c575f 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpoint.java @@ -10,21 +10,15 @@ * 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.endpoints; +package org.cloudfoundry.identity.uaa.account; -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.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; -import org.cloudfoundry.identity.uaa.login.ResetPasswordService; -import org.cloudfoundry.identity.uaa.login.ResetPasswordService.ResetPasswordResponse; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; +import org.cloudfoundry.identity.uaa.authentication.InvalidCodeException; 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 +70,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 +80,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 +95,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 LostPasswordChangeRequest 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); + ResetPasswordService.ResetPasswordResponse reset = resetPasswordService.resetPassword(passwordChangeRequest.getChangeCode(), passwordChangeRequest.getNewPassword()); + ScimUser user = reset.getUser(); + ExpiringCode loginCode = getCode(user.getId(), user.getUserName(), reset.getClientId()); + LostPasswordChangeResponse response = new LostPasswordChangeResponse(); + 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) { @@ -134,9 +128,9 @@ 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)); + return codeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000), null); } @ExceptionHandler(InvalidPasswordException.class) diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ProfileController.java similarity index 92% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/ProfileController.java index 79a3d647429..afe2c91e83e 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ProfileController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ProfileController.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.login; +package org.cloudfoundry.identity.uaa.account; -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.oauth.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalsService; +import org.cloudfoundry.identity.uaa.approval.DescribedApproval; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.approval.Approval; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.ClientDetails; @@ -109,7 +111,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/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java similarity index 95% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordController.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java index c63f41e877c..4d510bcb0cf 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java @@ -10,16 +10,17 @@ * 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; +package org.cloudfoundry.identity.uaa.account; 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.message.MessageService; +import org.cloudfoundry.identity.uaa.message.MessageType; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.user.UaaAuthority; @@ -166,7 +167,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"; } @@ -191,9 +192,9 @@ public String resetPassword(Model model, } try { - ResetPasswordResponse resetPasswordResponse = resetPasswordService.resetPassword(code, password); + ResetPasswordService.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/scim/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordService.java similarity index 96% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordService.java index 211659db82c..835cfecd6a4 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/ResetPasswordService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordService.java @@ -10,14 +10,12 @@ * 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; +package org.cloudfoundry.identity.uaa.account; import com.fasterxml.jackson.annotation.JsonProperty; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; -import java.util.Map; - public interface ResetPasswordService { ForgotPasswordInfo forgotPassword(String email, String clientId, String redirectUri); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/UaaChangePasswordService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaChangePasswordService.java similarity index 95% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/UaaChangePasswordService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaChangePasswordService.java index 195518e314e..eb54ef1dc71 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/UaaChangePasswordService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaChangePasswordService.java @@ -10,10 +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.login; +package org.cloudfoundry.identity.uaa.account; -import org.cloudfoundry.identity.uaa.password.event.PasswordChangeEvent; -import org.cloudfoundry.identity.uaa.password.event.PasswordChangeFailureEvent; +import org.cloudfoundry.identity.uaa.account.event.PasswordChangeEvent; +import org.cloudfoundry.identity.uaa.account.event.PasswordChangeFailureEvent; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java similarity index 92% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java index 01f817da29e..482d4a81c43 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java @@ -10,16 +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.login; +package org.cloudfoundry.identity.uaa.account; -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.error.InvalidCodeException; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.authentication.InvalidCodeException; import org.cloudfoundry.identity.uaa.error.UaaException; -import org.cloudfoundry.identity.uaa.password.event.PasswordChangeEvent; -import org.cloudfoundry.identity.uaa.password.event.PasswordChangeFailureEvent; -import org.cloudfoundry.identity.uaa.password.event.ResetPasswordRequestEvent; +import org.cloudfoundry.identity.uaa.account.event.PasswordChangeEvent; +import org.cloudfoundry.identity.uaa.account.event.PasswordChangeFailureEvent; +import org.cloudfoundry.identity.uaa.account.event.ResetPasswordRequestEvent; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordChange; @@ -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,11 +131,11 @@ 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()) { - throw new org.cloudfoundry.identity.uaa.login.NotFoundException(); + throw new NotFoundException(); } else { throw new ConflictException(results.get(0).getId()); } @@ -146,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/login/src/main/java/org/cloudfoundry/identity/uaa/openid2/UaaUserDetails.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaUserDetails.java similarity index 96% rename from login/src/main/java/org/cloudfoundry/identity/uaa/openid2/UaaUserDetails.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaUserDetails.java index 620cfd18b8c..f5b27a72eff 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/openid2/UaaUserDetails.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaUserDetails.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.openid2; +package org.cloudfoundry.identity.uaa.account; import java.util.List; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UserInfoEndpoint.java similarity index 63% rename from common/src/main/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/UserInfoEndpoint.java index 66b4cf461f0..658f0e1924d 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UserInfoEndpoint.java @@ -10,18 +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.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; +package org.cloudfoundry.identity.uaa.account; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.user.UaaUser; @@ -30,10 +19,11 @@ 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 +45,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 +59,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/scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/AbstractPasswordChangeEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/event/AbstractPasswordChangeEvent.java similarity index 96% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/AbstractPasswordChangeEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/event/AbstractPasswordChangeEvent.java index b03d96a644e..5fcbb5d4515 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/AbstractPasswordChangeEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/event/AbstractPasswordChangeEvent.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.password.event; +package org.cloudfoundry.identity.uaa.account.event; import java.security.Principal; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeEvent.java similarity index 94% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeEvent.java index 40377abe74c..a559175c0b1 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeEvent.java @@ -11,9 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.password.event; - -import java.security.Principal; +package org.cloudfoundry.identity.uaa.account.event; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeEventPublisher.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeEventPublisher.java similarity index 98% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeEventPublisher.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeEventPublisher.java index 147e21823a9..b749dc2482f 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeEventPublisher.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeEventPublisher.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.password.event; +package org.cloudfoundry.identity.uaa.account.event; import java.util.Date; import java.util.List; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeFailureEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeFailureEvent.java similarity index 95% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeFailureEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeFailureEvent.java index 67f78a4ee6d..7c527e9b347 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeFailureEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeFailureEvent.java @@ -11,9 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.password.event; - -import java.security.Principal; +package org.cloudfoundry.identity.uaa.account.event; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/ResetPasswordRequestEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/event/ResetPasswordRequestEvent.java similarity index 96% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/ResetPasswordRequestEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/account/event/ResetPasswordRequestEvent.java index 93ceb327482..9f99e007cbb 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/password/event/ResetPasswordRequestEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/event/ResetPasswordRequestEvent.java @@ -13,7 +13,7 @@ * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.password.event; +package org.cloudfoundry.identity.uaa.account.event; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalStore.java b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalStore.java similarity index 90% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalStore.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalStore.java index 3b427838e09..4074960924a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalStore.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalStore.java @@ -10,7 +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.oauth.approval; +package org.cloudfoundry.identity.uaa.approval; + +import org.cloudfoundry.identity.uaa.approval.Approval; import java.util.List; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsAdminEndpoints.java similarity index 90% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsAdminEndpoints.java index c41230ba844..6fc24a6385c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsAdminEndpoints.java @@ -10,11 +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.oauth.approval; +package org.cloudfoundry.identity.uaa.approval; import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -23,11 +22,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.error.ConvertingExceptionView; -import org.cloudfoundry.identity.uaa.error.ExceptionReport; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.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; @@ -42,6 +41,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; @@ -92,8 +92,8 @@ public void setUaaUserDatabase(UaaUserDatabase userDatabase) { @ResponseBody @Override public List getApprovals(@RequestParam(required = false, defaultValue = "user_id pr") String filter, - @RequestParam(required = false, defaultValue = "1") int startIndex, - @RequestParam(required = false, defaultValue = "100") int count) { + @RequestParam(required = false, defaultValue = "1") int startIndex, + @RequestParam(required = false, defaultValue = "100") int count) { String userId = getCurrentUserId(); logger.debug("Fetching all approvals for user: " + userId); List input = approvalStore.getApprovals( @@ -153,14 +153,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)); @@ -174,14 +173,13 @@ 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()); } else { approval.setUserId(currentUserId); } - approval.setLastUpdatedAt(new Date()); approvalStore.addApproval(approval); } return approvalStore.getApprovals(String.format(USER_AND_CLIENT_FILTER_TEMPLATE, currentUserId, clientId)); @@ -202,11 +200,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/server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsControllerService.java similarity index 85% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsControllerService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsControllerService.java index e2a20dc79d8..5f5217b563f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsControllerService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsControllerService.java @@ -10,15 +10,15 @@ * 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.approval; +package org.cloudfoundry.identity.uaa.approval; 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/login/src/main/java/org/cloudfoundry/identity/uaa/login/ApprovalsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsService.java similarity index 95% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/ApprovalsService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsService.java index 8ce6a9cf4d4..f1b462ac93e 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/ApprovalsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/ApprovalsService.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.login; +package org.cloudfoundry.identity.uaa.approval; import java.util.List; import java.util.Map; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/DescribedApproval.java b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/DescribedApproval.java similarity index 73% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/DescribedApproval.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/approval/DescribedApproval.java index 0320c67ac93..16b1dfdf82d 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/DescribedApproval.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/DescribedApproval.java @@ -10,19 +10,26 @@ * 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; +package org.cloudfoundry.identity.uaa.approval; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval; public class DescribedApproval extends Approval { private String description; 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/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/JdbcApprovalStore.java similarity index 91% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/approval/JdbcApprovalStore.java index 516704affa9..dcdddd29f42 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStore.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/JdbcApprovalStore.java @@ -10,9 +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.oauth.approval; +package org.cloudfoundry.identity.uaa.approval; -import static org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus.APPROVED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -25,10 +25,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.audit.event.ApprovalModifiedEvent; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; -import org.cloudfoundry.identity.uaa.rest.jdbc.SearchQueryConverter; -import org.cloudfoundry.identity.uaa.rest.jdbc.SearchQueryConverter.ProcessedFilter; +import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.SearchQueryConverter; +import org.cloudfoundry.identity.uaa.resources.jdbc.SearchQueryConverter.ProcessedFilter; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -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); @@ -223,7 +220,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/login/src/main/java/org/cloudfoundry/identity/uaa/login/LoginUaaApprovalsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/LoginUaaApprovalsService.java similarity index 88% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/LoginUaaApprovalsService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/approval/LoginUaaApprovalsService.java index 6622ce41538..d1e5d484923 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/LoginUaaApprovalsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/LoginUaaApprovalsService.java @@ -10,11 +10,13 @@ * 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; +package org.cloudfoundry.identity.uaa.approval; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval; -import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalsControllerService; +import org.cloudfoundry.identity.uaa.approval.ApprovalsService; +import org.cloudfoundry.identity.uaa.approval.DescribedApproval; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalsControllerService; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; @@ -53,7 +55,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/RestUaaApprovalsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/RestUaaApprovalsService.java similarity index 92% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/approval/RestUaaApprovalsService.java index ef37632e853..164d3aed037 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/approval/RestUaaApprovalsService.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.login; +package org.cloudfoundry.identity.uaa.approval; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalsService; +import org.cloudfoundry.identity.uaa.approval.DescribedApproval; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.approval.Approval; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; @@ -65,7 +67,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/common/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/audit/AuditEventType.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditService.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditService.java rename to server/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/server/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/common/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcFailedLoginCountingAuditService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcFailedLoginCountingAuditService.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcFailedLoginCountingAuditService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/audit/JdbcFailedLoginCountingAuditService.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/LoggingAuditService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/LoggingAuditService.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/LoggingAuditService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/audit/LoggingAuditService.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/UaaAuditService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/UaaAuditService.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/UaaAuditService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/audit/UaaAuditService.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java rename to server/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/server/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/audit/event/ApprovalModifiedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEvent.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEvent.java index e1032c3bfd7..21ab730ea73 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEvent.java @@ -16,12 +16,10 @@ import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval; 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/audit/event/AuditListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AuditListener.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AuditListener.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AuditListener.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/EntityDeletedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/EntityDeletedEvent.java new file mode 100644 index 00000000000..9b937e9fb5f --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/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.audit.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/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletable.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletable.java new file mode 100644 index 00000000000..da5be3e00ae --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/SystemDeletable.java @@ -0,0 +1,57 @@ +/* + * ***************************************************************************** + * 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.event; + +import org.apache.commons.logging.Log; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.zone.IdentityZone; +import org.springframework.context.ApplicationListener; + +public interface SystemDeletable extends ApplicationListener> { + default 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()); + } + } + + default boolean isUaaZone(String zoneId) { + return IdentityZone.getUaa().getId().equals(zoneId); + } + + int deleteByIdentityZone(String zoneId); + + int deleteByOrigin(String origin, String zoneId); + + Log getLogger(); +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/TokenIssuedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/TokenIssuedEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/TokenIssuedEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/TokenIssuedEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/package-info.java b/server/src/main/java/org/cloudfoundry/identity/uaa/audit/package-info.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/package-info.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/audit/package-info.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java similarity index 80% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java index 607cc9760b8..9b4ad9f3113 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/ClientParametersAuthenticationFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/AbstractClientParametersAuthenticationFilter.java @@ -47,17 +47,16 @@ * It sets the authentication to a client only * Oauth2Authentication object as that is expected by * the LoginAuthenticationManager. - * */ -public class ClientParametersAuthenticationFilter implements Filter { +public abstract class AbstractClientParametersAuthenticationFilter 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()); + protected final Log logger = LogFactory.getLog(getClass()); - private AuthenticationManager clientAuthenticationManager; + protected AuthenticationManager clientAuthenticationManager; - private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); + protected AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint(); public AuthenticationManager getClientAuthenticationManager() { return clientAuthenticationManager; @@ -76,36 +75,25 @@ public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationE @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, - ServletException { - + 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); - } - } catch (AuthenticationException e) { - logger.debug("Client Parameter Authentication failed"); - authenticationEntryPoint.commence(req, res, e); - return; - } + 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") @@ -128,7 +116,7 @@ private Authentication performClientAuthentication(HttpServletRequest req, Map loginInfo, String clientId) throws IOException, ServletException { + if (!StringUtils.hasText(req.getHeader("Authorization")) && isUrlEncodedForm(req)) { + try { + doClientCredentialLogin(req, loginInfo, clientId); + } catch(AuthenticationException e) { + logger.debug("Could not authenticate with client credentials."); + } + } + } + + 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/common/src/main/java/org/cloudfoundry/identity/uaa/error/InvalidCodeException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/InvalidCodeException.java similarity index 88% rename from common/src/main/java/org/cloudfoundry/identity/uaa/error/InvalidCodeException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/InvalidCodeException.java index 5cefce345dd..ff22b4a6d47 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/error/InvalidCodeException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/InvalidCodeException.java @@ -12,7 +12,9 @@ * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.error; +package org.cloudfoundry.identity.uaa.authentication; + +import org.cloudfoundry.identity.uaa.error.UaaException; public class InvalidCodeException extends UaaException { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/LoginClientParametersAuthenticationFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/LoginClientParametersAuthenticationFilter.java new file mode 100644 index 00000000000..85efb0ffab8 --- /dev/null +++ b/server/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/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/LoginServerTokenEndpointFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/LoginServerTokenEndpointFilter.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/LoginServerTokenEndpointFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/LoginServerTokenEndpointFilter.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/Origin.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/Origin.java similarity index 87% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/Origin.java rename to server/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/server/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/login/PasscodeAuthenticationFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasscodeAuthenticationFilter.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeAuthenticationFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasscodeAuthenticationFilter.java index 40f4dc96b12..8827c0584a6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeAuthenticationFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasscodeAuthenticationFilter.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.authentication; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -31,13 +31,10 @@ 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.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.login.PasscodeInformation; 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 +221,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 +279,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/authentication/PasswordExpiredException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasswordExpiredException.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasswordExpiredException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/PasswordExpiredException.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/RemoteAuthenticationEndpoint.java similarity index 78% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/RemoteAuthenticationEndpoint.java index d99d1c32864..7cd2260761f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/RemoteAuthenticationEndpoint.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.authentication.login; +package org.cloudfoundry.identity.uaa.authentication; import java.util.HashMap; import java.util.Map; @@ -22,9 +22,10 @@ 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.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,43 +77,43 @@ 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 = 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<>(); + 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<>(); userInfo.put("username", username); - userInfo.put(Origin.ORIGIN, origin); + userInfo.put(OriginKeys.ORIGIN, origin); if (StringUtils.hasText(email)) { userInfo.put("email", email); } @@ -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(Origin.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/authentication/SessionResetFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilter.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilter.java rename to server/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/server/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/UaaAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDeserializer.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDetails.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDetails.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDetails.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDetails.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDetailsSource.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDetailsSource.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDetailsSource.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationDetailsSource.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationJsonBase.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationJsonBase.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationJsonBase.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationJsonBase.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializer.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaExceptionTranslator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaExceptionTranslator.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaExceptionTranslator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaExceptionTranslator.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaPrincipal.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaPrincipal.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaPrincipal.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaPrincipal.java diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java similarity index 100% rename from login/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandler.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaAuthenticationEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaPrincipalEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaPrincipalEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaPrincipalEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/AbstractUaaPrincipalEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/ClientAuthenticationFailureEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/ClientAuthenticationFailureEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/ClientAuthenticationFailureEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/ClientAuthenticationFailureEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/ClientAuthenticationSuccessEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/ClientAuthenticationSuccessEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/ClientAuthenticationSuccessEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/ClientAuthenticationSuccessEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PrincipalAuthenticationFailureEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PrincipalAuthenticationFailureEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PrincipalAuthenticationFailureEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PrincipalAuthenticationFailureEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PrincipalNotFoundEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PrincipalNotFoundEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PrincipalNotFoundEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/PrincipalNotFoundEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UnverifiedUserAuthenticationEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UnverifiedUserAuthenticationEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UnverifiedUserAuthenticationEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UnverifiedUserAuthenticationEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationFailureEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationFailureEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationFailureEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationFailureEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationSuccessEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationSuccessEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationSuccessEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserAuthenticationSuccessEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserNotFoundEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserNotFoundEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserNotFoundEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/event/UserNotFoundEvent.java diff --git a/common/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 common/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/common/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..3a76a87608e --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/listener/UserAuthenticationSuccessListener.java @@ -0,0 +1,36 @@ +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()) { + scimUserProvisioning.verifyUser(user.getId(), -1); + } + } +} diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AccountLoginPolicy.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AccountLoginPolicy.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AccountLoginPolicy.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AccountLoginPolicy.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java similarity index 64% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java index 25e8e5dc509..8479b8a6741 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManager.java +++ b/server/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,13 +24,14 @@ 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; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.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; @@ -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.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) { - 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; @@ -159,7 +146,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) { @@ -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; } diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java similarity index 91% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java index 8840660e254..99ec3b5ed56 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinAuthenticationManager.java @@ -11,32 +11,29 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.authentication.manager; 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.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.error.InvalidCodeException; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.authentication.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/common/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequestConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinRequestConverter.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequestConverter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinRequestConverter.java index a2405ff596e..88112484269 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/AutologinRequestConverter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/AutologinRequestConverter.java @@ -10,11 +10,12 @@ * 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; +package org.cloudfoundry.identity.uaa.authentication.manager; import java.io.IOException; import java.util.Arrays; +import org.cloudfoundry.identity.uaa.login.AutologinRequest; import org.cloudfoundry.identity.uaa.util.LinkedMaskingMultiValueMap; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManager.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManager.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManager.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManager.java index c6090669ad9..3bc896965e2 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManager.java @@ -15,8 +15,8 @@ package org.cloudfoundry.identity.uaa.authentication.manager; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.security.authentication.AuthenticationManager; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CompositeAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CompositeAuthenticationManager.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CompositeAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/CompositeAuthenticationManager.java diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManager.java similarity index 94% rename from login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManager.java index ee0bf9f1a75..5d36cfb486d 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManager.java +++ b/server/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.impl.config.EnvironmentPropertiesFactoryBean; +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/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java similarity index 94% rename from login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java index 6aa3b61481a..ddf99a8db8d 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManager.java @@ -14,14 +14,14 @@ 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.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.dao.EmptyResultDataAccessException; @@ -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/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalGroupAuthorizationEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalGroupAuthorizationEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalGroupAuthorizationEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalGroupAuthorizationEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java index e5721ab18d2..b5f160a0018 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java @@ -93,18 +93,17 @@ public Authentication authenticate(Authentication request) throws Authentication return null; } - boolean addnew = false; + UaaUser scimUser; + try { - UaaUser temp = userDatabase.retrieveUserByName(user.getUsername(), getOrigin()); - if (temp != null) { - user = temp; - } else { - addnew = true; - } + 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 { @@ -201,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/main/java/org/cloudfoundry/identity/uaa/authentication/manager/InvitedUserAuthenticatedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/InvitedUserAuthenticatedEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/InvitedUserAuthenticatedEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/InvitedUserAuthenticatedEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/KeystoneAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/KeystoneAuthenticationManager.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/KeystoneAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/KeystoneAuthenticationManager.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java similarity index 93% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java index c5374c4bb99..2a26eed76a6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java @@ -16,13 +16,13 @@ package org.cloudfoundry.identity.uaa.authentication.manager; 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.ldap.extension.LdapAuthority; +import org.cloudfoundry.identity.uaa.provider.ldap.ExtendedLdapUserDetails; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -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/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManager.java rename to server/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/server/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/NewUserAuthenticatedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/NewUserAuthenticatedEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/NewUserAuthenticatedEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/NewUserAuthenticatedEvent.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java similarity index 91% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java index e5044d51cbe..82b486d5250 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicy.java +++ b/server/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.authentication.Origin; -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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.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; @@ -107,8 +107,8 @@ public void setLockoutPolicy(LockoutPolicy lockoutPolicy) { } private LockoutPolicy getLockoutPolicyFromDb() { - IdentityProvider idp = providerProvisioning.retrieveByOrigin(Origin.UAA, IdentityZoneHolder.get().getId()); - UaaIdentityProviderDefinition idpDefinition = ObjectUtils.castInstance(idp.getConfig(),UaaIdentityProviderDefinition.class); + 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/authentication/manager/PermitAllAccountLoginPolicy.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PermitAllAccountLoginPolicy.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PermitAllAccountLoginPolicy.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PermitAllAccountLoginPolicy.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/RestAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/RestAuthenticationManager.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/RestAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/RestAuthenticationManager.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationFilter.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationFilter.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationManager.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationManager.java diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/UsernamePasswordExtractingAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/UsernamePasswordExtractingAuthenticationManager.java similarity index 97% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/UsernamePasswordExtractingAuthenticationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/UsernamePasswordExtractingAuthenticationManager.java index 171a0cf8cb5..b34f73836a3 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/UsernamePasswordExtractingAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/UsernamePasswordExtractingAuthenticationManager.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.authentication.manager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/DoNothingExternalAuthorizationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authorization/DoNothingExternalAuthorizationManager.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authorization/DoNothingExternalAuthorizationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authorization/DoNothingExternalAuthorizationManager.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/ExternalGroupMappingAuthorizationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authorization/ExternalGroupMappingAuthorizationManager.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authorization/ExternalGroupMappingAuthorizationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authorization/ExternalGroupMappingAuthorizationManager.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authorization/LdapGroupMappingAuthorizationManager.java similarity index 85% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/authorization/LdapGroupMappingAuthorizationManager.java index 0c62ccbee26..df37bc05fa2 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authorization/LdapGroupMappingAuthorizationManager.java @@ -10,18 +10,15 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.authorization.external; +package org.cloudfoundry.identity.uaa.authorization; 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.ldap.extension.LdapAuthority; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.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 +40,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/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrap.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrap.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrap.java index 0c5308cb7b0..db322c1e861 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrap.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.client; import java.util.Arrays; import java.util.Collection; @@ -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/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpoints.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpoints.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpoints.java index 17d3c31991c..da6a2563ce7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpoints.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.client; import java.util.ArrayList; import java.util.Arrays; @@ -28,16 +28,17 @@ 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.client.ClientDetailsValidator.Mode; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; -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.rest.SearchResultsFactory; -import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; +import org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest; +import org.cloudfoundry.identity.uaa.resources.ActionResult; +import org.cloudfoundry.identity.uaa.resources.AttributeNameMapper; +import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; +import org.cloudfoundry.identity.uaa.resources.ResourceMonitor; +import org.cloudfoundry.identity.uaa.resources.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResultsFactory; +import org.cloudfoundry.identity.uaa.resources.SimpleAttributeNameMapper; import org.cloudfoundry.identity.uaa.security.DefaultSecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.util.UaaPagingUtils; @@ -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/ClientAdminEndpointsValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsValidator.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsValidator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsValidator.java index ae098a3aff6..09b0ff24de0 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsValidator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsValidator.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.client; import java.util.Arrays; import java.util.Collection; @@ -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.rest.QueryableResourceManager; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.resources.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/client/ClientAuthenticationFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAuthenticationFilter.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAuthenticationFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAuthenticationFilter.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientAuthenticationPublisher.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAuthenticationPublisher.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientAuthenticationPublisher.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAuthenticationPublisher.java index 963f1526406..3bb7cce1e18 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/client/ClientAuthenticationPublisher.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientAuthenticationPublisher.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.client; +package org.cloudfoundry.identity.uaa.client; import org.cloudfoundry.identity.uaa.authentication.event.ClientAuthenticationFailureEvent; import org.cloudfoundry.identity.uaa.authentication.event.ClientAuthenticationSuccessEvent; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientDetailsValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientDetailsValidator.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientDetailsValidator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientDetailsValidator.java index b4ef7f625dd..05881a7b6e0 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientDetailsValidator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientDetailsValidator.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth; +package org.cloudfoundry.identity.uaa.client; import org.springframework.security.oauth2.provider.ClientDetails; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientInfoEndpoint.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientInfoEndpoint.java index 0ce375ecb49..4c2a6837e2f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/ClientInfoEndpoint.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.client; import java.security.Principal; import java.util.Collections; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/InvalidClientDetailsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/InvalidClientDetailsException.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/InvalidClientDetailsException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/InvalidClientDetailsException.java index 5f6fdc869a0..a5a7b7d54b7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/InvalidClientDetailsException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/InvalidClientDetailsException.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.client; import org.cloudfoundry.identity.uaa.error.UaaException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcQueryableClientDetailsService.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcQueryableClientDetailsService.java index 3c5d95b20e8..e0d6ff4c7e5 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/JdbcQueryableClientDetailsService.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.client; import java.sql.ResultSet; import java.sql.SQLException; @@ -19,9 +19,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; -import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryable; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; +import org.cloudfoundry.identity.uaa.resources.jdbc.AbstractQueryable; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/client/OAuth2AccessTokenSource.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/OAuth2AccessTokenSource.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/client/OAuth2AccessTokenSource.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/OAuth2AccessTokenSource.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/client/PreAuthenticatedPrincipalSource.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/PreAuthenticatedPrincipalSource.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/client/PreAuthenticatedPrincipalSource.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/PreAuthenticatedPrincipalSource.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/RestrictUaaScopesClientValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/RestrictUaaScopesClientValidator.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/RestrictUaaScopesClientValidator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/RestrictUaaScopesClientValidator.java index 93cd0c5880b..099081e2ad9 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/RestrictUaaScopesClientValidator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/RestrictUaaScopesClientValidator.java @@ -12,7 +12,7 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.oauth; +package org.cloudfoundry.identity.uaa.client; import org.springframework.security.core.GrantedAuthority; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetails.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetails.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetails.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetails.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetailsSource.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetailsSource.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetailsSource.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetailsSource.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaScopes.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/UaaScopes.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaScopes.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/UaaScopes.java index 5a59c65ff15..a3caebc26ca 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaScopes.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/UaaScopes.java @@ -12,7 +12,7 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.oauth; +package org.cloudfoundry.identity.uaa.client; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; @@ -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/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/AbstractClientAdminEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/AbstractClientAdminEvent.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/AbstractClientAdminEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/event/AbstractClientAdminEvent.java index 54d8ba2f29d..43cb8e0e874 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/AbstractClientAdminEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/AbstractClientAdminEvent.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.event; +package org.cloudfoundry.identity.uaa.client.event; import java.security.Principal; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientAdminEventPublisher.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientAdminEventPublisher.java similarity index 99% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientAdminEventPublisher.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientAdminEventPublisher.java index 04f9b32efe5..4e60cf3dc36 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientAdminEventPublisher.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientAdminEventPublisher.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth.event; +package org.cloudfoundry.identity.uaa.client.event; import org.aspectj.lang.ProceedingJoinPoint; import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientApprovalsDeletedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientApprovalsDeletedEvent.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientApprovalsDeletedEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientApprovalsDeletedEvent.java index af552f2ac7a..0bc1567e498 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientApprovalsDeletedEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientApprovalsDeletedEvent.java @@ -11,15 +11,13 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth.event; +package org.cloudfoundry.identity.uaa.client.event; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.ClientDetails; -import java.security.Principal; - /** * @author Dave Syer * diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientCreateEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientCreateEvent.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientCreateEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientCreateEvent.java index 08e7bfc21ea..15cb7498867 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientCreateEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientCreateEvent.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth.event; +package org.cloudfoundry.identity.uaa.client.event; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientDeleteEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientDeleteEvent.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientDeleteEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientDeleteEvent.java index 6bb591d6812..4a8a5532711 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientDeleteEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientDeleteEvent.java @@ -11,9 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth.event; - -import java.security.Principal; +package org.cloudfoundry.identity.uaa.client.event; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientUpdateEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientUpdateEvent.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientUpdateEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientUpdateEvent.java index 0619027e153..b7658a0aa90 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/ClientUpdateEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/ClientUpdateEvent.java @@ -11,9 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth.event; - -import java.security.Principal; +package org.cloudfoundry.identity.uaa.client.event; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/SecretChangeEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/SecretChangeEvent.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/SecretChangeEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/event/SecretChangeEvent.java index c4f17126a19..19a54bb77c9 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/SecretChangeEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/SecretChangeEvent.java @@ -11,9 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth.event; - -import java.security.Principal; +package org.cloudfoundry.identity.uaa.client.event; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/SecretFailureEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/SecretFailureEvent.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/SecretFailureEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/client/event/SecretFailureEvent.java index 118081d7337..a91bcdd395b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/event/SecretFailureEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/client/event/SecretFailureEvent.java @@ -11,9 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth.event; - -import java.security.Principal; +package org.cloudfoundry.identity.uaa.client.event; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpoints.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpoints.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpoints.java index 12f45418c9c..0d9921b6ff5 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpoints.java @@ -12,10 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.codestore; -import javax.servlet.http.HttpServletRequest; - -import org.cloudfoundry.identity.uaa.error.ConvertingExceptionView; -import org.cloudfoundry.identity.uaa.error.ExceptionReport; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -31,6 +29,8 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.View; +import javax.servlet.http.HttpServletRequest; + @Controller public class CodeStoreEndpoints { @@ -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/CodeStoreException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreException.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreException.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStore.java b/server/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStore.java similarity index 84% rename from common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStore.java rename to server/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/server/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/ExpiringCodeType.java b/server/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeType.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeType.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeType.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/InMemoryExpiringCodeStore.java b/server/src/main/java/org/cloudfoundry/identity/uaa/codestore/InMemoryExpiringCodeStore.java similarity index 89% rename from common/src/main/java/org/cloudfoundry/identity/uaa/codestore/InMemoryExpiringCodeStore.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/codestore/InMemoryExpiringCodeStore.java index db443a74326..5d440185ab1 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/codestore/InMemoryExpiringCodeStore.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/codestore/InMemoryExpiringCodeStore.java @@ -12,21 +12,14 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.codestore; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.util.Assert; + import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.cloudfoundry.identity.uaa.util.JsonUtils; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; - public class InMemoryExpiringCodeStore implements ExpiringCodeStore { private RandomValueStringGenerator generator = new RandomValueStringGenerator(6); @@ -34,7 +27,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 +38,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 +67,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/server/src/main/java/org/cloudfoundry/identity/uaa/codestore/JdbcExpiringCodeStore.java similarity index 89% rename from common/src/main/java/org/cloudfoundry/identity/uaa/codestore/JdbcExpiringCodeStore.java rename to server/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/server/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/java/org/cloudfoundry/identity/uaa/db/BootstrapIdentityZones.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/BootstrapIdentityZones.java similarity index 90% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/BootstrapIdentityZones.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/BootstrapIdentityZones.java index 841320c589e..3e0661c4ac5 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/db/BootstrapIdentityZones.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/db/BootstrapIdentityZones.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.db; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.flywaydb.core.api.migration.spring.SpringJdbcMigration; import org.springframework.jdbc.core.JdbcTemplate; @@ -23,7 +23,7 @@ public void migrate(JdbcTemplate jdbcTemplate) throws Exception { jdbcTemplate.update("insert into identity_zone VALUES (?,?,?,?,?,?,?)", uaa.getId(),t,t,uaa.getVersion(),uaa.getSubdomain(),uaa.getName(),uaa.getDescription()); 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/db/Create_Groups_For_Zones_2_5_2.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/Create_Groups_For_Zones_2_5_2.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/Create_Groups_For_Zones_2_5_2.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/Create_Groups_For_Zones_2_5_2.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/DataSourceAccessor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/DataSourceAccessor.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/DataSourceAccessor.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/DataSourceAccessor.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/DatabaseInformation1_5_3.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/DatabaseInformation1_5_3.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/DatabaseInformation1_5_3.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/DatabaseInformation1_5_3.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/InitialPreDatabaseVersioningSchemaCreator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/InitialPreDatabaseVersioningSchemaCreator.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/InitialPreDatabaseVersioningSchemaCreator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/InitialPreDatabaseVersioningSchemaCreator.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/MigrateGroupZones_2_4_2.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/MigrateGroupZones_2_4_2.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/MigrateGroupZones_2_4_2.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/MigrateGroupZones_2_4_2.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/StoreSubDomainAsLowerCase_V2_7_3.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/StoreSubDomainAsLowerCase_V2_7_3.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/StoreSubDomainAsLowerCase_V2_7_3.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/StoreSubDomainAsLowerCase_V2_7_3.java index 38882232cb1..25d5b44116e 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/db/StoreSubDomainAsLowerCase_V2_7_3.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/db/StoreSubDomainAsLowerCase_V2_7_3.java @@ -17,8 +17,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; -import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.ZoneAlreadyExistsException; import org.cloudfoundry.identity.uaa.zone.ZoneDoesNotExistsException; import org.flywaydb.core.api.migration.spring.SpringJdbcMigration; @@ -31,11 +29,9 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Arrays; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_3__InitialDBScript.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_3__InitialDBScript.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_3__InitialDBScript.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_3__InitialDBScript.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_2__BootstrapIdentityZones.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_2__BootstrapIdentityZones.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_2__BootstrapIdentityZones.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_2__BootstrapIdentityZones.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_3__Migrate_Groups_For_Zones.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_3__Migrate_Groups_For_Zones.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_3__Migrate_Groups_For_Zones.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_3__Migrate_Groups_For_Zones.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V1_5_3__InitialDBScript.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V1_5_3__InitialDBScript.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V1_5_3__InitialDBScript.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V1_5_3__InitialDBScript.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V1_5_4__NormalizeTableAndColumnNames.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V1_5_4__NormalizeTableAndColumnNames.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V1_5_4__NormalizeTableAndColumnNames.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V1_5_4__NormalizeTableAndColumnNames.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_0_2__BootstrapIdentityZones.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_0_2__BootstrapIdentityZones.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_0_2__BootstrapIdentityZones.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_0_2__BootstrapIdentityZones.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_5_3__Migrate_Groups_For_Zones.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_5_3__Migrate_Groups_For_Zones.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_5_3__Migrate_Groups_For_Zones.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_5_3__Migrate_Groups_For_Zones.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/mysql/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_3__InitialDBScript.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_3__InitialDBScript.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_3__InitialDBScript.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_3__InitialDBScript.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_4__NormalizeTableAndColumnNames.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_4__NormalizeTableAndColumnNames.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_4__NormalizeTableAndColumnNames.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_4__NormalizeTableAndColumnNames.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_2__BootstrapIdentityZones.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_2__BootstrapIdentityZones.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_2__BootstrapIdentityZones.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_2__BootstrapIdentityZones.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_3__Migrate_Groups_For_Zones.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_3__Migrate_Groups_For_Zones.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_3__Migrate_Groups_For_Zones.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_3__Migrate_Groups_For_Zones.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java b/server/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_3__Migrate_Zone_Subdomain_To_Lowercase.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/error/UaaException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/error/UaaException.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/error/UaaException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/error/UaaException.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/error/UaaExceptionDeserializer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/error/UaaExceptionDeserializer.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/error/UaaExceptionDeserializer.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/error/UaaExceptionDeserializer.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/error/UaaExceptionSerializer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/error/UaaExceptionSerializer.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/error/UaaExceptionSerializer.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/error/UaaExceptionSerializer.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/web/HealthzEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/web/HealthzEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java index 172a6a3366a..f2b23c93c88 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/web/HealthzEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/health/HealthzEndpoint.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.web; +package org.cloudfoundry.identity.uaa.health; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/BuildInfo.java b/server/src/main/java/org/cloudfoundry/identity/uaa/home/BuildInfo.java similarity index 98% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/BuildInfo.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/home/BuildInfo.java index aa652e8249c..a4deead02a5 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/BuildInfo.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/home/BuildInfo.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.login; +package org.cloudfoundry.identity.uaa.home; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/HomeController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java similarity index 69% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/HomeController.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java index f89d4d0cb04..a1011ac32c2 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/HomeController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/home/HomeController.java @@ -10,15 +10,17 @@ * 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; +package org.cloudfoundry.identity.uaa.home; import java.security.Principal; +import java.util.HashMap; +import java.util.Map; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; @@ -29,9 +31,12 @@ import org.springframework.web.bind.annotation.RequestMapping; @Controller -public class HomeController extends AbstractControllerInfo { +public class HomeController { private final Log logger = LogFactory.getLog(getClass()); protected final Environment environment; + private Map links = new HashMap(); + private String baseUrl; + @Autowired private TileInfo tileInfo; @@ -39,6 +44,43 @@ public HomeController(Environment environment) { this.environment = environment; } + /** + * @param links the links to set + */ + public void setLinks(Map links) { + this.links = links; + } + + public Map getLinks() { + return links; + } + + /** + * @param baseUrl the base uaa url + */ + public void setUaaBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + protected String getUaaBaseUrl() { + return baseUrl; + } + + protected Map getLinksInfo() { + Map model = new HashMap(); + model.put(OriginKeys.UAA, getUaaBaseUrl()); + model.put("login", getUaaBaseUrl().replaceAll(OriginKeys.UAA, "login")); + model.putAll(getLinks()); + return model; + } + + protected void populateBuildAndLinkInfo(Model model) { + Map attributes = new HashMap(); + attributes.put("links", getLinksInfo()); + model.addAllAttributes(attributes); + model.addAttribute("links", getLinks()); + } + @RequestMapping(value = { "/", "/home" }) public String home(Model model, Principal principal) { model.addAttribute("principal", principal); diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/TileInfo.java b/server/src/main/java/org/cloudfoundry/identity/uaa/home/TileInfo.java similarity index 97% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/TileInfo.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/home/TileInfo.java index c8c8317d95e..b63e45df128 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/TileInfo.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/home/TileInfo.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.login; +package org.cloudfoundry.identity.uaa.home; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/LoginServerConfig.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/LoginServerConfig.java similarity index 79% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/LoginServerConfig.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/impl/LoginServerConfig.java index e13f82b7c6e..26751d75965 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/LoginServerConfig.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/LoginServerConfig.java @@ -1,5 +1,10 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.impl; +import org.cloudfoundry.identity.uaa.message.EmailService; +import org.cloudfoundry.identity.uaa.message.MessageService; +import org.cloudfoundry.identity.uaa.message.NotificationsService; +import org.cloudfoundry.identity.uaa.account.AccountCreationService; +import org.cloudfoundry.identity.uaa.account.AccountsController; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/CustomPropertyConstructor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/CustomPropertyConstructor.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/CustomPropertyConstructor.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/CustomPropertyConstructor.java index e9eb9b7fea9..7ff9a35c9ad 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/CustomPropertyConstructor.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/CustomPropertyConstructor.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.config; +package org.cloudfoundry.identity.uaa.impl.config; import java.beans.IntrospectionException; import java.util.HashMap; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/EnvironmentMapFactoryBean.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/EnvironmentMapFactoryBean.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/EnvironmentMapFactoryBean.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/EnvironmentMapFactoryBean.java index 1d17868027f..915c31cceaf 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/EnvironmentMapFactoryBean.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/EnvironmentMapFactoryBean.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.config; +package org.cloudfoundry.identity.uaa.impl.config; import java.util.Arrays; import java.util.Collection; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/EnvironmentPropertiesFactoryBean.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/EnvironmentPropertiesFactoryBean.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/EnvironmentPropertiesFactoryBean.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/EnvironmentPropertiesFactoryBean.java index b8ad1fbe15d..8df5fdd55f6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/EnvironmentPropertiesFactoryBean.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/EnvironmentPropertiesFactoryBean.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.config; +package org.cloudfoundry.identity.uaa.impl.config; import java.util.HashMap; import java.util.Map; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java similarity index 82% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java index 8d6b390a27b..11e970f9594 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java @@ -10,21 +10,24 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.impl.config; -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.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +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.provider.saml.SamlIdentityProviderConfigurator; +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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.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; @@ -38,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; @@ -71,7 +74,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 +92,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); @@ -113,7 +116,7 @@ protected LdapIdentityProviderDefinition getLdapConfigAsDefinition(Map ldapConfig) { @@ -144,12 +147,12 @@ protected AbstractIdentityProviderDefinition getKeystoneDefinition(Map cors; public static class Zones { @Valid @@ -233,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/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlConfigurationValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlConfigurationValidator.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlConfigurationValidator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlConfigurationValidator.java index be00412a071..424187b89e8 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlConfigurationValidator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlConfigurationValidator.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.impl.config; import java.util.Set; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlMapFactoryBean.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlMapFactoryBean.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlMapFactoryBean.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlMapFactoryBean.java index 5840c59af71..2c5df7bfdc1 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlMapFactoryBean.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlMapFactoryBean.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.config; +package org.cloudfoundry.identity.uaa.impl.config; import java.util.LinkedHashMap; import java.util.Map; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlProcessor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlProcessor.java similarity index 99% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlProcessor.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlProcessor.java index 90e0da07c1f..1d1d1619dfc 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlProcessor.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlProcessor.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.config; +package org.cloudfoundry.identity.uaa.impl.config; import java.io.FileNotFoundException; import java.io.IOException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlPropertiesFactoryBean.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlPropertiesFactoryBean.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlPropertiesFactoryBean.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlPropertiesFactoryBean.java index 8966a4edf6b..132da1dc0c6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlPropertiesFactoryBean.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlPropertiesFactoryBean.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.config; +package org.cloudfoundry.identity.uaa.impl.config; import java.util.Map; import java.util.Properties; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializer.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlServletProfileInitializer.java similarity index 85% rename from common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializer.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlServletProfileInitializer.java index 636bcf121f9..76f681f6599 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializer.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/YamlServletProfileInitializer.java @@ -10,16 +10,17 @@ * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.config; +package org.cloudfoundry.identity.uaa.impl.config; import org.apache.log4j.MDC; -import org.cloudfoundry.identity.uaa.config.YamlProcessor.ResolutionMethod; +import org.cloudfoundry.identity.uaa.impl.config.YamlProcessor.ResolutionMethod; import org.springframework.context.ApplicationContextInitializer; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertySource; 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 +36,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 +51,7 @@ * location (if it exists) * * - * @author Dave Syer + * * */ public class YamlServletProfileInitializer implements ApplicationContextInitializer { @@ -64,6 +67,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 +96,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 +128,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 +202,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/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java similarity index 94% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java index 429c51836c7..c8f0a05b7ef 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/EmailInvitationsService.java @@ -1,13 +1,14 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.invitations; 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.invitations.InvitationsService; -import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.message.MessageService; +import org.cloudfoundry.identity.uaa.message.MessageType; +import org.cloudfoundry.identity.uaa.account.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -100,7 +101,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/invitations/InvitationConstants.java b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationConstants.java similarity index 100% rename from login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationConstants.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationConstants.java diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsAuthenticationTrustResolver.java b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsAuthenticationTrustResolver.java similarity index 100% rename from login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsAuthenticationTrustResolver.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsAuthenticationTrustResolver.java diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java similarity index 94% rename from login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java index e4521296fec..fb5046aea53 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsController.java @@ -3,18 +3,18 @@ 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; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.login.saml.SamlRedirectUtils; +import org.cloudfoundry.identity.uaa.provider.ldap.ExtendedLdapUserDetails; +import org.cloudfoundry.identity.uaa.account.PasswordConfirmationValidation; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.saml.SamlRedirectUtils; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; @@ -24,8 +24,8 @@ 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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.http.HttpStatus; @@ -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; @@ -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()) { @@ -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); @@ -250,13 +250,13 @@ 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; 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,8 +273,8 @@ 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("code", expiringCodeStore.generateCode(expiringCode.getData(), new Timestamp(System.currentTimeMillis() + (10 * 60 * 1000))).getCode()); + model.addAttribute(OriginKeys.LDAP, OriginKeys.LDAP); + 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/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java similarity index 96% rename from login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java index 69c57463001..fd70db2cdaf 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsEndpoint.java @@ -9,8 +9,8 @@ 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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -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; @@ -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/invitations/InvitationsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsService.java similarity index 100% rename from login/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/invitations/InvitationsService.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index ff7a26591f5..5683fd5e962 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -10,23 +10,20 @@ * 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.login; +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.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; -import org.cloudfoundry.identity.uaa.login.AutologinRequest; -import org.cloudfoundry.identity.uaa.login.AutologinResponse; -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.login.saml.SamlRedirectUtils; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; +import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.saml.SamlRedirectUtils; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; @@ -81,7 +78,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 +223,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 +234,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,10 +375,10 @@ 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)); + ExpiringCode expiringCode = expiringCodeStore.generateCode(JsonUtils.writeValueAsString(codeData), new Timestamp(System.currentTimeMillis() + 5 * 60 * 1000), null); return new AutologinResponse(expiringCode.getCode()); } @@ -428,25 +425,27 @@ 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() { 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/login/PasscodeInformation.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeInformation.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeInformation.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeInformation.java index deb523396a1..da8cd0005e1 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeInformation.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/PasscodeInformation.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.saml.SamlUserAuthority; import java.util.ArrayList; import java.util.HashSet; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/Prompt.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/Prompt.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/Prompt.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/login/Prompt.java index 150bcc60a2a..acdab6ae964 100755 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/Prompt.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/Prompt.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.authentication.login; +package org.cloudfoundry.identity.uaa.login; import org.springframework.util.StringUtils; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/PromptEditor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/PromptEditor.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/PromptEditor.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/login/PromptEditor.java index cfac3abae15..1631230e78c 100755 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/login/PromptEditor.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/PromptEditor.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.authentication.login; +package org.cloudfoundry.identity.uaa.login; import java.beans.PropertyEditorSupport; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/message/EmailService.java similarity index 98% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/message/EmailService.java index e54b4ca70f3..5955b43bd08 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/EmailService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/message/EmailService.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.message; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/util/LocalUaaRestTemplate.java b/server/src/main/java/org/cloudfoundry/identity/uaa/message/LocalUaaRestTemplate.java similarity index 95% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/util/LocalUaaRestTemplate.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/message/LocalUaaRestTemplate.java index 9444c519eba..976fa5d724c 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/util/LocalUaaRestTemplate.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/message/LocalUaaRestTemplate.java @@ -10,13 +10,13 @@ * 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.util; +package org.cloudfoundry.identity.uaa.message; import org.apache.http.conn.ssl.SSLContextBuilder; 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; @@ -59,14 +59,14 @@ public LocalUaaRestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2Clien } @Override - protected OAuth2AccessToken acquireAccessToken(OAuth2ClientContext oauth2Context) throws UserRedirectRequiredException { + public OAuth2AccessToken acquireAccessToken(OAuth2ClientContext oauth2Context) throws UserRedirectRequiredException { ClientDetails client = clientDetailsService.loadClientByClientId(getClientId()); Set scopes = new HashSet<>(); for (GrantedAuthority authority : client.getAuthorities()) { 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/common/src/main/java/org/cloudfoundry/identity/uaa/login/MessageService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/message/MessageService.java similarity index 75% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/MessageService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/message/MessageService.java index e147cb7a76a..3d439e46f36 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/MessageService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/message/MessageService.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.message; public interface MessageService { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/MessageType.java b/server/src/main/java/org/cloudfoundry/identity/uaa/message/MessageType.java similarity index 70% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/MessageType.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/message/MessageType.java index 1fd79ee46d3..c6f99188b9b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/MessageType.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/message/MessageType.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.message; public enum MessageType { CHANGE_EMAIL, diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/NotificationsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/message/NotificationsService.java similarity index 98% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/NotificationsService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/message/NotificationsService.java index 86e90856775..c214f0df8c9 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/NotificationsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/message/NotificationsService.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.message; 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/login/util/FakeJavaMailSender.java b/server/src/main/java/org/cloudfoundry/identity/uaa/message/util/FakeJavaMailSender.java similarity index 98% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/util/FakeJavaMailSender.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/message/util/FakeJavaMailSender.java index 79373eb4ed6..b1bafbcdda0 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/util/FakeJavaMailSender.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/message/util/FakeJavaMailSender.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.login.util; +package org.cloudfoundry.identity.uaa.message.util; import org.springframework.mail.MailException; import org.springframework.mail.SimpleMailMessage; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java index 2cb1de1d3fb..48ce977cc69 100755 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/AccessController.java @@ -28,9 +28,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.client.ClientConstants; -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.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.util.OAuth2Utils; @@ -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/CheckTokenEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java similarity index 91% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java index 128fd767241..5c7e59dd6f3 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpoint.java @@ -12,11 +12,9 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth; -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 +59,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 +77,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 +90,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/DisableIdTokenResponseTypeFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/DisableIdTokenResponseTypeFilter.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/DisableIdTokenResponseTypeFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/DisableIdTokenResponseTypeFilter.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/HybridTokenGranterForAuthorizationCode.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/HybridTokenGranterForAuthorizationCode.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authorization/HybridTokenGranterForAuthorizationCode.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/HybridTokenGranterForAuthorizationCode.java index a187fe23ef4..891cbdb4dd6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/HybridTokenGranterForAuthorizationCode.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/HybridTokenGranterForAuthorizationCode.java @@ -12,7 +12,7 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.authorization; +package org.cloudfoundry.identity.uaa.oauth; import org.springframework.security.authentication.InsufficientAuthenticationException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServices.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServices.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServices.java rename to server/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/server/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/RemoteUserAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/RemoteUserAuthentication.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/RemoteUserAuthentication.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/RemoteUserAuthentication.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/SignerProvider.java similarity index 99% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProvider.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/SignerProvider.java index d6960563d71..0fa279a465f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/SignerProvider.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.token; +package org.cloudfoundry.identity.uaa.oauth; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenKeyEndpoint.java similarity index 82% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenKeyEndpoint.java index 7b2a3fccdff..16d1148d8c3 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenKeyEndpoint.java @@ -10,10 +10,12 @@ * 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; +package org.cloudfoundry.identity.uaa.oauth; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.oauth.token.VerificationKeyResponse; +import org.cloudfoundry.identity.uaa.oauth.token.VerificationKeysListResponse; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AnonymousAuthenticationToken; @@ -30,9 +32,6 @@ import java.security.Principal; import java.security.interfaces.RSAPublicKey; import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; /** * OAuth2 token services that produces JWT encoded token values. @@ -59,31 +58,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 +100,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/login/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java similarity index 98% rename from login/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevocationEndpoint.java rename to server/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/server/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/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenRevokedException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevokedException.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenRevokedException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevokedException.java index dd293c2c070..4d760416390 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenRevokedException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/TokenRevokedException.java @@ -12,7 +12,7 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.oauth.token; +package org.cloudfoundry.identity.uaa.oauth; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpoint.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpoint.java index 1686352746d..6d3ab1d1891 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authorization/UaaAuthorizationEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationEndpoint.java @@ -11,9 +11,9 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.authorization; +package org.cloudfoundry.identity.uaa.oauth; -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; @@ -156,7 +156,13 @@ public ModelAndView authorize(Map model, @RequestParam Map 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 +402,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 +411,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 +901,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/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenStore.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenStore.java similarity index 99% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenStore.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenStore.java index f12ab4c8004..2b63c34fb61 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenStore.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenStore.java @@ -12,7 +12,7 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.oauth.token; +package org.cloudfoundry.identity.uaa.oauth; import com.fasterxml.jackson.core.type.TypeReference; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java similarity index 84% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java index e81cd6959d1..accb39a7a00 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/UserManagedAuthzApprovalHandler.java @@ -12,8 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth; -import static org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus.APPROVED; -import static org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus.DENIED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED; import java.util.Calendar; import java.util.Collection; @@ -23,16 +23,14 @@ 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; import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.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; -import org.cloudfoundry.identity.uaa.util.UaaStringUtils; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; +import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; import org.cloudfoundry.identity.uaa.util.UaaTokenUtils; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.util.OAuth2Utils; @@ -128,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); } } @@ -143,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/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java similarity index 85% rename from login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java index 9b29615bbdb..2031879846c 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderEndpoints.java @@ -10,21 +10,23 @@ * 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 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.ldap.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.ObjectUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; 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; @@ -32,6 +34,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; @@ -48,13 +51,14 @@ import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; 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); @@ -63,6 +67,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, @@ -80,7 +90,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()); @@ -91,6 +101,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<>(UNPROCESSABLE_ENTITY); + } + } + + @RequestMapping(value = "{id}", method = PUT) public ResponseEntity updateIdentityProvider(@PathVariable String id, @RequestBody IdentityProvider body) throws MetadataProviderException { IdentityProvider existing = identityProviderProvisioning.retrieve(id); @@ -100,7 +123,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/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderProvisioning.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderProvisioning.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderProvisioning.java index d7bc5f78714..69bf7f31dee 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderProvisioning.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 java.util.List; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderValidationRequest.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderValidationRequest.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderValidationRequest.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderValidationRequest.java index d8957b9173d..6855fdf75e7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityProviderValidationRequest.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProviderValidationRequest.java @@ -10,11 +10,12 @@ * 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.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/IdpAlreadyExistsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdpAlreadyExistsException.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdpAlreadyExistsException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdpAlreadyExistsException.java index 805e0b9f670..10eaabe6856 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdpAlreadyExistsException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/IdpAlreadyExistsException.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 org.cloudfoundry.identity.uaa.error.UaaException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java similarity index 86% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java index 150523925f3..60d4144ba23 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java @@ -10,13 +10,12 @@ * 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 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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.util.ObjectUtils; import org.springframework.dao.DataIntegrityViolationException; @@ -35,7 +34,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"; @@ -49,6 +50,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=? "; @@ -142,14 +147,29 @@ 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); } } + @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 { @@ -166,16 +186,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/main/java/org/cloudfoundry/identity/uaa/ldap/CommaSeparatedScopesMapper.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/CommaSeparatedScopesMapper.java similarity index 90% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/CommaSeparatedScopesMapper.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/CommaSeparatedScopesMapper.java index 47bfd710c27..2888f99ce7e 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/CommaSeparatedScopesMapper.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/CommaSeparatedScopesMapper.java @@ -12,10 +12,9 @@ * subcomponent's license, as noted in the LICENSE file. * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.ldap; +package org.cloudfoundry.identity.uaa.provider.ldap; -import org.cloudfoundry.identity.uaa.ldap.extension.LdapAuthority; -import org.cloudfoundry.identity.uaa.user.UaaAuthority; +import org.cloudfoundry.identity.uaa.provider.ldap.extension.LdapAuthority; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.util.StringUtils; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/DynamicPasswordComparator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/DynamicPasswordComparator.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/DynamicPasswordComparator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/DynamicPasswordComparator.java index 86ba75272d5..3a66e48cf58 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/DynamicPasswordComparator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/DynamicPasswordComparator.java @@ -12,7 +12,7 @@ * subcomponent's license, as noted in the LICENSE file. * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.ldap; +package org.cloudfoundry.identity.uaa.provider.ldap; import org.apache.directory.api.ldap.model.constants.LdapSecurityConstants; import org.apache.directory.api.ldap.model.password.PasswordUtil; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ExtendedLdapUserDetails.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ExtendedLdapUserDetails.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ExtendedLdapUserDetails.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ExtendedLdapUserDetails.java index 4ae79ce5ba5..879156c1317 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ExtendedLdapUserDetails.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ExtendedLdapUserDetails.java @@ -12,7 +12,7 @@ * subcomponent's license, as noted in the LICENSE file. * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.ldap; +package org.cloudfoundry.identity.uaa.provider.ldap; import org.cloudfoundry.identity.uaa.user.DialableByPhone; import org.cloudfoundry.identity.uaa.user.ExternallyIdentifiable; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ExtendedLdapUserMapper.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ExtendedLdapUserMapper.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ExtendedLdapUserMapper.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ExtendedLdapUserMapper.java index 205d602fbf8..8d083242fe0 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ExtendedLdapUserMapper.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ExtendedLdapUserMapper.java @@ -10,12 +10,12 @@ * 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.ldap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.ldap.extension.ExtendedLdapUserImpl; +import org.cloudfoundry.identity.uaa.provider.ldap.extension.ExtendedLdapUserImpl; import org.springframework.ldap.core.DirContextAdapter; import org.springframework.ldap.core.DirContextOperations; import org.springframework.security.core.GrantedAuthority; @@ -23,18 +23,13 @@ import org.springframework.security.ldap.userdetails.LdapUserDetails; import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import static org.cloudfoundry.identity.uaa.ldap.extension.SpringSecurityLdapTemplate.DN_KEY; +import static org.cloudfoundry.identity.uaa.provider.ldap.extension.SpringSecurityLdapTemplate.DN_KEY; public class ExtendedLdapUserMapper extends LdapUserDetailsMapper { private static final Log logger = LogFactory.getLog(ExtendedLdapUserMapper.class); diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/LdapGroupToScopesMapper.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/LdapGroupToScopesMapper.java similarity index 88% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/LdapGroupToScopesMapper.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/LdapGroupToScopesMapper.java index 4035a4d377f..7d0de4fc9c7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/LdapGroupToScopesMapper.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/LdapGroupToScopesMapper.java @@ -12,18 +12,14 @@ * subcomponent's license, as noted in the LICENSE file. * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.ldap; +package org.cloudfoundry.identity.uaa.provider.ldap; import org.cloudfoundry.identity.uaa.authorization.ExternalGroupMappingAuthorizationManager; -import org.cloudfoundry.identity.uaa.ldap.extension.LdapAuthority; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; -import org.springframework.util.StringUtils; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; -import java.util.Set; public class LdapGroupToScopesMapper implements GrantedAuthoritiesMapper { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/PasswordComparisonAuthenticator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/PasswordComparisonAuthenticator.java similarity index 99% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/PasswordComparisonAuthenticator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/PasswordComparisonAuthenticator.java index 58caad3d66c..91c0814f8d6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/PasswordComparisonAuthenticator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/PasswordComparisonAuthenticator.java @@ -12,7 +12,7 @@ * subcomponent's license, as noted in the LICENSE file. * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.ldap; +package org.cloudfoundry.identity.uaa.provider.ldap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapProperties.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ProcessLdapProperties.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapProperties.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ProcessLdapProperties.java index c1cf9098255..f273de6ffaa 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapProperties.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/ProcessLdapProperties.java @@ -12,7 +12,7 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.ldap; +package org.cloudfoundry.identity.uaa.provider.ldap; import java.util.LinkedHashMap; import java.util.Map; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/DefaultLdapAuthoritiesPopulator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/DefaultLdapAuthoritiesPopulator.java similarity index 99% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/DefaultLdapAuthoritiesPopulator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/DefaultLdapAuthoritiesPopulator.java index 60814f5bf2a..5b2001f5989 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/DefaultLdapAuthoritiesPopulator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/DefaultLdapAuthoritiesPopulator.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package org.cloudfoundry.identity.uaa.ldap.extension; +package org.cloudfoundry.identity.uaa.provider.ldap.extension; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/ExtendedLdapUserImpl.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/ExtendedLdapUserImpl.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/ExtendedLdapUserImpl.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/ExtendedLdapUserImpl.java index 311ad84562f..c2b97f8583b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/ExtendedLdapUserImpl.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/ExtendedLdapUserImpl.java @@ -12,9 +12,9 @@ * subcomponent's license, as noted in the LICENSE file. * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.ldap.extension; +package org.cloudfoundry.identity.uaa.provider.ldap.extension; -import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserDetails; +import org.cloudfoundry.identity.uaa.provider.ldap.ExtendedLdapUserDetails; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.ldap.userdetails.LdapUserDetails; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/LdapAuthority.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/LdapAuthority.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/LdapAuthority.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/LdapAuthority.java index a248aa3fde7..b48952c653c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/LdapAuthority.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/LdapAuthority.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.cloudfoundry.identity.uaa.ldap.extension; +package org.cloudfoundry.identity.uaa.provider.ldap.extension; import org.springframework.security.core.GrantedAuthority; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/NestedLdapAuthoritiesPopulator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/NestedLdapAuthoritiesPopulator.java similarity index 99% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/NestedLdapAuthoritiesPopulator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/NestedLdapAuthoritiesPopulator.java index 91bf4f4791f..5968f0130ac 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/NestedLdapAuthoritiesPopulator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/NestedLdapAuthoritiesPopulator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.cloudfoundry.identity.uaa.ldap.extension; +package org.cloudfoundry.identity.uaa.provider.ldap.extension; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/SpringSecurityLdapTemplate.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/SpringSecurityLdapTemplate.java similarity index 99% rename from common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/SpringSecurityLdapTemplate.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/SpringSecurityLdapTemplate.java index ab16ef724af..831556675cd 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/extension/SpringSecurityLdapTemplate.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/ldap/extension/SpringSecurityLdapTemplate.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.cloudfoundry.identity.uaa.ldap.extension; +package org.cloudfoundry.identity.uaa.provider.ldap.extension; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ComparableProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProvider.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ComparableProvider.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProvider.java index 1ac4476c97a..33bd83ce738 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ComparableProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ComparableProvider.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import org.opensaml.saml2.metadata.EntitiesDescriptor; import org.opensaml.saml2.metadata.EntityDescriptor; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ConfigMetadataProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProvider.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ConfigMetadataProvider.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProvider.java index 3754bbe928f..35d6de07066 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ConfigMetadataProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProvider.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import org.opensaml.saml2.metadata.provider.AbstractMetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataProviderException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/FilesystemMetadataProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FilesystemMetadataProvider.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/FilesystemMetadataProvider.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FilesystemMetadataProvider.java index 42a36b8d913..5e5e6539e5e 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/FilesystemMetadataProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FilesystemMetadataProvider.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.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import org.opensaml.saml2.metadata.provider.MetadataProviderException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/FixedHttpMetaDataProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FixedHttpMetaDataProvider.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/FixedHttpMetaDataProvider.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FixedHttpMetaDataProvider.java index 32504fdbf5a..c9a8dfd2cc6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/FixedHttpMetaDataProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/FixedHttpMetaDataProvider.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSAMLAuthenticationFailureHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandler.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSAMLAuthenticationFailureHandler.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandler.java index 9f261c500c1..0a5d794b3b6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSAMLAuthenticationFailureHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandler.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import java.io.IOException; import java.net.URI; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSAMLException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLException.java similarity index 92% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSAMLException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLException.java index e69a78fab09..6c78c2f5652 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSAMLException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLException.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import org.springframework.security.authentication.BadCredentialsException; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java similarity index 78% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java index 0e74d67ee4e..4def3ffd36c 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProvider.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProvider.java @@ -10,33 +10,42 @@ * 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.saml; import org.apache.commons.collections.CollectionUtils; 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.login.SamlUserAuthority; +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.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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.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; @@ -59,6 +68,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; @@ -70,11 +80,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); @@ -130,7 +140,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); @@ -195,21 +205,18 @@ 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()) { 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); - } 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())); } - count++; } } } @@ -218,6 +225,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; @@ -249,16 +289,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)) { @@ -303,13 +348,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/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationToken.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationToken.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationToken.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationToken.java index 781afd920e8..1aa17b405d2 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationToken.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationToken.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.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; @@ -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/LoginSamlDiscovery.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlDiscovery.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlDiscovery.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlDiscovery.java index 88ab8a48e13..290db9588de 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlDiscovery.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlDiscovery.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.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import javax.servlet.FilterChain; import javax.servlet.ServletException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlEntryPoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlEntryPoint.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlEntryPoint.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlEntryPoint.java index d4b1a9f93c7..50fe08c241a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlEntryPoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlEntryPoint.java @@ -10,9 +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.login.saml; +package org.cloudfoundry.identity.uaa.provider.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/MetadataProviderNotFoundException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/MetadataProviderNotFoundException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java index f9c820f0302..ce6d66dce3a 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/MetadataProviderNotFoundException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/MetadataProviderNotFoundException.java @@ -12,7 +12,7 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import org.opensaml.saml2.metadata.provider.MetadataProviderException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ProviderChangedListener.java similarity index 92% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ProviderChangedListener.java index 31542b568ad..d14c1e92e07 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ProviderChangedListener.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ProviderChangedListener.java @@ -12,13 +12,14 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; 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.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; @@ -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/SamlIdentityProviderConfigurator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderConfigurator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.java index 9ac05d480a7..a62ccdd6ac2 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderConfigurator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderConfigurator.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.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.SimpleHttpConnectionManager; @@ -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); @@ -60,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/SamlLoginServerKeyManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.java index 96086339cfb..ab02c1c10bb 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlLoginServerKeyManager.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.login; +package org.cloudfoundry.identity.uaa.provider.saml; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java similarity index 92% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtils.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java index 1c1ea7d7ae8..1095d310926 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtils.java @@ -12,8 +12,9 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.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/SamlUserAuthority.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUserAuthority.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/SamlUserAuthority.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUserAuthority.java index d174e4ecb4a..4c3ae6ef22b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/SamlUserAuthority.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUserAuthority.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.login; +package org.cloudfoundry.identity.uaa.provider.saml; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/SamlUserDetails.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUserDetails.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/SamlUserDetails.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUserDetails.java index f7ec447d7bc..dd3ac7a7f41 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/SamlUserDetails.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/SamlUserDetails.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.login; +package org.cloudfoundry.identity.uaa.provider.saml; import java.util.Collection; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGenerator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGenerator.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGenerator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGenerator.java index d48a367499a..746977d7cb7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGenerator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGenerator.java @@ -12,9 +12,9 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.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/login/saml/ZoneAwareMetadataManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java index d6886ffdc03..add96683342 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataManager.java @@ -12,14 +12,15 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; 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.provider.IdentityProvider; +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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; @@ -48,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; @@ -128,8 +130,10 @@ 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())) { - if (Origin.SAML.equals(provider.getType()) && (ignoreTimestamp || lastRefresh < provider.getLastModified().getTime())) { + zoneDefinitions.remove(provider.getConfig()); + if (OriginKeys.SAML.equals(provider.getType()) && (ignoreTimestamp || lastRefresh < provider.getLastModified().getTime())) { try { SamlIdentityProviderDefinition definition = (SamlIdentityProviderDefinition)provider.getConfig(); try { @@ -141,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) { @@ -156,6 +156,10 @@ protected void refreshAllProviders(boolean ignoreTimestamp) throws MetadataProvi } } } + for (SamlIdentityProviderDefinition definition : zoneDefinitions) { + removeSamlProvider(zone, manager, definition); + hasChanges = true; + } if (hasChanges) { refreshZoneManager(manager); } @@ -163,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/common/src/main/java/org/cloudfoundry/identity/uaa/rest/AttributeNameMapper.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/AttributeNameMapper.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/AttributeNameMapper.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/AttributeNameMapper.java index f6afff4ff61..3fc060b9b82 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/AttributeNameMapper.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/AttributeNameMapper.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; /** * Helper to map attribute names between json requests/responses and internal diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/Queryable.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/Queryable.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/Queryable.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/Queryable.java index c6a17e3e51c..f4773c3a98e 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/Queryable.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/Queryable.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.List; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/QueryableResourceManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/QueryableResourceManager.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/QueryableResourceManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/QueryableResourceManager.java index ccbabb1efc2..cecdbadb2ff 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/QueryableResourceManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/QueryableResourceManager.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; public interface QueryableResourceManager extends Queryable, ResourceManager { } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/ResourceManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/ResourceManager.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/ResourceManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/ResourceManager.java index 4f1c0a8bb2c..5daac9906dd 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/ResourceManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/ResourceManager.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.List; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/ResourceMonitor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/ResourceMonitor.java similarity index 77% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/ResourceMonitor.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/ResourceMonitor.java index cea8e066799..cf1d2684af5 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/ResourceMonitor.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/ResourceMonitor.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.rest; +package org.cloudfoundry.identity.uaa.resources; public interface ResourceMonitor { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/SearchResultsFactory.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResultsFactory.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/SearchResultsFactory.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResultsFactory.java index df842139350..5cfbbee1973 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/SearchResultsFactory.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/SearchResultsFactory.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; @@ -19,7 +19,6 @@ import java.util.List; import java.util.Map; -import org.cloudfoundry.identity.uaa.util.UaaPagingUtils; 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/rest/SimpleAttributeNameMapper.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/SimpleAttributeNameMapper.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/SimpleAttributeNameMapper.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/SimpleAttributeNameMapper.java index 0594b732097..f572d415524 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/SimpleAttributeNameMapper.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/SimpleAttributeNameMapper.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.Collections; import java.util.Map; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/AbstractQueryable.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/AbstractQueryable.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java index 6dd83a30275..bcfde4fe257 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/AbstractQueryable.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/AbstractQueryable.java @@ -10,13 +10,13 @@ * 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; +package org.cloudfoundry.identity.uaa.resources.jdbc; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.rest.Queryable; +import org.cloudfoundry.identity.uaa.resources.Queryable; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/DefaultLimitSqlAdapter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/DefaultLimitSqlAdapter.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/DefaultLimitSqlAdapter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/DefaultLimitSqlAdapter.java index 18acb1a66e0..3d67798cc01 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/DefaultLimitSqlAdapter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/DefaultLimitSqlAdapter.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.jdbc; +package org.cloudfoundry.identity.uaa.resources.jdbc; public class DefaultLimitSqlAdapter implements LimitSqlAdapter { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/JdbcPagingList.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingList.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/JdbcPagingList.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingList.java index 8d95fcba808..31bd9f194e9 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/JdbcPagingList.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingList.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.rest.jdbc; +package org.cloudfoundry.identity.uaa.resources.jdbc; import java.util.AbstractList; import java.util.Collections; @@ -73,7 +73,7 @@ public JdbcPagingList(NamedParameterJdbcTemplate jdbcTemplate, LimitSqlAdapter l this.sql = sql; this.args = args; this.mapper = mapper; - this.size = parameterJdbcTemplate.queryForInt(getCountSql(sql), args); + this.size = parameterJdbcTemplate.queryForObject(getCountSql(sql), args, Integer.class); this.pageSize = pageSize; this.limitSqlAdapter = limitSqlAdapter; } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/JdbcPagingListFactory.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingListFactory.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/JdbcPagingListFactory.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingListFactory.java index e9bb03b2c7b..ef080616aa7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/JdbcPagingListFactory.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingListFactory.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.jdbc; +package org.cloudfoundry.identity.uaa.resources.jdbc; import java.util.List; import java.util.Map; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/LimitSqlAdapter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/LimitSqlAdapter.java similarity index 93% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/LimitSqlAdapter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/LimitSqlAdapter.java index 356e6d47dac..d84f38564e0 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/LimitSqlAdapter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/LimitSqlAdapter.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.jdbc; +package org.cloudfoundry.identity.uaa.resources.jdbc; public interface LimitSqlAdapter { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/OracleLimitSqlAdapter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/OracleLimitSqlAdapter.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/OracleLimitSqlAdapter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/OracleLimitSqlAdapter.java index 671fbf51132..1c2f540ed16 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/OracleLimitSqlAdapter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/OracleLimitSqlAdapter.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.jdbc; +package org.cloudfoundry.identity.uaa.resources.jdbc; public class OracleLimitSqlAdapter implements LimitSqlAdapter { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/SearchQueryConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SearchQueryConverter.java similarity index 93% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/SearchQueryConverter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SearchQueryConverter.java index 1fd8fd7e647..98a70ab4d9f 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/SearchQueryConverter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SearchQueryConverter.java @@ -10,11 +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.rest.jdbc; +package org.cloudfoundry.identity.uaa.resources.jdbc; import java.util.Map; -import org.cloudfoundry.identity.uaa.rest.AttributeNameMapper; +import org.cloudfoundry.identity.uaa.resources.AttributeNameMapper; public interface SearchQueryConverter { diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/SimpleSearchQueryConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/SimpleSearchQueryConverter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java index e3bb5492225..7d98b10b89e 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/rest/jdbc/SimpleSearchQueryConverter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/resources/jdbc/SimpleSearchQueryConverter.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.rest.jdbc; +package org.cloudfoundry.identity.uaa.resources.jdbc; import java.text.DateFormat; import java.text.ParseException; @@ -24,8 +24,8 @@ import com.unboundid.scim.sdk.SCIMFilter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.rest.AttributeNameMapper; -import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; +import org.cloudfoundry.identity.uaa.resources.AttributeNameMapper; +import org.cloudfoundry.identity.uaa.resources.SimpleAttributeNameMapper; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.StringUtils; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/DisableInternalUserManagementFilter.java similarity index 85% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/DisableInternalUserManagementFilter.java index 7db713c8a6a..bf57b0c1124 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableInternalUserManagementFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/DisableInternalUserManagementFilter.java @@ -10,10 +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.zone; +package org.cloudfoundry.identity.uaa.scim; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.ObjectUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; @@ -40,7 +44,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/server/src/main/java/org/cloudfoundry/identity/uaa/scim/DisableUserManagementSecurityFilter.java similarity index 87% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableUserManagementSecurityFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/DisableUserManagementSecurityFilter.java index 0c55e42f7a4..aa9bf9e7d28 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/DisableUserManagementSecurityFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/DisableUserManagementSecurityFilter.java @@ -10,12 +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.zone; +package org.cloudfoundry.identity.uaa.scim; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.error.ExceptionReport; -import org.cloudfoundry.identity.uaa.error.ExceptionReportHttpMessageConverter; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; +import org.cloudfoundry.identity.uaa.web.ExceptionReportHttpMessageConverter; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.util.ObjectUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.http.MediaType; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.web.filter.OncePerRequestFilter; @@ -66,7 +70,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/InternalUserManagementDisabledException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/InternalUserManagementDisabledException.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/InternalUserManagementDisabledException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/InternalUserManagementDisabledException.java index f22c147e415..24bf974c951 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/InternalUserManagementDisabledException.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/InternalUserManagementDisabledException.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.scim; import org.cloudfoundry.identity.uaa.error.UaaException; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMembershipManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMembershipManager.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMembershipManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupExternalMembershipManager.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java similarity index 97% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java index a5f51dd5afe..944d681c29b 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMembershipManager.java @@ -15,7 +15,7 @@ import java.util.List; import java.util.Set; -import org.cloudfoundry.identity.uaa.rest.Queryable; +import org.cloudfoundry.identity.uaa.resources.Queryable; import org.cloudfoundry.identity.uaa.scim.exception.MemberAlreadyExistsException; import org.cloudfoundry.identity.uaa.scim.exception.MemberNotFoundException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; @@ -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/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupProvisioning.java similarity index 87% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupProvisioning.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupProvisioning.java index 52e06c6aec6..f6646bbf340 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimGroupProvisioning.java @@ -12,8 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim; -import org.cloudfoundry.identity.uaa.rest.Queryable; -import org.cloudfoundry.identity.uaa.rest.ResourceManager; +import org.cloudfoundry.identity.uaa.resources.Queryable; +import org.cloudfoundry.identity.uaa.resources.ResourceManager; public interface ScimGroupProvisioning extends ResourceManager, Queryable { } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserProvisioning.java similarity index 93% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserProvisioning.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserProvisioning.java index 44241ae0269..e15e612fd08 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserProvisioning.java @@ -12,8 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim; -import org.cloudfoundry.identity.uaa.rest.Queryable; -import org.cloudfoundry.identity.uaa.rest.ResourceManager; +import org.cloudfoundry.identity.uaa.resources.Queryable; +import org.cloudfoundry.identity.uaa.resources.ResourceManager; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.scim.exception.InvalidScimResourceException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrap.java similarity index 98% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrap.java rename to server/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/server/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/ScimGroupBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrap.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java similarity index 95% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java index f0d640688dd..a4e2d13f09e 100644 --- a/scim/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,11 +14,12 @@ 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.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; 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 +100,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 +150,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 +184,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 +206,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/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java similarity index 70% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java index eddb1cbef9c..28228769fe0 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpoints.java @@ -1,13 +1,14 @@ 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.authentication.Origin; +import org.cloudfoundry.identity.uaa.scim.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.account.EmailChange; import org.cloudfoundry.identity.uaa.error.UaaException; -import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; +import org.cloudfoundry.identity.uaa.account.EmailChangeResponse; +import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -53,7 +54,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); } @@ -61,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); } @@ -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/PasswordChange.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordChange.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordChange.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordChange.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordScore.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordScore.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordScore.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordScore.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordScoreCalculator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordScoreCalculator.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordScoreCalculator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordScoreCalculator.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java similarity index 91% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java index 38c66531c63..d7275cccee8 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpoints.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.error.ConvertingExceptionView; -import org.cloudfoundry.identity.uaa.error.ExceptionReport; -import org.cloudfoundry.identity.uaa.rest.SearchResults; -import org.cloudfoundry.identity.uaa.rest.SearchResultsFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; +import org.cloudfoundry.identity.uaa.resources.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResultsFactory; import org.cloudfoundry.identity.uaa.scim.ScimCore; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; @@ -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,9 +157,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); if (!StringUtils.hasLength(attributesCommaSeparated)) { return new SearchResults<>(Arrays.asList(ScimCore.SCHEMAS), input, startIndex, count, @@ -222,7 +214,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 +241,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 +258,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 +266,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 +277,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); @@ -489,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/endpoints/ScimUserEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java similarity index 97% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java index 07bb8a8eeaf..a0101f9a08f 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java @@ -16,15 +16,15 @@ import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; -import org.cloudfoundry.identity.uaa.error.ConvertingExceptionView; -import org.cloudfoundry.identity.uaa.error.ExceptionReport; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval; -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.rest.SearchResultsFactory; -import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; +import org.cloudfoundry.identity.uaa.resources.AttributeNameMapper; +import org.cloudfoundry.identity.uaa.resources.ResourceMonitor; +import org.cloudfoundry.identity.uaa.resources.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SearchResultsFactory; +import org.cloudfoundry.identity.uaa.resources.SimpleAttributeNameMapper; import org.cloudfoundry.identity.uaa.scim.ScimCore; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMembershipManager; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java similarity index 95% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java index 5a9ed8ad8cb..a976429f4f3 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpoints.java @@ -17,14 +17,14 @@ 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.rest.SearchResults; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +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; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.beans.factory.InitializingBean; import org.springframework.http.HttpStatus; @@ -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/endpoints/VerificationResponse.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/VerificationResponse.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/VerificationResponse.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/VerificationResponse.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/GroupModifiedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/event/GroupModifiedEvent.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/GroupModifiedEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/event/GroupModifiedEvent.java index ac87ce4a705..0f9bd94e522 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/GroupModifiedEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/event/GroupModifiedEvent.java @@ -13,19 +13,19 @@ * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.audit.event; +package org.cloudfoundry.identity.uaa.scim.event; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; +import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/event/ScimEventPublisher.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/event/ScimEventPublisher.java similarity index 93% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/event/ScimEventPublisher.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/event/ScimEventPublisher.java index 37c8f97eefc..a53ed5e6451 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/event/ScimEventPublisher.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/event/ScimEventPublisher.java @@ -12,15 +12,12 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.event; -import org.cloudfoundry.identity.uaa.audit.event.GroupModifiedEvent; -import org.cloudfoundry.identity.uaa.audit.event.UserModifiedEvent; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.security.core.Authentication; import java.util.List; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/UserModifiedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/event/UserModifiedEvent.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/UserModifiedEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/event/UserModifiedEvent.java index 43381263ae5..5d85935a347 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/audit/event/UserModifiedEvent.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/event/UserModifiedEvent.java @@ -13,15 +13,14 @@ * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.audit.event; +package org.cloudfoundry.identity.uaa.scim.event; import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; +import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.security.core.Authentication; -import java.io.IOException; - public class UserModifiedEvent extends AbstractUaaEvent { private static final long serialVersionUID = 8139998613071093676L; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidPasswordException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidPasswordException.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidPasswordException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidPasswordException.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidScimResourceException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidScimResourceException.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidScimResourceException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidScimResourceException.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/MemberAlreadyExistsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/MemberAlreadyExistsException.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/MemberAlreadyExistsException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/MemberAlreadyExistsException.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/MemberNotFoundException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/MemberNotFoundException.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/MemberNotFoundException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/MemberNotFoundException.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimException.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimException.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceAlreadyExistsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceAlreadyExistsException.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceAlreadyExistsException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceAlreadyExistsException.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceConflictException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceConflictException.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceConflictException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceConflictException.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceConstraintFailedException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceConstraintFailedException.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceConstraintFailedException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceConstraintFailedException.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceNotFoundException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceNotFoundException.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceNotFoundException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/ScimResourceNotFoundException.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/UserAlreadyVerifiedException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/UserAlreadyVerifiedException.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/UserAlreadyVerifiedException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/exception/UserAlreadyVerifiedException.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManager.java similarity index 98% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManager.java index 13119de0ebe..4a8b7e36523 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManager.java @@ -14,9 +14,9 @@ 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.rest.jdbc.JdbcPagingListFactory; -import org.cloudfoundry.identity.uaa.rest.jdbc.SearchQueryConverter; +import org.cloudfoundry.identity.uaa.resources.jdbc.AbstractQueryable; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.SearchQueryConverter; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java similarity index 98% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java index 6349a439ece..1fb4b1463a0 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManager.java @@ -27,9 +27,9 @@ 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.rest.jdbc.JdbcPagingListFactory; -import org.cloudfoundry.identity.uaa.rest.jdbc.SearchQueryConverter; +import org.cloudfoundry.identity.uaa.resources.jdbc.AbstractQueryable; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.SearchQueryConverter; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupMembershipManager; @@ -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/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java similarity index 75% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java index 9c0b6b51f66..295b7aed403 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioning.java @@ -12,19 +12,12 @@ *******************************************************************************/ 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.rest.jdbc.JdbcPagingListFactory; -import org.cloudfoundry.identity.uaa.rest.jdbc.SearchQueryConverter; +import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; +import org.cloudfoundry.identity.uaa.resources.jdbc.AbstractQueryable; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.scim.ScimMeta; @@ -33,6 +26,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 +36,31 @@ 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 AbstractQueryable + implements ScimGroupProvisioning, ApplicationListener>, SystemDeletable { private JdbcTemplate jdbcTemplate; private final Log logger = LogFactory.getLog(getClass()); + @Override + public 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 EXTERNAL_GROUP_TABLE = "external_group_mapping"; public static final String ADD_GROUP_SQL = String.format("insert into %s ( %s ) values (?,?,?,?,?,?)", GROUP_TABLE, GROUP_FIELDS); @@ -58,12 +68,18 @@ 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); + 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(); public JdbcScimGroupProvisioning(JdbcTemplate jdbcTemplate, JdbcPagingListFactory pagingListFactory) { @@ -75,15 +91,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 @@ -171,6 +188,17 @@ public ScimGroup delete(String id, int version) throws ScimResourceNotFoundExcep return group; } + 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); + } + + 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); + } + 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/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java similarity index 89% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java index c53260fef89..c5d74ff0bc2 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioning.java @@ -14,10 +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.rest.ResourceMonitor; -import org.cloudfoundry.identity.uaa.rest.jdbc.AbstractQueryable; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; +import org.cloudfoundry.identity.uaa.resources.ResourceMonitor; +import org.cloudfoundry.identity.uaa.resources.jdbc.AbstractQueryable; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimMeta; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUser.Name; @@ -59,10 +60,16 @@ * @author Luke Taylor * @author Dave Syer */ -public class JdbcScimUserProvisioning extends AbstractQueryable implements ScimUserProvisioning, ResourceMonitor { +public class JdbcScimUserProvisioning extends AbstractQueryable + implements ScimUserProvisioning, ResourceMonitor, SystemDeletable { private final Log logger = LogFactory.getLog(getClass()); + @Override + public 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 @@ -84,6 +91,18 @@ public class JdbcScimUserProvisioning extends AbstractQueryable implem 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 = ?"; + protected final JdbcTemplate jdbcTemplate; private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); @@ -142,7 +161,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 +196,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 +240,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 @@ -389,6 +408,18 @@ public void setUsernamePattern(String usernamePattern) { this.usernamePattern = Pattern.compile(usernamePattern); } + 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); + } + + 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); + } + private static final class ScimUserRowMapper implements RowMapper { @Override public ScimUser mapRow(ResultSet rs, int rowNum) throws SQLException { diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverter.java similarity index 91% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverter.java index 0fc8d4ff0ad..5b019b1acbe 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverter.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.jdbc; -import org.cloudfoundry.identity.uaa.rest.jdbc.SimpleSearchQueryConverter; +import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; public class ScimSearchQueryConverter extends SimpleSearchQueryConverter { } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioning.java similarity index 98% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioning.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioning.java index 2b563ea27b5..3b260b979f0 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioning.java @@ -13,7 +13,7 @@ package org.cloudfoundry.identity.uaa.scim.remote; -import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; +import org.cloudfoundry.identity.uaa.account.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/security/GroupRoleCheck.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/security/GroupRoleCheck.java similarity index 94% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/security/GroupRoleCheck.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/security/GroupRoleCheck.java index b770723e98f..a82c31e2041 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/security/GroupRoleCheck.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/security/GroupRoleCheck.java @@ -50,9 +50,9 @@ public boolean isGroupRole(HttpServletRequest request, int pathVariableIndex, Sc Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if ( authentication!=null && authentication.getPrincipal() instanceof UaaPrincipal) { String userId = ((UaaPrincipal) authentication.getPrincipal()).getId(); - String pathInfo = UaaUrlUtils.getRequestPath(request); - if (StringUtils.hasText(pathInfo)) { - String groupId = UaaUrlUtils.extractPathVariableFromUrl(pathVariableIndex, pathInfo); + String path = UaaUrlUtils.getRequestPath(request); + if (StringUtils.hasText(path)) { + String groupId = UaaUrlUtils.extractPathVariableFromUrl(pathVariableIndex, path); if (manager.getMembers(groupId, role).contains(new ScimGroupMember(userId))) { return true; } diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/util/ScimUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/util/ScimUtils.java similarity index 96% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/util/ScimUtils.java rename to server/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/server/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/main/java/org/cloudfoundry/identity/uaa/scim/validate/NullPasswordValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/NullPasswordValidator.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/NullPasswordValidator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/NullPasswordValidator.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/PasswordValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/PasswordValidator.java similarity index 100% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/PasswordValidator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/PasswordValidator.java diff --git a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidator.java similarity index 89% rename from scim/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidator.java index 7dbf039213d..5023b39bd35 100644 --- a/scim/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidator.java @@ -1,12 +1,12 @@ 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.provider.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; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.passay.DigitCharacterRule; import org.passay.LengthRule; import org.passay.LowercaseCharacterRule; @@ -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/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/ContextSensitiveOAuth2SecurityExpressionMethods.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/ContextSensitiveOAuth2SecurityExpressionMethods.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/ContextSensitiveOAuth2SecurityExpressionMethods.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/ContextSensitiveOAuth2SecurityExpressionMethods.java index 9c999014d2c..1dcc24a5ad7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/ContextSensitiveOAuth2SecurityExpressionMethods.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/ContextSensitiveOAuth2SecurityExpressionMethods.java @@ -10,11 +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.oauth.expression; +package org.cloudfoundry.identity.uaa.security; 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/expression/ContextSensitiveOAuth2WebSecurityExpressionHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/ContextSensitiveOAuth2WebSecurityExpressionHandler.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/ContextSensitiveOAuth2WebSecurityExpressionHandler.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/ContextSensitiveOAuth2WebSecurityExpressionHandler.java index 5308f417209..a1c77f9cab0 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/ContextSensitiveOAuth2WebSecurityExpressionHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/ContextSensitiveOAuth2WebSecurityExpressionHandler.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.expression; +package org.cloudfoundry.identity.uaa.security; import org.cloudfoundry.identity.uaa.zone.IdentityZone; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandler.java similarity index 100% rename from login/src/main/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandler.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandler.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessor.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessor.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessor.java index a5643827586..17ad55b95a7 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessor.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessor.java @@ -18,7 +18,6 @@ import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.oauth.expression.ContextSensitiveOAuth2SecurityExpressionMethods; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.core.Authentication; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheck.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/IsUserSelfCheck.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheck.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/IsUserSelfCheck.java index a855211089d..15fb59aa0ea 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheck.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/IsUserSelfCheck.java @@ -12,7 +12,7 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.oauth.expression; +package org.cloudfoundry.identity.uaa.security; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/SavedRequestAwareAuthenticationDetails.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/SavedRequestAwareAuthenticationDetails.java similarity index 97% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/SavedRequestAwareAuthenticationDetails.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/SavedRequestAwareAuthenticationDetails.java index 57a69cb2b9f..a28b2de0536 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/SavedRequestAwareAuthenticationDetails.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/SavedRequestAwareAuthenticationDetails.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.security; import org.springframework.security.web.authentication.WebAuthenticationDetails; diff --git a/login/src/main/java/org/cloudfoundry/identity/uaa/login/SavedRequestAwareAuthenticationDetailsSource.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/SavedRequestAwareAuthenticationDetailsSource.java similarity index 90% rename from login/src/main/java/org/cloudfoundry/identity/uaa/login/SavedRequestAwareAuthenticationDetailsSource.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/SavedRequestAwareAuthenticationDetailsSource.java index 37dc0e307f1..fef36eceb4a 100644 --- a/login/src/main/java/org/cloudfoundry/identity/uaa/login/SavedRequestAwareAuthenticationDetailsSource.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/SavedRequestAwareAuthenticationDetailsSource.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.security; import org.springframework.security.authentication.AuthenticationDetailsSource; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/security/SecurityContextAccessor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/SecurityContextAccessor.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/security/SecurityContextAccessor.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/SecurityContextAccessor.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepository.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CookieBasedCsrfTokenRepository.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepository.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CookieBasedCsrfTokenRepository.java index aa431b1ebba..1500bc0523b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepository.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CookieBasedCsrfTokenRepository.java @@ -13,7 +13,7 @@ * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.web; +package org.cloudfoundry.identity.uaa.security.web; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.web.csrf.CsrfToken; 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 new file mode 100644 index 00000000000..ac9f1d0d883 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java @@ -0,0 +1,499 @@ +/******************************************************************************* + * 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.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 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; + +/** + * + * Modern browser include the X-Requested-With header when making calls through + * the XMLHttpRequest API which allows the server CORS filtering to mitigate + * against CSRF attacks performed by XHR requests. However, in some situations + * XHR requests are useful. For example, when a single page JavaScript apps that + * implements login using implicit grant wants to: 1) log the user out by + * calling the /logout.do URI 2) get user information by calling the /userinfo + * URI. + * + * To enable the scenarios described above, this filter allows CORS requests to + * include the "X-Requested-With" header for a whitelist of URIs and origins and + * only for the HTTP GET method. + * + * The implementation is based on guidance from: + * http://www.w3.org/TR/cors/ + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS + * + */ +public class CorsFilter extends OncePerRequestFilter { + + 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; + } + + public void setAllowedCredentials(boolean allowedCredentials) { + this.allowedCredentials = allowedCredentials; + } + + public List getAllowedHeaders() { + return allowedHeaders; + } + + public void setAllowedHeaders(List allowedHeaders) { + this.allowedHeaders = allowedHeaders; + } + + public List getAllowedMethods() { + return allowedMethods; + } + + public void setAllowedMethods(List allowedMethods) { + this.allowedMethods = allowedMethods; + } + + public List getAllowedOriginPatterns() { + return allowedOriginPatterns; + } + + public List getAllowedOrigins() { + return allowedOrigins; + } + + public void setAllowedOrigins(List allowedOrigins) { + this.allowedOrigins = allowedOrigins; + } + + 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)); + + 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); + } + } + } + } + } + + + @Override + protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, + 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 (logger.isDebugEnabled()) { + logger.debug("CORS Processing request: "+getRequestInfo(request)); + } + if (isXhrRequest(request)) { + handleRequest(request, response, filterChain, getXhrConfiguration()); + } else { + handleRequest(request, response, filterChain, getDefaultConfiguration()); + } + if (logger.isDebugEnabled()) { + logger.debug("CORS processing completed for: "+getRequestInfo(request)+" Status:"+response.getStatus()); + } + } + + protected boolean handleRequest(HttpServletRequest request, + HttpServletResponse response, + 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 (!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; + } + + + // 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); + } + + if (isPreflightRequest) { + 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; + } + + /** + * 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); + + } + + /** + * 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); + } + 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); + } + + 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.sendError(BAD_REQUEST.value(), "Access-Control-Request-Method header is missing"); + return; + } + + if (!isAllowedMethod(accessControlRequestMethod, configuration)) { + response.sendError(METHOD_NOT_ALLOWED.value(), "Illegal method requested"); + return; + } + + //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.sendError(BAD_REQUEST.value(),"Missing "+ACCESS_CONTROL_REQUEST_HEADERS+" header."); + return; + } + if (!headersAllowed(accessControlRequestHeaders, configuration)) { + response.sendError(FORBIDDEN.value(), "Illegal header requested"); + return; + } + + //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())); + } + + protected boolean containsHeader(final String accessControlRequestHeaders, final String header) { + List headers = splitCommaDelimitedString(accessControlRequestHeaders); + return containsIgnoreCase(headers, header); + } + + protected boolean headersAllowed(final String accessControlRequestHeaders, CorsConfiguration configuration) { + List headers = splitCommaDelimitedString(accessControlRequestHeaders); + for (String header : headers) { + if (!containsIgnoreCase(configuration.getAllowedHeaders(), header)) { + return false; + } + } + return true; + } + + protected boolean isAllowedMethod(final String method, CorsConfiguration configuration) { + return containsIgnoreCase(configuration.getAllowedMethods(), method); + } + + protected boolean isAllowedRequestUri(final String uri, CorsConfiguration configuration) { + if (StringUtils.isEmpty(uri)) { + return false; + } + + for (Pattern pattern : configuration.getAllowedUriPatterns()) { + // Making sure that the pattern matches + if (pattern.matcher(uri).find()) { + return true; + } + } + logger.debug(String.format("The '%s' URI does not allow CORS requests.", uri)); + return false; + } + + 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; + } + } + 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) { + this.xhrConfiguration.setAllowedUris(corsXhrAllowedUris); + } + + public void setCorsXhrAllowedOrigins(List 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 CorsConfiguration getXhrConfiguration() { + return xhrConfiguration; + } +} \ No newline at end of file diff --git a/login/src/main/java/org/cloudfoundry/identity/web/FixHttpsSchemeRequest.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/FixHttpsSchemeRequest.java similarity index 97% rename from login/src/main/java/org/cloudfoundry/identity/web/FixHttpsSchemeRequest.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/web/FixHttpsSchemeRequest.java index 6fa08314e9b..da8661562e4 100644 --- a/login/src/main/java/org/cloudfoundry/identity/web/FixHttpsSchemeRequest.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/FixHttpsSchemeRequest.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.web; +package org.cloudfoundry.identity.uaa.security.web; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; diff --git a/login/src/main/java/org/cloudfoundry/identity/web/HttpsHeaderFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/HttpsHeaderFilter.java similarity index 97% rename from login/src/main/java/org/cloudfoundry/identity/web/HttpsHeaderFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/web/HttpsHeaderFilter.java index 7d4cf0f01f3..4ae96045751 100644 --- a/login/src/main/java/org/cloudfoundry/identity/web/HttpsHeaderFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/HttpsHeaderFilter.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.web; +package org.cloudfoundry.identity.uaa.security.web; import java.io.IOException; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessor.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/security/web/TokenEndpointPostProcessor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/TokenEndpointPostProcessor.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/security/web/TokenEndpointPostProcessor.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/web/TokenEndpointPostProcessor.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/security/web/UaaRequestMatcher.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/UaaRequestMatcher.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/security/web/UaaRequestMatcher.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/web/UaaRequestMatcher.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/login/XFrameOptionsFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/XFrameOptionsFilter.java similarity index 92% rename from common/src/main/java/org/cloudfoundry/identity/uaa/login/XFrameOptionsFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/security/web/XFrameOptionsFilter.java index 93c70aeb47a..5b14654a137 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/login/XFrameOptionsFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/security/web/XFrameOptionsFilter.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login; +package org.cloudfoundry.identity.uaa.security.web; import org.springframework.web.filter.OncePerRequestFilter; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/DialableByPhone.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/DialableByPhone.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/DialableByPhone.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/DialableByPhone.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/ExtendedUaaAuthority.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/ExtendedUaaAuthority.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/ExtendedUaaAuthority.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/ExtendedUaaAuthority.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/ExternallyIdentifiable.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/ExternallyIdentifiable.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/ExternallyIdentifiable.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/ExternallyIdentifiable.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java similarity index 80% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java index 460134670f9..54c8e68be0d 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabase.java @@ -12,11 +12,12 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.user; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.util.Collection; import java.util.HashMap; import java.util.Map; -import org.springframework.security.core.userdetails.UsernameNotFoundException; - /** * In-memory user account information storage. * @@ -29,12 +30,12 @@ public class InMemoryUaaUserDatabase implements UaaUserDatabase { private final Map users; private final Map ids; - public InMemoryUaaUserDatabase(Map users) { + public InMemoryUaaUserDatabase(Collection users) { this.users = new HashMap<>(); this.ids = new HashMap<>(); - for (Map.Entry entry : users.entrySet()) { - this.ids.put(entry.getValue().getId(), entry.getValue()); - this.users.put(entry.getKey()+"-"+entry.getValue().getOrigin(), entry.getValue()); + for (UaaUser user : users) { + this.ids.put(user.getId(), user); + this.users.put(user.getUsername()+"-"+user.getOrigin(), user); } } @@ -57,6 +58,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/server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java similarity index 84% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java index 2601ff44d8f..e24b632ecb6 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabase.java @@ -12,18 +12,10 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.user; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -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; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.security.core.GrantedAuthority; @@ -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 @@ -39,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=?"; @@ -47,6 +48,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=? and identity_zone_id=?"; + private String userAuthoritiesQuery = null; private String userByUserNameQuery = DEFAULT_USER_BY_USERNAME_QUERY; @@ -92,6 +96,20 @@ public UaaUser retrieveUserById(String id) throws UsernameNotFoundException { } } + @Override + public UaaUser retrieveUserByEmail(String email, String origin) throws UsernameNotFoundException { + 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()); + } + } + private final class UaaUserRowMapper implements RowMapper { @Override public UaaUser mapRow(ResultSet rs, int rowNum) throws SQLException { @@ -100,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/common/src/main/java/org/cloudfoundry/identity/uaa/user/Mailable.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/Mailable.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/Mailable.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/Mailable.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/Named.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/Named.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/Named.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/Named.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaAuthority.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUser.java index 8da589efdbe..70e85195a55 100644 --- a/common/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/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserApprovalHandler.java similarity index 98% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandler.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserApprovalHandler.java index 96046f88e54..81a9d3303c1 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaUserApprovalHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserApprovalHandler.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.user; import java.util.Collection; import java.util.HashMap; @@ -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/user/UaaUserDatabase.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserDatabase.java similarity index 91% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserDatabase.java rename to server/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/server/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/main/java/org/cloudfoundry/identity/uaa/user/UaaUserEditor.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserEditor.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserEditor.java rename to server/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/server/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/user/UaaUserPrototype.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java similarity index 93% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/UaaUserPrototype.java index dba6078a149..ab59dd971c1 100644 --- a/common/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/common/src/main/java/org/cloudfoundry/identity/uaa/user/package-info.java b/server/src/main/java/org/cloudfoundry/identity/uaa/user/package-info.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/user/package-info.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/user/package-info.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/CachingPasswordEncoder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/CachingPasswordEncoder.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/CachingPasswordEncoder.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/util/CachingPasswordEncoder.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java similarity index 95% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java index 364ced9ea17..055508cf182 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/DomainFilter.java @@ -14,8 +14,8 @@ 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.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.util.StringUtils; @@ -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/util/FileThenClasspathResourceLoader.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/FileThenClasspathResourceLoader.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/FileThenClasspathResourceLoader.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/util/FileThenClasspathResourceLoader.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/util/LdapUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/LdapUtils.java new file mode 100644 index 00000000000..d6b53566e98 --- /dev/null +++ b/server/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.impl.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/server/src/main/java/org/cloudfoundry/identity/uaa/util/LineAwareLayout.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/LineAwareLayout.java new file mode 100644 index 00000000000..cd0d83df9a9 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/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.util; + +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/java/org/cloudfoundry/identity/uaa/util/LinkedMaskingMultiValueMap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/LinkedMaskingMultiValueMap.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/LinkedMaskingMultiValueMap.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/util/LinkedMaskingMultiValueMap.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaMapUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaMapUtils.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaMapUtils.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaMapUtils.java index 250d9681059..ffc09f74879 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaMapUtils.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaMapUtils.java @@ -15,7 +15,7 @@ package org.cloudfoundry.identity.uaa.util; -import org.cloudfoundry.identity.uaa.config.NestedMapPropertySource; +import org.cloudfoundry.identity.uaa.impl.config.NestedMapPropertySource; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaPagingUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaPagingUtils.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaPagingUtils.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaPagingUtils.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaStringUtils.java index af184ade335..b07402aedc7 100644 --- a/common/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/common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaTokenUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaTokenUtils.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaTokenUtils.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaTokenUtils.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java b/server/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java similarity index 89% rename from common/src/main/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtils.java rename to server/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/server/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 result = filterForCurrentZone(Arrays.asList(zoneDao.retrieve(id))); @@ -93,7 +119,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 +138,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 +153,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) { @@ -147,6 +186,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) { @@ -158,7 +225,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 +296,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/IdentityZoneHolder.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolder.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolder.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneHolder.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneProvisioning.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneProvisioning.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneProvisioning.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java similarity index 75% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java index dac3126d528..ad0d852d1c9 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilter.java +++ b/server/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; @@ -32,7 +33,7 @@ * sent. * */ -public class IdentityZoneResolvingFilter extends OncePerRequestFilter { +public class IdentityZoneResolvingFilter extends OncePerRequestFilter implements InitializingBean { private IdentityZoneProvisioning dao; private Set defaultZoneHostnames = new HashSet<>(); @@ -48,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; } } @@ -76,6 +78,12 @@ 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")) { + 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; } @@ -93,7 +101,18 @@ 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); + } + + @Override + public void afterPropertiesSet() throws ServletException { + super.afterPropertiesSet(); + logger.info("Zone Resolving Root domains are: "+ Arrays.toString(getDefaultZoneHostnames().toArray())); } } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilter.java index fb13fd0e01c..f2c43a07111 100644 --- a/common/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/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java similarity index 93% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java index be54117919a..b1fb1b4caac 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioning.java @@ -14,7 +14,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.config.IdentityZoneConfiguration; +import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.EmptyResultDataAccessException; @@ -31,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"; @@ -41,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=?"; @@ -137,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/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java similarity index 94% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java index 712fed9b262..6d3931d513b 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsService.java @@ -14,7 +14,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.rest.ResourceMonitor; +import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; +import org.cloudfoundry.identity.uaa.resources.ResourceMonitor; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.EmptyResultDataAccessException; @@ -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/main/java/org/cloudfoundry/identity/uaa/zone/ZoneAlreadyExistsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneAlreadyExistsException.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneAlreadyExistsException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneAlreadyExistsException.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneDoesNotExistsException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneDoesNotExistsException.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneDoesNotExistsException.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneDoesNotExistsException.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneEndpointsClientDetailsValidator.java similarity index 86% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneEndpointsClientDetailsValidator.java index 99290698adf..7f632d9fffe 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidator.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/ZoneEndpointsClientDetailsValidator.java @@ -1,10 +1,12 @@ -package org.cloudfoundry.identity.uaa.oauth; +package org.cloudfoundry.identity.uaa.zone; 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.client.ClientDetailsValidator; +import org.cloudfoundry.identity.uaa.client.InvalidClientDetailsException; +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; import org.springframework.security.oauth2.provider.client.BaseClientDetails; @@ -37,7 +39,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/zone/event/IdentityProviderEventPublisher.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderEventPublisher.java similarity index 96% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderEventPublisher.java rename to server/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/server/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/server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderModifiedEvent.java similarity index 97% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityProviderModifiedEvent.java rename to server/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/server/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/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityZoneEventPublisher.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityZoneEventPublisher.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityZoneEventPublisher.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityZoneEventPublisher.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityZoneModifiedEvent.java b/server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityZoneModifiedEvent.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityZoneModifiedEvent.java rename to server/src/main/java/org/cloudfoundry/identity/uaa/zone/event/IdentityZoneModifiedEvent.java diff --git a/common/src/main/resources/log4j.properties b/server/src/main/resources/log4j.properties similarity index 85% rename from common/src/main/resources/log4j.properties rename to server/src/main/resources/log4j.properties index 76cc90e2931..4585714ecb7 100644 --- a/common/src/main/resources/log4j.properties +++ b/server/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.util.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.util.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/login/src/main/resources/login-ui.xml b/server/src/main/resources/login-ui.xml similarity index 94% rename from login/src/main/resources/login-ui.xml rename to server/src/main/resources/login-ui.xml index 0a7375a3346..4ab185b99c0 100644 --- a/login/src/main/resources/login-ui.xml +++ b/server/src/main/resources/login-ui.xml @@ -33,7 +33,7 @@ - + @@ -75,7 +75,7 @@ - + + @@ -229,7 +229,7 @@ - + @@ -295,12 +295,12 @@ - + - + - + @@ -327,7 +327,7 @@ - + @@ -342,11 +342,11 @@ - + - + - + @@ -379,7 +379,7 @@ - + @@ -400,7 +400,7 @@ - + @@ -427,10 +427,10 @@ - + - + @@ -486,11 +486,11 @@ - - - - - + + + + + @@ -508,11 +508,11 @@ - + - + - + @@ -525,16 +525,16 @@ - + - + - + @@ -550,22 +550,22 @@ - + - + - + - + @@ -575,7 +575,7 @@ - + @@ -585,7 +585,7 @@ - + @@ -594,7 +594,7 @@ - + diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_10_0__SetVerifiedToTrueForExistingUsers.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_10_0__SetVerifiedToTrueForExistingUsers.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_10_0__SetVerifiedToTrueForExistingUsers.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_10_0__SetVerifiedToTrueForExistingUsers.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_2__initial_db.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_2__initial_db.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_2__initial_db.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_2__initial_db.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_5__CreateExpiringCodeStore.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_5__CreateExpiringCodeStore.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_5__CreateExpiringCodeStore.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_5_5__CreateExpiringCodeStore.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_6_0__ExtendAuthzApprovalUsername.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_6_0__ExtendAuthzApprovalUsername.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_6_0__ExtendAuthzApprovalUsername.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_6_0__ExtendAuthzApprovalUsername.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_0__OriginAndExternalIDColumns.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_0__OriginAndExternalIDColumns.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_0__OriginAndExternalIDColumns.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_0__OriginAndExternalIDColumns.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_1__OriginForGroupMembershipColumns.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_1__OriginForGroupMembershipColumns.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_1__OriginForGroupMembershipColumns.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_1__OriginForGroupMembershipColumns.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_3__ExtendClientAuthorities.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_3__ExtendClientAuthorities.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_3__ExtendClientAuthorities.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_7_3__ExtendClientAuthorities.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_8_4__Add_AutoApproveField.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_8_4__Add_AutoApproveField.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_8_4__Add_AutoApproveField.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V1_8_4__Add_AutoApproveField.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_0__Multitenancy.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_0__Multitenancy.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_0__Multitenancy.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_0__Multitenancy.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_3__PostBootstrapIdentityZones.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_3__PostBootstrapIdentityZones.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_3__PostBootstrapIdentityZones.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_3__PostBootstrapIdentityZones.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_4__Identity_Provider_Adjustments.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_4__Identity_Provider_Adjustments.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_4__Identity_Provider_Adjustments.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_4__Identity_Provider_Adjustments.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_5__Default_uaa.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_5__Default_uaa.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_5__Default_uaa.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_5__Default_uaa.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_6__Audit_identity_zone.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_6__Audit_identity_zone.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_6__Audit_identity_zone.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_0_6__Audit_identity_zone.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_0__Identity_Provider_Update_UAA_Type.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_0__Identity_Provider_Update_UAA_Type.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_0__Identity_Provider_Update_UAA_Type.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_0__Identity_Provider_Update_UAA_Type.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_1__Add_Last_Modified_To_Client_Details.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_1__Add_Last_Modified_To_Client_Details.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_1__Add_Last_Modified_To_Client_Details.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_1_1__Add_Last_Modified_To_Client_Details.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_0__Add_SaltFieldsToUsers.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_0__Add_SaltFieldsToUsers.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_0__Add_SaltFieldsToUsers.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_0__Add_SaltFieldsToUsers.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_1__Add_Index_To_Users_Email.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_1__Add_Index_To_Users_Email.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_1__Add_Index_To_Users_Email.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_1__Add_Index_To_Users_Email.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_2__Add_Password_Last_Modified_To_Users.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_2__Add_Password_Last_Modified_To_Users.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_2__Add_Password_Last_Modified_To_Users.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_2__Add_Password_Last_Modified_To_Users.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_3__ExtendApprovalClientId.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_3__ExtendApprovalClientId.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_3__ExtendApprovalClientId.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_3__ExtendApprovalClientId.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_4__SetDefaultUaaPasswordPolicy.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_4__SetDefaultUaaPasswordPolicy.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_4__SetDefaultUaaPasswordPolicy.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_4__SetDefaultUaaPasswordPolicy.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_5__LenientDefaultUaaPasswordPolicy.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_5__LenientDefaultUaaPasswordPolicy.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_5__LenientDefaultUaaPasswordPolicy.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_5__LenientDefaultUaaPasswordPolicy.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_6__Add_Index_To_Users_Id.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_6__Add_Index_To_Users_Id.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_6__Add_Index_To_Users_Id.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_3_6__Add_Index_To_Users_Id.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_4_0__OauthCodeTableImprovements.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_4_0__OauthCodeTableImprovements.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_4_0__OauthCodeTableImprovements.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_4_0__OauthCodeTableImprovements.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_4_1__Zonify_Group_Memberships.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_4_1__Zonify_Group_Memberships.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_4_1__Zonify_Group_Memberships.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_4_1__Zonify_Group_Memberships.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_0__Fix_Verified_Column.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_0__Fix_Verified_Column.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_0__Fix_Verified_Column.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_0__Fix_Verified_Column.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_2__Zonify_Groups.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_2__Zonify_Groups.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_2__Zonify_Groups.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_2__Zonify_Groups.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_4__Zonify_Groups.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_4__Zonify_Groups.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_4__Zonify_Groups.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_5_4__Zonify_Groups.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_0_1__Fix_Client_Id_Length.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_0_1__Fix_Client_Id_Length.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_0_1__Fix_Client_Id_Length.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_0_1__Fix_Client_Id_Length.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_0__Allow_User_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_0__Allow_User_Management.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_0__Allow_User_Management.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_0__Allow_User_Management.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_1__Update_User_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_1__Update_User_Management.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_1__Update_User_Management.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_1__Update_User_Management.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_2__Drop_User_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_2__Drop_User_Management.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_2__Drop_User_Management.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_2__Drop_User_Management.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_4__Add_Config_To_Identity_Zone.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_4__Add_Config_To_Identity_Zone.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_4__Add_Config_To_Identity_Zone.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_4__Add_Config_To_Identity_Zone.sql diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/hsqldb/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql b/server/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/server/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/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/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_10_0__SetVerifiedToTrueForExistingUsers.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_10_0__SetVerifiedToTrueForExistingUsers.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_10_0__SetVerifiedToTrueForExistingUsers.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_10_0__SetVerifiedToTrueForExistingUsers.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_5_2__initial_db.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_5_2__initial_db.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_5_2__initial_db.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_5_2__initial_db.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_5_5__CreateExpiringCodeStore.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_5_5__CreateExpiringCodeStore.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_5_5__CreateExpiringCodeStore.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_5_5__CreateExpiringCodeStore.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_6_0__ExtendAuthzApprovalUsername.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_6_0__ExtendAuthzApprovalUsername.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_6_0__ExtendAuthzApprovalUsername.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_6_0__ExtendAuthzApprovalUsername.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_0__OriginAndExternalIDColumns.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_0__OriginAndExternalIDColumns.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_0__OriginAndExternalIDColumns.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_0__OriginAndExternalIDColumns.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_1__OriginForGroupMembershipColumns.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_1__OriginForGroupMembershipColumns.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_1__OriginForGroupMembershipColumns.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_1__OriginForGroupMembershipColumns.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_3__ExtendClientAuthorities.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_3__ExtendClientAuthorities.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_3__ExtendClientAuthorities.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_7_3__ExtendClientAuthorities.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_8_4__Add_AutoApproveField.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_8_4__Add_AutoApproveField.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_8_4__Add_AutoApproveField.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V1_8_4__Add_AutoApproveField.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_0__Multitenancy.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_0__Multitenancy.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_0__Multitenancy.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_0__Multitenancy.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_3__PostBootstrapIdentityZones.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_3__PostBootstrapIdentityZones.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_3__PostBootstrapIdentityZones.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_3__PostBootstrapIdentityZones.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_4__Identity_Provider_Adjustments.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_4__Identity_Provider_Adjustments.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_4__Identity_Provider_Adjustments.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_4__Identity_Provider_Adjustments.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_5__Default_uaa.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_5__Default_uaa.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_5__Default_uaa.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_5__Default_uaa.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_6__Audit_identity_zone.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_6__Audit_identity_zone.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_6__Audit_identity_zone.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_0_6__Audit_identity_zone.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_0__Identity_Provider_Update_UAA_Type.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_0__Identity_Provider_Update_UAA_Type.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_0__Identity_Provider_Update_UAA_Type.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_0__Identity_Provider_Update_UAA_Type.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_1__Add_Last_Modified_To_Client_Details.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_1__Add_Last_Modified_To_Client_Details.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_1__Add_Last_Modified_To_Client_Details.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_1_1__Add_Last_Modified_To_Client_Details.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_0__Add_SaltFieldsToUsers.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_0__Add_SaltFieldsToUsers.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_0__Add_SaltFieldsToUsers.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_0__Add_SaltFieldsToUsers.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_1__Add_Index_To_Users_Email.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_1__Add_Index_To_Users_Email.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_1__Add_Index_To_Users_Email.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_1__Add_Index_To_Users_Email.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_2__Add_Password_Last_Modified_To_Users.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_2__Add_Password_Last_Modified_To_Users.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_2__Add_Password_Last_Modified_To_Users.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_2__Add_Password_Last_Modified_To_Users.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_3__ExtendApprovalClientId.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_3__ExtendApprovalClientId.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_3__ExtendApprovalClientId.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_3__ExtendApprovalClientId.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_4__SetDefaultUaaPasswordPolicy.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_4__SetDefaultUaaPasswordPolicy.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_4__SetDefaultUaaPasswordPolicy.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_4__SetDefaultUaaPasswordPolicy.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_5__LenientDefaultUaaPasswordPolicy.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_5__LenientDefaultUaaPasswordPolicy.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_5__LenientDefaultUaaPasswordPolicy.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_5__LenientDefaultUaaPasswordPolicy.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_6__Add_Index_To_Users_Id.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_6__Add_Index_To_Users_Id.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_6__Add_Index_To_Users_Id.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_3_6__Add_Index_To_Users_Id.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_4_0__OauthCodeTableImprovements.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_4_0__OauthCodeTableImprovements.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_4_0__OauthCodeTableImprovements.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_4_0__OauthCodeTableImprovements.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_4_1__Zonify_Group_Memberships.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_4_1__Zonify_Group_Memberships.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_4_1__Zonify_Group_Memberships.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_4_1__Zonify_Group_Memberships.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_0__Fix_Verified_Column.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_0__Fix_Verified_Column.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_0__Fix_Verified_Column.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_0__Fix_Verified_Column.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_2__Zonify_Groups.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_2__Zonify_Groups.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_2__Zonify_Groups.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_2__Zonify_Groups.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_4__Zonify_Groups.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_4__Zonify_Groups.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_4__Zonify_Groups.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_5_4__Zonify_Groups.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_0_1__Fix_Client_Id_Length.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_0_1__Fix_Client_Id_Length.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_0_1__Fix_Client_Id_Length.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_0_1__Fix_Client_Id_Length.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_0__Allow_User_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_0__Allow_User_Management.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_0__Allow_User_Management.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_0__Allow_User_Management.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_1__Update_User_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_1__Update_User_Management.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_1__Update_User_Management.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_1__Update_User_Management.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_2__Drop_User_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_2__Drop_User_Management.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_2__Drop_User_Management.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_2__Drop_User_Management.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_4__Add_Config_To_Identity_Zone.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_4__Add_Config_To_Identity_Zone.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_4__Add_Config_To_Identity_Zone.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_4__Add_Config_To_Identity_Zone.sql diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/mysql/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql b/server/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/server/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/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/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_10_0__SetVerifiedToTrueForExistingUsers.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_10_0__SetVerifiedToTrueForExistingUsers.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_10_0__SetVerifiedToTrueForExistingUsers.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_10_0__SetVerifiedToTrueForExistingUsers.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_2__initial_db.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_2__initial_db.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_2__initial_db.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_2__initial_db.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_5__CreateExpiringCodeStore.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_5__CreateExpiringCodeStore.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_5__CreateExpiringCodeStore.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_5_5__CreateExpiringCodeStore.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_6_0__ExtendAuthzApprovalUsername.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_6_0__ExtendAuthzApprovalUsername.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_6_0__ExtendAuthzApprovalUsername.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_6_0__ExtendAuthzApprovalUsername.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_0__OriginAndExternalIDColumns.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_0__OriginAndExternalIDColumns.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_0__OriginAndExternalIDColumns.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_0__OriginAndExternalIDColumns.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_1__OriginForGroupMembershipColumns.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_1__OriginForGroupMembershipColumns.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_1__OriginForGroupMembershipColumns.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_1__OriginForGroupMembershipColumns.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_3__ExtendClientAuthorities.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_3__ExtendClientAuthorities.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_3__ExtendClientAuthorities.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_7_3__ExtendClientAuthorities.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_8_2__DropCorrectPostgreSQLIndex.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_8_2__DropCorrectPostgreSQLIndex.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_8_2__DropCorrectPostgreSQLIndex.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_8_2__DropCorrectPostgreSQLIndex.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_8_4__Add_AutoApproveField.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_8_4__Add_AutoApproveField.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_8_4__Add_AutoApproveField.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V1_8_4__Add_AutoApproveField.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_0__Multitenancy.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_0__Multitenancy.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_0__Multitenancy.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_0__Multitenancy.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_3__PostBootstrapIdentityZones.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_3__PostBootstrapIdentityZones.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_3__PostBootstrapIdentityZones.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_3__PostBootstrapIdentityZones.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_4__Identity_Provider_Adjustments.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_4__Identity_Provider_Adjustments.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_4__Identity_Provider_Adjustments.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_4__Identity_Provider_Adjustments.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_5__Default_uaa.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_5__Default_uaa.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_5__Default_uaa.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_5__Default_uaa.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_6__Audit_identity_zone.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_6__Audit_identity_zone.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_6__Audit_identity_zone.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_0_6__Audit_identity_zone.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_0__Identity_Provider_Update_UAA_Type.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_0__Identity_Provider_Update_UAA_Type.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_0__Identity_Provider_Update_UAA_Type.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_0__Identity_Provider_Update_UAA_Type.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_1__Add_Last_Modified_To_Client_Details.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_1__Add_Last_Modified_To_Client_Details.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_1__Add_Last_Modified_To_Client_Details.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_1_1__Add_Last_Modified_To_Client_Details.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_0__Add_SaltFieldsToUsers.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_0__Add_SaltFieldsToUsers.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_0__Add_SaltFieldsToUsers.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_0__Add_SaltFieldsToUsers.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_1__Add_Index_To_Users_Email.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_1__Add_Index_To_Users_Email.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_1__Add_Index_To_Users_Email.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_1__Add_Index_To_Users_Email.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_2__Add_Password_Last_Modified_To_Users.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_2__Add_Password_Last_Modified_To_Users.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_2__Add_Password_Last_Modified_To_Users.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_2__Add_Password_Last_Modified_To_Users.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_3__ExtendApprovalClientId.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_3__ExtendApprovalClientId.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_3__ExtendApprovalClientId.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_3__ExtendApprovalClientId.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_4__SetDefaultUaaPasswordPolicy.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_4__SetDefaultUaaPasswordPolicy.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_4__SetDefaultUaaPasswordPolicy.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_4__SetDefaultUaaPasswordPolicy.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_5__LenientDefaultUaaPasswordPolicy.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_5__LenientDefaultUaaPasswordPolicy.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_5__LenientDefaultUaaPasswordPolicy.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_5__LenientDefaultUaaPasswordPolicy.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_6__Add_Index_To_Users_Id.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_6__Add_Index_To_Users_Id.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_6__Add_Index_To_Users_Id.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_3_6__Add_Index_To_Users_Id.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_4_0__OauthCodeTableImprovements.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_4_0__OauthCodeTableImprovements.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_4_0__OauthCodeTableImprovements.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_4_0__OauthCodeTableImprovements.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_4_1__Zonify_Group_Memberships.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_4_1__Zonify_Group_Memberships.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_4_1__Zonify_Group_Memberships.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_4_1__Zonify_Group_Memberships.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_0__Fix_Verified_Column.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_0__Fix_Verified_Column.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_0__Fix_Verified_Column.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_0__Fix_Verified_Column.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_1__Fix_Null_Values_In_GroupMbr_ZoneId.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_2__Zonify_Groups.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_2__Zonify_Groups.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_2__Zonify_Groups.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_2__Zonify_Groups.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_4__Zonify_Groups.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_4__Zonify_Groups.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_4__Zonify_Groups.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_5_4__Zonify_Groups.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_0_1__Fix_Client_Id_Length.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_0_1__Fix_Client_Id_Length.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_0_1__Fix_Client_Id_Length.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_0_1__Fix_Client_Id_Length.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_0__Allow_User_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_0__Allow_User_Management.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_0__Allow_User_Management.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_0__Allow_User_Management.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_1__Update_User_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_1__Update_User_Management.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_1__Update_User_Management.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_1__Update_User_Management.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_2__Drop_User_Management.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_2__Drop_User_Management.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_2__Drop_User_Management.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_2__Drop_User_Management.sql diff --git a/common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_4__Add_Config_To_Identity_Zone.sql b/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_4__Add_Config_To_Identity_Zone.sql similarity index 100% rename from common/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_4__Add_Config_To_Identity_Zone.sql rename to server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_4__Add_Config_To_Identity_Zone.sql diff --git a/server/src/main/resources/org/cloudfoundry/identity/uaa/db/postgresql/V2_7_5__Add_Intent_to_Expiring_Code_Store.sql b/server/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/server/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/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/common/src/main/resources/spring/data-source.xml b/server/src/main/resources/spring/data-source.xml similarity index 98% rename from common/src/main/resources/spring/data-source.xml rename to server/src/main/resources/spring/data-source.xml index 041dd7a3b33..1aa48c13f64 100755 --- a/common/src/main/resources/spring/data-source.xml +++ b/server/src/main/resources/spring/data-source.xml @@ -85,7 +85,7 @@ - + diff --git a/common/src/main/resources/spring/env.xml b/server/src/main/resources/spring/env.xml similarity index 93% rename from common/src/main/resources/spring/env.xml rename to server/src/main/resources/spring/env.xml index b60fc739026..fbbb209910a 100644 --- a/common/src/main/resources/spring/env.xml +++ b/server/src/main/resources/spring/env.xml @@ -22,7 +22,7 @@ - + @@ -53,7 +53,7 @@ - + @@ -78,7 +78,7 @@ - + @@ -95,7 +95,7 @@ - + @@ -112,7 +112,7 @@ - + @@ -129,7 +129,7 @@ - + diff --git a/login/src/main/resources/templates/mail/activate.html b/server/src/main/resources/templates/mail/activate.html similarity index 100% rename from login/src/main/resources/templates/mail/activate.html rename to server/src/main/resources/templates/mail/activate.html diff --git a/login/src/main/resources/templates/mail/invite.html b/server/src/main/resources/templates/mail/invite.html similarity index 100% rename from login/src/main/resources/templates/mail/invite.html rename to server/src/main/resources/templates/mail/invite.html diff --git a/login/src/main/resources/templates/mail/reset_password.html b/server/src/main/resources/templates/mail/reset_password.html similarity index 100% rename from login/src/main/resources/templates/mail/reset_password.html rename to server/src/main/resources/templates/mail/reset_password.html diff --git a/login/src/main/resources/templates/mail/reset_password_unavailable.html b/server/src/main/resources/templates/mail/reset_password_unavailable.html similarity index 100% rename from login/src/main/resources/templates/mail/reset_password_unavailable.html rename to server/src/main/resources/templates/mail/reset_password_unavailable.html diff --git a/login/src/main/resources/templates/mail/verify_email.html b/server/src/main/resources/templates/mail/verify_email.html similarity index 100% rename from login/src/main/resources/templates/mail/verify_email.html rename to server/src/main/resources/templates/mail/verify_email.html diff --git a/login/src/main/resources/templates/web/access_confirmation.html b/server/src/main/resources/templates/web/access_confirmation.html similarity index 100% rename from login/src/main/resources/templates/web/access_confirmation.html rename to server/src/main/resources/templates/web/access_confirmation.html diff --git a/login/src/main/resources/templates/web/access_confirmation_error.html b/server/src/main/resources/templates/web/access_confirmation_error.html similarity index 74% rename from login/src/main/resources/templates/web/access_confirmation_error.html rename to server/src/main/resources/templates/web/access_confirmation_error.html index 845536d8ecb..7d454fd650c 100644 --- a/login/src/main/resources/templates/web/access_confirmation_error.html +++ b/server/src/main/resources/templates/web/access_confirmation_error.html @@ -5,6 +5,9 @@

Error Message

+
+

Error Message

+

diff --git a/login/src/main/resources/templates/web/accounts/email_sent.html b/server/src/main/resources/templates/web/accounts/email_sent.html similarity index 100% rename from login/src/main/resources/templates/web/accounts/email_sent.html rename to server/src/main/resources/templates/web/accounts/email_sent.html diff --git a/server/src/main/resources/templates/web/accounts/link_prompt.html b/server/src/main/resources/templates/web/accounts/link_prompt.html new file mode 100644 index 00000000000..5e88381da13 --- /dev/null +++ b/server/src/main/resources/templates/web/accounts/link_prompt.html @@ -0,0 +1,23 @@ + + +
+
+

Create your account

+

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

+
+
+
+

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

+
+
+ +
+ diff --git a/login/src/main/resources/templates/web/accounts/new_activation_email.html b/server/src/main/resources/templates/web/accounts/new_activation_email.html similarity index 100% rename from login/src/main/resources/templates/web/accounts/new_activation_email.html rename to server/src/main/resources/templates/web/accounts/new_activation_email.html diff --git a/login/src/main/resources/templates/web/approvals.html b/server/src/main/resources/templates/web/approvals.html similarity index 100% rename from login/src/main/resources/templates/web/approvals.html rename to server/src/main/resources/templates/web/approvals.html diff --git a/login/src/main/resources/templates/web/change_email.html b/server/src/main/resources/templates/web/change_email.html similarity index 100% rename from login/src/main/resources/templates/web/change_email.html rename to server/src/main/resources/templates/web/change_email.html diff --git a/login/src/main/resources/templates/web/change_password.html b/server/src/main/resources/templates/web/change_password.html similarity index 100% rename from login/src/main/resources/templates/web/change_password.html rename to server/src/main/resources/templates/web/change_password.html diff --git a/login/src/main/resources/templates/web/email_sent.html b/server/src/main/resources/templates/web/email_sent.html similarity index 100% rename from login/src/main/resources/templates/web/email_sent.html rename to server/src/main/resources/templates/web/email_sent.html diff --git a/login/src/main/resources/templates/web/error.html b/server/src/main/resources/templates/web/error.html similarity index 100% rename from login/src/main/resources/templates/web/error.html rename to server/src/main/resources/templates/web/error.html diff --git a/login/src/main/resources/templates/web/forgot_password.html b/server/src/main/resources/templates/web/forgot_password.html similarity index 100% rename from login/src/main/resources/templates/web/forgot_password.html rename to server/src/main/resources/templates/web/forgot_password.html diff --git a/login/src/main/resources/templates/web/home.html b/server/src/main/resources/templates/web/home.html similarity index 100% rename from login/src/main/resources/templates/web/home.html rename to server/src/main/resources/templates/web/home.html diff --git a/login/src/main/resources/templates/web/invalid_request.html b/server/src/main/resources/templates/web/invalid_request.html similarity index 100% rename from login/src/main/resources/templates/web/invalid_request.html rename to server/src/main/resources/templates/web/invalid_request.html diff --git a/login/src/main/resources/templates/web/invitations/accept_invite.html b/server/src/main/resources/templates/web/invitations/accept_invite.html similarity index 100% rename from login/src/main/resources/templates/web/invitations/accept_invite.html rename to server/src/main/resources/templates/web/invitations/accept_invite.html diff --git a/login/src/main/resources/templates/web/invitations/invite_sent.html b/server/src/main/resources/templates/web/invitations/invite_sent.html similarity index 100% rename from login/src/main/resources/templates/web/invitations/invite_sent.html rename to server/src/main/resources/templates/web/invitations/invite_sent.html diff --git a/login/src/main/resources/templates/web/invitations/new_invite.html b/server/src/main/resources/templates/web/invitations/new_invite.html similarity index 100% rename from login/src/main/resources/templates/web/invitations/new_invite.html rename to server/src/main/resources/templates/web/invitations/new_invite.html diff --git a/login/src/main/resources/templates/web/layouts/main.html b/server/src/main/resources/templates/web/layouts/main.html similarity index 100% rename from login/src/main/resources/templates/web/layouts/main.html rename to server/src/main/resources/templates/web/layouts/main.html diff --git a/login/src/main/resources/templates/web/login.html b/server/src/main/resources/templates/web/login.html similarity index 100% rename from login/src/main/resources/templates/web/login.html rename to server/src/main/resources/templates/web/login.html diff --git a/login/src/main/resources/templates/web/nav.html b/server/src/main/resources/templates/web/nav.html similarity index 100% rename from login/src/main/resources/templates/web/nav.html rename to server/src/main/resources/templates/web/nav.html diff --git a/login/src/main/resources/templates/web/passcode.html b/server/src/main/resources/templates/web/passcode.html similarity index 100% rename from login/src/main/resources/templates/web/passcode.html rename to server/src/main/resources/templates/web/passcode.html diff --git a/login/src/main/resources/templates/web/pivotal_links.html b/server/src/main/resources/templates/web/pivotal_links.html similarity index 100% rename from login/src/main/resources/templates/web/pivotal_links.html rename to server/src/main/resources/templates/web/pivotal_links.html diff --git a/login/src/main/resources/templates/web/reset_password.html b/server/src/main/resources/templates/web/reset_password.html similarity index 100% rename from login/src/main/resources/templates/web/reset_password.html rename to server/src/main/resources/templates/web/reset_password.html diff --git a/login/src/main/resources/templates/web/saml_error.html b/server/src/main/resources/templates/web/saml_error.html similarity index 100% rename from login/src/main/resources/templates/web/saml_error.html rename to server/src/main/resources/templates/web/saml_error.html diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/ServerRunning.java b/server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunning.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/ServerRunning.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/ServerRunning.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/TestClassNullifier.java b/server/src/test/java/org/cloudfoundry/identity/uaa/TestClassNullifier.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/TestClassNullifier.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/TestClassNullifier.java diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/password/PasswordChangeEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordChangeEndpointTests.java similarity index 96% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/password/PasswordChangeEndpointTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordChangeEndpointTests.java index 01a741b39f7..3bcf594c14d 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/password/PasswordChangeEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordChangeEndpointTests.java @@ -10,11 +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.password; +package org.cloudfoundry.identity.uaa.account; -import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; -import org.cloudfoundry.identity.uaa.rest.jdbc.DefaultLimitSqlAdapter; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.DefaultLimitSqlAdapter; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; 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/password/PasswordCheckEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordCheckEndpointTests.java similarity index 92% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/password/PasswordCheckEndpointTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordCheckEndpointTests.java index 9e704d137b2..7e7a56cc15c 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/password/PasswordCheckEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordCheckEndpointTests.java @@ -11,8 +11,9 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.password; +package org.cloudfoundry.identity.uaa.account; +import org.cloudfoundry.identity.uaa.account.PasswordCheckEndpoint; import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordScore; import org.junit.Test; import org.springframework.mock.web.MockHttpServletResponse; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/password/UaaPasswordTestFactory.java b/server/src/test/java/org/cloudfoundry/identity/uaa/account/UaaPasswordTestFactory.java similarity index 96% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/password/UaaPasswordTestFactory.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/account/UaaPasswordTestFactory.java index 9933bbfba7d..5a262ebb873 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/password/UaaPasswordTestFactory.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/account/UaaPasswordTestFactory.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.password; +package org.cloudfoundry.identity.uaa.account; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeEventPublisherTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeEventPublisherTests.java similarity index 96% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeEventPublisherTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeEventPublisherTests.java index 7fe55fef4dd..7374d652e3e 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/password/event/PasswordChangeEventPublisherTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/account/event/PasswordChangeEventPublisherTests.java @@ -11,11 +11,11 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.password.event; +package org.cloudfoundry.identity.uaa.account.event; import java.util.Arrays; -import org.cloudfoundry.identity.uaa.password.UaaPasswordTestFactory; +import org.cloudfoundry.identity.uaa.account.UaaPasswordTestFactory; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.ScimUserTestFactory; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/audit/AuditEventTypeTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditServiceTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditServiceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcAuditServiceTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcFailedLoginCountingAuditServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcFailedLoginCountingAuditServiceTests.java similarity index 82% rename from common/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcFailedLoginCountingAuditServiceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcFailedLoginCountingAuditServiceTests.java index 5b11a5880bb..59db6a8debc 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcFailedLoginCountingAuditServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/JdbcFailedLoginCountingAuditServiceTests.java @@ -18,6 +18,8 @@ import static org.cloudfoundry.identity.uaa.audit.AuditEventType.PasswordChangeSuccess; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAuthenticationFailure; import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAuthenticationSuccess; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; @@ -54,27 +56,27 @@ public void userAuthenticationFailureAuditSucceeds() throws Exception { public void userAuthenticationFailureDeletesOldData() throws Exception { long now = System.currentTimeMillis(); auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe")); - assertEquals(1, jdbcTemplate.queryForInt("select count(*) from sec_audit where principal_id='1'")); + assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(1)); // Set the created column to 3 hours past jdbcTemplate.update("update sec_audit set created=?", new Timestamp(now - 3 * 3600 * 1000)); auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe")); - assertEquals(1, jdbcTemplate.queryForInt("select count(*) from sec_audit where principal_id='1'")); + assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(1)); } @Test public void userAuthenticationSuccessResetsData() throws Exception { auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe")); - assertEquals(1, jdbcTemplate.queryForInt("select count(*) from sec_audit where principal_id='1'")); + assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(1)); auditService.log(getAuditEvent(UserAuthenticationSuccess, "1", "joe")); - assertEquals(0, jdbcTemplate.queryForInt("select count(*) from sec_audit where principal_id='1'")); + assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(0)); } @Test public void userPasswordChangeSuccessResetsData() throws Exception { auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe")); - assertEquals(1, jdbcTemplate.queryForInt("select count(*) from sec_audit where principal_id='1'")); + assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(1)); auditService.log(getAuditEvent(PasswordChangeSuccess, "1", "joe")); - assertEquals(0, jdbcTemplate.queryForInt("select count(*) from sec_audit where principal_id='1'")); + assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(0)); } @Test diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayoutTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayoutTest.java new file mode 100644 index 00000000000..a4d58d53071 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/LineAwareLayoutTest.java @@ -0,0 +1,129 @@ +/* + * ****************************************************************************** + * 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.cloudfoundry.identity.uaa.util.LineAwareLayout; +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()); + } +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java similarity index 74% rename from common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java index d064c332ae7..844420b3bc7 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/ApprovalModifiedEventTest.java @@ -2,7 +2,7 @@ import org.cloudfoundry.identity.uaa.audit.AuditEvent; import org.cloudfoundry.identity.uaa.audit.AuditEventType; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.test.MockAuthentication; import org.junit.Assert; import org.junit.Test; @@ -16,7 +16,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); @@ -24,4 +29,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/audit/event/AuditListenerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AuditListenerTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AuditListenerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AuditListenerTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/AuthzAuthenticationFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/AuthzAuthenticationFilterTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/AuthzAuthenticationFilterTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/AuthzAuthenticationFilterTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java similarity index 93% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java index 94e98068de3..c2f269e1c1f 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/SessionResetFilterTests.java +++ b/server/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.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.Map; +import java.util.List; 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; @@ -63,7 +61,6 @@ public class SessionResetFilterTests { Date yesterday; UaaUser user; UaaUser userWithNoPasswordModification; - Map users; @Before public void setUpFilter() throws Exception { @@ -95,7 +92,7 @@ private void addUsersToInMemoryDb() { "family name", yesterday, yesterday, - Origin.UAA, + OriginKeys.UAA, null, true, IdentityZone.getUaa().getId(), @@ -113,7 +110,7 @@ private void addUsersToInMemoryDb() { "family name", yesterday, yesterday, - Origin.UAA, + OriginKeys.UAA, null, true, IdentityZone.getUaa().getId(), @@ -121,9 +118,9 @@ private void addUsersToInMemoryDb() { null ); - users = new HashMap<>(); - users.put(user.getId(), user); - users.put(userWithNoPasswordModification.getId(), userWithNoPasswordModification); + List users = new ArrayList<>(); + users.add(user); + users.add(userWithNoPasswordModification); userDatabase = new InMemoryUaaUserDatabase(users); } @@ -190,7 +187,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 +200,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/UaaAuthenticationSerializationTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializationTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializationTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationSerializationTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationTestFactory.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationTestFactory.java similarity index 93% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationTestFactory.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthenticationTestFactory.java index 2126d15eec5..3dc27792f32 100644 --- a/common/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/login/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java similarity index 100% rename from login/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/WhitelistLogoutHandlerTest.java 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..1777c27fd43 --- /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.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; +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); + + verify(scimUserProvisioning).verifyUser(eq(id), eq(-1)); + } + + @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()).verifyUser(anyString(), anyInt()); + } + +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpointTests.java similarity index 96% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpointTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpointTests.java index fc5f012ded4..bcc3003b167 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/RemoteAuthenticationEndpointTests.java @@ -18,8 +18,9 @@ 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.RemoteAuthenticationEndpoint; 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 +45,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/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java similarity index 78% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java index 8809ca755f7..9ce45292c11 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/AuthzAuthenticationManagerTests.java +++ b/server/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; @@ -23,17 +22,18 @@ 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.zone.IdentityProviderProvisioning; +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.zone.UaaIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.event.AuthenticationFailureLockedEvent; @@ -58,7 +58,6 @@ import static org.mockito.Matchers.isA; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; /** @@ -76,33 +75,34 @@ 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(), - Origin.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); mgr = new AuthzAuthenticationManager(db, encoder, providerProvisioning); mgr.setApplicationEventPublisher(publisher); - mgr.setOrigin(Origin.UAA); + 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", 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()); @@ -131,31 +131,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); @@ -167,7 +167,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(); @@ -179,7 +179,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); @@ -188,7 +188,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(); @@ -216,19 +216,34 @@ 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", 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()); + } + + @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 { 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"); @@ -241,7 +256,7 @@ public void unverifiedAuthenticationFailsWhenNotAllowed() throws Exception { 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/ChainedAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManagerTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManagerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ChainedAuthenticationManagerTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java similarity index 84% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java index 27f69e6824a..b75f979f5f6 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/CheckIdpEnabledAuthenticationManagerTest.java @@ -15,13 +15,13 @@ 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; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.junit.Before; import org.junit.Test; import org.springframework.security.authentication.ProviderNotFoundException; @@ -44,12 +44,12 @@ 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").withVerified(true)); 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/ExternalLoginAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java similarity index 84% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java index 56117b9a0d1..3e7683cbd93 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManagerTest.java @@ -3,10 +3,12 @@ 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.extension.ExtendedLdapUserImpl; +import org.cloudfoundry.identity.uaa.provider.ldap.ExtendedLdapUserDetails; +import org.cloudfoundry.identity.uaa.provider.ldap.extension.ExtendedLdapUserImpl; 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; @@ -22,11 +24,15 @@ 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.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); @@ -282,8 +295,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); @@ -335,6 +346,49 @@ 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); + 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(username),eq(origin))) + .thenThrow(new UsernameNotFoundException("")); + 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/authentication/manager/KeystoneAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/KeystoneAuthenticationManagerTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/KeystoneAuthenticationManagerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/KeystoneAuthenticationManagerTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java similarity index 95% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java index c736c23a5a4..2bc5006c4c0 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java @@ -14,16 +14,16 @@ */ 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.ldap.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.ldap.extension.ExtendedLdapUserImpl; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.Before; import org.junit.Test; @@ -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; @@ -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/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java similarity index 94% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LoginAuthenticationManagerTests.java rename to server/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/server/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/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java similarity index 93% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java index 4e7ebebb0e1..eed5ad1d953 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PeriodLockoutPolicyTests.java +++ b/server/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.authentication.Origin; -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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.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; @@ -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/authentication/manager/ScopeAuthenticationManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationManagerTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationManagerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/ScopeAuthenticationManagerTests.java diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManagerTests.java similarity index 95% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManagerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManagerTests.java index 45f99d88ff3..a07a1a35113 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/authorization/external/LdapGroupMappingAuthorizationManagerTests.java @@ -20,8 +20,10 @@ import static org.hamcrest.collection.IsArrayContainingInAnyOrder.arrayContainingInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import org.cloudfoundry.identity.uaa.ldap.extension.LdapAuthority; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; + +import org.cloudfoundry.identity.uaa.authorization.LdapGroupMappingAuthorizationManager; +import org.cloudfoundry.identity.uaa.provider.ldap.extension.LdapAuthority; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimExternalGroupBootstrap; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrapTests.java similarity index 98% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrapTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrapTests.java index 84936fc3201..45fef8abb35 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminBootstrapTests.java @@ -11,9 +11,10 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth; +package org.cloudfoundry.identity.uaa.client; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.client.ClientAdminBootstrap; +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/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsTests.java similarity index 98% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsTests.java index 4d011143aa5..2bf270c5eb4 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientAdminEndpointsTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/client/ClientAdminEndpointsTests.java @@ -11,7 +11,7 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.oauth; +package org.cloudfoundry.identity.uaa.client; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -32,13 +32,14 @@ import java.util.Map; import org.cloudfoundry.identity.uaa.error.UaaException; -import org.cloudfoundry.identity.uaa.oauth.ClientDetailsValidator.Mode; -import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; +import org.cloudfoundry.identity.uaa.client.ClientDetailsValidator.Mode; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; -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.rest.SimpleAttributeNameMapper; +import org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest; +import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; +import org.cloudfoundry.identity.uaa.resources.ResourceMonitor; +import org.cloudfoundry.identity.uaa.resources.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SimpleAttributeNameMapper; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.security.StubSecurityContextAccessor; import org.junit.Before; @@ -248,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}); } @@ -822,4 +825,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/client/OAuth2ClientAuthenticationFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/client/OAuth2ClientAuthenticationFilterTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/client/OAuth2ClientAuthenticationFilterTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/client/OAuth2ClientAuthenticationFilterTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/client/OAuthClientAuthenticationFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/client/OAuthClientAuthenticationFilterTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/client/OAuthClientAuthenticationFilterTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/client/OAuthClientAuthenticationFilterTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetailsSourceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetailsSourceTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetailsSourceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/client/SocialClientUserDetailsSourceTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/client/SourceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/client/SourceTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/client/SourceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/client/SourceTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpointsTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpointsTests.java similarity index 98% rename from common/src/test/java/org/cloudfoundry/identity/uaa/codestore/CodeStoreEndpointsTests.java rename to server/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/server/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/server/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStoreTests.java similarity index 76% rename from common/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStoreTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStoreTests.java index c990973fefa..1bd0090082f 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStoreTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeStoreTests.java @@ -12,33 +12,25 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.codestore; -import java.sql.SQLException; -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; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -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.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +import org.mockito.Mockito; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Collection; + @RunWith(Parameterized.class) public class ExpiringCodeStoreTests extends JdbcTestBase { @@ -75,69 +67,69 @@ 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); + Assert.assertNotNull(expiringCode); - assertNotNull(expiringCode.getCode()); - assertTrue(expiringCode.getCode().trim().length() > 0); + Assert.assertNotNull(expiringCode.getCode()); + Assert.assertTrue(expiringCode.getCode().trim().length() > 0); - assertEquals(expiresAt, expiringCode.getExpiresAt()); + Assert.assertEquals(expiresAt, expiringCode.getExpiresAt()); - assertEquals(data, expiringCode.getData()); + Assert.assertEquals(data, expiringCode.getData()); } @Test(expected = NullPointerException.class) 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) public void testGenerateCodeWithDuplicateCode() throws Exception { - RandomValueStringGenerator generator = mock(RandomValueStringGenerator.class); - when(generator.generate()).thenReturn("duplicate"); + RandomValueStringGenerator generator = Mockito.mock(RandomValueStringGenerator.class); + Mockito.when(generator.generate()).thenReturn("duplicate"); expiringCodeStore.setGenerator(generator); 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()); - assertEquals(generatedCode, retrievedCode); + Assert.assertEquals(generatedCode, retrievedCode); - assertNull(expiringCodeStore.retrieveCode(generatedCode.getCode())); + Assert.assertNull(expiringCodeStore.retrieveCode(generatedCode.getCode())); } @Test public void testRetrieveCodeWithCodeNotFound() throws Exception { ExpiringCode retrievedCode = expiringCodeStore.retrieveCode("unknown"); - assertNull(retrievedCode); + Assert.assertNull(retrievedCode); } @Test(expected = NullPointerException.class) @@ -151,34 +143,45 @@ 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); + Assert.assertEquals(expiringCode, actualCode); } @Test 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); + Assert.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()); + + Assert.assertNull(retrievedCode); } @Test public void testDatabaseDown() throws Exception { if (JdbcExpiringCodeStore.class == expiringCodeStoreClass) { - javax.sql.DataSource ds = mock(javax.sql.DataSource.class); - when(ds.getConnection()).thenThrow(new SQLException()); + javax.sql.DataSource ds = Mockito.mock(javax.sql.DataSource.class); + Mockito.when(ds.getConnection()).thenThrow(new SQLException()); ((JdbcExpiringCodeStore) expiringCodeStore).setDataSource(ds); try { String data = "{}"; Timestamp expiresAt = new Timestamp(System.currentTimeMillis() + 10000000); - ExpiringCode generatedCode = expiringCodeStore.generateCode(data, expiresAt); - fail("Database is down, should not generate a code"); + ExpiringCode generatedCode = expiringCodeStore.generateCode(data, expiresAt, null); + Assert.fail("Database is down, should not generate a code"); } catch (DataAccessException x) { } @@ -189,7 +192,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/common/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/codestore/ExpiringCodeTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentMapFactoryBeanTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentMapFactoryBeanTests.java similarity index 95% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentMapFactoryBeanTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentMapFactoryBeanTests.java index be49e4011e1..03cdb74a2db 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentMapFactoryBeanTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentMapFactoryBeanTests.java @@ -19,6 +19,8 @@ import java.util.Map; import java.util.Properties; +import org.cloudfoundry.identity.uaa.impl.config.EnvironmentMapFactoryBean; +import org.cloudfoundry.identity.uaa.impl.config.NestedMapPropertySource; import org.junit.Test; import org.springframework.core.env.StandardEnvironment; import org.springframework.util.StringUtils; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentPropertiesFactoryBeanTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentPropertiesFactoryBeanTests.java similarity index 96% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentPropertiesFactoryBeanTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentPropertiesFactoryBeanTests.java index d5bfdacf24f..3c212cd4269 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentPropertiesFactoryBeanTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/EnvironmentPropertiesFactoryBeanTests.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.Properties; +import org.cloudfoundry.identity.uaa.impl.config.EnvironmentPropertiesFactoryBean; import org.junit.Test; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.StandardEnvironment; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java similarity index 83% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java index 6e7cc865797..c5134e9625e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java @@ -14,18 +14,21 @@ package org.cloudfoundry.identity.uaa.config; -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; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.impl.config.IdentityProviderBootstrap; +import org.cloudfoundry.identity.uaa.provider.KeystoneIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; +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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.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.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -38,9 +41,10 @@ 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; import static org.junit.Assert.assertNotNull; @@ -60,16 +64,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 +96,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 +110,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 +170,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 +189,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 +252,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 +285,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 +294,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 +307,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 +315,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 +328,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 +336,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 +349,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 +357,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 +370,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 +378,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 +407,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 +422,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 +444,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 +460,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 +477,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/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java new file mode 100644 index 00000000000..83366fbe2f3 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationBootstrapTests.java @@ -0,0 +1,81 @@ +package org.cloudfoundry.identity.uaa.config; + +import org.cloudfoundry.identity.uaa.impl.config.IdentityZoneConfigurationBootstrap; +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.KeyPair; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; +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. + *

+ * 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 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(); + 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/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java similarity index 86% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java index 608b37c416c..ce8ea665a38 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityZoneConfigurationTests.java +++ b/server/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; @@ -54,7 +55,7 @@ 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()); + assertTrue(definition.getSamlConfig().isRequestSigned()); assertFalse(definition.getSamlConfig().isWantAssertionSigned()); definition.getSamlConfig().setWantAssertionSigned(true); definition.getSamlConfig().setRequestSigned(true); @@ -62,6 +63,12 @@ public void testDeserialize_Without_SamlConfig() { 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()); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/NestedMapPropertySourceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/NestedMapPropertySourceTests.java similarity index 98% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/NestedMapPropertySourceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/config/NestedMapPropertySourceTests.java index 29141c358b0..1685b88a585 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/NestedMapPropertySourceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/NestedMapPropertySourceTests.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.Map; +import org.cloudfoundry.identity.uaa.impl.config.NestedMapPropertySource; import org.junit.Test; import org.yaml.snakeyaml.Yaml; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/PasswordPolicyTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/PasswordPolicyTest.java similarity index 96% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/PasswordPolicyTest.java rename to server/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/server/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/config/YamlBindingTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlBindingTests.java similarity index 99% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlBindingTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlBindingTests.java index 4ee71869d8c..49bd6b39ec3 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlBindingTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlBindingTests.java @@ -32,6 +32,7 @@ import javax.validation.constraints.NotNull; import org.cloudfoundry.identity.uaa.config.YamlBindingTests.OAuthConfiguration.OAuthConfigurationValidator; +import org.cloudfoundry.identity.uaa.impl.config.YamlPropertiesFactoryBean; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlConfigurationValidatorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlConfigurationValidatorTests.java similarity index 93% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlConfigurationValidatorTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlConfigurationValidatorTests.java index bf06f5ef40a..a02d10299b8 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlConfigurationValidatorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlConfigurationValidatorTests.java @@ -17,6 +17,8 @@ import javax.validation.ConstraintViolationException; import javax.validation.constraints.NotNull; +import org.cloudfoundry.identity.uaa.impl.config.CustomPropertyConstructor; +import org.cloudfoundry.identity.uaa.impl.config.YamlConfigurationValidator; import org.junit.Test; import org.yaml.snakeyaml.error.YAMLException; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlMapFactoryBeanTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlMapFactoryBeanTests.java similarity index 95% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlMapFactoryBeanTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlMapFactoryBeanTests.java index 689a4f5670d..166aa4c3eb1 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlMapFactoryBeanTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlMapFactoryBeanTests.java @@ -19,7 +19,8 @@ import java.io.InputStream; import java.util.Map; -import org.cloudfoundry.identity.uaa.config.YamlProcessor.ResolutionMethod; +import org.cloudfoundry.identity.uaa.impl.config.YamlMapFactoryBean; +import org.cloudfoundry.identity.uaa.impl.config.YamlProcessor.ResolutionMethod; import org.junit.Test; import org.springframework.core.io.AbstractResource; import org.springframework.core.io.ByteArrayResource; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlPropertiesFactoryBeanTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlPropertiesFactoryBeanTests.java similarity index 98% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlPropertiesFactoryBeanTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlPropertiesFactoryBeanTests.java index 9bc3546d8fe..7b8248b251e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlPropertiesFactoryBeanTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlPropertiesFactoryBeanTests.java @@ -18,7 +18,8 @@ import java.util.Map; import java.util.Properties; -import org.cloudfoundry.identity.uaa.config.YamlProcessor.ResolutionMethod; +import org.cloudfoundry.identity.uaa.impl.config.YamlProcessor.ResolutionMethod; +import org.cloudfoundry.identity.uaa.impl.config.YamlPropertiesFactoryBean; import org.junit.Ignore; import org.junit.Test; import org.springframework.core.io.ByteArrayResource; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializerTests.java similarity index 85% rename from common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializerTests.java index be17e2b49b2..289d5ff8534 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/YamlServletProfileInitializerTests.java @@ -12,17 +12,8 @@ *******************************************************************************/ 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.impl.config.SystemEnvironmentAccessor; +import org.cloudfoundry.identity.uaa.impl.config.YamlServletProfileInitializer; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -40,6 +31,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 +233,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/common/src/test/java/org/cloudfoundry/identity/uaa/db/StoreSubDomainAsLowerCase_V2_7_3_Tests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/db/StoreSubDomainAsLowerCase_V2_7_3_Tests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/db/StoreSubDomainAsLowerCase_V2_7_3_Tests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/db/StoreSubDomainAsLowerCase_V2_7_3_Tests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/db/TableAndColumnNormalizationTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/db/TableAndColumnNormalizationTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/db/TableAndColumnNormalizationTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/db/TableAndColumnNormalizationTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/db/TestSchemaValidation.java b/server/src/test/java/org/cloudfoundry/identity/uaa/db/TestSchemaValidation.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/db/TestSchemaValidation.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/db/TestSchemaValidation.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/db/TestThatClientIdIsVchar255.java b/server/src/test/java/org/cloudfoundry/identity/uaa/db/TestThatClientIdIsVchar255.java similarity index 97% rename from common/src/test/java/org/cloudfoundry/identity/uaa/db/TestThatClientIdIsVchar255.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/db/TestThatClientIdIsVchar255.java index e692a025d55..b30b37e266a 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/db/TestThatClientIdIsVchar255.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/db/TestThatClientIdIsVchar255.java @@ -41,7 +41,7 @@ public TestThatClientIdIsVchar255(String springProfile, String tableName, String this.columnName = columnName; } - @Parameterized.Parameters(name = "{index}: db[{0}]; table[{1}]") + @Parameterized.Parameters(name = "{index}: org.cloudfoundry.identity.uaa.db[{0}]; table[{1}]") public static Collection data() { return Arrays.asList(new Object[][]{ {null, "authz_approvals", "client_id"}, diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/error/ConvertingExceptionViewTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/error/ConvertingExceptionViewTests.java similarity index 95% rename from common/src/test/java/org/cloudfoundry/identity/uaa/error/ConvertingExceptionViewTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/error/ConvertingExceptionViewTests.java index 2e2ba05a0d4..9f647f2ac63 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/error/ConvertingExceptionViewTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/error/ConvertingExceptionViewTests.java @@ -18,6 +18,8 @@ import java.util.HashMap; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; import org.junit.Test; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/error/UaaExceptionTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/error/UaaExceptionTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/error/UaaExceptionTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/error/UaaExceptionTests.java diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsAuthenticationTrustResolverTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsAuthenticationTrustResolverTest.java similarity index 100% rename from login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsAuthenticationTrustResolverTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsAuthenticationTrustResolverTest.java diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java similarity index 89% rename from login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java index 2669f434234..3787f2c648b 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/invitations/InvitationsControllerTest.java @@ -1,15 +1,16 @@ 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.ldap.ExtendedLdapUserDetails; -import org.cloudfoundry.identity.uaa.login.BuildInfo; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.ldap.ExtendedLdapUserDetails; +import org.cloudfoundry.identity.uaa.home.BuildInfo; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; @@ -18,8 +19,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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.After; @@ -52,10 +52,10 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import java.sql.Timestamp; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import static com.google.common.collect.Lists.newArrayList; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -133,10 +133,10 @@ 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(Origin.UAA); + provider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); @@ -156,12 +156,19 @@ 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 = 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(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"); @@ -177,11 +184,11 @@ 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(Origin.LDAP); + provider.setType(OriginKeys.LDAP); when(providerProvisioning.retrieveByOrigin(eq("ldap"), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") @@ -210,8 +217,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); @@ -233,7 +240,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") @@ -254,7 +261,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); @@ -270,7 +277,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") @@ -298,11 +305,11 @@ 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(Origin.UAA); + provider.setType(OriginKeys.UAA); when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(provider); MockHttpServletRequestBuilder get = get("/invitations/accept") .param("code", "the_secret_code"); @@ -326,7 +333,7 @@ public void testAcceptInvitePageWithExpiredCode() throws Exception { @Test public void testAcceptInviteWithContraveningPassword() throws Exception { - doThrow(new InvalidPasswordException(newArrayList("Msg 2c", "Msg 1c"))).when(passwordValidator).validate("a"); + doThrow(new InvalidPasswordException(Arrays.asList("Msg 2c", "Msg 1c"))).when(passwordValidator).validate("a"); MockHttpServletRequestBuilder post = startAcceptInviteFlow("a"); mockMvc.perform(post) @@ -352,7 +359,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 +371,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 +394,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 +417,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/AccountsControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerTest.java similarity index 96% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerTest.java index 3903b3ddd7a..60d25081efb 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/AccountsControllerTest.java @@ -15,7 +15,10 @@ import org.cloudfoundry.identity.uaa.TestClassNullifier; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.error.UaaException; +import org.cloudfoundry.identity.uaa.home.BuildInfo; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; +import org.cloudfoundry.identity.uaa.account.AccountCreationService; +import org.cloudfoundry.identity.uaa.account.AccountsController; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.junit.After; import org.junit.Before; @@ -40,7 +43,8 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; -import static com.google.common.collect.Lists.newArrayList; +import java.util.Arrays; + import static org.junit.Assert.*; import static org.mockito.Mockito.doThrow; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -124,7 +128,7 @@ public void testSendActivationEmailWithUserNameConflict() throws Exception { @Test public void testInvalidPassword() throws Exception { - doThrow(new InvalidPasswordException(newArrayList("Msg 2", "Msg 1"))).when(accountCreationService).beginActivation("user1@example.com", "password", "app", null); + doThrow(new InvalidPasswordException(Arrays.asList("Msg 2", "Msg 1"))).when(accountCreationService).beginActivation("user1@example.com", "password", "app", null); MockHttpServletRequestBuilder post = post("/create_account.do") .param("email", "user1@example.com") diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java similarity index 89% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java index 07a6f0fe949..38a0037a536 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/AutologinAuthenticationManagerTest.java @@ -1,17 +1,17 @@ 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.authentication.manager.AutologinAuthenticationManager; 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.error.InvalidCodeException; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.authentication.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,9 +62,9 @@ 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))); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData), null)); Authentication authenticate = manager.authenticate(authenticationToken); @@ -72,7 +72,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,9 +84,9 @@ 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))); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData), null)); manager.authenticate(authenticationToken); } @@ -96,9 +96,9 @@ 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))); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData), null)); manager.authenticate(authenticationToken); } @@ -115,8 +115,8 @@ 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); - when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData))); + codeData.put(OriginKeys.ORIGIN, OriginKeys.UAA); + when(codeStore.retrieveCode("the_secret_code")).thenReturn(new ExpiringCode("the_secret_code", new Timestamp(123), JsonUtils.writeValueAsString(codeData), null)); manager.authenticate(authenticationToken); } @@ -125,10 +125,10 @@ 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); } -} \ No newline at end of file +} diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ChangeEmailControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ChangeEmailControllerTest.java similarity index 95% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/ChangeEmailControllerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/ChangeEmailControllerTest.java index ebd6ca65a6d..eeff0e3ecb9 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ChangeEmailControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ChangeEmailControllerTest.java @@ -1,11 +1,14 @@ 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.home.BuildInfo; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; +import org.cloudfoundry.identity.uaa.account.ChangeEmailController; +import org.cloudfoundry.identity.uaa.account.ChangeEmailService; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; @@ -180,7 +183,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 +208,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 +260,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 +307,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/ChangePasswordControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ChangePasswordControllerTest.java similarity index 95% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/ChangePasswordControllerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/ChangePasswordControllerTest.java index 24b3070705d..6e1e66ac20e 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ChangePasswordControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ChangePasswordControllerTest.java @@ -13,6 +13,8 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.TestClassNullifier; +import org.cloudfoundry.identity.uaa.account.ChangePasswordController; +import org.cloudfoundry.identity.uaa.account.ChangePasswordService; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.junit.After; @@ -25,12 +27,10 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.client.RestClientException; import org.springframework.web.servlet.view.InternalResourceViewResolver; import java.util.Arrays; -import static com.google.common.collect.Lists.newArrayList; import static org.mockito.Mockito.*; import static org.mockito.Mockito.doThrow; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; @@ -103,7 +103,7 @@ public void changePassword_ConfirmationPasswordDoesNotMatch() throws Exception { @Test public void changePassword_PasswordPolicyViolationReported() throws Exception { - doThrow(new InvalidPasswordException(newArrayList("Msg 2b", "Msg 1b"))).when(changePasswordService).changePassword("bob", "secret", "new secret"); + doThrow(new InvalidPasswordException(Arrays.asList("Msg 2b", "Msg 1b"))).when(changePasswordService).changePassword("bob", "secret", "new secret"); MockHttpServletRequestBuilder post = createRequest("secret", "new secret", "new secret"); mockMvc.perform(post) diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java similarity index 96% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java index 6110493e6d6..1141ce02347 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailAccountCreationServiceTests.java @@ -1,10 +1,14 @@ 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.message.MessageService; +import org.cloudfoundry.identity.uaa.message.MessageType; +import org.cloudfoundry.identity.uaa.account.AccountCreationService; +import org.cloudfoundry.identity.uaa.account.EmailAccountCreationService; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; @@ -110,7 +114,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 +140,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 +156,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 +183,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"); @@ -292,7 +296,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); @@ -305,7 +309,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/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java similarity index 95% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java index 1eedda57d35..36294a847f1 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailChangeEmailServiceTest.java @@ -12,7 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; -import static org.cloudfoundry.identity.uaa.login.EmailChangeEmailService.CHANGE_EMAIL_REDIRECT_URL; +import static org.cloudfoundry.identity.uaa.account.EmailChangeEmailService.CHANGE_EMAIL_REDIRECT_URL; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertNull; @@ -30,6 +30,10 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.error.UaaException; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; +import org.cloudfoundry.identity.uaa.message.EmailService; +import org.cloudfoundry.identity.uaa.message.MessageService; +import org.cloudfoundry.identity.uaa.message.MessageType; +import org.cloudfoundry.identity.uaa.account.EmailChangeEmailService; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.endpoints.ChangeEmailEndpoints; @@ -205,7 +209,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 +250,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 +278,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/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java similarity index 93% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java index 75f7d6ac4e7..bc478a62197 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailInvitationsServiceTests.java @@ -1,9 +1,11 @@ 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.invitations.EmailInvitationsService; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; +import org.cloudfoundry.identity.uaa.message.MessageService; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -36,9 +38,9 @@ import java.util.HashMap; import java.util.Map; -import static org.cloudfoundry.identity.uaa.authentication.Origin.UAA; -import static org.cloudfoundry.identity.uaa.login.EmailInvitationsService.EMAIL; -import static org.cloudfoundry.identity.uaa.login.EmailInvitationsService.USER_ID; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA; +import static org.cloudfoundry.identity.uaa.invitations.EmailInvitationsService.EMAIL; +import static org.cloudfoundry.identity.uaa.invitations.EmailInvitationsService.USER_ID; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyObject; @@ -98,7 +100,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()); @@ -109,7 +111,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); @@ -119,7 +121,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 +145,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 +168,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(); @@ -183,7 +185,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); @@ -196,7 +198,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/EmailServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailServiceTests.java similarity index 92% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailServiceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailServiceTests.java index 9dff995195e..2340713ac91 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/EmailServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/EmailServiceTests.java @@ -1,6 +1,8 @@ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.login.util.FakeJavaMailSender; +import org.cloudfoundry.identity.uaa.message.util.FakeJavaMailSender; +import org.cloudfoundry.identity.uaa.message.EmailService; +import org.cloudfoundry.identity.uaa.message.MessageType; import org.junit.Before; import org.junit.Test; diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java similarity index 97% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java index 2ca2bec68e0..f20c62e4a56 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/HomeControllerViewTests.java @@ -1,6 +1,9 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.TestClassNullifier; +import org.cloudfoundry.identity.uaa.home.BuildInfo; +import org.cloudfoundry.identity.uaa.home.HomeController; +import org.cloudfoundry.identity.uaa.home.TileInfo; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java similarity index 95% rename from common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index 601c4931a68..d26ac88e8a7 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -1,15 +1,17 @@ -package org.cloudfoundry.identity.uaa.authentication.login; +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.client.ClientConstants; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.codestore.InMemoryExpiringCodeStore; -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.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.login.LoginInfoEndpoint; +import org.cloudfoundry.identity.uaa.login.Prompt; +import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; +import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +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; @@ -78,7 +80,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 +354,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(); @@ -473,21 +475,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/login/src/test/java/org/cloudfoundry/identity/uaa/login/NotificationsServiceTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/NotificationsServiceTest.java similarity index 97% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/NotificationsServiceTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/NotificationsServiceTest.java index 66ce63a0d6a..ea0bba4d0cf 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/NotificationsServiceTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/NotificationsServiceTest.java @@ -17,6 +17,8 @@ import static org.springframework.test.web.client.response.MockRestResponseCreators.withBadRequest; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; +import org.cloudfoundry.identity.uaa.message.MessageType; +import org.cloudfoundry.identity.uaa.message.NotificationsService; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/PasswordConfirmationValidationTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/PasswordConfirmationValidationTest.java similarity index 95% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/PasswordConfirmationValidationTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/PasswordConfirmationValidationTest.java index 808f03395bc..54cc6548eb8 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/PasswordConfirmationValidationTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/PasswordConfirmationValidationTest.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; +import org.cloudfoundry.identity.uaa.account.PasswordConfirmationValidation; import org.junit.Assert; import org.junit.Test; diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java similarity index 92% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java index d4a496db574..d1a8f557bd4 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ProfileControllerTests.java @@ -14,10 +14,14 @@ package org.cloudfoundry.identity.uaa.login; import org.cloudfoundry.identity.uaa.TestClassNullifier; -import org.cloudfoundry.identity.uaa.authentication.Origin; +import org.cloudfoundry.identity.uaa.approval.ApprovalsService; +import org.cloudfoundry.identity.uaa.approval.DescribedApproval; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.home.BuildInfo; +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.account.ProfileController; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.After; import org.junit.Assert; @@ -50,8 +54,8 @@ import java.util.List; import java.util.Map; -import static org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus.APPROVED; -import static org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus.DENIED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.hasSize; @@ -133,7 +137,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 +161,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 +173,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/ResetPasswordControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java similarity index 95% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java index 9e111973c31..a0654a1c58c 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java @@ -16,7 +16,14 @@ import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; import org.cloudfoundry.identity.uaa.error.UaaException; -import org.cloudfoundry.identity.uaa.login.ResetPasswordService.ResetPasswordResponse; +import org.cloudfoundry.identity.uaa.message.MessageService; +import org.cloudfoundry.identity.uaa.message.MessageType; +import org.cloudfoundry.identity.uaa.account.ConflictException; +import org.cloudfoundry.identity.uaa.account.ForgotPasswordInfo; +import org.cloudfoundry.identity.uaa.account.NotFoundException; +import org.cloudfoundry.identity.uaa.account.ResetPasswordController; +import org.cloudfoundry.identity.uaa.account.ResetPasswordService; +import org.cloudfoundry.identity.uaa.account.ResetPasswordService.ResetPasswordResponse; import org.cloudfoundry.identity.uaa.login.test.ThymeleafConfig; import org.cloudfoundry.identity.uaa.scim.ScimMeta; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -46,9 +53,9 @@ import org.thymeleaf.spring4.SpringTemplateEngine; import java.sql.Timestamp; +import java.util.Arrays; import java.util.Date; -import static com.google.common.collect.Lists.newArrayList; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.any; @@ -185,7 +192,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 +242,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()) @@ -304,7 +311,7 @@ public void testResetPasswordFormWithInvalidCode() throws Exception { @Test public void testResetPasswordFormWithInvalidPassword() throws Exception { - when(resetPasswordService.resetPassword("bad_code", "password")).thenThrow(new InvalidPasswordException(newArrayList("Msg 2a", "Msg 1a"))); + when(resetPasswordService.resetPassword("bad_code", "password")).thenThrow(new InvalidPasswordException(Arrays.asList("Msg 2a", "Msg 1a"))); MockHttpServletRequestBuilder post = post("/reset_password.do") .contentType(APPLICATION_FORM_URLENCODED) diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsServiceTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsServiceTest.java similarity index 97% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsServiceTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsServiceTest.java index ba984bb3ce4..a3083176754 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsServiceTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/RestUaaApprovalsServiceTest.java @@ -25,7 +25,9 @@ import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.DescribedApproval; +import org.cloudfoundry.identity.uaa.approval.RestUaaApprovalsService; import org.junit.Assert; import org.junit.Before; import org.junit.Test; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java similarity index 99% rename from common/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java index afadf5281c0..0019c3b611f 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/SamlLoginServerKeyManagerTests.java @@ -14,6 +14,7 @@ import static org.junit.Assert.assertNotNull; +import org.cloudfoundry.identity.uaa.provider.saml.SamlLoginServerKeyManager; import org.junit.Assert; import org.junit.Test; import org.opensaml.xml.security.credential.Credential; diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/TileInfoTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/TileInfoTest.java similarity index 97% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/TileInfoTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/TileInfoTest.java index d0199a359f2..d967eab0e12 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/TileInfoTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/TileInfoTest.java @@ -1,6 +1,7 @@ package org.cloudfoundry.identity.uaa.login; import com.google.inject.internal.Lists; +import org.cloudfoundry.identity.uaa.home.TileInfo; import org.junit.Before; import org.junit.Test; import org.springframework.core.env.AbstractEnvironment; diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/UaaChangePasswordServiceTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaChangePasswordServiceTest.java similarity index 98% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/UaaChangePasswordServiceTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaChangePasswordServiceTest.java index ef708409591..e54ebc6337e 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/UaaChangePasswordServiceTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaChangePasswordServiceTest.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.login; +import org.cloudfoundry.identity.uaa.account.UaaChangePasswordService; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java similarity index 92% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java index 0478adaeec2..bb5152d4e04 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java @@ -14,10 +14,13 @@ 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.authentication.InvalidCodeException; +import org.cloudfoundry.identity.uaa.account.ConflictException; +import org.cloudfoundry.identity.uaa.account.ForgotPasswordInfo; +import org.cloudfoundry.identity.uaa.account.NotFoundException; +import org.cloudfoundry.identity.uaa.account.ResetPasswordService.ResetPasswordResponse; +import org.cloudfoundry.identity.uaa.account.UaaResetPasswordService; +import org.cloudfoundry.identity.uaa.account.event.ResetPasswordRequestEvent; import org.cloudfoundry.identity.uaa.scim.ScimMeta; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; @@ -46,7 +49,6 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.sameInstance; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; @@ -92,7 +94,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 +115,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 +132,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 +185,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 +245,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/login/src/test/java/org/cloudfoundry/identity/uaa/login/UsernamePasswordExtractingAuthenticationManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UsernamePasswordExtractingAuthenticationManagerTests.java similarity index 96% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/UsernamePasswordExtractingAuthenticationManagerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/UsernamePasswordExtractingAuthenticationManagerTests.java index fec115fee64..6b6bcbd0ace 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/UsernamePasswordExtractingAuthenticationManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UsernamePasswordExtractingAuthenticationManagerTests.java @@ -15,6 +15,7 @@ import static org.junit.Assert.assertSame; +import org.cloudfoundry.identity.uaa.authentication.manager.UsernamePasswordExtractingAuthenticationManager; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/test/IfProfileActive.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/test/IfProfileActive.java similarity index 100% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/test/IfProfileActive.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/test/IfProfileActive.java diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/test/LoginServerClassRunner.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/test/LoginServerClassRunner.java similarity index 100% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/test/LoginServerClassRunner.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/test/LoginServerClassRunner.java diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/test/MockMvcTestClient.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/test/MockMvcTestClient.java similarity index 100% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/test/MockMvcTestClient.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/test/MockMvcTestClient.java diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/test/ProfileActiveUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/test/ProfileActiveUtils.java similarity index 100% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/test/ProfileActiveUtils.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/test/ProfileActiveUtils.java diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/test/ThymeleafConfig.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/test/ThymeleafConfig.java similarity index 100% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/test/ThymeleafConfig.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/test/ThymeleafConfig.java diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/test/UnlessProfileActive.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/test/UnlessProfileActive.java similarity index 100% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/test/UnlessProfileActive.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/test/UnlessProfileActive.java diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/util/FakeJavaMailSenderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/util/FakeJavaMailSenderTest.java similarity index 96% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/util/FakeJavaMailSenderTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/login/util/FakeJavaMailSenderTest.java index 5a61c630c46..634307f0dc1 100644 --- a/login/src/test/java/org/cloudfoundry/identity/uaa/login/util/FakeJavaMailSenderTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/util/FakeJavaMailSenderTest.java @@ -14,6 +14,7 @@ package org.cloudfoundry.identity.uaa.login.util; +import org.cloudfoundry.identity.uaa.message.util.FakeJavaMailSender; import org.junit.Test; import javax.mail.internet.MimeMessage; diff --git a/login/src/test/java/org/cloudfoundry/identity/uaa/login/util/SecurityUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/util/SecurityUtils.java similarity index 93% rename from login/src/test/java/org/cloudfoundry/identity/uaa/login/util/SecurityUtils.java rename to server/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/server/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/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/AccessControllerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/AccessControllerTests.java similarity index 97% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/AccessControllerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/AccessControllerTests.java index 3c9049d2104..3a9c3b789ac 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/AccessControllerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/AccessControllerTests.java @@ -18,8 +18,8 @@ import java.util.Map; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; -import org.cloudfoundry.identity.uaa.client.ClientConstants; -import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; import org.junit.Test; import org.mockito.Mockito; import org.springframework.mock.web.MockHttpServletRequest; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java similarity index 81% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java index ba02f42c291..31ff5e59646 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/CheckTokenEndpointTests.java @@ -12,30 +12,28 @@ *******************************************************************************/ 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.oauth.approval.Approval; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; -import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.oauth.approval.InMemoryApprovalStore; -import org.cloudfoundry.identity.uaa.oauth.token.SignerProvider; -import org.cloudfoundry.identity.uaa.oauth.token.TokenRevokedException; -import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.token.Claims; 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.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; +import org.cloudfoundry.identity.uaa.zone.TokenPolicy; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import org.springframework.security.core.GrantedAuthority; +import org.mockito.AdditionalMatchers; +import org.mockito.Matchers; +import org.mockito.Mockito; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.common.OAuth2AccessToken; @@ -60,10 +58,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.AdditionalMatchers.not; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; @RunWith(Parameterized.class) public class CheckTokenEndpointTests { @@ -210,7 +204,7 @@ public void setUp() { "FamilyName", new Date(System.currentTimeMillis() - 2000), new Date(System.currentTimeMillis() - 2000), - Origin.UAA, + OriginKeys.UAA, "externalId", false, IdentityZoneHolder.get().getId(), @@ -232,10 +226,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)); @@ -252,9 +256,9 @@ public void setUp() { } protected void mockUserDatabase(String userId, UaaUser user) { - userDatabase = mock(UaaUserDatabase.class); - when(userDatabase.retrieveUserById(eq(userId))).thenReturn(user); - when(userDatabase.retrieveUserById(not(eq(userId)))).thenThrow(new UsernameNotFoundException("mock")); + userDatabase = Mockito.mock(UaaUserDatabase.class); + Mockito.when(userDatabase.retrieveUserById(Matchers.eq(userId))).thenReturn(user); + Mockito.when(userDatabase.retrieveUserById(AdditionalMatchers.not(Matchers.eq(userId)))).thenThrow(new UsernameNotFoundException("mock")); tokenServices.setUserDatabase(userDatabase); } @@ -304,7 +308,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 +330,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 +352,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 +376,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(), @@ -445,9 +449,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 @@ -455,9 +459,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 @@ -468,9 +472,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(); } @@ -479,8 +483,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")); @@ -488,63 +492,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) @@ -557,39 +561,60 @@ 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) 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")); + 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()); } @Test(expected = InvalidTokenException.class) 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)); - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals(null, result.get("client_authorities")); + 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())); - Map result = endpoint.checkToken(accessToken.getValue()); - assertEquals(null, result.get("client_authorities")); + 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()); } @Test @@ -597,9 +622,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/ClientInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpointTests.java similarity index 97% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpointTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpointTests.java index c03d10305c8..0483d816b28 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/ClientInfoEndpointTests.java @@ -19,6 +19,7 @@ import java.util.Collections; +import org.cloudfoundry.identity.uaa.client.ClientInfoEndpoint; import org.junit.Test; import org.mockito.Mockito; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/DisableIdTokenResponseTypeFilterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/DisableIdTokenResponseTypeFilterTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/DisableIdTokenResponseTypeFilterTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/DisableIdTokenResponseTypeFilterTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsServiceTests.java similarity index 93% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsServiceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsServiceTests.java index f51be2081ee..8ae3698bc2f 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/JdbcQueryableClientDetailsServiceTests.java @@ -12,8 +12,10 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; -import org.cloudfoundry.identity.uaa.rest.jdbc.LimitSqlAdapter; +import org.cloudfoundry.identity.uaa.client.JdbcQueryableClientDetailsService; +import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.LimitSqlAdapter; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.zone.*; import org.junit.Before; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServicesTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServicesTests.java similarity index 88% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/RemoteTokenServicesTests.java rename to server/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/server/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/RestrictUaaScopesClientValidatorTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/RestrictUaaScopesClientValidatorTest.java similarity index 86% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/RestrictUaaScopesClientValidatorTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/RestrictUaaScopesClientValidatorTest.java index f223c9d9792..b975a1f0337 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/RestrictUaaScopesClientValidatorTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/RestrictUaaScopesClientValidatorTest.java @@ -14,6 +14,10 @@ package org.cloudfoundry.identity.uaa.oauth; +import org.cloudfoundry.identity.uaa.client.ClientDetailsValidator; +import org.cloudfoundry.identity.uaa.client.InvalidClientDetailsException; +import org.cloudfoundry.identity.uaa.client.RestrictUaaScopesClientValidator; +import org.cloudfoundry.identity.uaa.client.UaaScopes; import org.junit.Test; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.provider.client.BaseClientDetails; @@ -23,9 +27,9 @@ import java.util.LinkedList; import java.util.List; -import static org.cloudfoundry.identity.uaa.oauth.ClientDetailsValidator.Mode.CREATE; -import static org.cloudfoundry.identity.uaa.oauth.ClientDetailsValidator.Mode.DELETE; -import static org.cloudfoundry.identity.uaa.oauth.ClientDetailsValidator.Mode.MODIFY; +import static org.cloudfoundry.identity.uaa.client.ClientDetailsValidator.Mode.CREATE; +import static org.cloudfoundry.identity.uaa.client.ClientDetailsValidator.Mode.DELETE; +import static org.cloudfoundry.identity.uaa.client.ClientDetailsValidator.Mode.MODIFY; import static org.junit.Assert.fail; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java similarity index 98% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java index 7512670382d..47b53ea657d 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaAuthorizationRequestManagerTests.java @@ -13,13 +13,13 @@ 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.oauth.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; import org.cloudfoundry.identity.uaa.user.UaaUserDatabase; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; @@ -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/main/java/org/cloudfoundry/identity/uaa/oauth/UaaOauth2ErrorHandler.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaOauth2ErrorHandler.java similarity index 100% rename from common/src/main/java/org/cloudfoundry/identity/uaa/oauth/UaaOauth2ErrorHandler.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaOauth2ErrorHandler.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaOauth2ErrorHandlerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaOauth2ErrorHandlerTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaOauth2ErrorHandlerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaOauth2ErrorHandlerTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java similarity index 93% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java index 5a1aa4255b3..de3ed87d13e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaScopesTests.java @@ -14,6 +14,7 @@ package org.cloudfoundry.identity.uaa.oauth; +import org.cloudfoundry.identity.uaa.client.UaaScopes; import org.junit.Test; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -31,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 diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServicesTests.java similarity index 89% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServicesTests.java index 32c49f1ea68..d1794414213 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenServicesTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenServicesTests.java @@ -10,20 +10,21 @@ * 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; +package org.cloudfoundry.identity.uaa.oauth; 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.oauth.approval.Approval; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; -import org.cloudfoundry.identity.uaa.oauth.approval.ApprovalStore; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken; +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.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; import org.cloudfoundry.identity.uaa.oauth.approval.InMemoryApprovalStore; import org.cloudfoundry.identity.uaa.oauth.token.matchers.OAuth2AccessTokenMatchers; import org.cloudfoundry.identity.uaa.oauth.token.matchers.OAuth2RefreshTokenMatchers; @@ -157,7 +158,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()) @@ -168,10 +169,7 @@ public class UaaTokenServicesTests { // the token IAT is in seconds and the token // expiry // skew will not be long enough - private InMemoryUaaUserDatabase userDatabase = - new InMemoryUaaUserDatabase( - new HashMap<>(Collections.singletonMap(username, defaultUser)) - ); + private InMemoryUaaUserDatabase userDatabase = new InMemoryUaaUserDatabase(Collections.singleton(defaultUser)); private Authentication defaultUserAuthentication = new UsernamePasswordAuthenticationToken(new UaaPrincipal(defaultUser), "n/a", null); @@ -230,7 +228,7 @@ public void setUp() throws Exception { tokenServices.setApprovalStore(approvalStore); tokenServices.setApplicationEventPublisher(publisher); tokenServices.afterPropertiesSet(); - + OAuth2AccessTokenMatchers.signer = signerProvider; OAuth2RefreshTokenMatchers.signer = signerProvider; } @@ -255,9 +253,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 @@ -278,7 +276,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 +304,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 +334,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 +364,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 +376,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 +389,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)); } @@ -451,8 +449,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)); @@ -482,7 +492,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))); @@ -492,7 +502,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); @@ -504,7 +514,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))); @@ -530,7 +540,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))); @@ -540,7 +550,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); @@ -552,7 +562,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))); @@ -571,7 +581,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)); @@ -582,7 +598,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))); @@ -592,7 +608,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); @@ -604,7 +620,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))); @@ -623,7 +639,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)); @@ -634,7 +656,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))); @@ -644,7 +666,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); @@ -668,8 +690,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)); @@ -680,7 +714,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))); @@ -690,7 +724,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); @@ -719,7 +753,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)); } @@ -754,7 +788,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); @@ -766,7 +800,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 @@ -787,7 +821,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)); } @@ -823,7 +857,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)); } @@ -850,8 +884,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 +937,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)); @@ -918,7 +974,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 @@ -949,8 +1005,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()); @@ -961,7 +1027,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); @@ -979,8 +1045,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 +1098,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 +1125,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 +1162,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 +1199,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)); @@ -1132,7 +1243,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 @@ -1149,8 +1260,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 +1294,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); @@ -1260,21 +1395,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"); @@ -1284,7 +1419,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)), @@ -1299,14 +1434,14 @@ 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)), - origin(is(Origin.UAA)), + audience(is(resourceIds)), + origin(is(OriginKeys.UAA)), revocationSignature(is(not(nullValue()))), cid(is(CLIENT_ID)), userId(is(userId)), @@ -1316,7 +1451,7 @@ private void assertCommonUserAccessTokenProperties(OAuth2AccessToken accessToken expiry(is(greaterThan(0))) )); } - + @SuppressWarnings("unchecked") private void assertCommonUserRefreshTokenProperties(OAuth2RefreshToken refreshToken) { assertThat(refreshToken, allOf(/*issuer(is(issuerUri)),*/ @@ -1324,7 +1459,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))), @@ -1332,10 +1467,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/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenStoreTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenStoreTests.java similarity index 85% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenStoreTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenStoreTests.java index abcc3a4f983..d01d225dcf2 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/UaaTokenStoreTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/UaaTokenStoreTests.java @@ -12,12 +12,13 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.oauth.token; +package org.cloudfoundry.identity.uaa.oauth; -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.oauth.UaaTokenStore; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; @@ -55,6 +56,7 @@ import java.util.Set; import java.util.logging.Logger; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -72,7 +74,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 { @@ -116,9 +118,9 @@ public void test_deserialization_of_uaa_authentication() throws Exception { modifiedAuthentication.setExternalGroups(externalGroups); String code = store.createAuthorizationCode(uaaAuthentication); - assertEquals(1, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(1)); OAuth2Authentication authentication = store.consumeAuthorizationCode(code); - assertEquals(0, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(0)); assertNotNull(authentication); UaaAuthentication userAuthentication = (UaaAuthentication) authentication.getUserAuthentication(); @@ -135,31 +137,31 @@ public void test_deserialization_of_uaa_authentication() throws Exception { @Test public void test_ConsumeClientCredentials_From_OldStore() throws Exception { String code = legacyCodeServices.createAuthorizationCode(clientAuthentication); - assertEquals(1, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(1)); OAuth2Authentication authentication = store.consumeAuthorizationCode(code); assertNotNull(authentication); assertTrue(authentication.isClientOnly()); - assertEquals(0, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(0)); } @Test public void testStoreToken_ClientCredentials() throws Exception { String code = store.createAuthorizationCode(clientAuthentication); - assertEquals(1, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(1)); assertNotNull(code); } @Test public void testStoreToken_PasswordGrant_UsernamePasswordAuthentication() throws Exception { String code = store.createAuthorizationCode(usernamePasswordAuthentication); - assertEquals(1, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(1)); assertNotNull(code); } @Test public void testStoreToken_PasswordGrant_UaaAuthentication() throws Exception { String code = store.createAuthorizationCode(uaaAuthentication); - assertEquals(1, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(1)); assertNotNull(code); } @@ -173,28 +175,28 @@ public void deserialize_from_old_format() throws Exception { @Test public void testRetrieveToken() throws Exception { String code = store.createAuthorizationCode(clientAuthentication); - assertEquals(1, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(1)); OAuth2Authentication authentication = store.consumeAuthorizationCode(code); - assertEquals(0, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(0)); assertNotNull(authentication); code = store.createAuthorizationCode(usernamePasswordAuthentication); - assertEquals(1, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(1)); authentication = store.consumeAuthorizationCode(code); - assertEquals(0, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(0)); assertNotNull(authentication); code = store.createAuthorizationCode(uaaAuthentication); - assertEquals(1, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(1)); authentication = store.consumeAuthorizationCode(code); - assertEquals(0, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(0)); assertNotNull(authentication); } @Test(expected = InvalidGrantException.class) public void testRetrieve_Expired_Token() throws Exception { String code = store.createAuthorizationCode(clientAuthentication); - assertEquals(1, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(1)); jdbcTemplate.update("update oauth_code set expiresat = 1"); store.consumeAuthorizationCode(code); } @@ -202,7 +204,7 @@ public void testRetrieve_Expired_Token() throws Exception { @Test(expected = InvalidGrantException.class) public void testRetrieve_Non_Existent_Token() throws Exception { String code = store.createAuthorizationCode(clientAuthentication); - assertEquals(1, jdbcTemplate.queryForInt("SELECT count(*) FROM oauth_code WHERE code = ?", code)); + assertThat(jdbcTemplate.queryForObject("SELECT count(*) FROM oauth_code WHERE code = ?", new Object[] {code}, Integer.class), is(1)); store.consumeAuthorizationCode("non-existent"); } @@ -213,7 +215,7 @@ public void testCleanUpExpiredTokensBasedOnExpiresField() throws Exception { for (int i=0; i(Arrays.asList(new String[]{"cloud_controller.read", "openid"})),request.getScope()); @@ -238,8 +279,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 +313,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 +354,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 +404,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 +460,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 +521,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 +565,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 @@ -442,7 +593,7 @@ public void testSomeRequestedScopesMatchApproval() { @After public void cleanupDataSource() throws Exception { TestUtils.deleteFrom(dataSource, "authz_approvals"); - assertEquals(0, jdbcTemplate.queryForInt("select count(*) from authz_approvals")); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals", Integer.class), is(0)); } @SuppressWarnings("serial") diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java similarity index 84% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java index 40d0ef18d42..05b987b5286 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/ZoneEndpointsClientDetailsValidatorTests.java @@ -4,9 +4,11 @@ import java.util.Collections; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.client.ClientConstants; -import org.cloudfoundry.identity.uaa.oauth.ClientDetailsValidator.Mode; +import org.cloudfoundry.identity.uaa.client.InvalidClientDetailsException; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.client.ClientDetailsValidator.Mode; +import org.cloudfoundry.identity.uaa.zone.ZoneEndpointsClientDetailsValidator; import org.junit.Before; import org.junit.Test; import org.springframework.security.oauth2.provider.ClientDetails; @@ -25,14 +27,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 +53,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/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalTests.java new file mode 100644 index 00000000000..b9548c677ae --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalTests.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * 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.oauth.approval; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.junit.Test; + +public class ApprovalTests { + + @Test + public void testHashCode() throws Exception { + 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() + .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() + .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() + .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() + .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/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java similarity index 56% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java index 34b6d62304e..1c2b9a03ddc 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/ApprovalsAdminEndpointsTests.java @@ -18,8 +18,10 @@ import java.util.List; import java.util.Set; -import static org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus.APPROVED; -import static org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus.DENIED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -27,11 +29,14 @@ 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.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalsAdminEndpoints; +import org.cloudfoundry.identity.uaa.approval.JdbcApprovalStore; +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; -import org.cloudfoundry.identity.uaa.rest.jdbc.SimpleSearchQueryConverter; +import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; import org.cloudfoundry.identity.uaa.security.SecurityContextAccessor; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.test.TestUtils; @@ -62,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); @@ -84,7 +89,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) { @@ -99,8 +109,8 @@ private SecurityContextAccessor mockSecurityContextAccessor(String userName, Str public void cleanupDataSource() throws Exception { TestUtils.deleteFrom(dataSource, "authz_approvals"); TestUtils.deleteFrom(dataSource, "users"); - assertEquals(0, jdbcTemplate.queryForInt("select count(*) from authz_approvals")); - assertEquals(0, jdbcTemplate.queryForInt("select count(*) from users")); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals", Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from users", Integer.class), is(0)); } @Test @@ -116,7 +126,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 +158,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 +246,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 +275,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 +301,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/InMemoryApprovalStore.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/InMemoryApprovalStore.java similarity index 94% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/InMemoryApprovalStore.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/InMemoryApprovalStore.java index 61a46d3df14..9af06412278 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/InMemoryApprovalStore.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/InMemoryApprovalStore.java @@ -12,6 +12,9 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth.approval; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; + import java.util.ArrayList; import java.util.List; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStoreTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStoreTests.java similarity index 71% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStoreTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStoreTests.java index f95125bb4a7..aec2cee141d 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStoreTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/approval/JdbcApprovalStoreTests.java @@ -16,15 +16,20 @@ import java.util.Date; import java.util.List; -import static org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus.APPROVED; -import static org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus.DENIED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.APPROVED; +import static org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus.DENIED; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; + +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.JdbcApprovalStore; import org.cloudfoundry.identity.uaa.audit.event.ApprovalModifiedEvent; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval.ApprovalStatus; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; -import org.cloudfoundry.identity.uaa.rest.jdbc.SimpleSearchQueryConverter; +import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.SimpleSearchQueryConverter; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.cloudfoundry.identity.uaa.test.MockAuthentication; import org.cloudfoundry.identity.uaa.test.TestApplicationEventPublisher; @@ -59,14 +64,20 @@ 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); } @After public void cleanupDataSource() throws Exception { TestUtils.deleteFrom(dataSource, "authz_approvals"); - assertEquals(0, jdbcTemplate.queryForInt("select count(*) from authz_approvals")); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals", Integer.class), is(0)); } @Test @@ -79,18 +90,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 +119,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 +155,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 +177,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 +202,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 +234,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/client/ClientDetailsModificationTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModificationTests.java similarity index 97% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/client/ClientDetailsModificationTests.java rename to server/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/server/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/event/ClientAdminEventPublisherTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/event/ClientAdminEventPublisherTests.java similarity index 91% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/event/ClientAdminEventPublisherTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/event/ClientAdminEventPublisherTests.java index 810021b4521..935dd95d56b 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/event/ClientAdminEventPublisherTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/event/ClientAdminEventPublisherTests.java @@ -17,6 +17,12 @@ import org.aspectj.lang.ProceedingJoinPoint; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationTestFactory; +import org.cloudfoundry.identity.uaa.client.event.ClientAdminEventPublisher; +import org.cloudfoundry.identity.uaa.client.event.ClientCreateEvent; +import org.cloudfoundry.identity.uaa.client.event.ClientDeleteEvent; +import org.cloudfoundry.identity.uaa.client.event.ClientUpdateEvent; +import org.cloudfoundry.identity.uaa.client.event.SecretChangeEvent; +import org.cloudfoundry.identity.uaa.client.event.SecretFailureEvent; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheckTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheckTest.java similarity index 96% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheckTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheckTest.java index 0acbdcefed0..784ae2ed535 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheckTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/expression/IsUserSelfCheckTest.java @@ -14,10 +14,11 @@ 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.security.IsUserSelfCheck; import org.cloudfoundry.identity.uaa.util.UaaStringUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.After; @@ -51,7 +52,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 +112,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/SignerProviderTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProviderTests.java similarity index 99% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProviderTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProviderTests.java index 91a9877f9f4..1debbaefc68 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProviderTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/SignerProviderTests.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.oauth.token; +import org.cloudfoundry.identity.uaa.oauth.SignerProvider; import org.junit.Test; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.StringUtils; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java similarity index 56% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java index 0b2362c198d..ffff27f114a 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenKeyEndpointTests.java @@ -14,6 +14,9 @@ import static org.junit.Assert.assertEquals; +import org.cloudfoundry.identity.uaa.oauth.SignerProvider; +import org.cloudfoundry.identity.uaa.oauth.TokenKeyEndpoint; +import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.Before; import org.junit.Test; import org.springframework.security.access.AccessDeniedException; @@ -21,6 +24,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; +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("value")); + assertEquals("MAC", deserializedMap.get("kty")); + assertEquals("sig", deserializedMap.get("use")); + } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/AbstractOAuth2AccessTokenMatchers.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/AbstractOAuth2AccessTokenMatchers.java similarity index 96% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/AbstractOAuth2AccessTokenMatchers.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/AbstractOAuth2AccessTokenMatchers.java index bfcaed2f935..f86e0442e97 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/AbstractOAuth2AccessTokenMatchers.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/AbstractOAuth2AccessTokenMatchers.java @@ -4,7 +4,7 @@ import java.util.Map; -import org.cloudfoundry.identity.uaa.oauth.token.SignerProvider; +import org.cloudfoundry.identity.uaa.oauth.SignerProvider; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2AccessTokenMatchers.java b/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2AccessTokenMatchers.java similarity index 68% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2AccessTokenMatchers.java rename to server/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/server/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/server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2RefreshTokenMatchers.java similarity index 68% rename from common/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/matchers/OAuth2RefreshTokenMatchers.java rename to server/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/server/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/server/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java similarity index 76% rename from common/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java index f0f7579c0e9..5e06e96d217 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/openid/UserInfoEndpointTests.java @@ -15,11 +15,12 @@ import static org.junit.Assert.assertEquals; 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.account.UserInfoEndpoint; +import org.cloudfoundry.identity.uaa.account.UserInfoResponse; import org.cloudfoundry.identity.uaa.user.InMemoryUaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.user.UaaUserTestFactory; @@ -35,8 +36,7 @@ public class UserInfoEndpointTests { private UserInfoEndpoint endpoint = new UserInfoEndpoint(); - private InMemoryUaaUserDatabase userDatabase = new InMemoryUaaUserDatabase(Collections.singletonMap("olds", - UaaUserTestFactory.getUser("12345", "olds", "olds@vmware.com", "Dale", "Olds"))); + private InMemoryUaaUserDatabase userDatabase = new InMemoryUaaUserDatabase(Collections.singleton(UaaUserTestFactory.getUser("12345", "olds", "olds@vmware.com", "Dale", "Olds"))); public UserInfoEndpointTests() { endpoint.setUserDatabase(userDatabase); @@ -44,20 +44,20 @@ 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)); - 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/scim/src/test/java/org/cloudfoundry/identity/uaa/performance/TestMySQLEmailSearch.java b/server/src/test/java/org/cloudfoundry/identity/uaa/performance/TestMySQLEmailSearch.java similarity index 97% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/performance/TestMySQLEmailSearch.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/performance/TestMySQLEmailSearch.java index bf94b3374d2..4511f9a96f5 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/performance/TestMySQLEmailSearch.java +++ b/server/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.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.SearchResults; +import org.cloudfoundry.identity.uaa.resources.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; @@ -159,7 +158,7 @@ protected List addRecords() throws Exception { } protected int countRows() throws Exception { - return jdbcTemplate.queryForInt("select count(*) from users"); + return jdbcTemplate.queryForObject("select count(*) from users", Integer.class); } protected List getEmails() { diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/DynamicPasswordComparatorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/DynamicPasswordComparatorTests.java similarity index 98% rename from common/src/test/java/org/cloudfoundry/identity/uaa/ldap/DynamicPasswordComparatorTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/DynamicPasswordComparatorTests.java index f7bb95777bf..154e98be594 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/DynamicPasswordComparatorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/DynamicPasswordComparatorTests.java @@ -13,7 +13,7 @@ * ****************************************************************************** */ -package org.cloudfoundry.identity.uaa.ldap; +package org.cloudfoundry.identity.uaa.provider.ldap; import org.junit.Test; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/ExtendedLdapUserMapperTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/ExtendedLdapUserMapperTest.java similarity index 93% rename from common/src/test/java/org/cloudfoundry/identity/uaa/ldap/ExtendedLdapUserMapperTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/ExtendedLdapUserMapperTest.java index 64ae637e028..9001d8c4f1c 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/ExtendedLdapUserMapperTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/ExtendedLdapUserMapperTest.java @@ -1,6 +1,6 @@ -package org.cloudfoundry.identity.uaa.ldap; +package org.cloudfoundry.identity.uaa.provider.ldap; -import org.cloudfoundry.identity.uaa.ldap.extension.ExtendedLdapUserImpl; +import org.cloudfoundry.identity.uaa.provider.ldap.extension.ExtendedLdapUserImpl; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -16,7 +16,7 @@ import java.util.HashMap; import java.util.Map; -import static org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserMapper.SUBSTITUTE_MAIL_ATTR_NAME; +import static org.cloudfoundry.identity.uaa.provider.ldap.ExtendedLdapUserMapper.SUBSTITUTE_MAIL_ATTR_NAME; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/LdapIdentityProviderDefinitionTest.java similarity index 95% rename from common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/LdapIdentityProviderDefinitionTest.java index 1ab9cc09f62..e3620226a8e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/LdapIdentityProviderDefinitionTest.java @@ -10,11 +10,13 @@ * 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.ldap; -import org.cloudfoundry.identity.uaa.config.YamlMapFactoryBean; -import org.cloudfoundry.identity.uaa.config.YamlProcessor; +import org.cloudfoundry.identity.uaa.impl.config.YamlMapFactoryBean; +import org.cloudfoundry.identity.uaa.impl.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/ldap/ProcessLdapPropertiesTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/ProcessLdapPropertiesTest.java similarity index 90% rename from common/src/test/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapPropertiesTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/ProcessLdapPropertiesTest.java index 39b153ba8f0..757dd491d1c 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapPropertiesTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/ldap/ProcessLdapPropertiesTest.java @@ -12,14 +12,14 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.ldap; +package org.cloudfoundry.identity.uaa.provider.ldap; import org.junit.Test; import java.util.HashMap; import java.util.Map; -import static org.cloudfoundry.identity.uaa.ldap.ProcessLdapProperties.LDAP_SOCKET_FACTORY; +import static org.cloudfoundry.identity.uaa.provider.ldap.ProcessLdapProperties.LDAP_SOCKET_FACTORY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/ConfigMetadataProviderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProviderTest.java similarity index 95% rename from common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/ConfigMetadataProviderTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProviderTest.java index e5fa66cb891..d8a5093f17e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/ConfigMetadataProviderTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ConfigMetadataProviderTest.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.junit.Test; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/IdentityProviderConfiguratorTests.java similarity index 91% rename from common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/IdentityProviderConfiguratorTests.java index d7ab45d79ce..d2613c7e05c 100755 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/IdentityProviderConfiguratorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/IdentityProviderConfiguratorTests.java @@ -10,13 +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.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import org.apache.commons.httpclient.params.HttpClientParams; -import org.cloudfoundry.identity.uaa.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.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.impl.config.YamlMapFactoryBean; +import org.cloudfoundry.identity.uaa.impl.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; @@ -171,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"; @@ -182,28 +181,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) { @@ -306,7 +303,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); @@ -332,7 +338,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) { @@ -347,7 +362,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()); @@ -415,6 +439,7 @@ protected void testGetIdentityProviderDefinitions(int count, boolean addData) th } case "simplesamlphp-url" : { assertTrue(idp.isShowSamlLink()); + assertEquals("simplesamlphp-url", idp.getLinkText()); break; } default: @@ -456,24 +481,19 @@ public void testDuplicateAlias_In_LegacyConfig() throws Exception { conf.afterPropertiesSet(); } - @Test public void testDuplicate_EntityID_IsRejected() throws Exception { conf.setIdentityProviders(sampleData); 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") + .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/LoginSAMLAuthenticationFailureHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandlerTest.java similarity index 99% rename from common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSAMLAuthenticationFailureHandlerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandlerTest.java index 80d7014a0d1..dee6ada6a71 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSAMLAuthenticationFailureHandlerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSAMLAuthenticationFailureHandlerTest.java @@ -1,4 +1,4 @@ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java similarity index 92% rename from common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java index 9d9253c680b..b61e5f70d68 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIdentityProviderDefinitionTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIdentityProviderDefinitionTests.java @@ -1,15 +1,16 @@ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.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; @@ -19,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 @@ -163,4 +165,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/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java similarity index 70% rename from common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtilsTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java index 2f5736ea619..b3f44533b76 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlRedirectUtilsTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlRedirectUtilsTest.java @@ -12,8 +12,9 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.junit.Assert; import org.junit.Test; @@ -23,18 +24,16 @@ 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); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGeneratorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGeneratorTests.java similarity index 95% rename from common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGeneratorTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGeneratorTests.java index 1c498e13ba7..78eec63ec69 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/login/saml/ZoneAwareMetadataGeneratorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/ZoneAwareMetadataGeneratorTests.java @@ -12,9 +12,9 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.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/rest/MessageTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/resources/MessageTests.java similarity index 77% rename from common/src/test/java/org/cloudfoundry/identity/uaa/rest/MessageTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/resources/MessageTests.java index 01ac497fc96..f711221f291 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/rest/MessageTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/resources/MessageTests.java @@ -11,13 +11,11 @@ * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ -package org.cloudfoundry.identity.uaa.rest; +package org.cloudfoundry.identity.uaa.resources; 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/rest/jdbc/JdbcPagingListTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingListTests.java similarity index 99% rename from common/src/test/java/org/cloudfoundry/identity/uaa/rest/jdbc/JdbcPagingListTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingListTests.java index 74e468c6bd3..15da9dce30d 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/rest/jdbc/JdbcPagingListTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/resources/jdbc/JdbcPagingListTests.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.jdbc; +package org.cloudfoundry.identity.uaa.resources.jdbc; import java.util.Collections; import java.util.HashSet; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimCoreTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimCoreTests.java similarity index 100% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimCoreTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimCoreTests.java diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMemberTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMemberTests.java similarity index 97% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimGroupMemberTests.java rename to server/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/server/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/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTestFactory.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTestFactory.java similarity index 100% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTestFactory.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTestFactory.java diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTests.java similarity index 98% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTests.java index 1768387ddcd..ec768e7e5d1 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/ScimUserTests.java @@ -29,7 +29,7 @@ import java.util.List; import java.util.Set; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.scim.ScimUser.Group; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.Assert; @@ -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/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrapTests.java similarity index 81% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrapTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrapTests.java index b8424a7e45d..4c52f7017a4 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimExternalGroupBootstrapTests.java @@ -18,15 +18,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMembershipManager; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupExternalMembershipManager; 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/ScimGroupBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrapTests.java similarity index 97% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrapTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrapTests.java index 9a9e79ebfb7..1f2f4546809 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimGroupBootstrapTests.java @@ -12,14 +12,13 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.bootstrap; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupMembershipManager; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.test.TestUtils; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; -import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.Before; import org.junit.Test; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java similarity index 98% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java index 8bfeb1333b5..e0def785844 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java @@ -12,10 +12,10 @@ *******************************************************************************/ 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.rest.jdbc.DefaultLimitSqlAdapter; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.resources.jdbc.DefaultLimitSqlAdapter; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -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/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java similarity index 93% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java index d42515c7ea8..87257980b71 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ChangeEmailEndpointsTest.java @@ -1,11 +1,11 @@ package org.cloudfoundry.identity.uaa.scim.endpoints; 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.scim.event.UserModifiedEvent; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; -import org.cloudfoundry.identity.uaa.rest.QueryableResourceManager; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.resources.QueryableResourceManager; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.junit.Assert; @@ -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,15 +80,15 @@ 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"); 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") @@ -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"); @@ -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/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java similarity index 91% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java index c20521ccb16..acdfd6d9d8f 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/PasswordResetEndpointTest.java @@ -13,12 +13,13 @@ 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.error.ExceptionReportHttpMessageConverter; -import org.cloudfoundry.identity.uaa.login.ResetPasswordService; -import org.cloudfoundry.identity.uaa.login.UaaResetPasswordService; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.web.ExceptionReportHttpMessageConverter; +import org.cloudfoundry.identity.uaa.account.PasswordResetEndpoint; +import org.cloudfoundry.identity.uaa.account.ResetPasswordService; +import org.cloudfoundry.identity.uaa.account.UaaResetPasswordService; import org.cloudfoundry.identity.uaa.scim.ScimMeta; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; @@ -80,11 +81,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 @@ -95,12 +96,12 @@ 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); - 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 +113,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 @@ -121,12 +122,12 @@ 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); - 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 +137,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 @@ -145,7 +146,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 +162,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 +176,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,12 +202,12 @@ 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); - 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) @@ -218,9 +219,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)); @@ -236,14 +237,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 +281,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 +289,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 +338,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/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java similarity index 97% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java index 704ac7f7adb..3608e21be67 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimGroupEndpointsTests.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.error.ExceptionReportHttpMessageConverter; -import org.cloudfoundry.identity.uaa.rest.SearchResults; -import org.cloudfoundry.identity.uaa.rest.jdbc.DefaultLimitSqlAdapter; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.web.ExceptionReportHttpMessageConverter; +import org.cloudfoundry.identity.uaa.resources.SearchResults; +import org.cloudfoundry.identity.uaa.resources.jdbc.DefaultLimitSqlAdapter; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; @@ -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()); } @@ -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/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java similarity index 92% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java index 033bb3bdc4c..904a2e412d6 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java @@ -14,15 +14,15 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.error.ConvertingExceptionView; -import org.cloudfoundry.identity.uaa.error.ExceptionReportHttpMessageConverter; -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.rest.SimpleAttributeNameMapper; -import org.cloudfoundry.identity.uaa.rest.jdbc.DefaultLimitSqlAdapter; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.web.ConvertingExceptionView; +import org.cloudfoundry.identity.uaa.web.ExceptionReportHttpMessageConverter; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; +import org.cloudfoundry.identity.uaa.approval.JdbcApprovalStore; +import org.cloudfoundry.identity.uaa.resources.SearchResults; +import org.cloudfoundry.identity.uaa.resources.SimpleAttributeNameMapper; +import org.cloudfoundry.identity.uaa.resources.jdbc.DefaultLimitSqlAdapter; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimGroupMembershipManager; @@ -72,7 +72,7 @@ import java.util.Map; import java.util.Set; -import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -244,8 +244,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 +262,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 +296,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()); } @@ -336,7 +368,7 @@ public void userWithNoEmailNotAllowed() { assertTrue("Wrong message: " + message, message.contains("email")); } JdbcTemplate jdbcTemplate = new JdbcTemplate(database); - int count = jdbcTemplate.queryForInt("select count(*) from users where userName=?", "dave"); + int count = jdbcTemplate.queryForObject("select count(*) from users where userName=?", new Object[] {"dave"}, Integer.class); assertEquals(0, count); } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpointsTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpointsTests.java similarity index 97% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpointsTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpointsTests.java index 16dfcaff868..16c4eeb6e47 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpointsTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/UserIdConversionEndpointsTests.java @@ -17,15 +17,16 @@ import java.util.Collections; import static junit.framework.Assert.assertTrue; -import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.Matchers.containsString; 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; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidPasswordExceptionTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidPasswordExceptionTest.java similarity index 77% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidPasswordExceptionTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidPasswordExceptionTest.java index 5f3e91fef1b..c278297c5b4 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidPasswordExceptionTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/exception/InvalidPasswordExceptionTest.java @@ -2,14 +2,15 @@ import org.junit.Test; -import static com.google.common.collect.Lists.newArrayList; +import java.util.Arrays; + import static org.junit.Assert.*; public class InvalidPasswordExceptionTest { @Test public void errorMessagesEmitInSortedOrder() { - InvalidPasswordException exception = new InvalidPasswordException(newArrayList("a2", "b1", "a1")); + InvalidPasswordException exception = new InvalidPasswordException(Arrays.asList("a2", "b1", "a1")); assertEquals("a1 a2 b1", exception.getMessagesAsOneString()); } } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManagerTests.java similarity index 94% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManagerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManagerTests.java index 1b6dad4f3e4..2242335e7ce 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupExternalMembershipManagerTests.java @@ -16,11 +16,13 @@ import java.util.Arrays; import java.util.List; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupExternalMember; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; @@ -43,7 +45,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; @@ -80,7 +82,7 @@ private void addGroup(String id, String name) { } private void validateCount(int expected) { - int existingMemberCount = jdbcTemplate.queryForInt("select count(*) from external_group_mapping"); + int existingMemberCount = jdbcTemplate.queryForObject("select count(*) from external_group_mapping", Integer.class); assertEquals(expected, existingMemberCount); } @@ -118,8 +120,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 @@ -128,19 +130,19 @@ public void using_filter_delete_filters_by_zone() { assertEquals(3, edao.query("").size()); edao.delete(""); assertEquals(0, edao.query("").size()); - assertEquals(3, jdbcTemplate.queryForInt("select count(*) from external_group_mapping")); + assertThat(jdbcTemplate.queryForObject("select count(*) from external_group_mapping", Integer.class), is(3)); 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")); + assertThat(jdbcTemplate.queryForObject("select count(*) from external_group_mapping", Integer.class), is(3)); 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")); + assertThat(jdbcTemplate.queryForObject("select count(*) from external_group_mapping", Integer.class), is(6)); } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java similarity index 66% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java index 9873fd1249d..d87f13f5f38 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupMembershipManagerTests.java @@ -12,13 +12,14 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.jdbc; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.resources.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; @@ -31,6 +32,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; @@ -38,10 +40,14 @@ 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.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -59,6 +65,12 @@ public class JdbcScimGroupMembershipManagerTests extends JdbcTestBase { 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()); + @Before public void initJdbcScimGroupMembershipManagerTests() { @@ -73,20 +85,37 @@ 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); + 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,Origin.UAA); + 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)); } @@ -101,7 +130,7 @@ private void addUser(String id, String password, String zoneId) { } private void validateCount(int expected) { - int existingMemberCount = jdbcTemplate.queryForInt("select count(*) from groups g, group_membership gm where g.identity_zone_id=? and gm.group_id=g.id", IdentityZoneHolder.get().getId()); + int existingMemberCount = jdbcTemplate.queryForObject("select count(*) from groups g, group_membership gm where g.identity_zone_id=? and gm.group_id=g.id", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class); assertEquals(expected, existingMemberCount); } @@ -140,10 +169,10 @@ 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 \"" + Origin.UAA + "\"").size()); + assertEquals(0,dao.query("origin eq \"" + OriginKeys.UAA + "\"").size()); } @@ -151,7 +180,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 +188,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 +196,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 +204,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,18 +212,18 @@ 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); } @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); 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); } @@ -213,11 +242,71 @@ 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(); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(3)); + assertThat(jdbcTemplate.queryForObject("select count(*) from external_group_mapping where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(3)); + gdao.onApplicationEvent(new EntityDeletedEvent<>(zone)); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from external_group_mapping where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(0)); + } + + @Test + public void test_provider_deleted() { + IdentityZoneHolder.set(zone); + addMembers(LOGIN_SERVER); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(3)); + assertThat(jdbcTemplate.queryForObject("select count(*) from external_group_mapping where origin = ? and group_id in (select id from groups where identity_zone_id=?)", new Object[] {LOGIN_SERVER, IdentityZoneHolder.get().getId()}, Integer.class), is(1)); + + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(LOGIN_SERVER) + .setIdentityZoneId(zone.getId()); + gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(3)); + assertThat(jdbcTemplate.queryForObject("select count(*) from external_group_mapping where origin = ? and group_id in (select id from groups where identity_zone_id=?)", new Object[] {LOGIN_SERVER, IdentityZoneHolder.get().getId()}, Integer.class), is(0)); + } + + @Test + public void test_cannot_delete_uaa_zone() { + addMembers(); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + gdao.onApplicationEvent(new EntityDeletedEvent<>(IdentityZone.getUaa())); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + } + + @Test + public void test_cannot_delete_uaa_provider() { + IdentityZoneHolder.set(zone); + addMembers(LOGIN_SERVER); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(3)); + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(UAA) + .setIdentityZoneId(zone.getId()); + gdao.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where group_id in (select id from groups where identity_zone_id=?)", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + assertThat(jdbcTemplate.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(3)); + } @Test @@ -246,22 +335,22 @@ 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); - m1.setOrigin(Origin.UAA); + m1.setOrigin(OriginKeys.UAA); dao.addMember("g2", m1); } @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); ScimGroupMember m1 = new ScimGroupMember("m1", ScimGroupMember.Type.USER, null); - m1.setOrigin(Origin.UAA); + m1.setOrigin(OriginKeys.UAA); dao.addMember("g2", m1); } @@ -304,7 +393,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()); } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioningTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioningTests.java similarity index 97% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioningTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioningTests.java index cab38cedb2d..9e235fd7245 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioningTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimGroupProvisioningTests.java @@ -18,7 +18,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; @@ -48,7 +48,7 @@ public void initJdbcScimGroupProvisioningTests() { validateGroupCount(3); } private void validateGroupCount(int expected) { - existingGroupCount = jdbcTemplate.queryForInt("select count(id) from groups where identity_zone_id='"+IdentityZoneHolder.get().getId()+"'"); + existingGroupCount = jdbcTemplate.queryForObject("select count(id) from groups where identity_zone_id='"+IdentityZoneHolder.get().getId()+"'", Integer.class); assertEquals(expected, existingGroupCount); } diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java similarity index 78% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java index dbc878c0e50..d1d1919443e 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/JdbcScimUserProvisioningTests.java @@ -12,24 +12,24 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.jdbc; -import org.cloudfoundry.identity.uaa.authentication.Origin; -import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.resources.SimpleAttributeNameMapper; +import org.cloudfoundry.identity.uaa.resources.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; 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.zone.IdentityProvider; 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.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; import org.junit.After; @@ -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; @@ -52,6 +53,10 @@ 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.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -81,6 +86,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; @@ -101,9 +109,9 @@ public void initJdbcScimUserProvisioningTests() throws Exception { db.setQueryConverter(filterConverter); BCryptPasswordEncoder pe = new BCryptPasswordEncoder(4); - existingUserCount = jdbcTemplate.queryForInt("select count(id) from users"); + existingUserCount = jdbcTemplate.queryForObject("select count(id) from users", Integer.class); - 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"); @@ -148,6 +156,135 @@ 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"); + 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()); + assertThat(jdbcTemplate.queryForObject( + "select count(*) from users where origin=? and identity_zone_id=?", + new Object[] {LOGIN_SERVER,IdentityZone.getUaa().getId()}, + Integer.class + ), is(1) + ); + addApprovalAndMembership(created.getId(), created.getOrigin()); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {created.getId()}, Integer.class), is(1)); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where member_id=?", new Object[] {created.getId()}, Integer.class), is(1)); + + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(LOGIN_SERVER) + .setIdentityZoneId(IdentityZone.getUaa().getId()); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {LOGIN_SERVER, IdentityZone.getUaa().getId()}, Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {created.getId()}, Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where member_id=?", new Object[] {created.getId()}, Integer.class), is(0)); + } + + @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()); + assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {LOGIN_SERVER, zone.getId()}, Integer.class), is(1)); + addApprovalAndMembership(created.getId(), created.getOrigin()); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {created.getId()}, Integer.class), is(1)); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where member_id=?", new Object[] {created.getId()}, Integer.class), is(1)); + + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(LOGIN_SERVER) + .setIdentityZoneId(zone.getId()); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {LOGIN_SERVER, zone.getId()}, Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {created.getId()}, Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where member_id=?", new Object[] {created.getId()}, Integer.class), is(0)); + } + + @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()); + assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {UAA, zone.getId()}, Integer.class), is(1)); + addApprovalAndMembership(created.getId(), created.getOrigin()); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {created.getId()}, Integer.class), is(1)); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where member_id=?", new Object[] {created.getId()}, Integer.class), is(1)); + + db.onApplicationEvent(new EntityDeletedEvent<>(zone)); + assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {UAA, zone.getId()}, Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {created.getId()}, Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from group_membership where member_id=?", new Object[] {created.getId()}, Integer.class), is(0)); + } + + @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()); + assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {UAA, IdentityZone.getUaa().getId()}, Integer.class), is(3)); + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(UAA) + .setIdentityZoneId(IdentityZone.getUaa().getId()); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {UAA, IdentityZone.getUaa().getId()}, Integer.class), is(3)); + } + + @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()); + assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {UAA, zone.getId()}, Integer.class), is(1)); + IdentityProvider loginServer = + new IdentityProvider() + .setOriginKey(UAA) + .setIdentityZoneId(zone.getId()); + db.onApplicationEvent(new EntityDeletedEvent<>(loginServer)); + assertThat(jdbcTemplate.queryForObject("select count(*) from users where origin=? and identity_zone_id=?", new Object[] {UAA, zone.getId()}, Integer.class), is(1)); + } + + + @Test public void canCreateUserInDefaultIdentityZone() { ScimUser user = new ScimUser(null, "jo@foo.com", "Jo", "User"); @@ -160,7 +297,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 +324,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 +335,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/jdbc/ScimSearchQueryConverterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverterTests.java similarity index 98% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverterTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverterTests.java index 7111100d8bd..d95b5f2cca2 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverterTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/jdbc/ScimSearchQueryConverterTests.java @@ -12,8 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.jdbc; -import org.cloudfoundry.identity.uaa.rest.SimpleAttributeNameMapper; -import org.cloudfoundry.identity.uaa.rest.jdbc.SearchQueryConverter.ProcessedFilter; +import org.cloudfoundry.identity.uaa.resources.SimpleAttributeNameMapper; +import org.cloudfoundry.identity.uaa.resources.jdbc.SearchQueryConverter.ProcessedFilter; import org.junit.Before; import org.junit.Test; diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioningTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioningTests.java similarity index 98% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioningTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioningTests.java index 2977b7e61ab..edd489c4c42 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioningTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/remote/RemoteScimUserProvisioningTests.java @@ -17,7 +17,7 @@ import java.util.List; -import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; +import org.cloudfoundry.identity.uaa.account.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.junit.After; import org.junit.Before; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/security/GroupRoleCheckTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/security/GroupRoleCheckTests.java new file mode 100644 index 00000000000..9801f58e2d1 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/security/GroupRoleCheckTests.java @@ -0,0 +1,119 @@ +package org.cloudfoundry.identity.uaa.scim.security; + +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.scim.ScimGroupMember; +import org.cloudfoundry.identity.uaa.scim.ScimGroupMembershipManager; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by pivotal on 12/7/15. + */ +public class GroupRoleCheckTests { + + @Before + public void SetUp() { + + } + + @Test + public void testCheckGroupMember() { + SecurityContext context = getMockContext("member-id"); + SecurityContextHolder.setContext(context); + + ScimGroupMembershipManager manager = mock(ScimGroupMembershipManager.class); + ScimGroupMember member = new ScimGroupMember("member-id", ScimGroupMember.Type.USER, Collections.singletonList(ScimGroupMember.Role.MEMBER)); + when(manager.getMembers("group-id", ScimGroupMember.Role.MEMBER)).thenReturn(Arrays.asList(member)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/Groups/group-id"); + request.setServletPath("/Groups/group-id"); + + GroupRoleCheck checker = new GroupRoleCheck(manager); + + assertTrue(checker.isGroupMember(request, 1)); + } + + @Test + public void testCheckGroupMemberWhenUaaDeployedInNonRootPath() { + SecurityContext context = getMockContext("member-id"); + SecurityContextHolder.setContext(context); + + ScimGroupMembershipManager manager = mock(ScimGroupMembershipManager.class); + ScimGroupMember member = new ScimGroupMember("member-id", ScimGroupMember.Type.USER, Collections.singletonList(ScimGroupMember.Role.MEMBER)); + when(manager.getMembers("group-id", ScimGroupMember.Role.MEMBER)).thenReturn(Arrays.asList(member)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/uaa/Groups/group-id"); + request.setContextPath("/uaa"); + request.setServletPath("/Groups/group-id"); + + GroupRoleCheck checker = new GroupRoleCheck(manager); + + assertTrue(checker.isGroupMember(request, 1)); + } + + public static SecurityContext getMockContext(String userId) { + return new SecurityContext() { + @Override + public Authentication getAuthentication() { + return new Authentication() { + @Override + public Collection 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) { + + } + }; + } +} diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/test/JsonObjectMatcherUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/test/JsonObjectMatcherUtils.java similarity index 100% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/test/JsonObjectMatcherUtils.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/test/JsonObjectMatcherUtils.java diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/test/TestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/test/TestUtils.java similarity index 93% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/test/TestUtils.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/test/TestUtils.java index a97f348d9a4..6e57502462b 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/test/TestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/test/TestUtils.java @@ -12,6 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import java.sql.Connection; @@ -81,7 +83,7 @@ public static void deleteFrom(DataSource dataSource, String... tables) throws Ex } public static void assertNoSuchUser(JdbcTemplate template, String column, String value) { - assertEquals(0, template.queryForInt("select count(id) from users where " + column + "='" + value + "'")); + assertThat(template.queryForObject("select count(id) from users where " + column + "='" + value + "'", Integer.class), is(0)); } public static ScimUser scimUserInstance(String email) { diff --git a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidatorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidatorTests.java similarity index 90% rename from scim/src/test/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidatorTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidatorTests.java index 4446c822383..05c8853ee38 100644 --- a/scim/src/test/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidatorTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/validate/UaaPasswordPolicyValidatorTests.java @@ -15,15 +15,15 @@ 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.provider.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; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.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.junit.runner.RunWith; @@ -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/login/src/test/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandlerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandlerTest.java similarity index 100% rename from login/src/test/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandlerTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/security/CsrfAwareEntryPointAndDeniedHandlerTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessorTests.java similarity index 96% rename from common/src/test/java/org/cloudfoundry/identity/uaa/security/DefaultSecurityContextAccessorTests.java rename to server/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/server/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/security/StubSecurityContextAccessor.java b/server/src/test/java/org/cloudfoundry/identity/uaa/security/StubSecurityContextAccessor.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/security/StubSecurityContextAccessor.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/security/StubSecurityContextAccessor.java diff --git a/common/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 72% rename from common/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 66a30dfb776..dc146113802 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/web/CorsFilterTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/CorsFilterTests.java @@ -1,21 +1,18 @@ -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; @@ -26,7 +23,28 @@ import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.http.HttpStatus; + +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 { @@ -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(); @@ -166,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"); @@ -178,13 +233,13 @@ public void testPreFlightExpectStandardCorsResponse() throws ServletException, I corsFilter.doFilter(request, response, filterChain); - assertStandardCorsPreFlightResponse(response); + 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"); @@ -312,26 +367,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 +396,7 @@ public void doInitializeWithNoPropertiesSet() throws ServletException, IOExcepti corsFilter.doFilter(request, response, filterChain); - assertStandardCorsPreFlightResponse(response); + assertStandardCorsPreFlightResponse(response, "GET, OPTIONS, POST, PUT, DELETE", AUTHORIZATION, ACCEPT, CONTENT_TYPE, ACCEPT_LANGUAGE, CONTENT_LANGUAGE); } @Test @@ -350,10 +406,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 +422,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 +437,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 allowedMethods, 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(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", 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/common/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/security/web/SecurityFilterChainPostProcessorTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/security/web/UaaRequestMatcherTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/security/web/UaaRequestMatcherTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/security/web/UaaRequestMatcherTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/security/web/UaaRequestMatcherTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/CreateDB.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/CreateDB.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/CreateDB.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/CreateDB.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/IntegrationTestContextLoader.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/IntegrationTestContextLoader.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/IntegrationTestContextLoader.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/IntegrationTestContextLoader.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/JdbcTestBase.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/JdbcTestBase.java similarity index 96% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/JdbcTestBase.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/JdbcTestBase.java index feff6af4ee1..e3b12b46445 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/test/JdbcTestBase.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/test/JdbcTestBase.java @@ -13,7 +13,7 @@ package org.cloudfoundry.identity.uaa.test; import org.cloudfoundry.identity.uaa.TestClassNullifier; -import org.cloudfoundry.identity.uaa.rest.jdbc.LimitSqlAdapter; +import org.cloudfoundry.identity.uaa.resources.jdbc.LimitSqlAdapter; import org.flywaydb.core.Flyway; import org.junit.After; import org.junit.Before; @@ -23,8 +23,6 @@ import org.springframework.web.context.support.XmlWebApplicationContext; import javax.sql.DataSource; -import java.util.HashMap; -import java.util.Map; /** * Created by fhanik on 12/9/14. diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/MockAuthentication.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/MockAuthentication.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/MockAuthentication.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/MockAuthentication.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/NullSafeSystemProfileValueSource.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/NullSafeSystemProfileValueSource.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/NullSafeSystemProfileValueSource.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/NullSafeSystemProfileValueSource.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/ParentContextLoader.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/ParentContextLoader.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/ParentContextLoader.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/ParentContextLoader.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestAccountSetup.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestAccountSetup.java similarity index 98% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/TestAccountSetup.java rename to server/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/server/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/TestApplicationEventHandler.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventHandler.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventHandler.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventHandler.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventListener.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventListener.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventListener.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventListener.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventPublisher.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventPublisher.java similarity index 90% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/TestApplicationEventPublisher.java rename to server/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/server/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/test/TestProfileEnvironment.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestProfileEnvironment.java similarity index 92% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/TestProfileEnvironment.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/TestProfileEnvironment.java index c11907cf615..baf5cf10738 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestProfileEnvironment.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestProfileEnvironment.java @@ -18,10 +18,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.identity.uaa.config.EnvironmentMapFactoryBean; -import org.cloudfoundry.identity.uaa.config.NestedMapPropertySource; -import org.cloudfoundry.identity.uaa.config.YamlMapFactoryBean; -import org.cloudfoundry.identity.uaa.config.YamlProcessor.ResolutionMethod; +import org.cloudfoundry.identity.uaa.impl.config.EnvironmentMapFactoryBean; +import org.cloudfoundry.identity.uaa.impl.config.NestedMapPropertySource; +import org.cloudfoundry.identity.uaa.impl.config.YamlMapFactoryBean; +import org.cloudfoundry.identity.uaa.impl.config.YamlProcessor.ResolutionMethod; import org.springframework.core.env.AbstractEnvironment; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java similarity index 92% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java index e299d48fa81..21856f563ee 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java @@ -12,6 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import java.sql.Connection; @@ -69,7 +71,7 @@ public static void deleteFrom(DataSource dataSource, String... tables) throws Ex } public static void assertNoSuchUser(JdbcTemplate template, String column, String value) { - assertEquals(0, template.queryForInt("select count(id) from users where " + column + "='" + value + "'")); + assertThat(template.queryForObject("select count(id) from users where " + column + "='" + value + "'", Integer.class), is(0)); } } diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/UaaTestAccounts.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/UaaTestAccounts.java similarity index 99% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/UaaTestAccounts.java rename to server/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/server/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/test/UaaTestAccountsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/UaaTestAccountsTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/UaaTestAccountsTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/UaaTestAccountsTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/UrlHelper.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/UrlHelper.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/UrlHelper.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/UrlHelper.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/test/YamlServletProfileInitializerContextInitializer.java b/server/src/test/java/org/cloudfoundry/identity/uaa/test/YamlServletProfileInitializerContextInitializer.java similarity index 90% rename from common/src/test/java/org/cloudfoundry/identity/uaa/test/YamlServletProfileInitializerContextInitializer.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/test/YamlServletProfileInitializerContextInitializer.java index dc7cc11db90..e54283c5fc7 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/test/YamlServletProfileInitializerContextInitializer.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/test/YamlServletProfileInitializerContextInitializer.java @@ -1,6 +1,6 @@ package org.cloudfoundry.identity.uaa.test; -import org.cloudfoundry.identity.uaa.config.YamlServletProfileInitializer; +import org.cloudfoundry.identity.uaa.impl.config.YamlServletProfileInitializer; import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletContext; import org.springframework.web.context.ConfigurableWebApplicationContext; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/BCryptPasswordEncoderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/user/BCryptPasswordEncoderTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/user/BCryptPasswordEncoderTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/user/BCryptPasswordEncoderTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java similarity index 72% rename from common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java index 4c0c2908124..0715150de80 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/user/InMemoryUaaUserDatabaseTests.java @@ -1,26 +1,26 @@ 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; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; 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() { - Map users = new HashMap<>(); - users.put(user.getUsername(), user); - db = new InMemoryUaaUserDatabase(users); + db = new InMemoryUaaUserDatabase(Collections.singleton(user)); } @@ -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 @@ -49,6 +49,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())); @@ -71,4 +81,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/server/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java similarity index 75% rename from common/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java index cd5c67b812c..91e8a264e25 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/user/JdbcUaaUserDatabaseTests.java +++ b/server/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; @@ -25,10 +25,16 @@ 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.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class JdbcUaaUserDatabaseTests extends JdbcTestBase { @@ -56,7 +62,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) { @@ -94,9 +100,14 @@ 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",Origin.UAA); + UaaUser joe = db.retrieveUserByName("joe", OriginKeys.UAA); assertNotNull(joe); assertEquals(JOE_ID, joe.getId()); assertEquals("Joe", joe.getUsername()); @@ -111,18 +122,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 +145,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,14 +173,31 @@ public void getValidUserInOtherZoneFromOtherZone() { @Test(expected = UsernameNotFoundException.class) public void getValidUserInOtherZoneFromDefaultZoneFails() { - UaaUser alice = db.retrieveUserByName("alice",Origin.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/server/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java b/server/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java new file mode 100644 index 00000000000..651bf498e02 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/user/MockUaaUserDatabase.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Cloud Foundry + * 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. + * + * 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.user; + +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; + +import java.util.Collections; +import java.util.function.Function; + +public class MockUaaUserDatabase extends InMemoryUaaUserDatabase { + 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()) + )))); + } +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/UaaAuthorityTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/user/UaaAuthorityTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/user/UaaAuthorityTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/user/UaaAuthorityTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserEditorTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserEditorTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserEditorTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserEditorTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserTestFactory.java b/server/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserTestFactory.java similarity index 79% rename from common/src/test/java/org/cloudfoundry/identity/uaa/user/UaaUserTestFactory.java rename to server/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/server/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/CachingPasswordEncoderTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/CachingPasswordEncoderTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/CachingPasswordEncoderTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/CachingPasswordEncoderTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java similarity index 92% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java index 262a175f75d..b1042a09708 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/util/DomainFilterTest.java @@ -14,12 +14,12 @@ package org.cloudfoundry.identity.uaa.util; -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.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +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; @@ -29,8 +29,8 @@ 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.client.ClientConstants.ALLOWED_PROVIDERS; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LOGIN_SERVER; +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; @@ -91,16 +91,32 @@ 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(); } 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/util/EnsureOldLibrariesAreRemoved.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/EnsureOldLibrariesAreRemoved.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/EnsureOldLibrariesAreRemoved.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/EnsureOldLibrariesAreRemoved.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/LinkedMaskingMultiValueMapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/LinkedMaskingMultiValueMapTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/LinkedMaskingMultiValueMapTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/LinkedMaskingMultiValueMapTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/NullifyFields.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/NullifyFields.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/NullifyFields.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/NullifyFields.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/NullifyFieldsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/NullifyFieldsTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/NullifyFieldsTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/NullifyFieldsTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/SetServerNameRequestPostProcessor.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/SetServerNameRequestPostProcessor.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/SetServerNameRequestPostProcessor.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/SetServerNameRequestPostProcessor.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/UaaMapUtilsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaMapUtilsTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/UaaMapUtilsTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaMapUtilsTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/UaaPagingUtilsTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaPagingUtilsTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/UaaPagingUtilsTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaPagingUtilsTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/UaaStringUtilsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaStringUtilsTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/UaaStringUtilsTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaStringUtilsTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtilsTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtilsTest.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtilsTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/util/UaaUrlUtilsTest.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java similarity index 97% rename from common/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java index 7665b2c07bc..282c61b670a 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/web/CookieBasedCsrfTokenRepositoryTests.java @@ -15,6 +15,7 @@ package org.cloudfoundry.identity.uaa.web; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.junit.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/error/ExceptionReportHttpMessageConverterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/web/ExceptionReportHttpMessageConverterTest.java similarity index 94% rename from common/src/test/java/org/cloudfoundry/identity/uaa/error/ExceptionReportHttpMessageConverterTest.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/web/ExceptionReportHttpMessageConverterTest.java index e6e508b9dc7..8b3899de73e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/error/ExceptionReportHttpMessageConverterTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/web/ExceptionReportHttpMessageConverterTest.java @@ -1,5 +1,7 @@ -package org.cloudfoundry.identity.uaa.error; +package org.cloudfoundry.identity.uaa.web; +import org.cloudfoundry.identity.uaa.web.ExceptionReport; +import org.cloudfoundry.identity.uaa.web.ExceptionReportHttpMessageConverter; import org.junit.Before; import org.junit.Test; import org.springframework.http.HttpOutputMessage; diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/web/ForwardAwareInternalResourceViewResolverTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/web/ForwardAwareInternalResourceViewResolverTests.java similarity index 89% rename from common/src/test/java/org/cloudfoundry/identity/uaa/web/ForwardAwareInternalResourceViewResolverTests.java rename to server/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/server/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/common/src/test/java/org/cloudfoundry/identity/uaa/web/HealthzEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/web/HealthzEndpointTests.java similarity index 94% rename from common/src/test/java/org/cloudfoundry/identity/uaa/web/HealthzEndpointTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/web/HealthzEndpointTests.java index f9c65e50c09..264e5ea8721 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/web/HealthzEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/web/HealthzEndpointTests.java @@ -14,6 +14,7 @@ import static org.junit.Assert.assertEquals; +import org.cloudfoundry.identity.uaa.health.HealthzEndpoint; import org.junit.Test; public class HealthzEndpointTests { diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilterTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilterTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneResolvingFilterTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilterTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilterTests.java similarity index 100% rename from common/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilterTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/zone/IdentityZoneSwitchingFilterTests.java diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java similarity index 76% rename from common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java index e5ddc1455b4..5ebf86ceece 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityProviderProvisioningTests.java @@ -1,26 +1,36 @@ 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.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdpAlreadyExistsException; +import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; +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; 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; import java.util.Map; import java.util.UUID; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; 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 { @@ -33,6 +43,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); + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(1)); + db.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get())); + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(0)); + } + + @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); + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(5)); + db.onApplicationEvent(new EntityDeletedEvent<>(createdIdp)); + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + } + + @Test + public void test_cannot_delete_uaa_providers() { + //action try to delete uaa provider + //should not do anything + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + IdentityProvider uaa = db.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); + db.onApplicationEvent(new EntityDeletedEvent<>(uaa)); + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_provider where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(4)); + } + @Test public void testCreateAndUpdateIdentityProviderInDefaultZone() throws Exception { String zoneId = IdentityZone.getUaa().getId(); @@ -65,7 +113,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 +177,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/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java similarity index 81% rename from common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java index 237f6986ddd..9484594ae83 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/JdbcIdentityZoneProvisioningTests.java @@ -1,14 +1,15 @@ 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.audit.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.test.JdbcTestBase; import org.junit.Before; import org.junit.Test; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; public class JdbcIdentityZoneProvisioningTests extends JdbcTestBase { @@ -20,6 +21,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); + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_zone where id = ?", new Object[] {createdIdZone.getId()}, Integer.class), is(1)); + db.onApplicationEvent(new EntityDeletedEvent<>(identityZone)); + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_zone where id = ?", new Object[] {createdIdZone.getId()}, Integer.class), is(0)); + } + + @Test + public void test_cannot_delete_uaa_zone() { + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_zone where id = ?", new Object[] {IdentityZone.getUaa().getId()}, Integer.class), is(1)); + db.onApplicationEvent(new EntityDeletedEvent<>(IdentityZone.getUaa())); + assertThat(jdbcTemplate.queryForObject("select count(*) from identity_zone where id = ?", new Object[] {IdentityZone.getUaa().getId()}, Integer.class), is(1)); + } + @Test public void testCreateIdentityZone() throws Exception { IdentityZone identityZone = MultitenancyFixture.identityZone(generator.generate(),generator.generate()); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java similarity index 92% rename from common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenancyFixture.java rename to server/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/server/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/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java similarity index 83% rename from common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java rename to server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java index 3a2c8f5398f..895de271d0e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/zone/MultitenantJdbcClientDetailsServiceTests.java @@ -1,6 +1,7 @@ package org.cloudfoundry.identity.uaa.zone; -import org.cloudfoundry.identity.uaa.client.ClientConstants; +import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.flywaydb.core.Flyway; import org.junit.After; import org.junit.Before; @@ -10,6 +11,7 @@ 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; @@ -22,10 +24,12 @@ import java.util.Iterator; import java.util.Map; +import static org.hamcrest.CoreMatchers.is; 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.assertThat; import static org.junit.Assert.assertTrue; @@ -40,8 +44,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 +76,49 @@ 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); + assertThat(jdbcTemplate.queryForObject("select count(*) from oauth_client_details where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(1)); + addApproval(id); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where client_id=?", new Object[] {id}, Integer.class), is(1)); + + service.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get())); + assertThat(jdbcTemplate.queryForObject("select count(*) from oauth_client_details where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(0)); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where client_id=?", new Object[] {id}, Integer.class), is(0)); + } + + @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); + assertThat(jdbcTemplate.queryForObject("select count(*) from oauth_client_details where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(1)); + addApproval(id); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where client_id=?", new Object[] {id}, Integer.class), is(1)); + + service.onApplicationEvent(new EntityDeletedEvent<>(IdentityZoneHolder.get())); + assertThat(jdbcTemplate.queryForObject("select count(*) from oauth_client_details where identity_zone_id=?", new Object[] {IdentityZoneHolder.get().getId()}, Integer.class), is(1)); + assertThat(jdbcTemplate.queryForObject("select count(*) from authz_approvals where client_id=?", new Object[] {id}, Integer.class), is (1)); + } + + + @Test(expected = NoSuchClientException.class) public void testLoadingClientForNonExistingClientId() { service.loadClientByClientId("nonExistingClientId"); diff --git a/login/src/test/resources/test/config/login.yml b/server/src/test/resources/config/login.yml similarity index 100% rename from login/src/test/resources/test/config/login.yml rename to server/src/test/resources/config/login.yml diff --git a/login/src/test/resources/integration.test.properties b/server/src/test/resources/integration.test.properties similarity index 100% rename from login/src/test/resources/integration.test.properties rename to server/src/test/resources/integration.test.properties diff --git a/common/src/test/resources/log4j.properties b/server/src/test/resources/log4j.properties similarity index 100% rename from common/src/test/resources/log4j.properties rename to server/src/test/resources/log4j.properties diff --git a/login/src/test/resources/log4j_ci.properties b/server/src/test/resources/log4j_ci.properties similarity index 100% rename from login/src/test/resources/log4j_ci.properties rename to server/src/test/resources/log4j_ci.properties diff --git a/common/src/test/resources/test-file-metadata-2.xml b/server/src/test/resources/test-file-metadata-2.xml similarity index 100% rename from common/src/test/resources/test-file-metadata-2.xml rename to server/src/test/resources/test-file-metadata-2.xml diff --git a/common/src/test/resources/test-file-metadata.xml b/server/src/test/resources/test-file-metadata.xml similarity index 100% rename from common/src/test/resources/test-file-metadata.xml rename to server/src/test/resources/test-file-metadata.xml diff --git a/login/src/test/resources/test.saml.login.yml.txt b/server/src/test/resources/test.saml.login.yml.txt similarity index 100% rename from login/src/test/resources/test.saml.login.yml.txt rename to server/src/test/resources/test.saml.login.yml.txt diff --git a/settings.gradle b/settings.gradle index 637cdf64cc9..965683d4dd1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,20 +1,16 @@ rootProject.name = 'cloudfoundry-identity-parent' -include ':cloudfoundry-identity-payload' +include ':cloudfoundry-identity-model' include ':cloudfoundry-identity-client-lib' -include ':cloudfoundry-identity-common' -include ':cloudfoundry-identity-scim' -include ':cloudfoundry-identity-login' +include ':cloudfoundry-identity-server' include ':cloudfoundry-identity-uaa' 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-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 -project(':cloudfoundry-identity-login').projectDir = "$rootDir/login" as File +project(':cloudfoundry-identity-server').projectDir = "$rootDir/server" 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/shared_versions.gradle b/shared_versions.gradle index 12845f5551b..47e84d9d307 100644 --- a/shared_versions.gradle +++ b/shared_versions.gradle @@ -1,14 +1,38 @@ ext { - springVersion = '4.1.6.RELEASE' - springSecurityVersion = '4.0.1.RELEASE' - springSecurityOAuthVersion = '2.0.7.RELEASE' + apacheLdapApiVersion = '1.0.0-M22' + aspectJVersion = '1.6.9' + bcpkixVersion = '1.47' + cglibVersion = '2.2.2' + commonsHttpClientVersion = '4.3.3' + commonsLoggingVersion = '1.2' + flywayVersion = '3.2.1' + guavaVersion = '18.0' + hamcrestVersion = '1.3' + hibernateValidatorVersion = '4.3.1.Final' + hsqldbVersion = '2.3.1' + jacksonVersion = '2.5.3' + javamailVersion = '1.4.7' + jsonPathVersion = '2.1.0' + jstlVersion = '1.2' + junitVersion = '4.11' + mariaDBClientVersion = '1.1.8' + mockitoVersion = '1.8.5' + passayVersion = '1.0' + postgresqlVersion = '9.1-901.jdbc3' + scimSDKVersion = '1.6.0' + servletVersion = '3.1.0' + slf4jVersion = '1.7.7' + snakeYamlVersion = '1.12' + springVersion = '4.2.2.RELEASE' + springSecurityVersion = '4.0.3.RELEASE' + springSecurityJwtVersion = '1.0.3.RELEASE' + springSecurityOAuthVersion = '2.0.8.RELEASE' springSecurityLdapVersion = '2.0.3.RELEASE' springSecuritySamlVersion = '1.0.1.RELEASE' - postgresqlVersion = '9.1-901.jdbc3' + springRetryVersion = '1.0.2.RELEASE' + thymeleafVersion = '2.1.2.RELEASE' + thymeleafDialectVersion = '1.2.3' + thymeleafExtrasVersion = '2.1.1.RELEASE' tomcatVersion = '7.0.61' - springSecurityJwtVersion = '1.0.3.RELEASE' - bcpkixVersion = '1.47' - apacheLdapApiVersion = '1.0.0-M22' - jacksonVersion = '2.5.3' - flywayVersion = '3.2.1' + validationAPIVersion = '1.0.0.GA' } diff --git a/uaa/build.gradle b/uaa/build.gradle index e504d46a43c..a7128ac761f 100644 --- a/uaa/build.gradle +++ b/uaa/build.gradle @@ -1,7 +1,9 @@ -Project identityPayload = parent.subprojects.find { it.name.equals('cloudfoundry-identity-common') } -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') } +plugins { + id "org.asciidoctor.convert" version "1.5.2" +} + +Project identityModel = parent.subprojects.find { it.name.equals('cloudfoundry-identity-model') } +Project identityServer = parent.subprojects.find { it.name.equals('cloudfoundry-identity-server') } apply plugin: 'war' @@ -18,37 +20,30 @@ war { } apply plugin: 'eclipse-wtp' eclipse { - wtp { - component { - contextPath = 'uaa' - } - } + wtp { + component { + contextPath = 'uaa' + } + } } description = 'UAA' dependencies { - compile(identityCommon) { - exclude(module: 'jna') - } - compile(identityScim) { + compile(identityServer){ exclude(module: 'jna') } - 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 + providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version: parent.servletVersion - testCompile identityCommon.configurations.testCompile.dependencies - testCompile identityCommon.sourceSets.test.output - testCompile identityScim.sourceSets.test.output - testCompile identityLogin.sourceSets.test.output + testCompile identityServer.configurations.testCompile.dependencies + testCompile identityServer.sourceSets.test.output testCompile(group: 'org.apache.directory.server', name: 'apacheds-core', version:'1.5.5') { exclude(module: 'bcprov-jdk15') @@ -60,53 +55,30 @@ 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.jsp', name: 'jsp-api', version:'2.1' + testCompile group: 'junit', name: 'junit', version: parent.junitVersion 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' - - + testCompile group: 'org.springframework', name: 'spring-test', version: parent.springVersion + testCompile group: 'org.springframework.security', name: 'spring-security-test', version: parent.springSecurityVersion + testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: parent.hamcrestVersion + testCompile group: 'org.mockito', name: 'mockito-all', version: parent.mockitoVersion + testCompile group: 'org.apache.tomcat', name: 'tomcat-jdbc', version:parent.tomcatVersion } 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 - +integrationTest { 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/resources/ldap-integration.xml b/uaa/src/main/resources/ldap-integration.xml index 587599f11d9..1c92a07ca72 100644 --- a/uaa/src/main/resources/ldap-integration.xml +++ b/uaa/src/main/resources/ldap-integration.xml @@ -32,7 +32,7 @@ - + @@ -41,12 +41,12 @@ - + - + diff --git a/uaa/src/main/resources/ldap/ldap-groups-as-scopes.xml b/uaa/src/main/resources/ldap/ldap-groups-as-scopes.xml index 77dfab01e46..13eb945ec33 100644 --- a/uaa/src/main/resources/ldap/ldap-groups-as-scopes.xml +++ b/uaa/src/main/resources/ldap/ldap-groups-as-scopes.xml @@ -24,7 +24,7 @@ - + diff --git a/uaa/src/main/resources/ldap/ldap-groups-map-to-scopes.xml b/uaa/src/main/resources/ldap/ldap-groups-map-to-scopes.xml index 1e398cc2ce5..e727e391960 100644 --- a/uaa/src/main/resources/ldap/ldap-groups-map-to-scopes.xml +++ b/uaa/src/main/resources/ldap/ldap-groups-map-to-scopes.xml @@ -24,7 +24,7 @@ - + diff --git a/uaa/src/main/resources/ldap/ldap-groups-populator.xml b/uaa/src/main/resources/ldap/ldap-groups-populator.xml index 2aeacf10dee..1cf73fb95fc 100644 --- a/uaa/src/main/resources/ldap/ldap-groups-populator.xml +++ b/uaa/src/main/resources/ldap/ldap-groups-populator.xml @@ -22,7 +22,7 @@ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"> + class="org.cloudfoundry.identity.uaa.provider.ldap.extension.NestedLdapAuthoritiesPopulator"> diff --git a/uaa/src/main/resources/ldap/ldap-search-and-compare.xml b/uaa/src/main/resources/ldap/ldap-search-and-compare.xml index 2a081868964..18ca9249047 100644 --- a/uaa/src/main/resources/ldap/ldap-search-and-compare.xml +++ b/uaa/src/main/resources/ldap/ldap-search-and-compare.xml @@ -28,7 +28,7 @@ - + @@ -40,7 +40,7 @@ + class="${ldap.base.passwordEncoder:org.cloudfoundry.identity.uaa.provider.ldap.DynamicPasswordComparator}"> diff --git a/uaa/src/main/resources/login.yml b/uaa/src/main/resources/login.yml index d72f66196e9..348ac5fc6ad 100644 --- a/uaa/src/main/resources/login.yml +++ b/uaa/src/main/resources/login.yml @@ -28,12 +28,12 @@ # image: /resources/pivotal/images/partners-logo-gray.png # image-hover: /resources/pivotal/images/partners-logo-teal.png -links: +#links: # Custom self service links (will only be displayed if selfServiceLinksEnabled is true) # If selfServiceLinksEnabled is true and these custom links are not provided then the Login Server # will use internal links. - passwd: /forgot_password - signup: /create_account +# passwd: /forgot_password +# signup: /blah_account #notifications: # url: http://localhost:3001 @@ -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/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/main/resources/uaa.yml b/uaa/src/main/resources/uaa.yml index ae8f8f18e43..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: @@ -259,6 +255,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 diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index f0135621729..25c762db674 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -24,14 +24,31 @@ http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"> - + - + - + + + + + + + + + + + json=application/json + xml=application/xml + html=test/html + + + + + @@ -61,11 +78,96 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -106,18 +208,18 @@ + key="#{T(org.cloudfoundry.identity.uaa.security.web.SecurityFilterChainPostProcessor.FilterPosition).after(T(org.cloudfoundry.identity.uaa.scim.DisableUserManagementSecurityFilter))}"/> - + - + @@ -153,7 +255,7 @@ - + @@ -242,24 +344,24 @@ + class="org.cloudfoundry.identity.uaa.security.ContextSensitiveOAuth2WebSecurityExpressionHandler"> - + - + - + @@ -268,7 +370,7 @@ - + @@ -281,12 +383,11 @@ - + - - + @@ -297,7 +398,7 @@ - + @@ -308,4 +409,8 @@ + + + + diff --git a/uaa/src/main/webapp/WEB-INF/spring/approvals-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/approvals-endpoints.xml index ca663242868..3010a303a67 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/approvals-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/approvals-endpoints.xml @@ -22,13 +22,13 @@ - + - + @@ -42,7 +42,7 @@ - + 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 @@ - + diff --git a/uaa/src/main/webapp/WEB-INF/spring/client-admin-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/client-admin-endpoints.xml index cbe484d89b9..a5c1af77376 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/client-admin-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/client-admin-endpoints.xml @@ -55,14 +55,14 @@ - + - + @@ -79,20 +79,20 @@ - - + - + - + diff --git a/uaa/src/main/webapp/WEB-INF/spring/codestore-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/codestore-endpoints.xml index 0aff1d85ebc..012d8842f03 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/codestore-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/codestore-endpoints.xml @@ -19,7 +19,7 @@ - + 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..f96fb62b70e 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 @@ - + - - @@ -181,7 +179,7 @@ - + @@ -190,11 +188,11 @@ - + - + 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..641d1dda0b6 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/multitenant-endpoints.xml @@ -8,7 +8,7 @@ - + @@ -19,7 +19,7 @@ + class="org.cloudfoundry.identity.uaa.zone.ZoneEndpointsClientDetailsValidator"> @@ -31,7 +31,7 @@ - + @@ -58,6 +58,9 @@ + @@ -115,9 +118,13 @@ + + diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml index d0d47346581..0ff86a58f22 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-clients.xml @@ -20,13 +20,13 @@ - + - + @@ -35,24 +35,14 @@ value="#{@applicationProperties.containsKey('oauth.client.autoapprove')?@config['oauth']['client']['autoapprove']:'cf'}" /> - - - - - - - - - - + - @@ -61,7 +51,6 @@ - @@ -71,7 +60,6 @@ - - @@ -99,7 +86,6 @@ - @@ -108,7 +94,6 @@ - @@ -116,7 +101,6 @@ - @@ -126,7 +110,6 @@ - @@ -140,7 +123,6 @@ - @@ -149,7 +131,6 @@ - @@ -158,7 +139,6 @@ - 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 d9cb6b4c76a..433d6b5f06b 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml @@ -23,11 +23,11 @@ - + - + @@ -54,7 +54,7 @@ - + @@ -88,7 +88,7 @@ - + @@ -98,7 +98,7 @@ - + @@ -111,6 +111,7 @@ + @@ -151,6 +152,10 @@ + + + + @@ -166,7 +171,7 @@ - + @@ -261,11 +266,11 @@ - + - + @@ -279,7 +284,7 @@ - + @@ -292,30 +297,30 @@ - + - + - + - + - + @@ -338,7 +343,7 @@ entry-point-ref="oauthAuthenticationEntryPoint" /> - + @@ -376,7 +381,7 @@ - + - + - + diff --git a/uaa/src/main/webapp/WEB-INF/spring/password-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/password-endpoints.xml index 26bc27010f4..8a80bb63071 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/password-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/password-endpoints.xml @@ -15,7 +15,7 @@ - + diff --git a/uaa/src/main/webapp/WEB-INF/spring/resource-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/resource-endpoints.xml index 2a5d8c8bd65..3294c27b0e4 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/resource-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/resource-endpoints.xml @@ -50,7 +50,7 @@ - + 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..f162ab0eebc 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/saml-providers.xml @@ -42,7 +42,7 @@ - + @@ -99,7 +99,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + @@ -116,7 +116,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + @@ -134,7 +134,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + @@ -156,7 +156,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - @@ -178,13 +178,13 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + - + @@ -306,7 +306,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + @@ -318,7 +318,7 @@ qy45ptdwJLqLJCeNoR0JUcDNIRhOCuOPND7pcMtX6hI/ - + diff --git a/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml index ad67a824842..2114242cb0f 100644 --- a/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/scim-endpoints.xml @@ -29,7 +29,7 @@ - + @@ -39,7 +39,7 @@ - + @@ -114,7 +114,7 @@ - + @@ -125,7 +125,7 @@ - + @@ -162,18 +162,18 @@ - + - + - + @@ -196,7 +196,7 @@ - + xFrameOptionsFilter - org.cloudfoundry.identity.uaa.login.XFrameOptionsFilter + org.cloudfoundry.identity.uaa.security.web.XFrameOptionsFilter @@ -45,7 +45,7 @@ backwardsCompatibleScopeParameter - org.cloudfoundry.identity.uaa.oauth.BackwardsCompatibleScopeParsingFilter + org.cloudfoundry.identity.uaa.web.BackwardsCompatibleScopeParsingFilter @@ -63,7 +63,7 @@ org.springframework.web.servlet.DispatcherServlet contextInitializerClasses - org.cloudfoundry.identity.uaa.config.YamlServletProfileInitializer + org.cloudfoundry.identity.uaa.impl.config.YamlServletProfileInitializer environmentConfigDefaults diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/BootstrapTests.java index 033552d4de8..868a8d5782d 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/BootstrapTests.java @@ -13,8 +13,8 @@ package org.cloudfoundry.identity.uaa; import org.cloudfoundry.identity.uaa.authentication.manager.DynamicZoneAwareAuthenticationManager; -import org.cloudfoundry.identity.uaa.config.YamlServletProfileInitializer; -import org.cloudfoundry.identity.uaa.oauth.ClientAdminBootstrap; +import org.cloudfoundry.identity.uaa.impl.config.YamlServletProfileInitializer; +import org.cloudfoundry.identity.uaa.client.ClientAdminBootstrap; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.test.TestUtils; import org.cloudfoundry.identity.uaa.user.JdbcUaaUserDatabase; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/UaaConfigurationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/UaaConfigurationTests.java index ceb54555d1f..c7c64c4820d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/UaaConfigurationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/UaaConfigurationTests.java @@ -16,7 +16,8 @@ import javax.validation.ConstraintViolationException; -import org.cloudfoundry.identity.uaa.config.YamlConfigurationValidator; +import org.cloudfoundry.identity.uaa.impl.config.UaaConfiguration; +import org.cloudfoundry.identity.uaa.impl.config.YamlConfigurationValidator; import org.junit.Test; /** 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 8fe9bded99c..730c6a35d32 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,12 +2,12 @@ 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.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture; @@ -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..7f3fc1bf572 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; @@ -37,6 +38,8 @@ import java.util.List; import java.util.Map; +import static org.hamcrest.CoreMatchers.is; + public class TestZonifyGroupSchema_V2_4_1 extends InjectedMockContextTest { public static final int ENTITY_COUNT = 5; @@ -52,7 +55,17 @@ public void populateDataUsingEndpoints() { for (int i=0; i groups = new LinkedList<>(); IdentityZoneHolder.set(zone); for (int j=0; j 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\"")); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CfScimUserEndpointIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CfScimUserEndpointIntegrationTests.java index e81abe45813..66cf34d8e29 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CfScimUserEndpointIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CfScimUserEndpointIntegrationTests.java @@ -13,7 +13,7 @@ package org.cloudfoundry.identity.uaa.integration; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; +import org.cloudfoundry.identity.uaa.account.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.oauth.UaaOauth2ErrorHandler; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CfUserIdTranslationEndpointIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CfUserIdTranslationEndpointIntegrationTests.java index 8cb84df2775..2efea8bd38d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CfUserIdTranslationEndpointIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CfUserIdTranslationEndpointIntegrationTests.java @@ -13,7 +13,7 @@ package org.cloudfoundry.identity.uaa.integration; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; +import org.cloudfoundry.identity.uaa.account.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUser.Group; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CheckTokenEndpointIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CheckTokenEndpointIntegrationTests.java index da8309430d2..b8f086417d3 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CheckTokenEndpointIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/CheckTokenEndpointIntegrationTests.java @@ -24,18 +24,20 @@ import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; -import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.junit.Rule; import org.junit.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.security.crypto.codec.Base64; 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.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -148,13 +150,15 @@ public void testDecodeToken() throws Exception { tokenResponse = serverRunning.postForMap("/check_token", formData, headers); assertEquals(HttpStatus.OK, tokenResponse.getStatusCode()); //System.err.println(tokenResponse.getBody()); - assertNotNull(tokenResponse.getBody().get("iss")); @SuppressWarnings("unchecked") Map map = tokenResponse.getBody(); + assertNotNull(map.get("iss")); assertEquals(testAccounts.getUserName(), map.get("user_name")); assertEquals(testAccounts.getEmail(), map.get("email")); + // Test that Spring's default converter can create an auth from the response. + Authentication auth = (new DefaultUserAuthenticationConverter()).extractAuthentication(map); } @Test 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..e0770613ebe 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 @@ -14,9 +14,9 @@ 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.approval.Approval; +import org.cloudfoundry.identity.uaa.client.InvalidClientDetailsException; +import org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest; +import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; @@ -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"); @@ -535,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); @@ -552,8 +574,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 +589,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/integration/FormLoginIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/FormLoginIntegrationTests.java index 69f20f25a86..b47c05eed4a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/FormLoginIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/FormLoginIntegrationTests.java @@ -17,7 +17,6 @@ import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.impl.client.BasicCookieStore; @@ -29,12 +28,11 @@ import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; -import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import java.util.Arrays; 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 667ea622618..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,17 +1,17 @@ package org.cloudfoundry.identity.uaa.integration; 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.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; 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; @@ -118,7 +118,7 @@ public void testCreateZone() throws Exception { IdentityProvider identityProvider = idpList.getBody().get(0); assertThat(identityProvider.getIdentityZoneId(), is(zoneId)); - assertThat(identityProvider.getOriginKey(), is(Origin.UAA)); + assertThat(identityProvider.getOriginKey(), is(OriginKeys.UAA)); //the default created zone does have a definition, but no policy assertNotNull(identityProvider.getConfig()); @@ -142,7 +142,7 @@ public void testCreateZoneWithClient() throws IOException { BaseClientDetails clientDetails = new BaseClientDetails("test123", null,"openid", "authorization_code", "uaa.resource"); clientDetails.setClientSecret("testSecret"); - clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singleton(Origin.UAA)); + clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, Collections.singleton(OriginKeys.UAA)); ResponseEntity clientCreateResponse = client.exchange( serverRunning.getUrl("/identity-zones/"+id+"/clients"), diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ImplicitTokenGrantIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ImplicitTokenGrantIntegrationTests.java index 5be576291c4..72632adb8be 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ImplicitTokenGrantIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ImplicitTokenGrantIntegrationTests.java @@ -23,7 +23,7 @@ import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; -import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.junit.Rule; import org.junit.Test; import org.springframework.http.HttpHeaders; 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..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 @@ -15,15 +15,15 @@ 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.oauth.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; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +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; -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; @@ -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()); @@ -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,13 +180,13 @@ 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() { 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..d9027bdea71 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,9 +14,9 @@ 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.message.PasswordChangeRequest; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.account.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; @@ -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/OpenIdTokenAuthorizationWithApprovalIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/OpenIdTokenAuthorizationWithApprovalIntegrationTests.java index d8e6422784d..3a0857f31ec 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/OpenIdTokenAuthorizationWithApprovalIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/OpenIdTokenAuthorizationWithApprovalIntegrationTests.java @@ -19,7 +19,7 @@ import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; -import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Assume; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordChangeEndpointIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordChangeEndpointIntegrationTests.java index bb15055d7af..87fc070361e 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordChangeEndpointIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/PasswordChangeEndpointIntegrationTests.java @@ -14,7 +14,7 @@ package org.cloudfoundry.identity.uaa.integration; import org.cloudfoundry.identity.uaa.ServerRunning; -import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; +import org.cloudfoundry.identity.uaa.account.PasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RefreshTokenSupportIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RefreshTokenSupportIntegrationTests.java index b62a245ff71..541660950b0 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RefreshTokenSupportIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/RefreshTokenSupportIntegrationTests.java @@ -25,7 +25,7 @@ import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; -import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.junit.Before; import org.junit.Rule; import org.junit.Test; 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/ScimGroupEndpointsIntegrationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java index 5287fc4e5b2..b4af7194d0a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/ScimGroupEndpointsIntegrationTests.java @@ -21,7 +21,7 @@ import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.test.TestAccountSetup; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; -import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.junit.After; import org.junit.Before; 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/AppApprovalIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AppApprovalIT.java index e41c2e2aa4d..0e63aaa735f 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 (.*) did 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); + } + } } diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AutologinIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AutologinIT.java index bc10477a3ba..5d8de621564 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AutologinIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/AutologinIT.java @@ -20,7 +20,7 @@ import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; import org.cloudfoundry.identity.uaa.test.UaaTestAccounts; -import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.junit.After; import org.junit.Assert; import org.junit.Before; 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..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 @@ -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) { @@ -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 { @@ -219,7 +220,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/integration/feature/LoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java index dab91a0abab..9660477897c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/LoginIT.java @@ -15,10 +15,9 @@ import com.dumbster.smtp.SimpleSmtpServer; import com.dumbster.smtp.SmtpMessage; import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; -import org.cloudfoundry.identity.uaa.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.hamcrest.Matchers; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -45,8 +44,6 @@ import java.security.SecureRandom; import java.util.Iterator; -import static org.apache.commons.lang3.StringUtils.isEmpty; -import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; 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..fb26456e415 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,10 +15,10 @@ 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; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; @@ -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 24ed755d328..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.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; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.oauth.Claims; +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.zone.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.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.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME; -import static org.cloudfoundry.identity.uaa.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX; 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.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"); @@ -235,7 +236,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()); @@ -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()); @@ -313,7 +314,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, @@ -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"); @@ -393,7 +394,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 +408,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); @@ -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 @@ -492,7 +493,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()); @@ -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"); @@ -567,7 +568,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()); @@ -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"); @@ -673,7 +674,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()); @@ -736,19 +737,124 @@ 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)); } + @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( @@ -774,7 +880,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 +895,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()); @@ -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); + } + } 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..d6397deee78 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,17 +19,17 @@ 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.login.saml.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.rest.SearchResults; +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; 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.web.CookieBasedCsrfTokenRepository; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; import org.junit.Assert; @@ -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..874f56acd2a 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,19 +1,18 @@ 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; 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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.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; @@ -26,10 +25,11 @@ 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.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; @@ -66,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()); @@ -117,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); @@ -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..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 @@ -3,13 +3,17 @@ 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.message.EmailService; 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; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -47,8 +51,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; @@ -236,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()) @@ -247,7 +257,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 +285,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 +327,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 +379,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 +432,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 @@ -448,6 +458,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()='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()='here']/@href").string(signUpLink)); + } + private BaseClientDetails createTestClient() throws Exception { BaseClientDetails clientDetails = new BaseClientDetails(); clientDetails.setClientId("test-client-" + RandomStringUtils.randomAlphanumeric(2)); @@ -495,6 +528,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 3fcf3c83507..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 @@ -15,25 +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.authentication.Origin; -import org.cloudfoundry.identity.uaa.authentication.login.Prompt; +import org.cloudfoundry.identity.uaa.account.ResetPasswordController; import org.cloudfoundry.identity.uaa.authentication.manager.PeriodLockoutPolicy; -import org.cloudfoundry.identity.uaa.config.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.config.YamlServletProfileInitializer; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderConfigurator; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.login.util.FakeJavaMailSender; -import org.cloudfoundry.identity.uaa.oauth.Claims; -import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices; -import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenStore; -import org.cloudfoundry.identity.uaa.rest.jdbc.SimpleSearchQueryConverter; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; +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.message.util.FakeJavaMailSender; +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.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; @@ -84,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 { @@ -201,6 +210,33 @@ 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()); + assertFalse(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 @@ -254,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()); @@ -399,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"); @@ -414,14 +450,30 @@ 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()) { assertNotNull(providerProvisioning.retrieveByOrigin(def.getIdpEntityAlias(), IdentityZone.getUaa().getId())); } - assertNotNull(providerProvisioning.retrieveByOrigin(Origin.LDAP, IdentityZone.getUaa().getId())); + 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 @@ -660,6 +712,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); 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..3b25cd1d84a 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,17 @@ package org.cloudfoundry.identity.uaa.login; -import org.cloudfoundry.identity.uaa.AbstractIdentityProviderDefinition; -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.login.util.FakeJavaMailSender; +import org.cloudfoundry.identity.uaa.message.EmailService; +import org.cloudfoundry.identity.uaa.provider.AbstractIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.message.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; @@ -42,6 +43,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 +84,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"); } @@ -107,7 +110,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 +121,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 +164,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 +190,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 +209,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 +255,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 +291,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 +310,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)); } @@ -316,17 +319,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 ac000deeea4..8e93205128a 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,16 +12,14 @@ *******************************************************************************/ 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; -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.config.LockoutPolicy; -import org.cloudfoundry.identity.uaa.login.saml.IdentityProviderConfiguratorTests; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.saml.IdentityProviderConfiguratorTests; +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; @@ -32,12 +30,12 @@ import org.cloudfoundry.identity.uaa.user.UaaAuthority; 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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.security.web.CorsFilter; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.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; @@ -618,9 +616,15 @@ 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(Origin.SAML); + activeIdentityProvider.setType(OriginKeys.SAML); activeIdentityProvider.setName("Active SAML Provider"); activeIdentityProvider.setConfig(activeSamlIdentityProviderDefinition); activeIdentityProvider.setActive(true); @@ -628,9 +632,14 @@ 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(Origin.SAML); + inactiveIdentityProvider.setType(OriginKeys.SAML); inactiveIdentityProvider.setName("Inactive SAML Provider"); inactiveIdentityProvider.setConfig(inactiveSamlIdentityProviderDefinition); inactiveIdentityProvider.setActive(false); @@ -655,9 +664,14 @@ 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(Origin.SAML); + activeIdentityProvider.setType(OriginKeys.SAML); activeIdentityProvider.setName("Active SAML Provider"); activeIdentityProvider.setActive(true); activeIdentityProvider.setConfig(activeSamlIdentityProviderDefinition); @@ -709,28 +723,28 @@ 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(Origin.SAML); + activeIdentityProvider3.setType(OriginKeys.SAML); activeIdentityProvider3.setName("Active 3 SAML Provider"); activeIdentityProvider3.setActive(true); activeIdentityProvider3.setConfig(activeSamlIdentityProviderDefinition3); 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(Origin.SAML); + activeIdentityProvider2.setType(OriginKeys.SAML); activeIdentityProvider2.setName("Active 2 SAML Provider"); activeIdentityProvider2.setActive(true); activeIdentityProvider2.setConfig(activeSamlIdentityProviderDefinition2); @@ -784,9 +798,15 @@ 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(Origin.SAML); + identityProvider.setType(OriginKeys.SAML); identityProvider.setName("SAML Provider"); identityProvider.setActive(true); identityProvider.setConfig(samlIdentityProviderDefinition); @@ -1225,7 +1245,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..68023321259 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,11 +1,11 @@ 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.login.saml.LoginSamlAuthenticationToken; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.oauth.RemoteUserAuthentication; import org.cloudfoundry.identity.uaa.security.web.UaaRequestMatcher; @@ -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)); } } @@ -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 c269e9a90ab..a7def93f071 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,14 +12,15 @@ *******************************************************************************/ 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; +import org.cloudfoundry.identity.uaa.account.UaaResetPasswordService; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordChange; @@ -80,7 +81,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()) @@ -94,7 +95,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 @@ -107,7 +108,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 +134,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 +215,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 +227,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 +239,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"); @@ -255,7 +256,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 @@ -266,10 +267,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/login/util/LocalUaaRestTemplateMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/util/LocalUaaRestTemplateMockMvcTests.java index 9bc8255aa41..09704d7dd14 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/util/LocalUaaRestTemplateMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/util/LocalUaaRestTemplateMockMvcTests.java @@ -13,6 +13,7 @@ package org.cloudfoundry.identity.uaa.login.util; +import org.cloudfoundry.identity.uaa.message.LocalUaaRestTemplate; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.junit.Ignore; import org.junit.Test; diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/DefaultConfigurationTestSuite.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/DefaultConfigurationTestSuite.java index f40663babb6..addede84853 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/DefaultConfigurationTestSuite.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/DefaultConfigurationTestSuite.java @@ -12,8 +12,6 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.mock; -import org.cloudfoundry.identity.uaa.login.saml.SamlIDPRefreshMockMvcTests; -import org.cloudfoundry.identity.uaa.mock.zones.IdentityProviderEndpointsMockMvcTests; import org.cloudfoundry.identity.uaa.test.YamlServletProfileInitializerContextInitializer; import org.flywaydb.core.Flyway; import org.junit.AfterClass; @@ -28,7 +26,6 @@ import org.springframework.web.context.support.XmlWebApplicationContext; import java.util.Arrays; -import java.util.Comparator; @RunWith(UaaJunitSuiteRunner.class) public class DefaultConfigurationTestSuite extends UaaBaseSuite { 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..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 @@ -20,9 +20,9 @@ import org.cloudfoundry.identity.uaa.audit.UaaAuditService; import org.cloudfoundry.identity.uaa.audit.event.AbstractUaaEvent; import org.cloudfoundry.identity.uaa.audit.event.ApprovalModifiedEvent; -import org.cloudfoundry.identity.uaa.audit.event.GroupModifiedEvent; +import org.cloudfoundry.identity.uaa.scim.event.GroupModifiedEvent; import org.cloudfoundry.identity.uaa.audit.event.TokenIssuedEvent; -import org.cloudfoundry.identity.uaa.audit.event.UserModifiedEvent; +import org.cloudfoundry.identity.uaa.scim.event.UserModifiedEvent; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.event.ClientAuthenticationFailureEvent; import org.cloudfoundry.identity.uaa.authentication.event.ClientAuthenticationSuccessEvent; @@ -34,15 +34,14 @@ import org.cloudfoundry.identity.uaa.authentication.manager.AuthzAuthenticationManager; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; -import org.cloudfoundry.identity.uaa.oauth.approval.Approval; -import org.cloudfoundry.identity.uaa.password.event.PasswordChangeEvent; -import org.cloudfoundry.identity.uaa.password.event.PasswordChangeFailureEvent; -import org.cloudfoundry.identity.uaa.password.event.ResetPasswordRequestEvent; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.account.event.PasswordChangeEvent; +import org.cloudfoundry.identity.uaa.account.event.PasswordChangeFailureEvent; +import org.cloudfoundry.identity.uaa.account.event.ResetPasswordRequestEvent; 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.account.LostPasswordChangeRequest; import org.cloudfoundry.identity.uaa.scim.event.ScimEventPublisher; import org.cloudfoundry.identity.uaa.test.TestApplicationEventListener; import org.cloudfoundry.identity.uaa.test.TestClient; @@ -54,10 +53,10 @@ 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; +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; @@ -76,10 +75,10 @@ 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; -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; @@ -101,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 { @@ -122,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; @@ -131,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 @@ -204,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) @@ -226,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) @@ -252,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 @@ -285,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") @@ -430,7 +453,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"); + LostPasswordChangeRequest pwch = new LostPasswordChangeRequest(expiringCode, "Koala2"); MockHttpServletRequestBuilder changePasswordPost = post("/password_change") .accept(MediaType.APPLICATION_JSON_VALUE) @@ -442,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()); @@ -490,12 +513,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) @@ -713,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()); @@ -802,13 +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(verified); MockHttpServletRequestBuilder userPost = post("/Users") .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 aaf920f7ce7..e1373c58509 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 @@ -1,25 +1,25 @@ package org.cloudfoundry.identity.uaa.mock.clients; import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.collect.Iterables; 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; -import org.cloudfoundry.identity.uaa.oauth.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; +import org.cloudfoundry.identity.uaa.client.InvalidClientDetailsException; +import org.cloudfoundry.identity.uaa.oauth.client.SecretChangeRequest; +import org.cloudfoundry.identity.uaa.client.UaaScopes; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.Approval.ApprovalStatus; import org.cloudfoundry.identity.uaa.oauth.client.ClientDetailsModification; -import org.cloudfoundry.identity.uaa.oauth.event.ClientAdminEventPublisher; -import org.cloudfoundry.identity.uaa.oauth.event.ClientApprovalsDeletedEvent; -import org.cloudfoundry.identity.uaa.oauth.event.ClientCreateEvent; -import org.cloudfoundry.identity.uaa.oauth.event.ClientDeleteEvent; -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.client.event.ClientAdminEventPublisher; +import org.cloudfoundry.identity.uaa.client.event.ClientApprovalsDeletedEvent; +import org.cloudfoundry.identity.uaa.client.event.ClientCreateEvent; +import org.cloudfoundry.identity.uaa.client.event.ClientDeleteEvent; +import org.cloudfoundry.identity.uaa.client.event.ClientUpdateEvent; +import org.cloudfoundry.identity.uaa.client.event.SecretChangeEvent; +import org.cloudfoundry.identity.uaa.client.event.SecretFailureEvent; +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; @@ -27,17 +27,16 @@ 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; import org.mockito.ArgumentCaptor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.GrantedAuthority; +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; @@ -51,14 +50,19 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; -import java.util.UUID; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.iterableWithSize; +import static org.hamcrest.core.Is.is; 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.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -68,6 +72,7 @@ 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.request.MockMvcRequestBuilders.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -164,15 +169,16 @@ private void setupAdminUserToken() throws Exception { @Test public void testCreateClient() throws Exception { - createClient(adminToken, new RandomValueStringGenerator().generate(), "client_credentials"); + ClientDetails client = createClient(adminToken, new RandomValueStringGenerator().generate(), Collections.singleton("client_credentials")); verify(applicationEventPublisher, times(1)).publishEvent(captor.capture()); assertEquals(AuditEventType.ClientCreateSuccess, captor.getValue().getAuditEvent().getType()); + assertEquals(makeClientName(client.getClientId()), client.getAdditionalInformation().get("name")); } @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()); @@ -188,7 +194,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) @@ -215,8 +223,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) @@ -236,7 +245,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) @@ -253,7 +263,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 +331,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"); @@ -583,7 +593,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); } @@ -639,7 +649,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", @@ -680,7 +690,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", @@ -713,7 +723,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(), @@ -740,11 +750,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); } @@ -753,7 +763,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); } @@ -828,7 +838,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) @@ -846,8 +856,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) @@ -876,7 +886,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); } @@ -963,7 +973,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); } @@ -1032,7 +1042,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); @@ -1057,7 +1067,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); @@ -1083,7 +1093,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); @@ -1108,7 +1118,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); @@ -1133,7 +1143,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); @@ -1196,12 +1206,70 @@ 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()); } + @Test + public void testPutClientModifyAuthorities() throws Exception { + ClientDetails client = createClient(adminToken, "testClientForModifyAuthorities", Collections.singleton("client_credentials")); + + BaseClientDetails modified = new BaseClientDetails(client); + modified.setAuthorities(Collections.singleton((GrantedAuthority) () -> "newAuthority")); + + MockHttpServletRequestBuilder put = put("/oauth/clients/" + client.getClientId()) + .header("Authorization", "Bearer " + adminToken) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(modified)); + MvcResult result = getMockMvc().perform(put).andExpect(status().isOk()).andReturn(); + + client = getClient(client.getClientId()); + assertThat(client.getAuthorities(), iterableWithSize(1)); + GrantedAuthority authority = Iterables.get(client.getAuthorities(), 0); + assertEquals("newAuthority", authority.getAuthority()); + } + + @Test + public void testPutClientModifyAccessTokenValidity() throws Exception { + ClientDetails client = createClient(adminToken, "testClientForModifyAccessTokenValidity", Collections.singleton("client_credentials")); + + BaseClientDetails modified = new BaseClientDetails(client); + modified.setAccessTokenValiditySeconds(73); + + MockHttpServletRequestBuilder put = put("/oauth/clients/" + client.getClientId()) + .header("Authorization", "Bearer " + adminToken) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(modified)); + MvcResult result = getMockMvc().perform(put).andExpect(status().isOk()).andReturn(); + + client = getClient(client.getClientId()); + assertThat(client.getAccessTokenValiditySeconds(), is(73)); + } + + @Test + public void testPutClientModifyName() throws Exception { + ClientDetails client = createClient(adminToken, "testClientForModifyName", Collections.singleton("client_credentials")); + Map requestBody = JsonUtils.readValue(JsonUtils.writeValueAsString(new BaseClientDetails(client)), new TypeReference>() {}); + requestBody.put("name", "New Client Name"); + + MockHttpServletRequestBuilder put = put("/oauth/clients/" + client.getClientId()) + .header("Authorization", "Bearer " + adminToken) + .accept(APPLICATION_JSON) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(requestBody)); + MvcResult result = getMockMvc().perform(put).andExpect(status().isOk()).andReturn(); + + MockHttpServletResponse response = getClientHttpResponse(client.getClientId()); + Map map = JsonUtils.readValue(response.getContentAsString(), new TypeReference>() {}); + assertThat(map, hasEntry(is("name"), is("New Client Name"))); + + client = getClientResponseAsClientDetails(response); + assertThat(client.getAdditionalInformation(), hasEntry(is("name"), is("New Client Name"))); + } private Approval[] getApprovals(String token, String clientId) throws Exception { String filter = "client_id eq \""+clientId+"\""; @@ -1221,9 +1289,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) @@ -1234,7 +1320,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) @@ -1246,13 +1332,14 @@ private ClientDetails createClient(String token, String id, String grantTypes) t } private ClientDetails getClient(String id) throws Exception { - MockHttpServletRequestBuilder getClient = get("/oauth/clients/" + id) - .header("Authorization", "Bearer " + adminToken) - .accept(APPLICATION_JSON); - ResultActions result = getMockMvc().perform(getClient); - int responseCode = result.andReturn().getResponse().getStatus(); + MockHttpServletResponse response = getClientHttpResponse(id); + return getClientResponseAsClientDetails(response); + } + + private ClientDetails getClientResponseAsClientDetails(MockHttpServletResponse response) throws Exception { + int responseCode = response.getStatus(); HttpStatus status = HttpStatus.valueOf(responseCode); - String body = result.andReturn().getResponse().getContentAsString(); + String body = response.getContentAsString(); if (status == HttpStatus.OK) { return clientFromString(body); } else if ( status == HttpStatus.NOT_FOUND) { @@ -1262,9 +1349,17 @@ private ClientDetails getClient(String id) throws Exception { } } + private MockHttpServletResponse getClientHttpResponse(String id) throws Exception { + MockHttpServletRequestBuilder getClient = get("/oauth/clients/" + id) + .header("Authorization", "Bearer " + adminToken) + .accept(APPLICATION_JSON); + ResultActions result = getMockMvc().perform(getClient); + return result.andReturn().getResponse(); + } + 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) @@ -1275,8 +1370,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) @@ -1287,8 +1382,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) @@ -1300,8 +1395,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) @@ -1315,27 +1410,36 @@ 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(); + client.setClientId(id); + client.setScope(scopes); + client.setAuthorizedGrantTypes(grantTypes); + if(authorities != null) { + client.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", authorities))); } - ClientDetailsModification client = new ClientDetailsModification(id, "", scopes, grantTypes, authorities); client.setClientSecret("secret"); - client.setAdditionalInformation(Collections. singletonMap("foo", Arrays.asList("bar"))); + Map additionalInformation = new HashMap<>(); + additionalInformation.put("foo", "bar"); + additionalInformation.put("name", makeClientName(id)); + client.setAdditionalInformation(additionalInformation); 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 clazz) throws Exception { return (Object[])JsonUtils.readValue(body, clazz); } + private static String makeClientName(String id) { + return "Client " + id; + } + } 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..c95f614eaa8 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 @@ -26,6 +26,8 @@ import java.sql.Timestamp; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -48,7 +50,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 +70,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 +88,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 +103,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 +118,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 +134,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 +150,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 +180,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 +206,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 +223,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) @@ -233,14 +235,14 @@ public void testCodeThatIsExpiredIsDeletedOnCreateOfNewCode() throws Exception { .andExpect(status().isCreated()) .andReturn(); - assertEquals(1, getWebApplicationContext().getBean(JdbcTemplate.class).queryForInt("select count(*) from expiring_code_store")); + assertThat(getWebApplicationContext().getBean(JdbcTemplate.class).queryForObject("select count(*) from expiring_code_store", Integer.class), is(1)); } @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 +261,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) @@ -271,7 +273,7 @@ public void testCodeThatIsExpirationIntervalWorks() throws Exception { .andExpect(status().isCreated()) .andReturn(); - assertEquals(2, getWebApplicationContext().getBean(JdbcTemplate.class).queryForInt("select count(*) from expiring_code_store")); + assertThat(getWebApplicationContext().getBean(JdbcTemplate.class).queryForObject("select count(*) from expiring_code_store", Integer.class), is(2)); }finally { getWebApplicationContext().getBean(JdbcExpiringCodeStore.class).setExpirationInterval(interval); } 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 ac5ede9b162..88ab751baa2 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,16 +13,17 @@ 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.ldap.ExtendedLdapUserMapper; -import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; -import org.cloudfoundry.identity.uaa.ldap.ProcessLdapProperties; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.ldap.ExtendedLdapUserMapper; +import org.cloudfoundry.identity.uaa.provider.ldap.ProcessLdapProperties; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.ZoneScimInviteData; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; -import org.cloudfoundry.identity.uaa.rest.jdbc.LimitSqlAdapter; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; +import org.cloudfoundry.identity.uaa.resources.jdbc.LimitSqlAdapter; import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; @@ -33,10 +34,9 @@ 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.zone.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderValidationRequest; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderValidationRequest.UsernamePasswordAuthentication; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderValidationRequest; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderValidationRequest.UsernamePasswordAuthentication; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneSwitchingFilter; @@ -82,10 +82,11 @@ 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 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"} }); } @@ -225,7 +227,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 +254,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 +291,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 +300,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") @@ -331,11 +333,11 @@ 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); - 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"); @@ -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 { @@ -445,9 +444,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()); @@ -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", ""); @@ -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()); } @@ -861,11 +861,13 @@ public void runLdapTestblock() throws Exception { deleteLdapUsers(); acceptInvitation_for_ldap_user_whose_username_is_not_email(); deleteLdapUsers(); + testLoginInNonDefaultZone(); + deleteLdapUsers(); } 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 +875,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 +929,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 +960,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 { @@ -981,9 +983,28 @@ 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.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"; @@ -1053,19 +1074,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/password/PasswordChangeEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/password/PasswordChangeEndpointMockMvcTests.java index 3ea1ac82369..717aa0d8e41 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 @@ -12,9 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.mock.password; -import org.cloudfoundry.identity.uaa.message.PasswordChangeRequest; +import org.cloudfoundry.identity.uaa.account.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.test.TestClient; import org.cloudfoundry.identity.uaa.util.JsonUtils; @@ -26,8 +25,6 @@ import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import javax.servlet.http.HttpSession; - import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; import static org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.utils; import static org.junit.Assert.assertNotNull; @@ -117,6 +114,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 +125,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/token/TokenKeyEndpointMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenKeyEndpointMockMvcTests.java index 815bc818192..b32e084246b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenKeyEndpointMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenKeyEndpointMockMvcTests.java @@ -14,7 +14,7 @@ import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; -import org.cloudfoundry.identity.uaa.oauth.token.SignerProvider; +import org.cloudfoundry.identity.uaa.oauth.SignerProvider; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.junit.After; import org.junit.Before; 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 f4733b29a96..74b177288f2 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,19 +13,21 @@ 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.oauth.UaaAuthorizationEndpoint; +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.DisableIdTokenResponseTypeFilter; -import org.cloudfoundry.identity.uaa.oauth.token.SignerProvider; -import org.cloudfoundry.identity.uaa.oauth.token.UaaTokenServices; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants; +import org.cloudfoundry.identity.uaa.oauth.SignerProvider; +import org.cloudfoundry.identity.uaa.oauth.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.zone.IdentityProvider; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.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.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -92,9 +92,15 @@ import java.util.TreeSet; import java.util.UUID; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.isIn; import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.StringStartsWith.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -158,7 +164,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(); @@ -240,6 +246,86 @@ protected ScimGroup createIfNotExist(String scope, String zoneId) { } } + @Test + 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")); + + 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 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"; @@ -248,7 +334,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 +344,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 +376,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 +387,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 +464,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 +493,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 +551,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 +595,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 +635,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 +679,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 +726,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()); @@ -736,25 +822,25 @@ public void testAuthorizationCode_ShouldNot_Throw_500_If_Client_Doesnt_Exist() t @Test public void testImplicitGrantWithFragmentInRedirectURL() throws Exception { String redirectUri = "https://example.com/dashboard/?appGuid=app-guid#test"; - testImplicitGrantRedirectUri(redirectUri, "&"); + testImplicitGrantRedirectUri(redirectUri); } @Test public void testImplicitGrantWithNoFragmentInRedirectURL() throws Exception { String redirectUri = "https://example.com/dashboard/?appGuid=app-guid"; - testImplicitGrantRedirectUri(redirectUri, "#"); + testImplicitGrantRedirectUri(redirectUri); } - protected void testImplicitGrantRedirectUri(String redirectUri, String delim) throws Exception { + protected void testImplicitGrantRedirectUri(String redirectUri) throws Exception { String clientId = "authclient-"+new RandomValueStringGenerator().generate(); String scopes = "openid"; 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()); @@ -777,7 +863,22 @@ protected void testImplicitGrantRedirectUri(String redirectUri, String delim) th MvcResult result = getMockMvc().perform(authRequest).andExpect(status().is3xxRedirection()).andReturn(); String location = result.getResponse().getHeader("Location"); - assertTrue(location.startsWith(redirectUri + delim + "token_type=bearer&access_token")); + + constainsExactlyOneInstance(location, "#"); + String[] locationParts = location.split("#"); + + String locationUri = locationParts[0]; + String locationToken = locationParts[1]; + + assertEquals(redirectUri.split("#")[0], locationUri); + String[] locationParams = locationToken.split("&"); + assertThat(Arrays.asList(locationParams), hasItem(is("token_type=bearer"))); + assertThat(Arrays.asList(locationParams), hasItem(startsWith("access_token="))); + } + + private static void constainsExactlyOneInstance(String string, String substring) { + assertTrue(string.contains(substring)); + assertEquals(string.indexOf(substring), string.lastIndexOf(substring)); } @@ -788,7 +889,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 +940,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); @@ -858,7 +959,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 +991,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 +1203,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); @@ -1165,7 +1266,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)); @@ -1180,7 +1281,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()); @@ -1197,7 +1298,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)); @@ -1253,7 +1354,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, @@ -1312,10 +1413,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 +1431,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 +1452,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 +1477,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 +1502,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 +1526,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 +1540,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 +1553,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 +1566,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 +1580,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 +1593,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 +1606,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 +1619,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 +1637,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 +1651,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 +1676,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 +1701,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 +1725,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 +1739,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 +1752,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 +1765,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 +1779,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 +1792,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()); } @@ -1701,15 +1802,13 @@ 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, 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 @@ -1775,15 +1874,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 +1900,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); } @@ -1870,7 +1969,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 +2120,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 +2176,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 = @@ -2096,9 +2195,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) { 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..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 @@ -17,17 +17,17 @@ 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.authentication.Origin; +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.resources.SearchResults; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -40,14 +40,14 @@ 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.web.CookieBasedCsrfTokenRepository; -import org.cloudfoundry.identity.uaa.zone.IdentityProvider; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.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; @@ -61,6 +61,7 @@ import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; @@ -91,6 +92,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; @@ -105,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 { @@ -252,11 +255,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 +304,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 +366,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); @@ -432,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) { @@ -448,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); } @@ -508,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( @@ -550,11 +574,19 @@ public BaseClientDetails createClient(MockMvc mockMvc, String accessToken, BaseC .andReturn().getResponse().getContentAsString(), BaseClientDetails.class); } - public ClientDetails createClient(MockMvc mockMvc, String adminAccessToken, String id, String secret, String resourceIds, String scopes, List 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); } @@ -595,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, @@ -643,7 +687,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); @@ -811,21 +855,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/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/zones/DisableUserManagementSecurityFilterMockMvcTest.java index 3cda8e8ac98..bbce1307913 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.message.PasswordChangeRequest; +import org.cloudfoundry.identity.uaa.account.EmailChange; +import org.cloudfoundry.identity.uaa.account.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()); @@ -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() { @@ -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; 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..47ce3b7ada4 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,22 +15,22 @@ 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.authentication.Origin; -import org.cloudfoundry.identity.uaa.config.PasswordPolicy; -import org.cloudfoundry.identity.uaa.login.saml.IdentityProviderConfiguratorTests; -import org.cloudfoundry.identity.uaa.login.saml.SamlIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.saml.IdentityProviderConfiguratorTests; +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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.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; @@ -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(); @@ -106,9 +108,12 @@ 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); + 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"); @@ -127,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); @@ -232,19 +264,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,19 +315,15 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Other_Zone() throws IdentityProvider identityProvider = MultitenancyFixture.identityProvider(origin1, zone.getId()); - identityProvider.setType(Origin.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() - ); + identityProvider.setType(OriginKeys.SAML); + + 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 +335,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()); @@ -334,19 +358,15 @@ public void test_Create_Duplicate_Saml_Identity_Provider_In_Default_Zone() throw IdentityProvider identityProvider = MultitenancyFixture.identityProvider(origin1, IdentityZone.getUaa().getId()); - identityProvider.setType(Origin.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() - ); + identityProvider.setType(OriginKeys.SAML); + + 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 +376,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()); @@ -539,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"); } } 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..c0ee78a1697 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 @@ -4,38 +4,46 @@ import com.fasterxml.jackson.databind.JsonNode; import org.cloudfoundry.identity.uaa.audit.AuditEventType; 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.scim.event.GroupModifiedEvent; +import org.cloudfoundry.identity.uaa.scim.event.UserModifiedEvent; 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.event.ClientCreateEvent; -import org.cloudfoundry.identity.uaa.oauth.event.ClientDeleteEvent; +import org.cloudfoundry.identity.uaa.approval.Approval; +import org.cloudfoundry.identity.uaa.approval.ApprovalStore; +import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; +import org.cloudfoundry.identity.uaa.client.event.ClientCreateEvent; +import org.cloudfoundry.identity.uaa.client.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.scim.exception.ScimResourceNotFoundException; 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.zone.IdentityProvider; -import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.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,15 +57,24 @@ 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.cloudfoundry.identity.uaa.mock.util.MockMvcUtils.CookieCsrfPostProcessor.cookieCsrf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; 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.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; 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; @@ -239,6 +256,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 +455,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(UAA, identityZone.getId()); + IdentityProvider idp2 = idpp.retrieveByOrigin(UAA, IdentityZone.getUaa().getId()); assertNotEquals(idp1, idp2); IdentityZoneProvisioning identityZoneProvisioning = (IdentityZoneProvisioning) getWebApplicationContext().getBean("identityZoneProvisioning"); @@ -414,6 +467,148 @@ 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.queryForObject("select count(*) from sec_audit where identity_zone_id=?", new Object[] {user.getZoneId()}, Integer.class), 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 + getMockMvc().perform( + delete("/identity-zones/{id}", zone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .accept(APPLICATION_JSON)) + .andExpect(status().isOk()); + + getMockMvc().perform( + delete("/identity-zones/{id}", zone.getId()) + .header("Authorization", "Bearer " + identityClientToken) + .accept(APPLICATION_JSON)) + .andExpect(status().isNotFound()); + + assertThat(template.queryForObject("select count(*) from identity_zone where id=?", new Object[] {zone.getId()}, Integer.class), is(0)); + + assertThat(template.queryForObject("select count(*) from oauth_client_details where identity_zone_id=?", new Object[] {zone.getId()}, Integer.class), is(0)); + + assertThat(template.queryForObject("select count(*) from groups where identity_zone_id=?", new Object[] {zone.getId()}, Integer.class), is(0)); + + assertThat(template.queryForObject("select count(*) from sec_audit where identity_zone_id=?", new Object[] {zone.getId()}, Integer.class), is(0)); + + assertThat(template.queryForObject("select count(*) from users where identity_zone_id=?", new Object[] {zone.getId()}, Integer.class), is(0)); + + assertThat(template.queryForObject("select count(*) from external_group_mapping where origin=?", new Object[] {LOGIN_SERVER}, Integer.class), is(0)); + try { + externalMembershipManager.getExternalGroupMapsByGroupId(group.getId(), LOGIN_SERVER); + fail("no external groups should be found"); + } catch (ScimResourceNotFoundException e) { + } + + assertThat(template.queryForObject("select count(*) from authz_approvals where user_id=?", new Object[] {user.getId()}, Integer.class), is(0)); + assertEquals(0, approvalStore.getApprovals(user.getId(), client.getClientId()).size()); + + + + } + @Test public void testCreateAndDeleteLimitedClientInNewZoneUsingZoneEndpoint() throws Exception { String id = generator.generate(); @@ -421,7 +616,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(UAA)); client.addAdditionalInformation("foo", "bar"); for (String url : Arrays.asList("","/")) { getMockMvc().perform( @@ -443,7 +638,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(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 +663,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(UAA)); getMockMvc().perform( post("/identity-zones/uaa/clients") .header("Authorization", "Bearer " + identityClientToken) @@ -503,20 +698,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/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()); + } + + + +} 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/login/saml/LoginSamlAuthenticationProviderTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java similarity index 76% rename from uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java rename to uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java index 25063d1a59d..aac84c258e9 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/LoginSamlAuthenticationProviderTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java @@ -12,12 +12,14 @@ * ***************************************************************************** */ -package org.cloudfoundry.identity.uaa.login.saml; +package org.cloudfoundry.identity.uaa.provider.saml; -import org.cloudfoundry.identity.uaa.authentication.Origin; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent; -import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; +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.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning; import org.cloudfoundry.identity.uaa.scim.ScimUser; @@ -31,19 +33,29 @@ 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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; -import org.cloudfoundry.identity.uaa.zone.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.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; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; @@ -59,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; @@ -67,9 +80,10 @@ 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.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -107,6 +121,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<>(); @@ -121,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); @@ -141,20 +187,20 @@ 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, 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 +230,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); } @@ -207,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), @@ -217,7 +273,7 @@ private SAMLCredential getUserCredential(String username, String firstName, Stri @Test public void testAuthenticateSimple() { - authprovider.authenticate(mockSamlAuthentication(Origin.SAML)); + authprovider.authenticate(mockSamlAuthentication(OriginKeys.SAML)); } @Test @@ -254,19 +310,48 @@ public void test_group_mapping() throws Exception { } @Test - public void externalGroup_NotMapped_ToScope() throws Exception { - providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups"); + 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("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()) - ) - ); + 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 { + 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 @@ -338,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); @@ -364,7 +450,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 +458,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,13 +478,51 @@ 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()); 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<>(); @@ -409,7 +533,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,13 +566,12 @@ 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; } - protected SAMLAuthenticationToken mockSamlAuthentication(String originKey) { ExtendedMetadata metadata = mock(ExtendedMetadata.class); when(metadata.getAlias()).thenReturn(originKey); @@ -473,8 +596,13 @@ 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 = "\n" + @@ -509,4 +637,3 @@ public void publishEvent(ApplicationEvent event) { " \n" + ""; } - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java similarity index 81% rename from uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java rename to uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java index b47a8be5936..8ffbbe52e1a 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/saml/SamlIDPRefreshMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/SamlIDPRefreshMockMvcTests.java @@ -10,17 +10,19 @@ * 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.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.audit.event.EntityDeletedEvent; +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; import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest; 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.zone.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning; @@ -33,6 +35,7 @@ import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -66,6 +69,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(); @@ -83,7 +127,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 +148,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(); } @@ -124,26 +168,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 + assertThat(getWebApplicationContext().getBean(JdbcTemplate.class).queryForObject("select count(*) from identity_provider where id=?", new Object[] {provider.getId()}, Integer.class), is(0)); + //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(); @@ -151,15 +199,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"); @@ -174,7 +227,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()) @@ -183,26 +236,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(); @@ -218,59 +256,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 { @@ -417,6 +410,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); @@ -427,6 +424,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); @@ -463,7 +464,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/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..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 @@ -12,10 +12,12 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.scim.endpoints; -import org.cloudfoundry.identity.uaa.authentication.Origin; +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; 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; @@ -29,40 +31,47 @@ 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; +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; 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; -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; @@ -82,6 +91,13 @@ 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; + private JdbcTemplate template; + private ScimExternalGroupBootstrap bootstrap; + + private List ephemeralUserIds = new ArrayList<>(); @Before public void setUp() throws Exception { @@ -89,29 +105,44 @@ 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 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, "oauth", "foo,bar", Collections.singletonList(MockMvcUtils.GrantType.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",""); } + @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); @@ -161,7 +192,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)); @@ -227,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); @@ -294,130 +325,210 @@ 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 testGetGroups() throws Exception { - MockHttpServletRequestBuilder get = MockMvcRequestBuilders.get("/Groups") + 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); - 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") + 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); - getMockMvc().perform(get) - .andExpect(status().isOk()); + mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); - get = MockMvcRequestBuilders.get("/Groups") + searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + assertThat(searchResults.getResources().size(), is(5)); + + get = 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(); - get = MockMvcRequestBuilders.get("/Groups") + searchResults = JsonUtils.readValue(mvcResult.getResponse().getContentAsString(), SearchResults.class); + assertThat(searchResults.getResources().size(), is(greaterThanOrEqualTo(15))); + + get = 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 ); - MockHttpServletRequestBuilder 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()); + ScimGroup group1 = new ScimGroup(null, "scim.whatever", result.getIdentityZone().getId()); + ScimGroup group2 = new ScimGroup(null, "another.group", result.getIdentityZone().getId()); - get = MockMvcRequestBuilders.get("/Groups") + 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 = 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()); + MvcResult mvcResult = getMockMvc().perform(get) + .andExpect(status().isOk()) + .andReturn(); - 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()); + 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) .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 = 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 = get("/Groups") + .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\""); + MockHttpServletRequestBuilder get = get("/Groups") + .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\""); + get = get("/Groups") + .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 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) @@ -426,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) @@ -675,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); @@ -684,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"); @@ -749,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) @@ -799,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); @@ -850,7 +1193,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); @@ -873,35 +1216,48 @@ 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"); + ephemeralUserIds.add(user.getId()); + + 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; } 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..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 @@ -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; @@ -89,7 +92,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 +209,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); @@ -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); @@ -536,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; 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..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 @@ -12,14 +12,14 @@ *******************************************************************************/ 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; 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; @@ -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); @@ -277,7 +278,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"); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java index e299d48fa81..21856f563ee 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/test/TestUtils.java @@ -12,6 +12,8 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import java.sql.Connection; @@ -69,7 +71,7 @@ public static void deleteFrom(DataSource dataSource, String... tables) throws Ex } public static void assertNoSuchUser(JdbcTemplate template, String column, String value) { - assertEquals(0, template.queryForInt("select count(id) from users where " + column + "='" + value + "'")); + assertThat(template.queryForObject("select count(id) from users where " + column + "='" + value + "'", Integer.class), is(0)); } } 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