From b30440bb9f60e5388b6c75cd6ceb1e9edc7df312 Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Wed, 23 Oct 2019 16:56:34 -1000 Subject: [PATCH 1/2] gh-3572 Upgrade to Spring Security 5.2 * Upgrade Spring Cloud Common Security dependency * Fix tests * Update documentation --- pom.xml | 13 +- .../main/asciidoc/configuration-local.adoc | 331 +++++++++++------- ...loudFoundryOAuthSecurityConfiguration.java | 47 +-- ...loudFoundryDataflowAuthoritiesMapper.java} | 34 +- .../CloudFoundryPrincipalExtractor.java | 43 --- .../support/CloudFoundrySecurityService.java | 26 +- .../config/DataFlowClientProperties.java | 2 +- .../DataFlowControllerAutoConfiguration.java | 7 +- .../config/features/TaskConfiguration.java | 7 +- .../impl/DefaultTaskExecutionService.java | 13 +- .../ManualOAuthAuthenticationProvider.java | 114 ------ .../DataFlowServerConfigurationTests.java | 7 + .../DefaultEnvironmentPostProcessorTests.java | 6 + .../server/configuration/JobDependencies.java | 10 +- .../TaskServiceDependencies.java | 14 +- .../configuration/TestDependencies.java | 12 +- .../DefaultTaskExecutionServiceTests.java | 14 +- ...tTaskExecutionServiceTransactionTests.java | 6 +- spring-cloud-dataflow-server/pom.xml | 5 + spring-cloud-starter-dataflow-server/pom.xml | 6 + .../LocalServerSecurityWithOAuth2Tests.java | 6 +- ...LocalServerSecurityWithUsersFileTests.java | 6 + .../server/single/security/oauthConfig.yml | 58 +-- .../oauth2TestServerConfig.yml | 2 +- 24 files changed, 394 insertions(+), 395 deletions(-) rename spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/{CloudFoundryDataflowAuthoritiesExtractor.java => CloudFoundryDataflowAuthoritiesMapper.java} (70%) delete mode 100644 spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundryPrincipalExtractor.java delete mode 100644 spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/service/impl/ManualOAuthAuthenticationProvider.java diff --git a/pom.xml b/pom.xml index efbd5040e0..1adecdca29 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ 2.1.0.BUILD-SNAPSHOT - 3.2.0.RELEASE + 3.3.0.RELEASE 2.1.0.BUILD-SNAPSHOT 4.1.0 @@ -74,15 +74,13 @@ 2.2.0.BUILD-SNAPSHOT - 2.2.0.RC1 - Hoxton.BUILD-SNAPSHOT 1.1.1.RELEASE - 1.2.0.M2 + 1.2.0.BUILD-SNAPSHOT 1.2.0.RELEASE @@ -224,13 +222,6 @@ commons-lang ${commons-lang.version} - - - org.springframework.security.oauth.boot - spring-security-oauth2-autoconfigure - ${spring-security-oauth2-autoconfigure.version} - io.fabric8 kubernetes-client diff --git a/spring-cloud-dataflow-docs/src/main/asciidoc/configuration-local.adoc b/spring-cloud-dataflow-docs/src/main/asciidoc/configuration-local.adoc index e2f8a3d8b0..8e32d04187 100644 --- a/spring-cloud-dataflow-docs/src/main/asciidoc/configuration-local.adoc +++ b/spring-cloud-dataflow-docs/src/main/asciidoc/configuration-local.adoc @@ -537,25 +537,45 @@ NOTE: When authentication is set up, it is strongly recommended to enable HTTPS as well, especially in production environments. You can turn on OAuth2 authentication by adding the following to `application.yml` or by setting -environment variables: +environment variables. The following example shows the minimal setup needed for +https://github.com/cloudfoundry/uaa[CloudFoundry User Account and Authentication (UAA) Server]: [source,yaml] ---- -security: - oauth2: - client: - client-id: myclient # <1> - client-secret: mysecret - access-token-uri: http://127.0.0.1:9999/oauth/token - user-authorization-uri: http://127.0.0.1:9999/oauth/authorize - resource: - user-info-uri: http://127.0.0.1:9999/me # <2> - token-info-uri: https://dataflow.local:8080/uaa/check_token # <3> ----- - -<1> Providing the Client ID in the OAuth Configuration Section activates OAuth2 security -<2> Used to retrieve user information such as the username. Mandatory. -<3> Used to introspect and validate a directly passed-in token. Mandatory. +spring: + security: + oauth2: # <1> + client: + registration: + uaa: # <2> + client-id: myclient + client-secret: mysecret + redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' + authorization-grant-type: authorization_code + scope: + - openid # <3> + provider: + uaa: + jwk-set-uri: http://uaa.local:8080/uaa/token_keys + token-uri: http://uaa.local:8080/uaa/oauth/token + user-info-uri: http://uaa.local:8080/uaa/userinfo # <4> + user-name-attribute: user_name # <5> + authorization-uri: http://uaa.local:8080/uaa/oauth/authorize + resourceserver: + opaquetoken: + introspection-uri: http://uaa.local:8080/uaa/introspect # <6> + client-id: dataflow + client-secret: dataflow +---- + +<1> Providing this property activates OAuth2 security +<2> The provider id. It is possible to specify more than 1 provider +<3> As the UAA is an OpenID provider, you must at least specify the `openid` scope. + If your provider also provides additional scopes to control the role assignments, + you must specify those scopes here as well +<4> OpenID endpoint. Used to retrieve user information such as the username. Mandatory. +<5> The JSON property of the response that contains the username +<6> Used to introspect and validate a directly passed-in token. Mandatory. You can verify that basic authentication is working properly by using curl, as follows: @@ -597,11 +617,14 @@ Spring Cloud Data Flow, assigns all roles to authenticated OAuth2 users by default. The `DefaultDataflowAuthoritiesExtractor` class is used for that purpose. Alternatively, Spring Cloud Data Flow can map OAuth2 scopes to Data Flow roles by -setting `spring.cloud.dataflow.security.authorization.map-oauth-scopes` to `true.` For more -details, please see the chapter on <>. +setting the boolean property `map-oauth-scopes` for your provider to `true` (False is the default). +For example, if your provider's id is `uaa`, the property would be +`spring.cloud.dataflow.security.authorization.provider-role-mappings.uaa.map-oauth-scopes`. + +For more details, please see the chapter on <>. Lastly, you can customize the role mapping behavior by providing your own Spring bean definition that -extends Spring Security OAuth's `AuthoritiesExtractor` interface. In that case, +extends Spring Cloud Data Flow's `AuthorityMapper` interface. In that case, the custom bean definition takes precedence over the default one provided by Spring Cloud Data Flow. @@ -635,6 +658,8 @@ spring: security: authorization: enabled: true + loginUrl: "/" + permit-all-paths: "/authenticate,/security/info,/assets/**,/dashboard/logout-success-oauth.html,/favicon.ico" rules: # About @@ -667,6 +692,7 @@ spring: - GET /jobs/executions => hasRole('ROLE_VIEW') - PUT /jobs/executions/** => hasRole('ROLE_MODIFY') - GET /jobs/executions/** => hasRole('ROLE_VIEW') + - GET /jobs/thinexecutions => hasRole('ROLE_VIEW') # Batch Job Instances @@ -700,6 +726,9 @@ spring: - GET /streams/validation/ => hasRole('ROLE_VIEW') - GET /streams/validation/* => hasRole('ROLE_VIEW') + # Stream Logs + - GET /streams/logs/* => hasRole('ROLE_VIEW') + # Task Definitions - POST /tasks/definitions => hasRole('ROLE_CREATE') @@ -712,6 +741,7 @@ spring: - GET /tasks/executions => hasRole('ROLE_VIEW') - GET /tasks/executions/* => hasRole('ROLE_VIEW') - POST /tasks/executions => hasRole('ROLE_DEPLOY') + - POST /tasks/executions/* => hasRole('ROLE_DEPLOY') - DELETE /tasks/executions/* => hasRole('ROLE_DESTROY') # Task Schedules @@ -723,11 +753,18 @@ spring: - POST /tasks/schedules => hasRole('ROLE_SCHEDULE') - DELETE /tasks/schedules/* => hasRole('ROLE_SCHEDULE') + # Task Platform Account List */ + + - GET /tasks/platforms => hasRole('ROLE_VIEW') + # Task Validations - GET /tasks/validation/ => hasRole('ROLE_VIEW') - GET /tasks/validation/* => hasRole('ROLE_VIEW') + # Task Logs + - GET /tasks/logs/* => hasRole('ROLE_VIEW') + # Tools - POST /tools/** => hasRole('ROLE_VIEW') @@ -798,44 +835,105 @@ While the UAA is used by https://www.cloudfoundry.org/[Cloud Foundry], it is also a fully featured stand alone OAuth2 server with enterprise features such as https://github.com/cloudfoundry/uaa/blob/develop/docs/UAA-LDAP.md[LDAP integration]. +===== Requirements + Checkout, Build and Run UAA: +- Make sure you use Java 8 +- https://git-scm.com/[Git] installed +- You need the https://github.com/cloudfoundry/cf-uaac[CloudFoundry UAA Command Line Client] installed +- Use a different host name for UAA when running on the same machine, e.g. `http://uaa/` + +In case you run into issues installing _uaac_, you may have to set the `GEM_HOME` environment +variable: + [source,bash] ---- -$ git clone https://github.com/cloudfoundry/uaa.git -$ cd uaa/ -$ ./gradlew run +export GEM_HOME="$HOME/.gem" +---- + +and add `~/.gem/gems/cf-uaac-4.2.0/bin` to your path. + +===== Prepare UAA for JWT + +As the UAA is an OpenID provider it uses JSON Web Tokens (JWT) it needs to have +a private key for signing those JWTs: + +[source,bash] +---- +openssl genrsa -out signingkey.pem 2048 +openssl rsa -in signingkey.pem -pubout -out verificationkey.pem +export JWT_TOKEN_SIGNING_KEY=$(cat signingkey.pem) +export JWT_TOKEN_VERIFICATION_KEY=$(cat verificationkey.pem) +---- + +Later, once the UAA is started you can see the keys when accessing `http://uaa:8080/uaa/token_keys` + +===== Download + Start UAA + +[source,bash] +---- +git clone https://github.com/pivotal/uaa-bundled.git +cd uaa-bundled +./mvnw clean install +java -jar target/uaa-bundled-1.0.0.BUILD-SNAPSHOT.jar +---- + +The configuration of the UAA is driven by either a Yaml file `uaa.yml` or you can script the configuration +using the UAA Command Line Client: + +[source,bash] +---- +uaac target http://uaa:8080/uaa +uaac token client get admin -s adminsecret +uaac client add dataflow \ + --name dataflow \ + --secret dataflow \ + --scope cloud_controller.read,cloud_controller.write,openid,password.write,scim.userids,foo.create,foo.view \ + --authorized_grant_types password,authorization_code,client_credentials,refresh_token \ + --authorities uaa.resource,dataflow.create,dataflow.deploy,dataflow.destroy,dataflow.manage,dataflow.modify,dataflow.schedule,dataflow.view,foo.view,foo.create\ + --redirect_uri http://localhost:9393/login \ + --autoapprove openid \ + +uaac group add "foo.view" +uaac group add "foo.create" + +uaac user add springrocks -p mysecret --emails springrocks@someplace.com +uaac user add vieweronly -p mysecret --emails mrviewer@someplace.com + +uaac member add "foo.view" springrocks +uaac member add "foo.create" springrocks +uaac member add "foo.view" vieweronly ---- -IMPORTANT: The UAA requires *Java 8*. +This script will set up the dataflow client as well as 2 users: -The configuration of the UAA is driven by a -https://github.com/cloudfoundry/uaa/blob/develop/uaa/src/main/resources/uaa.yml[uaa.yml] -file. You can provide custom configuration but in this case we will use the -embedded default configuration that provides also a default user: +- User _springrocks_ will have both scopes `foo.view` and `foo.create` +- User _vieweronly_ will only have one scope `foo.view` +Once added, you can quickly double-check that the UAA has the users created: [source,bash] ---- -curl -v -d"username=marissa&password=koala&client_id=app&grant_type=password" -u "app:appclientsecret" http://localhost:8080/uaa/oauth/token -d 'token_format=opaque' +curl -v -d"username=springrocks&password=mysecret&client_id=dataflow&grant_type=password" -u "dataflow:dataflow" http://uaa:8080/uaa/oauth/token -d 'token_format=opaque' ---- This should produce output similar to the following: [source,bash] ---- -* Trying ::1... +* Trying 127.0.0.1... * TCP_NODELAY set -* Connected to localhost (::1) port 8080 (#0) -* Server auth using Basic with user 'app' +* Connected to uaa (127.0.0.1) port 8080 (#0) +* Server auth using Basic with user 'dataflow' > POST /uaa/oauth/token HTTP/1.1 -> Host: localhost:8080 -> Authorization: Basic YXBwOmFwcGNsaWVudHNlY3JldA== +> Host: uaa:8080 +> Authorization: Basic ZGF0YWZsb3c6ZGF0YWZsb3c= > User-Agent: curl/7.54.0 > Accept: */* -> Content-Length: 85 +> Content-Length: 97 > Content-Type: application/x-www-form-urlencoded > -* upload completely sent off: 85 out of 85 bytes +* upload completely sent off: 97 out of 97 bytes < HTTP/1.1 200 < Cache-Control: no-store < Pragma: no-cache @@ -844,10 +942,10 @@ This should produce output similar to the following: < X-Content-Type-Options: nosniff < Content-Type: application/json;charset=UTF-8 < Transfer-Encoding: chunked -< Date: Mon, 03 Dec 2018 23:58:41 GMT +< Date: Thu, 31 Oct 2019 21:22:59 GMT < -* Connection #0 to host localhost left intact -{"access_token":"0f935cea42fd4516bb36e4088f6d7c44","token_type":"bearer","id_token":"eyJhbGciOiJIUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0OjgwODAvdWFhL3Rva2VuX2tleXMiLCJraWQiOiJsZWdhY3ktdG9rZW4ta2V5IiwidHlwIjoiSldUIn0.eyJzdWIiOiI1ODExMWNlNC02NGRlLTQ3ZDYtYjZiZi0wZDRhNDFhNGFlMDAiLCJhdWQiOlsiYXBwIl0sImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC91YWEvb2F1dGgvdG9rZW4iLCJleHAiOjE1NDM5MjQ3MjEsImlhdCI6MTU0Mzg4MTUyMSwiYW1yIjpbInB3ZCJdLCJhenAiOiJhcHAiLCJzY29wZSI6WyJvcGVuaWQiXSwiZW1haWwiOiJtYXJpc3NhQHRlc3Qub3JnIiwiemlkIjoidWFhIiwib3JpZ2luIjoidWFhIiwianRpIjoiMGY5MzVjZWE0MmZkNDUxNmJiMzZlNDA4OGY2ZDdjNDQiLCJwcmV2aW91c19sb2dvbl90aW1lIjoxNTQzODgxMTk1NzU2LCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiY2xpZW50X2lkIjoiYXBwIiwiY2lkIjoiYXBwIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9uYW1lIjoibWFyaXNzYSIsInJldl9zaWciOiI1ZmI4N2M1NiIsInVzZXJfaWQiOiI1ODExMWNlNC02NGRlLTQ3ZDYtYjZiZi0wZDRhNDFhNGFlMDAiLCJhdXRoX3RpbWUiOjE1NDM4ODE1MjF9.qSxoygd7LUUJSEN0wGI3-U0x2tYZzHnTDuzk6AJUfBk","refresh_token":"d106513b98804a508c35d3f7b656a7fe-r","expires_in":43199,"scope":"scim.userids openid cloud_controller.read password.write cloud_controller.write","jti":"0f935cea42fd4516bb36e4088f6d7c44"} +* Connection #0 to host uaa left intact +{"access_token":"0329c8ecdf594ee78c271e022138be9d","token_type":"bearer","id_token":"eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vbG9jYWxob3N0OjgwODAvdWFhL3Rva2VuX2tleXMiLCJraWQiOiJsZWdhY3ktdG9rZW4ta2V5IiwidHlwIjoiSldUIn0.eyJzdWIiOiJlZTg4MDg4Ny00MWM2LTRkMWQtYjcyZC1hOTQ4MmFmNGViYTQiLCJhdWQiOlsiZGF0YWZsb3ciXSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDkwL3VhYS9vYXV0aC90b2tlbiIsImV4cCI6MTU3MjYwMDE3OSwiaWF0IjoxNTcyNTU2OTc5LCJhbXIiOlsicHdkIl0sImF6cCI6ImRhdGFmbG93Iiwic2NvcGUiOlsib3BlbmlkIl0sImVtYWlsIjoic3ByaW5ncm9ja3NAc29tZXBsYWNlLmNvbSIsInppZCI6InVhYSIsIm9yaWdpbiI6InVhYSIsImp0aSI6IjAzMjljOGVjZGY1OTRlZTc4YzI3MWUwMjIxMzhiZTlkIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImNsaWVudF9pZCI6ImRhdGFmbG93IiwiY2lkIjoiZGF0YWZsb3ciLCJncmFudF90eXBlIjoicGFzc3dvcmQiLCJ1c2VyX25hbWUiOiJzcHJpbmdyb2NrcyIsInJldl9zaWciOiJlOTkyMDQxNSIsInVzZXJfaWQiOiJlZTg4MDg4Ny00MWM2LTRkMWQtYjcyZC1hOTQ4MmFmNGViYTQiLCJhdXRoX3RpbWUiOjE1NzI1NTY5Nzl9.bqYvicyCPB5cIIu_2HEe5_c7nSGXKw7B8-reTvyYjOQ2qXSMq7gzS4LCCQ-CMcb4IirlDaFlQtZJSDE-_UsM33-ThmtFdx--TujvTR1u2nzot4Pq5A_ThmhhcCB21x6-RNNAJl9X9uUcT3gKfKVs3gjE0tm2K1vZfOkiGhjseIbwht2vBx0MnHteJpVW6U0pyCWG_tpBjrNBSj9yLoQZcqrtxYrWvPHaa9ljxfvaIsOnCZBGT7I552O1VRHWMj1lwNmRNZy5koJFPF7SbhiTM8eLkZVNdR3GEiofpzLCfoQXrr52YbiqjkYT94t3wz5C6u1JtBtgc2vq60HmR45bvg","refresh_token":"6ee95d017ada408697f2d19b04f7aa6c-r","expires_in":43199,"scope":"scim.userids openid foo.create cloud_controller.read password.write cloud_controller.write foo.view","jti":"0329c8ecdf594ee78c271e022138be9d"} ---- Using `token_format` parameter you can requested token to be either: @@ -855,95 +953,89 @@ Using `token_format` parameter you can requested token to be either: - opaque - jwt -In order to configure the UAA, it is recommended to use the `uaac` command line tool and target the UAA server: - -[source,bash] ----- -$ gem install cf-uaac -$ uaac target http://localhost:8080/uaa ----- - -Check out the user details of user `marissa`: - -[source,bash] ----- -$ uaac token owner get cf marissa -s "" -p koala -$ uaac contexts ----- - -This should also provide the relevant token details. Next, switch to the -admin user (password is `adminsecret`): - -[source,bash] ----- -$ uaac token client get admin -s adminsecret ----- - -Get a list of all configured clients: - -[source,bash] ----- -$ uaac clients ----- - -This returns the interesting client app among others: +===== Start Skipper [source,bash] ---- -… - app - scope: cloud_controller.read cloud_controller.write openid password.write scim.userids organizations.acme - resource_ids: none - authorized_grant_types: password implicit authorization_code client_credentials refresh_token - redirect_uri: http://localhost:8080/** http://localhost:8080/app/ - autoapprove: openid - authorities: uaa.resource - name: The Ultimate Oauth App - signup_redirect_url: http://localhost:8080/app/ - change_email_redirect_url: http://localhost:8080/app/ - lastmodified: 1543879731285 -… +git clone https://github.com/spring-cloud/spring-cloud-skipper.git +cd spring-cloud/spring-cloud-skipper +./mvnw clean package -DskipTests=true +java -jar spring-cloud-skipper-server/target/spring-cloud-skipper-server-2.2.0.BUILD-SNAPSHOT.jar ---- -We are going to use that `app` for Spring Cloud Data Flow but need to update -the `redirect_uri`. We will also update the client secret: +===== Start Spring Cloud Data Flow [source,bash] ---- -$ uaac client update app --redirect_uri http://localhost:9393/login -$ uaac secret set app --secret dataflow +git clone https://github.com/spring-cloud/spring-cloud-dataflow.git +cd spring-cloud-dataflow +./mvnw clean package -DskipTests=true +cd .. ---- -Now we can update the relavant OAuth configuration in Spring Cloud Data Flow: +Create a yaml file scdf.yml with the following contents: [source,yaml] ---- -security: - oauth2: - client: - client-id: app - client-secret: dataflow - scope: openid # <1> - access-token-uri: http://localhost:8080/uaa/oauth/token - user-authorization-uri: http://localhost:8080/uaa/oauth/authorize - resource: - user-info-uri: http://localhost:8080/uaa/userinfo # <2> - token-info-uri: http://localhost:8080/uaa/check_token # <3> +spring: + cloud: + dataflow: + security: + authorization: + provider-role-mappings: + uaa: + map-oauth-scopes: true + role-mappings: + ROLE_CREATE: foo.create + ROLE_DEPLOY: foo.create + ROLE_DESTROY: foo.create + ROLE_MANAGE: foo.create + ROLE_MODIFY: foo.create + ROLE_SCHEDULE: foo.create + ROLE_VIEW: foo.view + security: + oauth2: + client: + registration: + uaa: + redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' + authorization-grant-type: authorization_code + client-id: dataflow + client-secret: dataflow + scope: <1> + - openid + - foo.create + - foo.view + provider: + uaa: + jwk-set-uri: http://uaa:8080/uaa/token_keys + token-uri: http://uaa:8080/uaa/oauth/token + user-info-uri: http://uaa:8080/uaa/userinfo <2> + user-name-attribute: user_name + authorization-uri: http://uaa:8080/uaa/oauth/authorize + resourceserver: + opaquetoken: <3> + introspection-uri: http://uaa:8080/uaa/introspect + client-id: dataflow + client-secret: dataflow ---- <1> If you use scopes to identify roles, please make sure to also request -the relevant scopes, e.g `dataflow.view`, `dataflow.create` etc. + the relevant scopes, e.g `dataflow.view`, `dataflow.create` and don't forget to request the `openid` scope <2> Used to retrieve profile information, e.g. username for display purposes (mandatory) <3> Used for token introspection and validation (mandatory) -The `token-info-uri` property is especially important when passing an externally retrieved OAuth Access Token -to Spring Cloud Data Flow. In that case Spring Cloud Data Flow will take the OAuth Access Token via the -https://github.com/spring-cloud/spring-cloud-common-security-config/blob/master/spring-cloud-common-security-config-web/src/main/java/org/springframework/cloud/common/security/support/TokenValidatingUserInfoTokenServices.java[TokenValidatingUserInfoTokenServices] -, and use the UAA's -https://docs.cloudfoundry.org/api/uaa/version/4.27.0/index.html#check-token[Check Token Endpoint] +The `introspection-uri` property is especially important when passing an externally retrieved (opaque) +OAuth Access Token to Spring Cloud Data Flow. In that case Spring Cloud Data Flow will take the OAuth Access, +and use the UAA's https://docs.cloudfoundry.org/api/uaa/version/74.4.0/index.html#introspect-token[Introspect Token Endpoint] to not only check the validity of the token but also retrieve the associated OAuth scopes from the UAA -and store the successful authentication in Spring Cloud Data Flow's -https://docs.spring.io/spring-security/oauth/apidocs/org/springframework/security/oauth2/provider/token/TokenStore.html[TokenStore]. + +Finally startup Spring Cloud Data Flow: + +[source,bash] +---- +java -jar spring-cloud-dataflow/spring-cloud-dataflow-server/target/spring-cloud-dataflow-server-2.3.0.BUILD-SNAPSHOT.jar --spring.config.additional-location=scdf.yml +---- [[configuration-security-role-mapping]] ===== Role Mappings @@ -951,7 +1043,7 @@ https://docs.spring.io/spring-security/oauth/apidocs/org/springframework/securit By default all roles are assigned to users that login to Spring Cloud Data Flow. However, you can set the property: -`spring.cloud.dataflow.security.authorization.map-oauth-scopes: true` +`spring.cloud.dataflow.security.authorization.provider-role-mappings.uaa.map-oauth-scopes: true` This will instruct the underlying `DefaultAuthoritiesExtractor` to map OAuth scopes to the respective authorities. The following scopes are supported: @@ -970,21 +1062,20 @@ Additionally you can also map arbitrary scopes to each of the Data Flow roles: ---- spring: cloud: - deployer: - local: - free-disk-space-percentage: 0 dataflow: security: authorization: - map-oauth-scopes: true # <1> - role-mappings: - ROLE_CREATE: dataflow.create # <2> - ROLE_DEPLOY: dataflow.deploy - ROLE_DESTROY: dataflow.destoy - ROLE_MANAGE: dataflow.manage - ROLE_MODIFY: dataflow.modify - ROLE_SCHEDULE: dataflow.schedule - ROLE_VIEW: dataflow.view + provider-role-mappings: + uaa: + map-oauth-scopes: true # <1> + role-mappings: + ROLE_CREATE: dataflow.create # <2> + ROLE_DEPLOY: dataflow.deploy + ROLE_DESTROY: dataflow.destoy + ROLE_MANAGE: dataflow.manage + ROLE_MODIFY: dataflow.modify + ROLE_SCHEDULE: dataflow.schedule + ROLE_VIEW: dataflow.view ---- <1> Enables explicit mapping support from OAuth scopes to Data Flow roles diff --git a/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/CloudFoundryOAuthSecurityConfiguration.java b/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/CloudFoundryOAuthSecurityConfiguration.java index 2c4933dfb1..2b08cddf31 100644 --- a/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/CloudFoundryOAuthSecurityConfiguration.java +++ b/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/CloudFoundryOAuthSecurityConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,20 +24,19 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnCloudPlatform; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor; -import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices; import org.springframework.boot.cloud.CloudPlatform; import org.springframework.cloud.common.security.OAuthSecurityConfiguration; -import org.springframework.cloud.common.security.support.DefaultAuthoritiesExtractor; +import org.springframework.cloud.common.security.support.CustomAuthoritiesOpaqueTokenIntrospector; +import org.springframework.cloud.common.security.support.DefaultAuthoritiesMapper; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.common.security.support.OnOAuth2SecurityEnabled; -import org.springframework.cloud.dataflow.server.config.cloudfoundry.security.support.CloudFoundryDataflowAuthoritiesExtractor; -import org.springframework.cloud.dataflow.server.config.cloudfoundry.security.support.CloudFoundryPrincipalExtractor; +import org.springframework.cloud.dataflow.server.config.cloudfoundry.security.support.CloudFoundryDataflowAuthoritiesMapper; import org.springframework.cloud.dataflow.server.config.cloudfoundry.security.support.CloudFoundrySecurityService; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; +import org.springframework.web.client.RestTemplate; /** * When running inside Cloud Foundry, this {@link Configuration} class will reconfigure @@ -48,7 +47,7 @@ * {@code Space Developers} have access to the underlying REST API's. *

