From 22272ba3c13828cd3ad3b8c2b3f4f432a03d40d7 Mon Sep 17 00:00:00 2001
From: Michael Hirsch
Date: Mon, 31 Oct 2016 13:16:03 +0100
Subject: [PATCH] Feature hawkbit uaa extension (#317)
* use UserPrincipal to determine tenant at runtime
Signed-off-by: Michael Hirsch
* add hawkbit-uaa extension
Signed-off-by: Michael Hirsch
* adapt WithSpringAuthorityRule with UserPrincipal for determine tenant
Signed-off-by: Michael Hirsch
* fix String principal in DDI download resource
Signed-off-by: Michael Hirsch
* merge the email to the UserPrincipal from the master manually
Signed-off-by: Michael Hirsch
* Fixed some grammar issues and typos
Signed-off-by: Dominic Schabel
---
extensions/README.md | 12 ++
extensions/hawkbit-extension-uaa/README.md | 119 +++++++++++
extensions/hawkbit-extension-uaa/pom.xml | 62 ++++++
.../security/uaa/UaaClientProperties.java | 46 ++++
.../uaa/UaaOAuthAutoConfiguration.java | 199 ++++++++++++++++++
.../uaa/UserPrincipalInfoTokenServices.java | 105 +++++++++
.../main/resources/META-INF/spring.factories | 3 +
extensions/pom.xml | 27 +++
.../InMemoryUserManagementConfiguration.java | 18 +-
.../DdiDlArtifactStoreControllerRestApi.java | 6 +-
.../resource/DdiArtifactStoreController.java | 9 +-
.../test/util/WithSpringAuthorityRule.java | 5 +-
.../im/authentication/UserPrincipal.java | 77 +++----
.../security/SecurityContextTenantAware.java | 24 ++-
pom.xml | 1 +
15 files changed, 654 insertions(+), 59 deletions(-)
create mode 100644 extensions/README.md
create mode 100644 extensions/hawkbit-extension-uaa/README.md
create mode 100644 extensions/hawkbit-extension-uaa/pom.xml
create mode 100644 extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UaaClientProperties.java
create mode 100644 extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UaaOAuthAutoConfiguration.java
create mode 100644 extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UserPrincipalInfoTokenServices.java
create mode 100644 extensions/hawkbit-extension-uaa/src/main/resources/META-INF/spring.factories
create mode 100644 extensions/pom.xml
diff --git a/extensions/README.md b/extensions/README.md
new file mode 100644
index 000000000..5613013c4
--- /dev/null
+++ b/extensions/README.md
@@ -0,0 +1,12 @@
+# hawkBit Extensions
+
+hawkBit extensions are implementations to extend the functionality of hawkBit which are maintained by the hawkBit community. The extensions can be used to integrate in a hawkBit application to exchange or extend hawkBit functionality. Extensions should work with the hawkBit example application. All extensions provide a `README.md` which explains the use of the extension and how to use them.
+
+hawkBit extensions are implementations which are not included in the default implementation of hawkBit's security and auto-configuration mechanism or extending functionality by e.g. integrating third-party services to hawkBit.
+
+hawkBit makes use of the spring-bean and configuration mechanism which allows an flexible configuration and the exchange of beans in spring-configurations. Many beans are `@Conditional` annotated in hawkBit so they can be overwritten. Extensions can also leverage and implemented functionalities based on hawkBit's event mechanism by subscribing to events and implement additional functionality.
+
+### hawkBit Extension follows
+* Containing a `README.md` which explains the extension in detail and how to use it
+* Working with the hawkBit example application
+* Following the maven-artifact-id `hawkbit-extension-`
diff --git a/extensions/hawkbit-extension-uaa/README.md b/extensions/hawkbit-extension-uaa/README.md
new file mode 100644
index 000000000..70722fb60
--- /dev/null
+++ b/extensions/hawkbit-extension-uaa/README.md
@@ -0,0 +1,119 @@
+# hawkBit UAA Extension
+The hawkBit UAA extension enables hawkBit to use the [User Account and Authentication (UAA)](https://github.com/cloudfoundry/uaa) based on the Spring Security OAuth project.
+In general the mechanism can be adapted to use any OAUTH2 server. The `UserPrincipalInfoTokenServices` might be adapted for other OAUTH2 providers to extract the principal information.
+
+The `UaaOAuthAutoConfiguration` setups the necessary servlet-filter to intercept the security-chain to implement the OAUTH2 workflow. It allows to redirect to the UAA-Login-Form via the URL `/uaalogin` as well as to use the bearer access token within the hawkBit REST-API using the HTTP Authorization header `Authorization: bearer ezro63ut....`.
+
+To retrieve the bearer access token, check out the OAUTH2 authorization resource of the [UAA-API](https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst)
+
+## Using UAA Extension
+To use this extension in the example application you just need to add the maven-dependencies to the example-application `pom.xml` and configure the necessary properties in the `application.properties`
+```
+
+ org.eclipse.hawkbit
+ hawkbit-extension-uaa
+ ${project.version}
+
+```
+
+## Configuration
+The `UaaClientProperties` must contain the necessary configuration to setup the OAUTH2 client-id and client-credentials, as well as the necessary OAUTH2 URLS as well as the JWT signing key.
+
+```
+uaa.client.clientId=hawkBit
+uaa.client.clientSecret=hawkBitSecret
+uaa.client.accessTokenUri=http://localhost:8080/uaa/oauth/token
+uaa.client.userAuthorizationUri=http://localhost:8080/uaa/oauth/authorize
+uaa.client.clientAuthenticationScheme=form
+uaa.resource.userInfoUri=http://localhost:8080/uaa/userinfo
+uaa.resource.jwt.keyValue=uaasign
+```
+
+## Multitenancy
+The implementation uses the `zid` (zoneId) as tenant information. The default zone-id in the UAA is `uaa`, so every user which logs into hawkBit logs in for the tenant `uaa`. You can use the UAA zones mechanism to implement multi-tenancy mechanism to hawkbit or you can change the strategy. To change the tenant extraction strategy you can adapt the `UserPrincipalInfoTokenServices` which creates the hawkBit `UserPrincipal` which contains the tenant information of the principal.
+
+## Token Signing
+Using the bearer token within the hawkBit REST-APIs the bearer token is verified by hawkBit using either a symmetric or asymmetric keys. The necessary `key-value` must be configured in the configuration.
+
+# UAA Configuration
+The [User Account and Authentication (UAA)](https://github.com/cloudfoundry/uaa) can be started as an stand-alone application.
+More information to configuration see [UAA-Docs](https://github.com/cloudfoundry/uaa/blob/master/docs).
+
+## uaa.yml
+The `uaa.yml` contains the necessary bootstrap configuration of the UAA. To work with hawkBit you'll need to setup the OAUTH2 client which hawkBit is using. Furthermore you need to setup the necessary hawkBit permissions to allow hawkBit to do authorization decision based on the known permissions.
+
+Example `uaa.yml` configuration:
+```
+scim:
+ users:
+ - hawkbitadmin|hawkbitadmin|hawkbitadmin@test.org|hawkbitadmin|hawkbitadmin|uaa.admin,READ_TARGET,CREATE_TARGET,UPDATE_TARGET,DELETE_TARGET,READ_REPOSITORY,UPDATE_REPOSITORY,CREATE_REPOSITORY,DELETE_REPOSITORY,SYSTEM_MONITOR,SYSTEM_DIAG,SYSTEM_ADMIN,DOWNLOAD_REPOSITORY_ARTIFACT,TENANT_CONFIGURATION,ROLLOUT_MANAGEMENT
+ groups:
+ zones.read: Read identity zones
+ zones.write: Create and update identity zones
+ idps.read: Retrieve identity providers
+ idps.write: Create and update identity providers
+ clients.admin: Create, modify and delete OAuth clients
+ clients.write: Create and modify OAuth clients
+ clients.read: Read information about OAuth clients
+ clients.secret: Change the password of an OAuth client
+ scim.write: Create, modify and delete SCIM entities, i.e. users and groups
+ scim.read: Read all SCIM entities, i.e. users and groups
+ scim.create: Create users
+ scim.userids: Read user IDs and retrieve users by ID
+ scim.zones: Control a user's ability to manage a zone
+ scim.invite: Send invitations to users
+ password.write: Change your password
+ oauth.approval: Manage approved scopes
+ oauth.login: Authenticate users outside of the UAA
+ openid: Access profile information, i.e. email, first and last name, and phone number
+ groups.update: Update group information and memberships
+ uaa.user: Act as a user in the UAA
+ uaa.resource: Serve resources protected by the UAA
+ uaa.admin: Act as an administrator throughout the UAA
+ uaa.none: Forbid acting as a user
+ uaa.offline_token: Allow offline access
+
+jwt:
+ token:
+ signing-key: uaasign
+ verification-key: uaasign
+login:
+ branding:
+ companyName: hawkbit
+# squareLogo: |
+# this is an invalid
+# base64 logo with
+# line feeds
+# productLogo: |
+# this is an invalid
+# base64 logo with
+# line feeds
+
+oauth:
+ user:
+ authorities:
+ - openid
+ clients:
+ hawkbit:
+ id: hawkbit
+ secret: hawkbitsecret
+ authorized-grant-types: password,implicit,authorization_code,client_credentials,refresh_token
+ scope: READ_TARGET,CREATE_TARGET,UPDATE_TARGET,DELETE_TARGET,READ_REPOSITORY,UPDATE_REPOSITORY,CREATE_REPOSITORY,DELETE_REPOSITORY,SYSTEM_MONITOR,SYSTEM_DIAG,SYSTEM_ADMIN,DOWNLOAD_REPOSITORY_ARTIFACT,TENANT_CONFIGURATION,ROLLOUT_MANAGEMENT,openid,uaa.user,uaa.admin,password.write,scim.userids,cloud_controller.admin,scim.read,scim.write
+ authorities: uaa.admin,openid,scim.read,zones.uaa.admin,scim.userids,scim.zones
+ autoapprove: true
+```
+
+## Dockerize UAA
+The UAA is not shipped as a docker container unfortunately so you build the UAA as docker image by your own
+Dockerfile
+```
+FROM tomcat:8.5.6-jre8
+COPY uaa.war /usr/local/tomcat/webapps/uaa.war
+RUN echo "CLOUD_FOUNDRY_CONFIG_PATH=/etc/uaa" >> /usr/local/tomcat/conf/catalina.properties
+```
+The UAA configuration is based on the `uaa.yml` file which can be placed into the `/etc/uaa/uaa.yml` on the docker host system.
+
+After building the `uaa` docker image you can then start the `uaa-server` using docker
+```
+docker run -p 8080:8080 -d -e "SPRING_PROFILES_ACTIVE=hsqldb" -v "/etc/uaa:/etc/uaa" uaa
+```
diff --git a/extensions/hawkbit-extension-uaa/pom.xml b/extensions/hawkbit-extension-uaa/pom.xml
new file mode 100644
index 000000000..9cfa9932c
--- /dev/null
+++ b/extensions/hawkbit-extension-uaa/pom.xml
@@ -0,0 +1,62 @@
+
+
+ 4.0.0
+
+
+ org.eclipse.hawkbit
+ hawkbit-parent
+ 0.2.0-SNAPSHOT
+
+
+ hawkbit-extension-uaa
+
+
+
+ org.eclipse.hawkbit
+ hawkbit-security-core
+ ${project.version}
+
+
+ org.springframework.security
+ spring-security-jwt
+
+
+ org.springframework.boot
+ spring-boot
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.security.oauth
+ spring-security-oauth2
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
diff --git a/extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UaaClientProperties.java b/extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UaaClientProperties.java
new file mode 100644
index 000000000..ee3e281eb
--- /dev/null
+++ b/extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UaaClientProperties.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.security.uaa;
+
+import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
+
+/**
+ * Configuration properties for setting the UAA OAUTH2 client-properties and
+ * resource-properties.
+ *
+ *
+ uaa.client.clientId=app
+ uaa.client.clientSecret=appsecret
+ uaa.client.accessTokenUri=http://localhost:8080/uaa/oauth/token
+ uaa.client.userAuthorizationUri=http://localhost:8080/uaa/oauth/authorize
+ uaa.client.clientAuthenticationScheme=form
+ uaa.resource.userInfoUri=http://localhost:8080/uaa/userinfo
+ uaa.resource.jwt.keyValue=abc
+ *
+ */
+@ConfigurationProperties("uaa")
+public class UaaClientProperties {
+
+ @NestedConfigurationProperty
+ private final AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();
+
+ @NestedConfigurationProperty
+ private final ResourceServerProperties resource = new ResourceServerProperties();
+
+ public AuthorizationCodeResourceDetails getClient() {
+ return client;
+ }
+
+ public ResourceServerProperties getResource() {
+ return resource;
+ }
+}
diff --git a/extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UaaOAuthAutoConfiguration.java b/extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UaaOAuthAutoConfiguration.java
new file mode 100644
index 000000000..8ca4f8d7f
--- /dev/null
+++ b/extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UaaOAuthAutoConfiguration.java
@@ -0,0 +1,199 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.security.uaa;
+
+import java.io.IOException;
+
+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 org.eclipse.hawkbit.im.authentication.UserAuthenticationFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.embedded.FilterRegistrationBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.oauth2.client.OAuth2ClientContext;
+import org.springframework.security.oauth2.client.OAuth2RestTemplate;
+import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
+import org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter;
+import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
+import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor;
+import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager;
+import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter;
+import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
+import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
+import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
+import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
+import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+
+import com.google.common.base.Throwables;
+
+/**
+ * The Spring-Auto-Configuration implementation for integrating the UAA
+ * (https://github.com/cloudfoundry/uaa) as an identity management.
+ *
+ * To use the OAUTH2 redirect login flow the
+ * {@link OAuth2ClientAuthenticationProcessingFilter} is listing on the path
+ * {@code /uaalogin}. This will then re-direct to the configured UAA login form.
+ *
+ * The {@link UserAuthenticationFilter} implementation delegates to the
+ * {@link OAuth2AuthenticationProcessingFilter} which validates given bearer
+ * tokens in the {@code Authorization} header '
+ * {@code Authorization: bearer eyJhbGciOiJIUzI1NiIsImtpZCI6Imx}' to
+ * authenticate bearer tokens for the REST API. Only the signed token is
+ * verified, there is no extra round-trip back to the OAUTH2-Server (UAA).
+ *
+ *
+ * Example configuration:
+ *
+ *
+ uaa.client.clientId=app
+ uaa.client.clientSecret=appsecret
+ uaa.client.accessTokenUri=http://localhost:8080/uaa/oauth/token
+ uaa.client.userAuthorizationUri=http://localhost:8080/uaa/oauth/authorize
+ uaa.client.clientAuthenticationScheme=form
+ uaa.resource.userInfoUri=http://localhost:8080/uaa/userinfo
+ uaa.resource.jwt.keyValue=abc
+ *
+ *
+ */
+@EnableOAuth2Client
+@EnableConfigurationProperties(UaaClientProperties.class)
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class UaaOAuthAutoConfiguration extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ private OAuth2ClientContext oauth2ClientContext;
+
+ @Autowired
+ private UaaClientProperties uaaClientResources;
+
+ @Override
+ protected void configure(final HttpSecurity http) throws Exception {
+ http.regexMatcher("\\/uaalogin.*").addFilterBefore(ssoFilter("/uaalogin"), BasicAuthenticationFilter.class);
+ }
+
+ /**
+ * @return The {@link UserPrincipalInfoTokenServices} which extract
+ * authentication, principal and authorities information from an JWT
+ * access token.
+ */
+ @Bean
+ public UserPrincipalInfoTokenServices userPrincipalInfoTokenServices() {
+ return new UserPrincipalInfoTokenServices(uaaClientResources.getResource().getUserInfoUri(),
+ uaaClientResources.getClient().getClientId(), oauth2ClientContext);
+ }
+
+ /**
+ * @return The {@link JwtTokenStore} verifies access tokens and extract
+ * authentication and authorities from it.
+ */
+ @Bean
+ public JwtTokenStore jwtTokenStore() {
+ final DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
+ accessTokenConverter.setUserTokenConverter(userPrincipalInfoTokenServices());
+ final JwtAccessTokenConverter jwtTokenEnhancer = new JwtAccessTokenConverter();
+ jwtTokenEnhancer.setAccessTokenConverter(accessTokenConverter);
+ jwtTokenEnhancer.setSigningKey(uaaClientResources.getResource().getJwt().getKeyValue());
+ jwtTokenEnhancer.setVerifierKey(uaaClientResources.getResource().getJwt().getKeyValue());
+ try {
+ jwtTokenEnhancer.afterPropertiesSet();
+ } catch (final Exception e) {
+ throw Throwables.propagate(e);
+ }
+ return new JwtTokenStore(jwtTokenEnhancer);
+ }
+
+ /**
+ * @param filter
+ * the {@link OAuth2ClientContextFilter} to register.
+ * @return the Spring {@link FilterRegistrationBean} to register a filter in
+ * the spring filter-chain
+ */
+ @Bean
+ public FilterRegistrationBean oauth2ClientFilterRegistration(final OAuth2ClientContextFilter filter) {
+ final FilterRegistrationBean registration = new FilterRegistrationBean();
+ registration.setFilter(filter);
+ registration.setOrder(-100);
+ return registration;
+ }
+
+ /**
+ * @return The adapter for the hawkBit {@link UserAuthenticationFilter}
+ * which delegates to the oAuth-filter mechanism to authenticate JWT
+ * bearer tokens in the hawkBit security filter chain.
+ */
+ @Bean
+ public UserAuthenticationFilter userAuthenticationFilter() {
+ return new UserAuthenticationFilterAdapter(resourceOAuthFilter());
+ }
+
+ private Filter resourceOAuthFilter() {
+ final DefaultTokenServices remoteTokenService = new DefaultTokenServices();
+ remoteTokenService.setTokenStore(jwtTokenStore());
+ final OAuth2AuthenticationManager oauth2Manager = new OAuth2AuthenticationManager();
+ oauth2Manager.setTokenServices(remoteTokenService);
+ final OAuth2AuthenticationProcessingFilter oAuth2AuthenticationProcessingFilter = new OAuth2AuthenticationProcessingFilter();
+ oAuth2AuthenticationProcessingFilter.setTokenExtractor(new BearerTokenExtractor());
+ oAuth2AuthenticationProcessingFilter.setAuthenticationManager(oauth2Manager);
+ return oAuth2AuthenticationProcessingFilter;
+ }
+
+ private Filter ssoFilter(final String path) {
+ final OAuth2ClientAuthenticationProcessingFilter oAuth2ClientAuthenticationFilter = new OAuth2ClientAuthenticationProcessingFilter(
+ path);
+ final SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
+ oAuth2ClientAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
+ successHandler.setAlwaysUseDefaultTargetUrl(true);
+ successHandler.setDefaultTargetUrl("/UI");
+ final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(uaaClientResources.getClient(),
+ oauth2ClientContext);
+ oAuth2ClientAuthenticationFilter.setRestTemplate(oAuth2RestTemplate);
+ final UserPrincipalInfoTokenServices tokenServices = new UserPrincipalInfoTokenServices(
+ uaaClientResources.getResource().getUserInfoUri(), uaaClientResources.getClient().getClientId(),
+ oauth2ClientContext);
+ tokenServices.setRestTemplate(oAuth2RestTemplate);
+ tokenServices.setAuthoritiesExtractor(tokenServices);
+ oAuth2ClientAuthenticationFilter.setTokenServices(tokenServices);
+ return oAuth2ClientAuthenticationFilter;
+ }
+
+ private static final class UserAuthenticationFilterAdapter implements UserAuthenticationFilter {
+ private final Filter delegate;
+
+ private UserAuthenticationFilterAdapter(final Filter delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void init(final FilterConfig filterConfig) throws ServletException {
+ delegate.init(filterConfig);
+ }
+
+ @Override
+ public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
+ throws IOException, ServletException {
+ delegate.doFilter(request, response, chain);
+ }
+
+ @Override
+ public void destroy() {
+ delegate.destroy();
+ }
+ }
+}
diff --git a/extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UserPrincipalInfoTokenServices.java b/extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UserPrincipalInfoTokenServices.java
new file mode 100644
index 000000000..078bc0019
--- /dev/null
+++ b/extensions/hawkbit-extension-uaa/src/main/java/org/eclipse/hawkbit/security/uaa/UserPrincipalInfoTokenServices.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.eclipse.hawkbit.security.uaa;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.eclipse.hawkbit.im.authentication.UserPrincipal;
+import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
+import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.jwt.Jwt;
+import org.springframework.security.jwt.JwtHelper;
+import org.springframework.security.oauth2.client.OAuth2ClientContext;
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.common.util.JsonParser;
+import org.springframework.security.oauth2.common.util.JsonParserFactory;
+import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
+
+/**
+ * Implementation which maps principals from the OAUTH2 access tokens to the
+ * hawkBit {@link UserPrincipal}, so hawkBit is able to work with the user and
+ * tenant information. Additional extracting the {@code scope}-list from the JWT
+ * into {@link GrantedAuthority}.
+ *
+ * This implementation concentrates all necessary authentication, principal and
+ * authorities mapping within the OAUTH2 workflow, e.g. using redirect login
+ * form or using the REST-API with a access bearer token.
+ */
+public class UserPrincipalInfoTokenServices extends UserInfoTokenServices
+ implements UserAuthenticationConverter, AuthoritiesExtractor {
+
+ private final OAuth2ClientContext oauth2ClientContext;
+ private final JsonParser jsonParser = JsonParserFactory.create();
+
+ /**
+ * Constructor.
+ *
+ * @param userInfoEndpointUrl
+ * the OAUTH2 info endpoint to retrieve user information
+ * @param clientId
+ * the OAUTH2 client-id to execute the user info endpoint
+ * @param oauth2ClientContext
+ * the spring {@link OAuth2ClientContext}
+ */
+ public UserPrincipalInfoTokenServices(final String userInfoEndpointUrl, final String clientId,
+ final OAuth2ClientContext oauth2ClientContext) {
+ super(userInfoEndpointUrl, clientId);
+ this.oauth2ClientContext = oauth2ClientContext;
+ }
+
+ @Override
+ protected Object getPrincipal(final Map map) {
+ final String username = String.valueOf(map.get("user_name"));
+ final String firstname = String.valueOf(map.get("given_name"));
+ final String lastname = String.valueOf(map.get("family_name"));
+ final String email = String.valueOf(map.get("email"));
+ final String zoneId = String.valueOf(getAccessTokenMap().get("zid"));
+ return new UserPrincipal(username, firstname, lastname, username, email, zoneId);
+ }
+
+ @Override
+ public Map convertUserAuthentication(final Authentication userAuthentication) {
+ throw new UnsupportedOperationException("converting an authentication object to a map is not implemented");
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Authentication extractAuthentication(final Map map) {
+ final Object principal = getPrincipal((Map) map);
+ return new UsernamePasswordAuthenticationToken(principal, "N/A", extractAuthorities((Map) map));
+ }
+
+ @Override
+ public List extractAuthorities(final Map map) {
+ final Map accessTokenMap;
+ if (map.containsKey("scope")) {
+ accessTokenMap = map;
+ } else {
+ accessTokenMap = getAccessTokenMap();
+ }
+ @SuppressWarnings("unchecked")
+ final List scopes = (List) accessTokenMap.get("scope");
+ return scopes.stream().map(scope -> new SimpleGrantedAuthority(scope)).collect(Collectors.toList());
+
+ }
+
+ private Map getAccessTokenMap() {
+ final Map accessTokenMap;
+ final OAuth2AccessToken accessToken = oauth2ClientContext.getAccessToken();
+ final Jwt decode = JwtHelper.decode(accessToken.getValue());
+ accessTokenMap = jsonParser.parseMap(decode.getClaims());
+ return accessTokenMap;
+ }
+}
diff --git a/extensions/hawkbit-extension-uaa/src/main/resources/META-INF/spring.factories b/extensions/hawkbit-extension-uaa/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..90e1d3b47
--- /dev/null
+++ b/extensions/hawkbit-extension-uaa/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,3 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.eclipse.hawkbit.security.uaa.UaaOAuthAutoConfiguration
diff --git a/extensions/pom.xml b/extensions/pom.xml
new file mode 100644
index 000000000..d98ad0c48
--- /dev/null
+++ b/extensions/pom.xml
@@ -0,0 +1,27 @@
+
+
+ 4.0.0
+
+ org.eclipse.hawkbit
+ hawkbit-parent
+ 0.2.0-SNAPSHOT
+
+ hawkbit-extensions-parent
+ pom
+ hawkBit :: Extensions
+ Parent pom for hawkBit extensions
+
+
+ hawkbit-extension-uaa
+
+
+
diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/InMemoryUserManagementConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/InMemoryUserManagementConfiguration.java
index 15a9c5bcc..b809a4181 100644
--- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/InMemoryUserManagementConfiguration.java
+++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/InMemoryUserManagementConfiguration.java
@@ -13,6 +13,7 @@ import java.util.ArrayList;
import org.eclipse.hawkbit.im.authentication.MultitenancyIndicator;
import org.eclipse.hawkbit.im.authentication.PermissionUtils;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
+import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
@@ -52,7 +53,7 @@ public class InMemoryUserManagementConfiguration extends GlobalAuthenticationCon
@Bean
@ConditionalOnMissingBean
public UserDetailsService userDetailsService() {
- final InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(new ArrayList<>());
+ final InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserPrincipalDetailsManager();
inMemoryUserDetailsManager.setAuthenticationManager(null);
inMemoryUserDetailsManager.createUser(new User(securityProperties.getUser().getName(),
securityProperties.getUser().getPassword(), PermissionUtils.createAllAuthorityList()));
@@ -79,4 +80,19 @@ public class InMemoryUserManagementConfiguration extends GlobalAuthenticationCon
return result;
}
}
+
+ private static final class InMemoryUserPrincipalDetailsManager extends InMemoryUserDetailsManager {
+
+ private InMemoryUserPrincipalDetailsManager() {
+ super(new ArrayList<>());
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(final String username) {
+ final UserDetails loadUserByUsername = super.loadUserByUsername(username);
+ return new UserPrincipal(loadUserByUsername.getUsername(), loadUserByUsername.getPassword(),
+ loadUserByUsername.getUsername(), loadUserByUsername.getUsername(),
+ loadUserByUsername.getUsername(), null, "DEFAULT", loadUserByUsername.getAuthorities());
+ }
+ }
}
diff --git a/hawkbit-ddi-dl-api/src/main/java/org/eclipse/hawkbit/ddi/dl/rest/api/DdiDlArtifactStoreControllerRestApi.java b/hawkbit-ddi-dl-api/src/main/java/org/eclipse/hawkbit/ddi/dl/rest/api/DdiDlArtifactStoreControllerRestApi.java
index 0d60eb7a2..4f6ddeab1 100644
--- a/hawkbit-ddi-dl-api/src/main/java/org/eclipse/hawkbit/ddi/dl/rest/api/DdiDlArtifactStoreControllerRestApi.java
+++ b/hawkbit-ddi-dl-api/src/main/java/org/eclipse/hawkbit/ddi/dl/rest/api/DdiDlArtifactStoreControllerRestApi.java
@@ -32,8 +32,8 @@ public interface DdiDlArtifactStoreControllerRestApi {
* name of the client
* @param fileName
* to search for
- * @param targetid
- * of authenticated target
+ * @param principal
+ * the authentication principal stored in the security context
*
* @return response of the servlet which in case of success is status code
* {@link HttpStatus#OK} or in case of partial download
@@ -43,7 +43,7 @@ public interface DdiDlArtifactStoreControllerRestApi {
+ "/{fileName}")
@ResponseBody
ResponseEntity downloadArtifactByFilename(@PathVariable("tenant") final String tenant,
- @PathVariable("fileName") final String fileName, @AuthenticationPrincipal final String targetid);
+ @PathVariable("fileName") final String fileName, @AuthenticationPrincipal final Object principal);
/**
* Handles GET MD5 checksum file download request.
diff --git a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java
index 39eeca293..66b66391a 100644
--- a/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java
+++ b/hawkbit-ddi-resource/src/main/java/org/eclipse/hawkbit/ddi/rest/resource/DdiArtifactStoreController.java
@@ -16,6 +16,7 @@ import javax.servlet.http.HttpServletRequest;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.ddi.dl.rest.api.DdiDlArtifactStoreControllerRestApi;
+import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
import org.eclipse.hawkbit.repository.EntityFactory;
@@ -70,7 +71,7 @@ public class DdiArtifactStoreController implements DdiDlArtifactStoreControllerR
@Override
public ResponseEntity downloadArtifactByFilename(@PathVariable("tenant") final String tenant,
- @PathVariable("fileName") final String fileName, @AuthenticationPrincipal final String targetid) {
+ @PathVariable("fileName") final String fileName, @AuthenticationPrincipal final Object principal) {
final List foundArtifacts = artifactManagement.findArtifactByFilename(fileName);
if (foundArtifacts.isEmpty()) {
@@ -92,9 +93,11 @@ public class DdiArtifactStoreController implements DdiDlArtifactStoreControllerR
// we set a download status only if we are aware of the
// targetid, i.e. authenticated and not anonymous
- if (targetid != null && !"anonymous".equals(targetid)) {
+ if (principal instanceof UserPrincipal && ((UserPrincipal) principal).getUsername() != null
+ && !"anonymous".equals(((UserPrincipal) principal).getUsername())) {
final ActionStatus actionStatus = checkAndReportDownloadByTarget(
- requestResponseContextHolder.getHttpServletRequest(), targetid, artifact);
+ requestResponseContextHolder.getHttpServletRequest(), ((UserPrincipal) principal).getUsername(),
+ artifact);
result = RestResourceConversionHelper.writeFileResponse(artifact,
requestResponseContextHolder.getHttpServletResponse(),
requestResponseContextHolder.getHttpServletRequest(), file, controllerManagement,
diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java
index ce5c39843..8324d1d56 100644
--- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java
+++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java
@@ -17,6 +17,7 @@ import java.util.concurrent.Callable;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
+import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.eclipse.hawkbit.repository.jpa.model.helper.SystemManagementHolder;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -78,7 +79,9 @@ public class WithSpringAuthorityRule implements TestRule {
authorities = annotation.authorities();
}
final TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(
- annotation.principal(), annotation.credentials(), authorities);
+ new UserPrincipal(annotation.principal(), annotation.principal(), annotation.principal(),
+ annotation.principal(), null, annotation.tenantId()),
+ annotation.credentials(), authorities);
testingAuthenticationToken
.setDetails(new TenantAwareAuthenticationDetails(annotation.tenantId(), false));
return testingAuthenticationToken;
diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/UserPrincipal.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/UserPrincipal.java
index 454be1f57..2171b445a 100644
--- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/UserPrincipal.java
+++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/UserPrincipal.java
@@ -8,30 +8,22 @@
*/
package org.eclipse.hawkbit.im.authentication;
-import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
-import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.User;
/**
* A software provisioning user principal definition stored in the
* {@link SecurityContext} which contains the user specific attributes.
*
- *
- *
- *
*/
-public class UserPrincipal implements UserDetails, Serializable {
+public class UserPrincipal extends User {
- /**
- *
- */
private static final long serialVersionUID = 1L;
- private final String username;
private final String firstname;
private final String lastname;
private final String loginname;
@@ -53,8 +45,35 @@ public class UserPrincipal implements UserDetails, Serializable {
* address of the user
*/
public UserPrincipal(final String username, final String firstname, final String lastname, final String loginname,
- final String tenant, final String email) {
- this.username = username;
+ final String email, final String tenant) {
+ this(username, "***", lastname, firstname, loginname, email, tenant, Collections.emptyList());
+ }
+
+ /**
+ * @param username
+ * the user name of the user
+ * @param password
+ * the password of the user
+ * @param firstname
+ * the first name of the user
+ * @param lastname
+ * the last name of the user
+ * @param loginname
+ * the login name of user
+ * @param tenant
+ * the tenant of the user
+ * @param email
+ * address of the user
+ * @param authorities
+ * the authorities which the user has
+ */
+ // too many parameters, builder pattern wouldn't work easy due the super
+ // constructor.
+ @SuppressWarnings("squid:S00107")
+ public UserPrincipal(final String username, final String password, final String firstname, final String lastname,
+ final String loginname, final String email, final String tenant,
+ final Collection extends GrantedAuthority> authorities) {
+ super(username, password, authorities);
this.firstname = firstname;
this.lastname = lastname;
this.loginname = loginname;
@@ -62,38 +81,18 @@ public class UserPrincipal implements UserDetails, Serializable {
this.email = email;
}
- /**
- * @return the username
- */
- @Override
- public String getUsername() {
- return username;
- }
-
- /**
- * @return the firstname
- */
public String getFirstname() {
return firstname;
}
- /**
- * @return the lastname
- */
public String getLastname() {
return lastname;
}
- /**
- * @return the loginname
- */
public String getLoginname() {
return loginname;
}
- /**
- * @return the tenant
- */
public String getTenant() {
return tenant;
}
@@ -102,16 +101,6 @@ public class UserPrincipal implements UserDetails, Serializable {
return email;
}
- @Override
- public Collection getAuthorities() {
- return Collections.emptyList();
- }
-
- @Override
- public String getPassword() {
- return null;
- }
-
@Override
public boolean isAccountNonExpired() {
return true;
@@ -119,7 +108,7 @@ public class UserPrincipal implements UserDetails, Serializable {
@Override
public boolean isAccountNonLocked() {
- return false;
+ return true;
}
@Override
diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java
index 6e4ac7f8f..e79aafcee 100644
--- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java
+++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java
@@ -11,14 +11,19 @@ package org.eclipse.hawkbit.security;
import java.util.Collection;
import java.util.Collections;
+import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
+import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
+import com.google.common.collect.Lists;
+
/**
* A {@link TenantAware} implemenation which retrieves the ID of the tenant from
* the {@link SecurityContext#getAuthentication()}
@@ -32,9 +37,9 @@ public class SecurityContextTenantAware implements TenantAware {
public String getCurrentTenant() {
final SecurityContext context = SecurityContextHolder.getContext();
if (context.getAuthentication() != null) {
- final Object authDetails = context.getAuthentication().getDetails();
- if (authDetails instanceof TenantAwareAuthenticationDetails) {
- return ((TenantAwareAuthenticationDetails) authDetails).getTenant();
+ final Object principal = context.getAuthentication().getPrincipal();
+ if (principal instanceof UserPrincipal) {
+ return ((UserPrincipal) principal).getTenant();
}
}
return null;
@@ -66,12 +71,17 @@ public class SecurityContextTenantAware implements TenantAware {
private static final class AuthenticationDelegate implements Authentication {
private static final long serialVersionUID = 1L;
+ private static final String SYSTEM_USER = "system";
+ private static final Collection extends GrantedAuthority> SYSTEM_AUTHORITIES = Lists
+ .newArrayList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE));
private final Authentication delegate;
- private final TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails;
+
+ private final UserPrincipal systemPrincipal;
private AuthenticationDelegate(final Authentication delegate, final String tenant) {
this.delegate = delegate;
- tenantAwareAuthenticationDetails = new TenantAwareAuthenticationDetails(tenant, false);
+ this.systemPrincipal = new UserPrincipal(SYSTEM_USER, SYSTEM_USER, SYSTEM_USER, SYSTEM_USER, SYSTEM_USER,
+ null, tenant, SYSTEM_AUTHORITIES);
}
@Override
@@ -111,12 +121,12 @@ public class SecurityContextTenantAware implements TenantAware {
@Override
public Object getDetails() {
- return tenantAwareAuthenticationDetails;
+ return (delegate != null) ? delegate.getDetails() : null;
}
@Override
public Object getPrincipal() {
- return (delegate != null) ? delegate.getPrincipal() : null;
+ return systemPrincipal;
}
@Override
diff --git a/pom.xml b/pom.xml
index ddb3ead7a..f86c58177 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,7 @@
hawkbit-cache-redis
hawkbit-test-report
examples
+ extensions