Improve hawkBit user management (#1666)

1. Definded with properties users (static) are configured using property map (no need of indexes)
2. AuthenticationProvider that authenticates them is always registered (if not needed - don't configure them)
3. UserDetailsService (in case of missing - won't be registered)
4. Spring security user (spring.security.username) will be registered together with other users (if any). If any - it will be system-wide, otherwise tenant-scoped.
5. UserPrincipal renamed to TenantAwareUser in order to match its purpose.
6. Some if its fields are removes as not needed - to be closer to spring security user
7. DefaultRolloutApprovalStrategy now use UserAuthoritiesResolver instead of UserDetailsService as the central point of truth

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-02-26 16:56:37 +02:00
committed by GitHub
parent 783a5be2dd
commit 24d70827b7
16 changed files with 266 additions and 327 deletions

View File

@@ -1,110 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.im.authentication;
import java.util.Collection;
import java.util.Collections;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.userdetails.User;
/**
* A software provisioning user principal definition stored in the
* {@link SecurityContext} which contains the user specific attributes.
*
*/
@Getter
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserPrincipal extends User {
private static final long serialVersionUID = 1L;
private final String firstname;
private final String lastname;
private final String loginname;
private final String tenant;
private final String email;
/**
* @param username
* the user name 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
*/
public UserPrincipal(final String username, final String firstname, final String lastname, final String loginname,
final String email, final String tenant) {
this(username, "***", firstname, lastname, 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;
this.tenant = tenant;
this.email = email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.im.authentication;
import java.io.Serial;
import java.util.Collection;
import java.util.Collections;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.userdetails.User;
/**
* A software provisioning user principal definition stored in the
* {@link SecurityContext} which contains the user specific attributes.
*/
@Getter
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class UserTenantAware extends User {
@Serial
private static final long serialVersionUID = 1L;
private final String tenant;
/**
* @param username the username of the user
* @param password the password of the user
* @param authorities the authorities which the user has
* @param tenant the tenant of the user
*/
public UserTenantAware(final String username, final String password,
final Collection<? extends GrantedAuthority> authorities, final String tenant) {
super(username, password, authorities == null ? Collections.emptyList() : authorities);
this.tenant = tenant;
}
/**
* Create user without password and any credentials. For test purposes only.
*
* @param username the username of the user
* @param tenant the tenant of the user
*/
public UserTenantAware(final String username, String tenant) {
this(username, "***", null, tenant);
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@@ -27,8 +27,7 @@ public class InMemoryUserAuthoritiesResolver implements UserAuthoritiesResolver
/**
* Constructs the resolver based on the given authority lookup map.
*
* @param usernamesToAuthorities
* The authority map to read from. Must not be <code>null</code>.
* @param usernamesToAuthorities The authority map to read from. Must not be <code>null</code>.
*/
public InMemoryUserAuthoritiesResolver(final Map<String, List<String>> usernamesToAuthorities) {
this.usernamesToAuthorities = usernamesToAuthorities;

View File

@@ -21,7 +21,7 @@ import java.util.stream.Collectors;
import org.eclipse.hawkbit.ContextAware;
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.im.authentication.UserTenantAware;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.springframework.lang.Nullable;
@@ -30,6 +30,7 @@ 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.userdetails.User;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
/**
@@ -84,8 +85,8 @@ public class SecurityContextTenantAware implements ContextAware {
final Object principal = context.getAuthentication().getPrincipal();
if (context.getAuthentication().getDetails() instanceof TenantAwareAuthenticationDetails) {
return ((TenantAwareAuthenticationDetails) context.getAuthentication().getDetails()).getTenant();
} else if (principal instanceof UserPrincipal) {
return ((UserPrincipal) principal).getTenant();
} else if (principal instanceof UserTenantAware) {
return ((UserTenantAware) principal).getTenant();
}
}
return null;
@@ -96,12 +97,12 @@ public class SecurityContextTenantAware implements ContextAware {
final SecurityContext context = SecurityContextHolder.getContext();
if (context.getAuthentication() != null) {
final Object principal = context.getAuthentication().getPrincipal();
if (principal instanceof UserPrincipal) {
return ((UserPrincipal) principal).getUsername();
}
if (principal instanceof OidcUser) {
return ((OidcUser) principal).getPreferredUsername();
}
if (principal instanceof User) {
return ((User) principal).getUsername();
}
}
return null;
}
@@ -184,19 +185,20 @@ public class SecurityContextTenantAware implements ContextAware {
* a specific tenant and user.
*/
private static final class AuthenticationDelegate implements Authentication {
@Serial
private static final long serialVersionUID = 1L;
private final Authentication delegate;
private final UserPrincipal principal;
private final UserTenantAware principal;
private final TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails;
private AuthenticationDelegate(final Authentication delegate, final String tenant, final String username,
final Collection<? extends GrantedAuthority> authorities) {
this.delegate = delegate;
this.principal = new UserPrincipal(username, username, null, null, username, null, tenant, authorities);
this.principal = new UserTenantAware(username, username, authorities, tenant);
tenantAwareAuthenticationDetails = new TenantAwareAuthenticationDetails(tenant, false);
}