* For this to happen, a REST call will be made to the Cloud Foundry Permissions API via - * CloudFoundrySecurityService inside the {@link DefaultAuthoritiesExtractor}. + * CloudFoundrySecurityService inside the {@link DefaultAuthoritiesMapper}. *

* If the user has the respective permissions, the CF_SPACE_DEVELOPER_ROLE will be * assigned to the user. @@ -68,26 +67,16 @@ public class CloudFoundryOAuthSecurityConfiguration { private static final Logger logger = LoggerFactory.getLogger(CloudFoundryOAuthSecurityConfiguration.class); @Autowired - private UserInfoTokenServices userInfoTokenServices; + private CustomAuthoritiesOpaqueTokenIntrospector customAuthoritiesOpaqueTokenIntrospector; @Autowired(required = false) - private CloudFoundryDataflowAuthoritiesExtractor cloudFoundryDataflowAuthoritiesExtractor; - - @Autowired(required = false) - private PrincipalExtractor principalExtractor; + private CloudFoundryDataflowAuthoritiesMapper cloudFoundryDataflowAuthoritiesExtractor; @PostConstruct public void init() { if (this.cloudFoundryDataflowAuthoritiesExtractor != null) { logger.info("Setting up Cloud Foundry AuthoritiesExtractor for UAA."); - this.userInfoTokenServices.setAuthoritiesExtractor(this.cloudFoundryDataflowAuthoritiesExtractor); - } - if (this.principalExtractor != null) { - logger.info("Setting up Cloud Foundry PrincipalExtractor."); - this.userInfoTokenServices.setPrincipalExtractor(this.principalExtractor); - } - else { - this.userInfoTokenServices.setPrincipalExtractor(new CloudFoundryPrincipalExtractor()); + this.customAuthoritiesOpaqueTokenIntrospector.setAuthorityMapper(this.cloudFoundryDataflowAuthoritiesExtractor); } } @@ -101,17 +90,19 @@ public class CloudFoundryUAAConfiguration { @Value("${vcap.application.application_id}") private String applicationId; - @Autowired - private OAuth2RestTemplate oAuth2RestTemplate; - @Bean - public CloudFoundryDataflowAuthoritiesExtractor authoritiesExtractor() { - return new CloudFoundryDataflowAuthoritiesExtractor(cloudFoundrySecurityService()); + public CloudFoundryDataflowAuthoritiesMapper authoritiesExtractor( + CloudFoundrySecurityService cloudFoundrySecurityService + ) { + return new CloudFoundryDataflowAuthoritiesMapper(cloudFoundrySecurityService); } @Bean - public CloudFoundrySecurityService cloudFoundrySecurityService() { - return new CloudFoundrySecurityService(this.oAuth2RestTemplate, this.cloudControllerUrl, + public CloudFoundrySecurityService cloudFoundrySecurityService( + OAuth2TokenUtilsService oauth2TokenUtilsService, + RestTemplate restTemplate) { + return new CloudFoundrySecurityService(oauth2TokenUtilsService, restTemplate, + this.cloudControllerUrl, this.applicationId); } diff --git a/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundryDataflowAuthoritiesExtractor.java b/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundryDataflowAuthoritiesMapper.java similarity index 70% rename from spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundryDataflowAuthoritiesExtractor.java rename to spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundryDataflowAuthoritiesMapper.java index c27c986ad4..38f0915cc4 100644 --- a/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundryDataflowAuthoritiesExtractor.java +++ b/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundryDataflowAuthoritiesMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,16 @@ package org.springframework.cloud.dataflow.server.config.cloudfoundry.security.support; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor; +import org.springframework.cloud.common.security.support.AuthoritiesMapper; import org.springframework.cloud.common.security.support.CoreSecurityRoles; import org.springframework.cloud.common.security.support.SecurityConfigUtils; import org.springframework.security.config.core.GrantedAuthorityDefaults; @@ -34,21 +35,21 @@ import org.springframework.util.StringUtils; /** - * This Spring Cloud Data Flow {@link AuthoritiesExtractor} will assign all + * This Spring Cloud Data Flow {@link AuthoritiesMapper} will assign all * {@link CoreSecurityRoles} to the authenticated OAuth2 user IF the user is a "Space * Developer" in Cloud Foundry. * * @author Gunnar Hillert * */ -public class CloudFoundryDataflowAuthoritiesExtractor implements AuthoritiesExtractor { +public class CloudFoundryDataflowAuthoritiesMapper implements AuthoritiesMapper { private static final Logger logger = LoggerFactory - .getLogger(CloudFoundryDataflowAuthoritiesExtractor.class); + .getLogger(CloudFoundryDataflowAuthoritiesMapper.class); private final CloudFoundrySecurityService cloudFoundrySecurityService; - public CloudFoundryDataflowAuthoritiesExtractor(CloudFoundrySecurityService cloudFoundrySecurityService) { + public CloudFoundryDataflowAuthoritiesMapper(CloudFoundrySecurityService cloudFoundrySecurityService) { this.cloudFoundrySecurityService = cloudFoundrySecurityService; } @@ -60,24 +61,29 @@ public CloudFoundryDataflowAuthoritiesExtractor(CloudFoundrySecurityService clou * @param map Must not be null. Is only used for logging */ @Override - public List extractAuthorities(Map map) { - Assert.notNull(map, "The map argument must not be null."); + public Set mapScopesToAuthorities(Set scopes) { + Assert.notNull(scopes, "The scopes argument must not be null."); if (cloudFoundrySecurityService.isSpaceDeveloper()) { final List rolesAsStrings = new ArrayList<>(); - final List grantedAuthorities = Stream.of(CoreSecurityRoles.values()) + final Set grantedAuthorities = Stream.of(CoreSecurityRoles.values()) .map(roleEnum -> { final String roleName = SecurityConfigUtils.ROLE_PREFIX + roleEnum.getKey(); rolesAsStrings.add(roleName); return new SimpleGrantedAuthority(roleName); }) - .collect(Collectors.toList()); - logger.info("Adding ALL roles {} to Cloud Foundry Space Developer user {}", - StringUtils.collectionToCommaDelimitedString(rolesAsStrings), map); + .collect(Collectors.toSet()); + logger.info("Adding ALL roles {} to Cloud Foundry Space Developer user.", + StringUtils.collectionToCommaDelimitedString(rolesAsStrings)); return grantedAuthorities; } else { - return new ArrayList<>(0); + return Collections.emptySet(); } } + + @Override + public Set mapScopesToAuthorities(String providerId, Set scopes) { + throw new UnsupportedOperationException("Don't call this AuthoritiesMapper with a providerId."); + } } diff --git a/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundryPrincipalExtractor.java b/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundryPrincipalExtractor.java deleted file mode 100644 index 20f0d85e43..0000000000 --- a/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundryPrincipalExtractor.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.dataflow.server.config.cloudfoundry.security.support; - -import java.util.Map; - -import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor; - -/** - * A Cloud Foundry specific {@link PrincipalExtractor} that extracts the username. - * - * @author Gunnar Hillert - * - */ -public class CloudFoundryPrincipalExtractor implements PrincipalExtractor { - - private static final String[] PRINCIPAL_KEYS = new String[] { "user_name", "user", "username", - "userid", "user_id", "login", "id", "name" }; - - @Override - public Object extractPrincipal(Map map) { - for (String key : PRINCIPAL_KEYS) { - if (map.containsKey(key)) { - return map.get(key); - } - } - return null; - } - -} diff --git a/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundrySecurityService.java b/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundrySecurityService.java index c57a3a53e4..15f00963e4 100644 --- a/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundrySecurityService.java +++ b/spring-cloud-dataflow-platform-cloudfoundry/src/main/java/org/springframework/cloud/dataflow/server/config/cloudfoundry/security/support/CloudFoundrySecurityService.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.http.HttpStatus; import org.springframework.http.RequestEntity; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; -import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.util.Assert; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestTemplate; /** * Cloud Foundry security service to handle REST calls to the cloud controller and UAA. @@ -42,20 +42,24 @@ public class CloudFoundrySecurityService { private static final Logger logger = LoggerFactory.getLogger(CloudFoundrySecurityService.class); - private final OAuth2RestTemplate oAuth2RestTemplate; + private final OAuth2TokenUtilsService oauth2TokenUtilsService; + private final RestTemplate restTemplate; private final String cloudControllerUrl; private final String applicationId; - public CloudFoundrySecurityService(OAuth2RestTemplate oAuth2RestTemplate, String cloudControllerUrl, + public CloudFoundrySecurityService(OAuth2TokenUtilsService oauth2TokenUtilsService, + RestTemplate restTemplate, String cloudControllerUrl, String applicationId) { - Assert.notNull(oAuth2RestTemplate, "OAuth2RestTemplate must not be null."); + Assert.notNull(oauth2TokenUtilsService, "oauth2TokenUtilsService must not be null."); + Assert.notNull(restTemplate, "restTemplate must not be null."); Assert.notNull(cloudControllerUrl, "CloudControllerUrl must not be null."); Assert.notNull(applicationId, "ApplicationId must not be null."); - this.oAuth2RestTemplate = oAuth2RestTemplate; + this.oauth2TokenUtilsService = oauth2TokenUtilsService; this.cloudControllerUrl = cloudControllerUrl; this.applicationId = applicationId; + this.restTemplate = restTemplate; } /** @@ -66,10 +70,10 @@ public CloudFoundrySecurityService(OAuth2RestTemplate oAuth2RestTemplate, String * @return true of the user is a space developer in Cloud Foundry */ public boolean isSpaceDeveloper() { - final OAuth2AccessToken accessToken = this.oAuth2RestTemplate.getAccessToken(); - logger.info("The accessToken is: " + accessToken.getValue()); + final String accessToken = this.oauth2TokenUtilsService.getAccessTokenOfAuthenticatedUser(); + logger.info("The accessToken is: " + accessToken); final AccessLevel accessLevel = getAccessLevel( - accessToken.getValue(), applicationId); + accessToken, applicationId); if (AccessLevel.FULL.equals(accessLevel)) { return true; @@ -93,7 +97,7 @@ public AccessLevel getAccessLevel(String token, String applicationId) logger.info("Using PermissionsUri: " + permissionsUri); RequestEntity request = RequestEntity.get(permissionsUri) .header("Authorization", "bearer " + token).build(); - Map body = this.oAuth2RestTemplate.exchange(request, Map.class).getBody(); + Map body = this.restTemplate.exchange(request, Map.class).getBody(); if (Boolean.TRUE.equals(body.get("read_sensitive_data"))) { return AccessLevel.FULL; } diff --git a/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/config/DataFlowClientProperties.java b/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/config/DataFlowClientProperties.java index 334e79aa2a..6c92d2f096 100644 --- a/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/config/DataFlowClientProperties.java +++ b/spring-cloud-dataflow-rest-client/src/main/java/org/springframework/cloud/dataflow/rest/client/config/DataFlowClientProperties.java @@ -37,7 +37,7 @@ public class DataFlowClientProperties { /** * Skip Ssl validation. */ - private boolean skipSslValidation = true; + private boolean skipSslValidation; /** diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java index 0ef33da502..968b5dba4b 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -35,6 +36,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.cloud.common.security.AuthorizationProperties; import org.springframework.cloud.common.security.support.OAuth2AccessTokenProvidingClientHttpRequestInterceptor; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.common.security.support.SecurityStateBean; import org.springframework.cloud.dataflow.audit.repository.AuditRecordRepository; import org.springframework.cloud.dataflow.audit.service.AuditRecordService; @@ -363,7 +365,8 @@ public StreamDeploymentController updatableStreamDeploymentController( @Bean public SkipperClient skipperClient(SkipperClientProperties properties, - RestTemplateBuilder restTemplateBuilder, ObjectMapper objectMapper) { + RestTemplateBuilder restTemplateBuilder, ObjectMapper objectMapper, + @Autowired(required = false) OAuth2TokenUtilsService oauth2TokenUtilsService) { // TODO (Tzolov) review the manual Hal convertion configuration objectMapper.registerModule(new Jackson2HalModule()); @@ -373,7 +376,7 @@ public SkipperClient skipperClient(SkipperClientProperties properties, RestTemplate restTemplate = restTemplateBuilder .errorHandler(new SkipperClientResponseErrorHandler(objectMapper)) - .interceptors(new OAuth2AccessTokenProvidingClientHttpRequestInterceptor()) + .interceptors(new OAuth2AccessTokenProvidingClientHttpRequestInterceptor(oauth2TokenUtilsService)) .messageConverters(Arrays.asList(new StringHttpMessageConverter(), new MappingJackson2HttpMessageConverter(objectMapper))) .build(); diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/features/TaskConfiguration.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/features/TaskConfiguration.java index 0a581ef3b4..4add5e235d 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/features/TaskConfiguration.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/features/TaskConfiguration.java @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.dataflow.audit.service.AuditRecordService; import org.springframework.cloud.dataflow.configuration.metadata.ApplicationConfigurationMetadataResolver; import org.springframework.cloud.dataflow.core.TaskPlatform; @@ -181,12 +182,14 @@ public TaskExecutionService taskService(LauncherRepository launcherRepository, TaskAppDeploymentRequestCreator taskAppDeploymentRequestCreator, TaskExplorer taskExplorer, DataflowTaskExecutionDao dataflowTaskExecutionDao, - DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao) { + DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao, + @Autowired(required = false) OAuth2TokenUtilsService oauth2TokenUtilsService) { return new DefaultTaskExecutionService( launcherRepository, auditRecordService, taskRepository, taskExecutionInfoService, taskDeploymentRepository, taskExecutionRepositoryService, taskAppDeploymentRequestCreator, taskExplorer, dataflowTaskExecutionDao, - dataflowTaskExecutionMetadataDao); + dataflowTaskExecutionMetadataDao, oauth2TokenUtilsService); + } @Bean diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionService.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionService.java index 02f81a0aa2..6008994be1 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionService.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionService.java @@ -33,7 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.cloud.common.security.support.TokenUtils; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.dataflow.audit.service.AuditRecordService; import org.springframework.cloud.dataflow.core.AuditActionType; import org.springframework.cloud.dataflow.core.AuditOperationType; @@ -122,6 +122,8 @@ public class DefaultTaskExecutionService implements TaskExecutionService { private final DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao; + private OAuth2TokenUtilsService oauth2TokenUtilsService; + private final Map> tasksBeingUpgraded = new ConcurrentHashMap<>(); /** @@ -144,7 +146,8 @@ public DefaultTaskExecutionService(LauncherRepository launcherRepository, TaskAppDeploymentRequestCreator taskAppDeploymentRequestCreator, TaskExplorer taskExplorer, DataflowTaskExecutionDao dataflowTaskExecutionDao, - DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao) { + DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao, + OAuth2TokenUtilsService oauth2TokenUtilsService) { Assert.notNull(launcherRepository, "launcherRepository must not be null"); Assert.notNull(auditRecordService, "auditRecordService must not be null"); Assert.notNull(taskExecutionInfoService, "taskExecutionInfoService must not be null"); @@ -157,6 +160,7 @@ public DefaultTaskExecutionService(LauncherRepository launcherRepository, Assert.notNull(dataflowTaskExecutionDao, "dataflowTaskExecutionDao must not be null"); Assert.notNull(dataflowTaskExecutionMetadataDao, "dataflowTaskExecutionMetadataDao must not be null"); + this.oauth2TokenUtilsService = oauth2TokenUtilsService; this.launcherRepository = launcherRepository; this.auditRecordService = auditRecordService; this.taskRepository = taskRepository; @@ -242,7 +246,6 @@ public long executeTask(String taskName, Map taskDeploymentPrope logger.debug("Deleting %s and all related resources from the platform", taskName); taskLauncher.destroy(taskName); - } this.dataflowTaskExecutionMetadataDao.save(taskExecution, taskManifest); @@ -307,8 +310,8 @@ private void handleAccessToken(List commandLineArgs, TaskExecutionInform } } - if (!containsAccessToken && useUserAccessToken) { - final String token = TokenUtils.getAccessToken(); + if (!containsAccessToken && useUserAccessToken && oauth2TokenUtilsService != null) { + final String token = oauth2TokenUtilsService.getAccessTokenOfAuthenticatedUser(); if (token != null) { taskExecutionInformation.getTaskDeploymentProperties().put(dataflowAccessTokenPropertyKey, token); diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/service/impl/ManualOAuthAuthenticationProvider.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/service/impl/ManualOAuthAuthenticationProvider.java deleted file mode 100644 index c01aaca50c..0000000000 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/service/impl/ManualOAuthAuthenticationProvider.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2016-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.dataflow.server.service.impl; - -import org.slf4j.LoggerFactory; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties; -import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.AuthenticationServiceException; -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.client.DefaultOAuth2ClientContext; -import org.springframework.security.oauth2.client.OAuth2RestTemplate; -import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; -import org.springframework.security.oauth2.client.token.AccessTokenProvider; -import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; -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.OAuth2Exception; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.web.client.ResourceAccessException; - -/** - * Provides a custom {@link AuthenticationProvider} that allows for authentication - * (username and password) against an OAuth Server using a {@code password grant}. - * - * @author Gunnar Hillert - */ -public class ManualOAuthAuthenticationProvider implements AuthenticationProvider { - - private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ManualOAuthAuthenticationProvider.class); - - @Autowired - private OAuth2ClientProperties oAuth2ClientProperties; - - @Value("${security.oauth2.client.access-token-uri}") - private String accessTokenUri; - - @Autowired - private UserInfoTokenServices userInfoTokenServices; - - public AccessTokenProvider userAccessTokenProvider() { - ResourceOwnerPasswordAccessTokenProvider accessTokenProvider = new ResourceOwnerPasswordAccessTokenProvider(); - return accessTokenProvider; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - final String username = authentication.getName(); - final String password = authentication.getCredentials().toString(); - - final ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails(); - - resource.setUsername(username); - resource.setPassword(password); - - resource.setAccessTokenUri(accessTokenUri); - resource.setClientId(oAuth2ClientProperties.getClientId()); - resource.setClientSecret(oAuth2ClientProperties.getClientSecret()); - resource.setGrantType("password"); - - final OAuth2RestTemplate template = new OAuth2RestTemplate(resource, - new DefaultOAuth2ClientContext(new DefaultAccessTokenRequest())); - template.setAccessTokenProvider(userAccessTokenProvider()); - - final OAuth2AccessToken accessToken; - try { - logger.warn("Authenticating user '{}' using accessTokenUri '{}'.", username, accessTokenUri); - accessToken = template.getAccessToken(); - } - catch (OAuth2AccessDeniedException e) { - if (e.getCause() instanceof ResourceAccessException) { - final String errorMessage = String.format( - "While authenticating user '%s': " + "Unable to access accessTokenUri '%s'.", username, - accessTokenUri); - logger.error(errorMessage + " Error message: {}.", e.getCause().getMessage()); - throw new AuthenticationServiceException(errorMessage, e); - } - throw new BadCredentialsException(String.format("Access denied for user '%s'.", username), e); - } - catch (OAuth2Exception e) { - throw new AuthenticationServiceException( - String.format("Unable to perform OAuth authentication for user '%s'.", username), e); - } - - final OAuth2Authentication auth2Authentication = userInfoTokenServices - .loadAuthentication(accessToken.getValue()); - return auth2Authentication; - } - - @Override - public boolean supports(Class authentication) { - return authentication.equals(UsernamePasswordAuthenticationToken.class); - } -} diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/config/DataFlowServerConfigurationTests.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/config/DataFlowServerConfigurationTests.java index 44f8afa759..56fc4538b7 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/config/DataFlowServerConfigurationTests.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/config/DataFlowServerConfigurationTests.java @@ -33,6 +33,7 @@ import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.dataflow.server.EnableDataFlowServer; import org.springframework.cloud.dataflow.server.config.features.SchedulerConfiguration; import org.springframework.cloud.dataflow.server.config.web.WebConfiguration; @@ -63,6 +64,7 @@ /** * @author Glenn Renfro * @author Ilayaperumal Gopinathan + * @author Gunnar Hillert */ public class DataFlowServerConfigurationTests { @@ -191,5 +193,10 @@ public Scheduler scheduler() { public StreamValidationService streamValidationService() { return mock(StreamValidationService.class); } + + @Bean + public OAuth2TokenUtilsService oauth2TokenUtilsService() { + return mock(OAuth2TokenUtilsService.class); + } } } diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/config/DefaultEnvironmentPostProcessorTests.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/config/DefaultEnvironmentPostProcessorTests.java index 9e3832468c..93a4506a06 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/config/DefaultEnvironmentPostProcessorTests.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/config/DefaultEnvironmentPostProcessorTests.java @@ -22,6 +22,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.session.SessionAutoConfiguration; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.dataflow.server.EnableDataFlowServer; import org.springframework.cloud.dataflow.server.service.SchedulerService; import org.springframework.cloud.dataflow.server.service.TaskExecutionService; @@ -115,5 +116,10 @@ public SchedulerService schedulerService() { public Scheduler scheduler() { return mock(Scheduler.class); } + + @Bean + public OAuth2TokenUtilsService oauth2TokenUtilsService() { + return mock(OAuth2TokenUtilsService.class); + } } } diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/JobDependencies.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/JobDependencies.java index 1dddb9e678..40eec67161 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/JobDependencies.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/JobDependencies.java @@ -39,6 +39,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.dataflow.audit.repository.AuditRecordRepository; import org.springframework.cloud.dataflow.audit.service.AuditRecordService; import org.springframework.cloud.dataflow.audit.service.DefaultAuditRecordService; @@ -265,14 +266,15 @@ public TaskExecutionService taskService(LauncherRepository launcherRepository, TaskExecutionCreationService taskExecutionRepositoryService, TaskAppDeploymentRequestCreator taskAppDeploymentRequestCreator, TaskExplorer taskExplorer, DataflowTaskExecutionDao dataflowTaskExecutionDao, - DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao) { + DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao, + OAuth2TokenUtilsService oauth2TokenUtilsService) { return new DefaultTaskExecutionService( launcherRepository, auditRecordService, taskRepository, taskExecutionInfoService, taskDeploymentRepository, taskExecutionRepositoryService, taskAppDeploymentRequestCreator, taskExplorer, dataflowTaskExecutionDao, - dataflowTaskExecutionMetadataDao); + dataflowTaskExecutionMetadataDao, oauth2TokenUtilsService); } @Bean @@ -440,4 +442,8 @@ public ScheduleInfo getSchedule(String scheduleName) { }; } + @Bean + public OAuth2TokenUtilsService oauth2TokenUtilsService() { + return mock(OAuth2TokenUtilsService.class); + } } diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TaskServiceDependencies.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TaskServiceDependencies.java index 468ae9aab6..6e38f91cf3 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TaskServiceDependencies.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TaskServiceDependencies.java @@ -34,6 +34,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.dataflow.audit.service.AuditRecordService; import org.springframework.cloud.dataflow.audit.service.DefaultAuditRecordService; import org.springframework.cloud.dataflow.completion.CompletionConfiguration; @@ -280,12 +281,14 @@ public TaskExecutionService defaultTaskService(LauncherRepository launcherReposi TaskExecutionCreationService taskExecutionRepositoryService, TaskAppDeploymentRequestCreator taskAppDeploymentRequestCreator, TaskExplorer taskExplorer, DataflowTaskExecutionDao dataflowTaskExecutionDao, - DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao) { + DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao, + OAuth2TokenUtilsService oauth2TokenUtilsService) { return new DefaultTaskExecutionService( launcherRepository, auditRecordService, taskRepository, taskExecutionInfoService, taskDeploymentRepository, taskExecutionRepositoryService, taskAppDeploymentRequestCreator, - taskExplorer, dataflowTaskExecutionDao, dataflowTaskExecutionMetadataDao); + taskExplorer, dataflowTaskExecutionDao, dataflowTaskExecutionMetadataDao, + oauth2TokenUtilsService); } @Bean @@ -328,4 +331,11 @@ public TaskPlatform taskPlatform(Scheduler scheduler) { public Scheduler scheduler() { return new SimpleTestScheduler(); } + + @Bean + public OAuth2TokenUtilsService oauth2TokenUtilsService() { + final OAuth2TokenUtilsService oauth2TokenUtilsService = mock(OAuth2TokenUtilsService.class); + when(oauth2TokenUtilsService.getAccessTokenOfAuthenticatedUser()).thenReturn("foo-bar-123-token"); + return oauth2TokenUtilsService; + } } diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java index 40184b9e5e..3a6b07ee4d 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/configuration/TestDependencies.java @@ -42,6 +42,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.common.security.support.SecurityStateBean; import org.springframework.cloud.dataflow.audit.repository.AuditRecordRepository; import org.springframework.cloud.dataflow.audit.service.AuditRecordService; @@ -510,12 +511,14 @@ public TaskExecutionService taskService(LauncherRepository launcherRepository, TaskExecutionCreationService taskExecutionRepositoryService, TaskAppDeploymentRequestCreator taskAppDeploymentRequestCreator, TaskExplorer taskExplorer, DataflowTaskExecutionDao dataflowTaskExecutionDao, - DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao) { + DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao, + OAuth2TokenUtilsService oauth2TokenUtilsService) { return new DefaultTaskExecutionService( launcherRepository, auditRecordService, taskRepository, taskExecutionInfoService, taskDeploymentRepository, taskExecutionRepositoryService, taskAppDeploymentRequestCreator, - taskExplorer, dataflowTaskExecutionDao, dataflowTaskExecutionMetadataDao); + taskExplorer, dataflowTaskExecutionDao, dataflowTaskExecutionMetadataDao, + oauth2TokenUtilsService); } @Bean @@ -624,4 +627,9 @@ public JobStepExecutionProgressController jobStepExecutionProgressController() { public JobInstanceController jobInstanceController() { return mock(JobInstanceController.class); } + + @Bean + public OAuth2TokenUtilsService oauth2TokenUtilsService() { + return mock(OAuth2TokenUtilsService.class); + } } diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionServiceTests.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionServiceTests.java index 934e7f8177..95393753c9 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionServiceTests.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionServiceTests.java @@ -45,6 +45,7 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.system.OutputCaptureRule; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.dataflow.audit.service.AuditRecordService; import org.springframework.cloud.dataflow.core.AppRegistration; import org.springframework.cloud.dataflow.core.ApplicationType; @@ -83,9 +84,6 @@ import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileUrlResource; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; @@ -659,7 +657,9 @@ public void executeTaskWithNullDefinitionTest() { launcherRepository, auditRecordService, taskRepository, taskExecutionInfoService, mock(TaskDeploymentRepository.class), taskExecutionRepositoryService, taskAppDeploymentRequestCreator, - this.taskExplorer, this.dataflowTaskExecutionDao, this.dataflowTaskExecutionMetadataDao); + this.taskExplorer, this.dataflowTaskExecutionDao, this.dataflowTaskExecutionMetadataDao, + mock(OAuth2TokenUtilsService.class)); + try { taskExecutionService.executeTask(TASK_NAME_ORIG, new HashMap<>(), new LinkedList<>()); } @@ -871,12 +871,6 @@ public void executeComposedTaskWithAccessTokenOverrideAsArgument() { private Map prepareEnvironmentForTokenTests() { - final OAuth2Authentication oAuth2Authentication = mock(OAuth2Authentication.class); - final OAuth2AuthenticationDetails oAuth2AuthenticationDetails = mock(OAuth2AuthenticationDetails.class); - when(oAuth2AuthenticationDetails.getTokenValue()).thenReturn("foo-bar-123-token"); - when(oAuth2Authentication.getDetails()).thenReturn(oAuth2AuthenticationDetails); - SecurityContextHolder.getContext().setAuthentication(oAuth2Authentication); - taskSaveService.saveTaskDefinition(new TaskDefinition("seqTask", "AAA && BBB")); when(taskLauncher.launch(any())).thenReturn("0"); when(appRegistry.appExist(anyString(), any(ApplicationType.class))).thenReturn(true); diff --git a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionServiceTransactionTests.java b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionServiceTransactionTests.java index 9988519ed9..87ec56f162 100644 --- a/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionServiceTransactionTests.java +++ b/spring-cloud-dataflow-server-core/src/test/java/org/springframework/cloud/dataflow/server/service/impl/DefaultTaskExecutionServiceTransactionTests.java @@ -35,6 +35,7 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.common.security.support.OAuth2TokenUtilsService; import org.springframework.cloud.dataflow.audit.service.AuditRecordService; import org.springframework.cloud.dataflow.core.AppRegistration; import org.springframework.cloud.dataflow.core.ApplicationType; @@ -74,6 +75,7 @@ /** * @author Glenn Renfro + * @author Gunnar Hillert */ @RunWith(SpringRunner.class) @SpringBootTest(classes = { TaskServiceDependencies.class }, properties = { @@ -139,8 +141,8 @@ public void setupMocks() { launcherRepository, auditRecordService, taskRepository, taskExecutionInfoService, mock(TaskDeploymentRepository.class), taskExecutionRepositoryService, taskAppDeploymentRequestCreator, - this.taskExplorer, this.dataflowTaskExecutionDao, this.dataflowTaskExecutionMetadataDao); - + this.taskExplorer, this.dataflowTaskExecutionDao, this.dataflowTaskExecutionMetadataDao, + mock(OAuth2TokenUtilsService.class)); } @Test diff --git a/spring-cloud-dataflow-server/pom.xml b/spring-cloud-dataflow-server/pom.xml index c847079598..54e7268910 100644 --- a/spring-cloud-dataflow-server/pom.xml +++ b/spring-cloud-dataflow-server/pom.xml @@ -19,6 +19,11 @@ + + io.projectreactor + reactor-core + 3.3.0.RELEASE + org.springframework.cloud spring-cloud-starter-dataflow-server diff --git a/spring-cloud-starter-dataflow-server/pom.xml b/spring-cloud-starter-dataflow-server/pom.xml index 31482ec10b..3bc828405a 100644 --- a/spring-cloud-starter-dataflow-server/pom.xml +++ b/spring-cloud-starter-dataflow-server/pom.xml @@ -46,6 +46,12 @@ spring-cloud-dataflow-rest-client test + + org.springframework.security.oauth + spring-security-oauth2 + 2.3.7.RELEASE + test + diff --git a/spring-cloud-starter-dataflow-server/src/test/java/org/springframework/cloud/dataflow/server/single/security/LocalServerSecurityWithOAuth2Tests.java b/spring-cloud-starter-dataflow-server/src/test/java/org/springframework/cloud/dataflow/server/single/security/LocalServerSecurityWithOAuth2Tests.java index a416d49927..36ed3adfeb 100644 --- a/spring-cloud-starter-dataflow-server/src/test/java/org/springframework/cloud/dataflow/server/single/security/LocalServerSecurityWithOAuth2Tests.java +++ b/spring-cloud-starter-dataflow-server/src/test/java/org/springframework/cloud/dataflow/server/single/security/LocalServerSecurityWithOAuth2Tests.java @@ -264,11 +264,9 @@ public void testAccessSecurityInfoUrlWithOAuth2AccessToken2TimesAndLogout() thro assertTrue(Boolean.valueOf(oAuthServerResponse)); - // At this point the Token is still cached by the Data Flow Server, and therefore a positive response is expected - localDataflowResource.getMockMvc() .perform(get("/security/info").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) - .andExpect(status().isOk()); + .andExpect(status().isUnauthorized()); localDataflowResource.getMockMvc() .perform(get("/logout").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) @@ -276,7 +274,7 @@ public void testAccessSecurityInfoUrlWithOAuth2AccessToken2TimesAndLogout() thro localDataflowResource.getMockMvc() .perform(get("/security/info").header("Authorization", "bearer " + accessTokenAsString)).andDo(print()) - .andExpect(status().isOk()); //FIXME + .andExpect(status().isUnauthorized()); } diff --git a/spring-cloud-starter-dataflow-server/src/test/java/org/springframework/cloud/dataflow/server/single/security/LocalServerSecurityWithUsersFileTests.java b/spring-cloud-starter-dataflow-server/src/test/java/org/springframework/cloud/dataflow/server/single/security/LocalServerSecurityWithUsersFileTests.java index b0f4be7abd..a408225708 100644 --- a/spring-cloud-starter-dataflow-server/src/test/java/org/springframework/cloud/dataflow/server/single/security/LocalServerSecurityWithUsersFileTests.java +++ b/spring-cloud-starter-dataflow-server/src/test/java/org/springframework/cloud/dataflow/server/single/security/LocalServerSecurityWithUsersFileTests.java @@ -724,5 +724,11 @@ public String getUsername() { public String getPassword() { return password; } + + @Override + public String toString() { + return String.format("UserCredentials: %s/%s.", this.username, this.password); + } + } } diff --git a/spring-cloud-starter-dataflow-server/src/test/resources/org/springframework/cloud/dataflow/server/single/security/oauthConfig.yml b/spring-cloud-starter-dataflow-server/src/test/resources/org/springframework/cloud/dataflow/server/single/security/oauthConfig.yml index 999b71623e..4c372f2c60 100644 --- a/spring-cloud-starter-dataflow-server/src/test/resources/org/springframework/cloud/dataflow/server/single/security/oauthConfig.yml +++ b/spring-cloud-starter-dataflow-server/src/test/resources/org/springframework/cloud/dataflow/server/single/security/oauthConfig.yml @@ -11,24 +11,40 @@ spring: dataflow: security: authorization: - map-oauth-scopes: true - role-mappings: - ROLE_VIEW: dataflow.view - ROLE_CREATE: dataflow.create - ROLE_MANAGE: dataflow.manage - ROLE_DEPLOY: dataflow.create - ROLE_DESTROY: dataflow.create - ROLE_MODIFY: dataflow.create - ROLE_SCHEDULE: dataflow.create -security: - oauth2: - client: - client-id: myclient - client-secret: mysecret - access-token-uri: http://127.0.0.1:${oauth2.port}/oauth/token - user-authorization-uri: http://127.0.0.1:${oauth2.port}/oauth/authorize - resource: - user-info-uri: http://127.0.0.1:${oauth2.port}/me - token-info-uri: http://127.0.0.1:${oauth2.port}/oauth/check_token - authorization: - check-token-access: isAuthenticated() + provider-role-mappings: + uaa: + map-oauth-scopes: true + role-mappings: + ROLE_VIEW: dataflow.view + ROLE_CREATE: dataflow.create + ROLE_MANAGE: dataflow.manage + ROLE_DEPLOY: dataflow.create + ROLE_DESTROY: dataflow.create + ROLE_MODIFY: dataflow.create + ROLE_SCHEDULE: dataflow.create + security: + oauth2: + client: + registration: + uaa: + redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' + authorization-grant-type: authorization_code + client-id: myclient + client-secret: mysecret + access-token-uri: http://127.0.0.1:${oauth2.port}/oauth/token + user-authorization-uri: http://127.0.0.1:${oauth2.port}/oauth/authorize + provider: + uaa: + authorization-uri: http://127.0.0.1:${oauth2.port}/oauth/authorize + user-info-uri: http://127.0.0.1:${oauth2.port}/me + token-uri: http://127.0.0.1:${oauth2.port}/oauth/token + resourceserver: + opaquetoken: + introspection-uri: http://127.0.0.1:${oauth2.port}/oauth/check_token + client-id: myclient + client-secret: mysecret + authorization: + check-token-access: isAuthenticated() +logging: + level: + org.springframework.security: WARN diff --git a/spring-cloud-starter-dataflow-server/src/test/resources/org/springframework/cloud/dataflow/server/single/security/support/oauth2testserver/oauth2TestServerConfig.yml b/spring-cloud-starter-dataflow-server/src/test/resources/org/springframework/cloud/dataflow/server/single/security/support/oauth2testserver/oauth2TestServerConfig.yml index d0abe19067..c7d439893b 100644 --- a/spring-cloud-starter-dataflow-server/src/test/resources/org/springframework/cloud/dataflow/server/single/security/support/oauth2testserver/oauth2TestServerConfig.yml +++ b/spring-cloud-starter-dataflow-server/src/test/resources/org/springframework/cloud/dataflow/server/single/security/support/oauth2testserver/oauth2TestServerConfig.yml @@ -8,7 +8,7 @@ security: client-secret: "{noop}mysecret" scope: dataflow.view,dataflow.manage,dataflow.create registered-redirect-uri: - - http://localhost:9393/login + - http://localhost:${dataflow.port}/login/oauth2/code/uaa auto-approve-scopes: '.*' authorized-grant-types: - authorization_code From 069fb2def248428e2a026d8deb6fb3484de3ecc1 Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Mon, 4 Nov 2019 09:58:47 -1000 Subject: [PATCH 2/2] gh-3572 Code review changes --- pom.xml | 3 +++ .../src/main/asciidoc/configuration-local.adoc | 2 +- .../server/config/DataFlowControllerAutoConfiguration.java | 3 +-- .../dataflow/server/config/features/TaskConfiguration.java | 2 +- spring-cloud-starter-dataflow-server/pom.xml | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 1adecdca29..fd827a16aa 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,9 @@ 2.2.0.BUILD-SNAPSHOT + + 2.3.7.RELEASE + diff --git a/spring-cloud-dataflow-docs/src/main/asciidoc/configuration-local.adoc b/spring-cloud-dataflow-docs/src/main/asciidoc/configuration-local.adoc index 8e32d04187..cc40a693de 100644 --- a/spring-cloud-dataflow-docs/src/main/asciidoc/configuration-local.adoc +++ b/spring-cloud-dataflow-docs/src/main/asciidoc/configuration-local.adoc @@ -581,7 +581,7 @@ You can verify that basic authentication is working properly by using curl, as f [source,bash] ---- -$ curl -u myusername:mypassword http://localhost:9393/ -H 'Accept: application/json' +curl -u myusername:mypassword http://localhost:9393/ -H 'Accept: application/json' ---- As a result, you should see a list of available REST endpoints. diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java index 968b5dba4b..07df215189 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/DataFlowControllerAutoConfiguration.java @@ -26,7 +26,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -366,7 +365,7 @@ public StreamDeploymentController updatableStreamDeploymentController( @Bean public SkipperClient skipperClient(SkipperClientProperties properties, RestTemplateBuilder restTemplateBuilder, ObjectMapper objectMapper, - @Autowired(required = false) OAuth2TokenUtilsService oauth2TokenUtilsService) { + @Nullable OAuth2TokenUtilsService oauth2TokenUtilsService) { // TODO (Tzolov) review the manual Hal convertion configuration objectMapper.registerModule(new Jackson2HalModule()); diff --git a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/features/TaskConfiguration.java b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/features/TaskConfiguration.java index 4add5e235d..e4e10f485b 100644 --- a/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/features/TaskConfiguration.java +++ b/spring-cloud-dataflow-server-core/src/main/java/org/springframework/cloud/dataflow/server/config/features/TaskConfiguration.java @@ -183,7 +183,7 @@ public TaskExecutionService taskService(LauncherRepository launcherRepository, TaskExplorer taskExplorer, DataflowTaskExecutionDao dataflowTaskExecutionDao, DataflowTaskExecutionMetadataDao dataflowTaskExecutionMetadataDao, - @Autowired(required = false) OAuth2TokenUtilsService oauth2TokenUtilsService) { + @Nullable OAuth2TokenUtilsService oauth2TokenUtilsService) { return new DefaultTaskExecutionService( launcherRepository, auditRecordService, taskRepository, taskExecutionInfoService, taskDeploymentRepository, taskExecutionRepositoryService, diff --git a/spring-cloud-starter-dataflow-server/pom.xml b/spring-cloud-starter-dataflow-server/pom.xml index 3bc828405a..83918761a0 100644 --- a/spring-cloud-starter-dataflow-server/pom.xml +++ b/spring-cloud-starter-dataflow-server/pom.xml @@ -49,7 +49,7 @@ org.springframework.security.oauth spring-security-oauth2 - 2.3.7.RELEASE + ${spring-security-oauth2.version} test