OpenID Connect support (#865)
* Added OpenID Connect support Utilized Spring Security's OAuth2 respectively OIDC support as another possibility to manage users and their permissions. Signed-off-by: Brandon Schmitt <Brandon.Schmitt@kiwigrid.com> * Document OpenID Connect Support Signed-off-by: Brandon Schmitt <Brandon.Schmitt@kiwigrid.com> * Updated license in OidcUserManagementAutoConfiguration.java Signed-off-by: Brandon Schmitt <Brandon.Schmitt@kiwigrid.com> * Revert updated license notice and add Kiwigrid license file This reverts commit 23d36245 Signed-off-by: Brandon Schmitt <Brandon.Schmitt@kiwigrid.com> * Resolve SonarQube issues - Explicitly import the needed specific classes - Document public methods - Add `static` to the constant `JwtAuthoritiesOidcUserService.INVALID_REQUEST` - Remove superfluous runtime exception `OAuth2AuthenticationException` Signed-off-by: Brandon Schmitt <Brandon.Schmitt@kiwigrid.com> * Add OidcUser support in SpringSecurityAuditorAware Signed-off-by: Brandon Schmitt <Brandon.Schmitt@kiwigrid.com> * Secure Management API using OpenID Connect, too. Signed-off-by: Brandon Schmitt <Brandon.Schmitt@kiwigrid.com>
This commit is contained in:
committed by
Dominic Schabel
parent
38017ba7bc
commit
1bcced9838
@@ -70,4 +70,5 @@ Authentication is provided by _RabbitMQ_ [vhost and user credentials](https://ww
|
|||||||
|
|
||||||
## Management UI
|
## Management UI
|
||||||
- Login Dialog
|
- Login Dialog
|
||||||
|
- OpenID Connect
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ weight: 52
|
|||||||
Authorization is handled separately for _Direct Device Integration (DDI) API_ and _Device Management Federation (DMF) API_ (where successful authentication includes full authorization) and _Management API_ and _UI_ which is based on Spring security [authorities](https://github.com/eclipse/hawkbit/blob/master/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java).
|
Authorization is handled separately for _Direct Device Integration (DDI) API_ and _Device Management Federation (DMF) API_ (where successful authentication includes full authorization) and _Management API_ and _UI_ which is based on Spring security [authorities](https://github.com/eclipse/hawkbit/blob/master/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/im/authentication/SpPermission.java).
|
||||||
<!--more-->
|
<!--more-->
|
||||||
|
|
||||||
However, keep in mind that hawkBit does not offer an off the shelf authentication provider to leverage these permissions and the underlying multi user/tenant capabilities of hawkBit. Check out [Spring security documentation](http://projects.spring.io/spring-security/) for further information. In hawkBit [SecurityAutoConfiguration](https://github.com/eclipse/hawkbit/blob/master/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java) is a good starting point for integration.
|
However, keep in mind that hawkBit does not offer an off the shelf authentication provider to leverage these permissions and the underlying multi user/tenant capabilities of hawkBit but it supports authentication providers offering an OpenID Connect interface. Check out [Spring security documentation](http://projects.spring.io/spring-security/) for further information. In hawkBit [SecurityAutoConfiguration](https://github.com/eclipse/hawkbit/blob/master/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java) is a good starting point for integration.
|
||||||
|
|
||||||
The default implementation is single user/tenant with basic auth and the logged in user is provided with all permissions. Additionally, the application properties may be configured for multiple static users; see [Multiple Users](#multiple-users) for details.
|
The default implementation is single user/tenant with basic auth and the logged in user is provided with all permissions. Additionally, the application properties may be configured for multiple static users; see [Multiple Users](#multiple-users) for details.
|
||||||
|
|
||||||
@@ -41,6 +41,18 @@ An example configuration is given below.
|
|||||||
|
|
||||||
A permissions value of `ALL` will provide that user with all possible permissions. Passwords need to be specified with the used password encoder in brackets. In this example, `noop` is used as the plaintext encoder. For production use, it is recommended to use a hash function designed for passwords such as *bcrypt*. See this [blog post](https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-format) for more information on password encoders in Spring Security.
|
A permissions value of `ALL` will provide that user with all possible permissions. Passwords need to be specified with the used password encoder in brackets. In this example, `noop` is used as the plaintext encoder. For production use, it is recommended to use a hash function designed for passwords such as *bcrypt*. See this [blog post](https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-format) for more information on password encoders in Spring Security.
|
||||||
|
|
||||||
|
### OpenID Connect
|
||||||
|
hawkbit supports authentication providers which use the OpenID Connect standard, an authentication layer built on top of the OAuth 2.0 protocol.
|
||||||
|
An example configuration is given below.
|
||||||
|
|
||||||
|
spring.security.oauth2.client.registration.oidc.client-id=clientID
|
||||||
|
spring.security.oauth2.client.registration.oidc.client-secret=oidc-client-secret
|
||||||
|
spring.security.oauth2.client.provider.oidc.issuer-uri=https://oidc-provider/issuer-uri
|
||||||
|
spring.security.oauth2.client.provider.oidc.authorization-uri=https://oidc-provider/authorization-uri
|
||||||
|
spring.security.oauth2.client.provider.oidc.token-uri=https://oidc-provider/token-uri
|
||||||
|
spring.security.oauth2.client.provider.oidc.user-info-uri=https://oidc-provider/user-info-uri
|
||||||
|
spring.security.oauth2.client.provider.oidc.jwk-set-uri=https://oidc-provider/jwk-set-uri
|
||||||
|
|
||||||
### Delivered Permissions
|
### Delivered Permissions
|
||||||
- READ_/UPDATE_/CREATE_/DELETE_TARGETS for:
|
- READ_/UPDATE_/CREATE_/DELETE_TARGETS for:
|
||||||
- Target entities including metadata (that includes also the installed and assigned distribution sets)
|
- Target entities including metadata (that includes also the installed and assigned distribution sets)
|
||||||
|
|||||||
@@ -0,0 +1,326 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2019 Kiwigrid 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.autoconfigure.security;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
|
||||||
|
import org.eclipse.hawkbit.im.authentication.UserAuthenticationFilter;
|
||||||
|
import org.eclipse.hawkbit.repository.SystemManagement;
|
||||||
|
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.client.ClientsConfiguredCondition;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Conditional;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.*;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
|
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||||
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtException;
|
||||||
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||||
|
import org.springframework.security.web.authentication.*;
|
||||||
|
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||||
|
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
|
import javax.servlet.*;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-configuration for OpenID Connect user management.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@Conditional(value = ClientsConfiguredCondition.class)
|
||||||
|
public class OidcUserManagementAutoConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the oauth2 user details service to load a user from oidc user
|
||||||
|
* manager
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserDetailsService(JwtAuthoritiesExtractor extractor) {
|
||||||
|
return new JwtAuthoritiesOidcUserService(extractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the OpenID Connect authentication success handler
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public AuthenticationSuccessHandler oidcAuthenticationSuccessHandler() {
|
||||||
|
return new OidcAuthenticationSuccessHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the OpenID Connect logout handler
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public LogoutHandler oidcLogoutHandler() {
|
||||||
|
return new OidcLogoutHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a jwt authorities extractor which interprets the roles of a user as their authorities.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public JwtAuthoritiesExtractor jwtAuthoritiesExtractor() {
|
||||||
|
final SimpleAuthorityMapper authorityMapper = new SimpleAuthorityMapper();
|
||||||
|
authorityMapper.setPrefix("");
|
||||||
|
authorityMapper.setConvertToUpperCase(true);
|
||||||
|
|
||||||
|
return new JwtAuthoritiesExtractor(authorityMapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return an authentication filter for using OAuth2 Bearer Tokens.
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public OidcBearerTokenAuthenticationFilter oidcBearerTokenAuthenticationFilter() {
|
||||||
|
return new OidcBearerTokenAuthenticationFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended {@link OidcUserService} supporting JWT containing authorities
|
||||||
|
*/
|
||||||
|
class JwtAuthoritiesOidcUserService extends OidcUserService {
|
||||||
|
|
||||||
|
private final JwtAuthoritiesExtractor authoritiesExtractor;
|
||||||
|
|
||||||
|
JwtAuthoritiesOidcUserService(JwtAuthoritiesExtractor authoritiesExtractor) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.authoritiesExtractor = authoritiesExtractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OidcUser loadUser(OidcUserRequest userRequest) {
|
||||||
|
OidcUser user = super.loadUser(userRequest);
|
||||||
|
ClientRegistration clientRegistration = userRequest.getClientRegistration();
|
||||||
|
|
||||||
|
Set<GrantedAuthority> authorities = new LinkedHashSet<>(authoritiesExtractor.extract(clientRegistration,
|
||||||
|
userRequest.getAccessToken().getTokenValue()));
|
||||||
|
if (authorities.isEmpty()) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
String userNameAttributeName = clientRegistration.getProviderDetails().getUserInfoEndpoint()
|
||||||
|
.getUserNameAttributeName();
|
||||||
|
OidcUser oidcUser;
|
||||||
|
if (StringUtils.hasText(userNameAttributeName)) {
|
||||||
|
oidcUser = new DefaultOidcUser(authorities, userRequest.getIdToken(), user.getUserInfo(),
|
||||||
|
userNameAttributeName);
|
||||||
|
} else {
|
||||||
|
oidcUser = new DefaultOidcUser(authorities, userRequest.getIdToken(), user.getUserInfo());
|
||||||
|
}
|
||||||
|
return oidcUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenID Connect Authentication Success Handler which load tenant data
|
||||||
|
*/
|
||||||
|
class OidcAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemManagement systemManagement;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemSecurityContext systemSecurityContext;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Authentication authentication) throws ServletException, IOException {
|
||||||
|
if (authentication instanceof AbstractAuthenticationToken) {
|
||||||
|
final String defaultTenant = "DEFAULT";
|
||||||
|
|
||||||
|
AbstractAuthenticationToken token = (AbstractAuthenticationToken) authentication;
|
||||||
|
token.setDetails(new TenantAwareAuthenticationDetails(defaultTenant, false));
|
||||||
|
|
||||||
|
systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, defaultTenant);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onAuthenticationSuccess(request, response, authentication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LogoutHandler to invalidate OpenID Connect tokens
|
||||||
|
*/
|
||||||
|
class OidcLogoutHandler extends SecurityContextLogoutHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||||
|
super.logout(request, response, authentication);
|
||||||
|
|
||||||
|
Object principal = authentication.getPrincipal();
|
||||||
|
if (principal instanceof OidcUser) {
|
||||||
|
OidcUser user = (OidcUser) authentication.getPrincipal();
|
||||||
|
String endSessionEndpoint = user.getIssuer() + "/protocol/openid-connect/logout";
|
||||||
|
|
||||||
|
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(endSessionEndpoint)
|
||||||
|
.queryParam("id_token_hint", user.getIdToken().getTokenValue());
|
||||||
|
|
||||||
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
restTemplate.getForEntity(builder.toUriString(), String.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to extract authorities out of the jwt. It interprets the user's role as their authorities.
|
||||||
|
*/
|
||||||
|
class JwtAuthoritiesExtractor {
|
||||||
|
|
||||||
|
private final GrantedAuthoritiesMapper authoritiesMapper;
|
||||||
|
|
||||||
|
private static final OAuth2Error INVALID_REQUEST = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||||
|
|
||||||
|
JwtAuthoritiesExtractor(GrantedAuthoritiesMapper authoritiesMapper) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.authoritiesMapper = authoritiesMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<? extends GrantedAuthority> extract(ClientRegistration clientRegistration,
|
||||||
|
String tokenValue) {
|
||||||
|
Jwt token;
|
||||||
|
try {
|
||||||
|
// Token is already verified by spring security
|
||||||
|
JwtDecoder jwtDecoder = new NimbusJwtDecoderJwkSupport(
|
||||||
|
clientRegistration.getProviderDetails().getJwkSetUri());
|
||||||
|
token = jwtDecoder.decode(tokenValue);
|
||||||
|
} catch (JwtException e) {
|
||||||
|
throw new OAuth2AuthenticationException(INVALID_REQUEST, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extract(clientRegistration.getClientId(), token.getClaims());
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<? extends GrantedAuthority> extract(String clientId, Map<String, Object> claims) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> resourceMap = (Map<String, Object>) claims.get("resource_access");
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Map<String, Object>> clientResource = (Map<String, Map<String, Object>>) resourceMap.get(clientId);
|
||||||
|
if (CollectionUtils.isEmpty(clientResource)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<String> roles = (List<String>) clientResource.get("roles");
|
||||||
|
if (CollectionUtils.isEmpty(roles)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
|
||||||
|
.createAuthorityList(roles.toArray(new String[0]));
|
||||||
|
if (authoritiesMapper != null) {
|
||||||
|
authorities = authoritiesMapper.mapAuthorities(authorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OidcBearerTokenAuthenticationFilter implements UserAuthenticationFilter, Filter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtAuthoritiesExtractor authoritiesExtractor;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemManagement systemManagement;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SystemSecurityContext systemSecurityContext;
|
||||||
|
|
||||||
|
private ClientRegistration clientRegistration;
|
||||||
|
|
||||||
|
void setClientRegistration(ClientRegistration clientRegistration) {
|
||||||
|
this.clientRegistration = clientRegistration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(final FilterConfig filterConfig) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (authentication instanceof JwtAuthenticationToken) {
|
||||||
|
final String defaultTenant = "DEFAULT";
|
||||||
|
|
||||||
|
JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) authentication;
|
||||||
|
Jwt jwt = jwtAuthenticationToken.getToken();
|
||||||
|
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(),
|
||||||
|
jwt.getClaims());
|
||||||
|
OidcUserInfo userInfo = new OidcUserInfo(jwt.getClaims());
|
||||||
|
|
||||||
|
Collection<? extends GrantedAuthority> authorities = authoritiesExtractor.extract(
|
||||||
|
clientRegistration.getClientId(), jwt.getClaims());
|
||||||
|
|
||||||
|
if (authorities.isEmpty()) {
|
||||||
|
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultOidcUser user = new DefaultOidcUser(authorities, idToken, userInfo);
|
||||||
|
|
||||||
|
OAuth2AuthenticationToken oAuth2AuthenticationToken = new OAuth2AuthenticationToken(
|
||||||
|
user, authorities, clientRegistration.getRegistrationId());
|
||||||
|
|
||||||
|
oAuth2AuthenticationToken.setDetails(new TenantAwareAuthenticationDetails(defaultTenant, false));
|
||||||
|
|
||||||
|
systemSecurityContext.runAsSystemAsTenant(systemManagement::getTenantMetadata, defaultTenant);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(oAuth2AuthenticationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,15 +75,24 @@ import org.springframework.security.config.http.SessionCreationPolicy;
|
|||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
|
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
|
||||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||||
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
|
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||||
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
||||||
import org.springframework.security.web.session.SessionManagementFilter;
|
import org.springframework.security.web.session.SessionManagementFilter;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.vaadin.spring.security.VaadinSecurityContext;
|
import org.vaadin.spring.security.VaadinSecurityContext;
|
||||||
import org.vaadin.spring.security.annotation.EnableVaadinSecurity;
|
import org.vaadin.spring.security.annotation.EnableVaadinSecurity;
|
||||||
@@ -457,6 +466,12 @@ public class SecurityManagedConfiguration {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserAuthenticationFilter userAuthenticationFilter;
|
private UserAuthenticationFilter userAuthenticationFilter;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private OidcBearerTokenAuthenticationFilter oidcBearerTokenAuthenticationFilter;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private InMemoryClientRegistrationRepository clientRegistrationRepository;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SystemManagement systemManagement;
|
private SystemManagement systemManagement;
|
||||||
|
|
||||||
@@ -492,38 +507,61 @@ public class SecurityManagedConfiguration {
|
|||||||
@Override
|
@Override
|
||||||
protected void configure(final HttpSecurity http) throws Exception {
|
protected void configure(final HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
final BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
|
|
||||||
basicAuthEntryPoint.setRealmName(securityProperties.getBasicRealm());
|
|
||||||
|
|
||||||
HttpSecurity httpSec = http.regexMatcher("\\/rest.*|\\/system/admin.*").csrf().disable();
|
HttpSecurity httpSec = http.regexMatcher("\\/rest.*|\\/system/admin.*").csrf().disable();
|
||||||
if (securityProperties.isRequireSsl()) {
|
if (securityProperties.isRequireSsl()) {
|
||||||
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
|
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
|
||||||
}
|
}
|
||||||
|
|
||||||
httpSec.addFilterBefore(new Filter() {
|
httpSec
|
||||||
@Override
|
|
||||||
public void init(final FilterConfig filterConfig) throws ServletException {
|
|
||||||
userAuthenticationFilter.init(filterConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doFilter(final ServletRequest request, final ServletResponse response,
|
|
||||||
final FilterChain chain) throws IOException, ServletException {
|
|
||||||
userAuthenticationFilter.doFilter(request, response, chain);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
userAuthenticationFilter.destroy();
|
|
||||||
}
|
|
||||||
}, RequestHeaderAuthenticationFilter.class)
|
|
||||||
.addFilterAfter(new AuthenticationSuccessTenantMetadataCreationFilter(systemManagement,
|
|
||||||
systemSecurityContext), SessionManagementFilter.class)
|
|
||||||
.authorizeRequests().anyRequest().authenticated()
|
.authorizeRequests().anyRequest().authenticated()
|
||||||
.antMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**")
|
.antMatchers(MgmtRestConstants.BASE_SYSTEM_MAPPING + "/admin/**")
|
||||||
.hasAnyAuthority(SpPermission.SYSTEM_ADMIN);
|
.hasAnyAuthority(SpPermission.SYSTEM_ADMIN);
|
||||||
|
|
||||||
httpSec.httpBasic().and().exceptionHandling().authenticationEntryPoint(basicAuthEntryPoint);
|
if (oidcBearerTokenAuthenticationFilter != null) {
|
||||||
|
|
||||||
|
// Only get the first client registration. Testing against every client could increase the
|
||||||
|
// attack vector
|
||||||
|
ClientRegistration clientRegistration = null;
|
||||||
|
for (ClientRegistration cr : clientRegistrationRepository) {
|
||||||
|
clientRegistration = cr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.notNull(clientRegistration, "There must be a valid client registration");
|
||||||
|
httpSec.oauth2ResourceServer()
|
||||||
|
.jwt().jwkSetUri(clientRegistration.getProviderDetails().getJwkSetUri());
|
||||||
|
|
||||||
|
oidcBearerTokenAuthenticationFilter.setClientRegistration(clientRegistration);
|
||||||
|
|
||||||
|
httpSec.addFilterAfter(oidcBearerTokenAuthenticationFilter, BearerTokenAuthenticationFilter.class);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final BasicAuthenticationEntryPoint basicAuthEntryPoint = new BasicAuthenticationEntryPoint();
|
||||||
|
basicAuthEntryPoint.setRealmName(securityProperties.getBasicRealm());
|
||||||
|
|
||||||
|
httpSec.addFilterBefore(new Filter() {
|
||||||
|
@Override
|
||||||
|
public void init(final FilterConfig filterConfig) throws ServletException {
|
||||||
|
userAuthenticationFilter.init(filterConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(final ServletRequest request, final ServletResponse response,
|
||||||
|
final FilterChain chain) throws IOException, ServletException {
|
||||||
|
userAuthenticationFilter.doFilter(request, response, chain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
userAuthenticationFilter.destroy();
|
||||||
|
}
|
||||||
|
}, RequestHeaderAuthenticationFilter.class);
|
||||||
|
httpSec.httpBasic().and().exceptionHandling().authenticationEntryPoint(basicAuthEntryPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpSec.addFilterAfter(new AuthenticationSuccessTenantMetadataCreationFilter(systemManagement,
|
||||||
|
systemSecurityContext), SessionManagementFilter.class);
|
||||||
|
|
||||||
httpSec.anonymous().disable();
|
httpSec.anonymous().disable();
|
||||||
httpSec.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
httpSec.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||||
}
|
}
|
||||||
@@ -546,6 +584,15 @@ public class SecurityManagedConfiguration {
|
|||||||
|
|
||||||
private final VaadinUrlAuthenticationSuccessHandler handler;
|
private final VaadinUrlAuthenticationSuccessHandler handler;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private AuthenticationSuccessHandler oidcAuthenticationSuccessHandler;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private LogoutHandler oidcLogoutHandler;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
|
||||||
|
|
||||||
public UISecurityConfigurationAdapter(final VaadinRedirectStrategy redirectStrategy) {
|
public UISecurityConfigurationAdapter(final VaadinRedirectStrategy redirectStrategy) {
|
||||||
handler = new TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler();
|
handler = new TenantMetadataSavedRequestAwareVaadinAuthenticationSuccessHandler();
|
||||||
handler.setRedirectStrategy(redirectStrategy);
|
handler.setRedirectStrategy(redirectStrategy);
|
||||||
@@ -614,13 +661,21 @@ public class SecurityManagedConfiguration {
|
|||||||
@Override
|
@Override
|
||||||
protected void configure(final HttpSecurity http) throws Exception {
|
protected void configure(final HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
boolean enableOidc = oidcUserService != null && oidcAuthenticationSuccessHandler != null
|
||||||
|
&& oidcLogoutHandler != null;
|
||||||
|
|
||||||
// workaround regex: we need to exclude the URL /UI/HEARTBEAT here
|
// workaround regex: we need to exclude the URL /UI/HEARTBEAT here
|
||||||
// because we bound the vaadin application to /UI and not to root,
|
// because we bound the vaadin application to /UI and not to root,
|
||||||
// described in vaadin-forum:
|
// described in vaadin-forum:
|
||||||
// https://vaadin.com/forum#!/thread/3200565.
|
// https://vaadin.com/forum#!/thread/3200565.
|
||||||
HttpSecurity httpSec = http.regexMatcher("(?!.*HEARTBEAT)^.*\\/UI.*$")
|
HttpSecurity httpSec = null;
|
||||||
// disable as CSRF is handled by Vaadin
|
if (enableOidc) {
|
||||||
.csrf().disable();
|
httpSec = http.regexMatcher("(?!.*HEARTBEAT)^.*\\/(UI|oauth2).*$");
|
||||||
|
} else {
|
||||||
|
httpSec = http.regexMatcher("(?!.*HEARTBEAT)^.*\\/UI.*$");
|
||||||
|
}
|
||||||
|
// disable as CSRF is handled by Vaadin
|
||||||
|
httpSec.csrf().disable();
|
||||||
|
|
||||||
if (hawkbitSecurityProperties.isRequireSsl()) {
|
if (hawkbitSecurityProperties.isRequireSsl()) {
|
||||||
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
|
httpSec = httpSec.requiresChannel().anyRequest().requiresSecure().and();
|
||||||
@@ -634,16 +689,27 @@ public class SecurityManagedConfiguration {
|
|||||||
httpSec.headers().contentSecurityPolicy(hawkbitSecurityProperties.getContentSecurityPolicy());
|
httpSec.headers().contentSecurityPolicy(hawkbitSecurityProperties.getContentSecurityPolicy());
|
||||||
}
|
}
|
||||||
|
|
||||||
final SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
|
if (enableOidc) {
|
||||||
simpleUrlLogoutSuccessHandler.setTargetUrlParameter("login");
|
httpSec.authorizeRequests().antMatchers("/UI/login/**").permitAll().antMatchers("/UI/UIDL/**")
|
||||||
|
.permitAll().anyRequest().authenticated().and()
|
||||||
|
// OIDC
|
||||||
|
.oauth2Login().userInfoEndpoint().oidcUserService(oidcUserService).and()
|
||||||
|
.successHandler(oidcAuthenticationSuccessHandler).and().oauth2Client().and()
|
||||||
|
// logout
|
||||||
|
.logout().logoutUrl("/UI/logout").addLogoutHandler(oidcLogoutHandler).logoutSuccessUrl("/");
|
||||||
|
} else {
|
||||||
|
final SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
|
||||||
|
simpleUrlLogoutSuccessHandler.setTargetUrlParameter("login");
|
||||||
|
|
||||||
httpSec
|
httpSec
|
||||||
// UI
|
// UI
|
||||||
.authorizeRequests().antMatchers("/UI/login/**").permitAll().antMatchers("/UI/UIDL/**").permitAll()
|
.authorizeRequests().antMatchers("/UI/login/**").permitAll().antMatchers("/UI/UIDL/**")
|
||||||
.anyRequest().authenticated().and()
|
.permitAll().anyRequest().authenticated().and()
|
||||||
// UI login / logout
|
// UI login / logout
|
||||||
.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/UI/login/#/"))
|
.exceptionHandling()
|
||||||
.and().logout().logoutUrl("/UI/logout").logoutSuccessHandler(simpleUrlLogoutSuccessHandler);
|
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/UI/login/#/")).and().logout()
|
||||||
|
.logoutUrl("/UI/logout").logoutSuccessHandler(simpleUrlLogoutSuccessHandler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,5 +13,6 @@ org.eclipse.hawkbit.autoconfigure.scheduling.AsyncConfigurerAutoConfiguration,\
|
|||||||
org.eclipse.hawkbit.autoconfigure.scheduling.ExecutorAutoConfiguration,\
|
org.eclipse.hawkbit.autoconfigure.scheduling.ExecutorAutoConfiguration,\
|
||||||
org.eclipse.hawkbit.autoconfigure.security.SecurityAutoConfiguration,\
|
org.eclipse.hawkbit.autoconfigure.security.SecurityAutoConfiguration,\
|
||||||
org.eclipse.hawkbit.autoconfigure.security.InMemoryUserManagementAutoConfiguration,\
|
org.eclipse.hawkbit.autoconfigure.security.InMemoryUserManagementAutoConfiguration,\
|
||||||
|
org.eclipse.hawkbit.autoconfigure.security.OidcUserManagementAutoConfiguration,\
|
||||||
org.eclipse.hawkbit.autoconfigure.web.WebMvcAutoConfiguration,\
|
org.eclipse.hawkbit.autoconfigure.web.WebMvcAutoConfiguration,\
|
||||||
org.eclipse.hawkbit.autoconfigure.PropertyHostnameResolverAutoConfiguration
|
org.eclipse.hawkbit.autoconfigure.PropertyHostnameResolverAutoConfiguration
|
||||||
|
|||||||
@@ -43,6 +43,18 @@
|
|||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.security</groupId>
|
||||||
<artifactId>spring-security-core</artifactId>
|
<artifactId>spring-security-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-oauth2-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-oauth2-resource-server</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-oauth2-jose</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot</artifactId>
|
<artifactId>spring-boot</artifactId>
|
||||||
@@ -66,4 +78,4 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import org.springframework.data.domain.AuditorAware;
|
|||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auditor class that allows BaseEntitys to insert current logged in user for
|
* Auditor class that allows BaseEntitys to insert current logged in user for
|
||||||
@@ -38,6 +39,9 @@ public class SpringSecurityAuditorAware implements AuditorAware<String> {
|
|||||||
if (authentication.getPrincipal() instanceof UserDetails) {
|
if (authentication.getPrincipal() instanceof UserDetails) {
|
||||||
return ((UserDetails) authentication.getPrincipal()).getUsername();
|
return ((UserDetails) authentication.getPrincipal()).getUsername();
|
||||||
}
|
}
|
||||||
|
if (authentication.getPrincipal() instanceof OidcUser) {
|
||||||
|
return ((OidcUser) authentication.getPrincipal()).getPreferredUsername();
|
||||||
|
}
|
||||||
return authentication.getPrincipal().toString();
|
return authentication.getPrincipal().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,14 @@
|
|||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.security</groupId>
|
||||||
<artifactId>spring-security-web</artifactId>
|
<artifactId>spring-security-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-oauth2-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-oauth2-jose</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.vaadin</groupId>
|
<groupId>com.vaadin</groupId>
|
||||||
|
|||||||
@@ -12,14 +12,18 @@ import java.util.Collections;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
|
||||||
import org.eclipse.hawkbit.im.authentication.UserPrincipal;
|
import org.eclipse.hawkbit.im.authentication.UserPrincipal;
|
||||||
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
||||||
import org.eclipse.hawkbit.ui.utils.SpringContextHelper;
|
import org.eclipse.hawkbit.ui.utils.SpringContextHelper;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
|
|
||||||
import com.vaadin.server.VaadinService;
|
import com.vaadin.server.VaadinService;
|
||||||
@@ -187,7 +191,20 @@ public final class UserDetailsFormatter {
|
|||||||
public static UserDetails getCurrentUser() {
|
public static UserDetails getCurrentUser() {
|
||||||
final SecurityContext context = (SecurityContext) VaadinService.getCurrentRequest().getWrappedSession()
|
final SecurityContext context = (SecurityContext) VaadinService.getCurrentRequest().getWrappedSession()
|
||||||
.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
|
.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
|
||||||
return (UserDetails) context.getAuthentication().getPrincipal();
|
Authentication authentication = context.getAuthentication();
|
||||||
|
if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
|
OidcUser oidcUser = (OidcUser) authentication.getPrincipal();
|
||||||
|
Object details = authentication.getDetails();
|
||||||
|
String tenant = "DEFAULT";
|
||||||
|
if (details instanceof TenantAwareAuthenticationDetails) {
|
||||||
|
tenant = ((TenantAwareAuthenticationDetails) details).getTenant();
|
||||||
|
}
|
||||||
|
return new UserPrincipal(oidcUser.getPreferredUsername(), "***", oidcUser.getGivenName(),
|
||||||
|
oidcUser.getFamilyName(), oidcUser.getPreferredUsername(), oidcUser.getEmail(), tenant,
|
||||||
|
oidcUser.getAuthorities());
|
||||||
|
} else {
|
||||||
|
return (UserDetails) authentication.getPrincipal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String trimAndFormatDetail(final String formatString, final int expectedDetailLength) {
|
private static String trimAndFormatDetail(final String formatString, final int expectedDetailLength) {
|
||||||
|
|||||||
6
licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt
Normal file
6
licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Copyright (c) 2019 Kiwigrid 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
|
||||||
1
pom.xml
1
pom.xml
@@ -327,6 +327,7 @@
|
|||||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_MICROSOFT_18.txt</validHeader>
|
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_MICROSOFT_18.txt</validHeader>
|
||||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_BOSCH_18.txt</validHeader>
|
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_BOSCH_18.txt</validHeader>
|
||||||
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_DEVOLO_19.txt</validHeader>
|
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_DEVOLO_19.txt</validHeader>
|
||||||
|
<validHeader>licenses/LICENSE_HEADER_TEMPLATE_KIWIGRID_19.txt</validHeader>
|
||||||
</validHeaders>
|
</validHeaders>
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>**/banner.txt</exclude>
|
<exclude>**/banner.txt</exclude>
|
||||||
|
|||||||
Reference in New Issue
Block a user