Browse Source

Merge remote-tracking branch 'origin/fix/oauth2-user-tenant-lts-4.2' into lts-4.2

pull/15319/merge
Viacheslav Klimov 5 days ago
parent
commit
66075d6c02
  1. 28
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  2. 115
      application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperTest.java

28
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java

@ -95,7 +95,7 @@ public abstract class AbstractOAuth2ClientMapper {
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail());
User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); User user = findUserForClient(oauth2User.getEmail(), oAuth2Client);
if (user == null && !config.isAllowUserCreation()) { if (user == null && !config.isAllowUserCreation()) {
throw new UsernameNotFoundException("User not found: " + oauth2User.getEmail()); throw new UsernameNotFoundException("User not found: " + oauth2User.getEmail());
@ -104,7 +104,7 @@ public abstract class AbstractOAuth2ClientMapper {
if (user == null) { if (user == null) {
userCreationLock.lock(); userCreationLock.lock();
try { try {
user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); user = findUserForClient(oauth2User.getEmail(), oAuth2Client);
if (user == null) { if (user == null) {
user = new User(); user = new User();
if (oauth2User.getCustomerId() == null && StringUtils.isEmpty(oauth2User.getCustomerName())) { if (oauth2User.getCustomerId() == null && StringUtils.isEmpty(oauth2User.getCustomerName())) {
@ -112,7 +112,12 @@ public abstract class AbstractOAuth2ClientMapper {
} else { } else {
user.setAuthority(Authority.CUSTOMER_USER); user.setAuthority(Authority.CUSTOMER_USER);
} }
TenantId tenantId = oauth2User.getTenantId() != null ? oauth2User.getTenantId() : getTenantId(oauth2User.getTenantName()); TenantId tenantId;
if (oAuth2Client.getTenantId().isSysTenantId()) {
tenantId = oauth2User.getTenantId() != null ? oauth2User.getTenantId() : getTenantId(oauth2User.getTenantName());
} else {
tenantId = oAuth2Client.getTenantId();
}
user.setTenantId(tenantId); user.setTenantId(tenantId);
CustomerId customerId = oauth2User.getCustomerId() != null ? CustomerId customerId = oauth2User.getCustomerId() != null ?
oauth2User.getCustomerId() : getCustomerId(user.getTenantId(), oauth2User.getCustomerName()); oauth2User.getCustomerId() : getCustomerId(user.getTenantId(), oauth2User.getCustomerName());
@ -164,6 +169,23 @@ public abstract class AbstractOAuth2ClientMapper {
} }
} }
/**
* The user is matched by email alone, and the email is whatever the provider chose to send. A client registered by a
* tenant must therefore only ever resolve users of that same tenant; a system client is platform-wide by design.
*/
private User findUserForClient(String email, OAuth2Client oAuth2Client) {
User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, email);
if (user == null || oAuth2Client.getTenantId().isSysTenantId()) {
return user;
}
if (user.getTenantId().equals(oAuth2Client.getTenantId())) {
return user;
}
log.warn("OAuth2 client [{}] of tenant [{}] cannot resolve user [{}] [{}] of tenant [{}]: outside of the client tenant",
oAuth2Client.getId(), oAuth2Client.getTenantId(), user.getId(), email, user.getTenantId());
throw new UsernameNotFoundException("User not found: " + email);
}
private TenantId getTenantId(String name) throws Exception { private TenantId getTenantId(String name) throws Exception {
Tenant tenant = tenantService.findTenantByName(name); Tenant tenant = tenantService.findTenantByName(name);
if (tenant != null) { if (tenant != null) {

115
application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperTest.java

@ -0,0 +1,115 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security.auth.oauth2;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.model.SecurityUser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
@DaoSqlTest
public class OAuth2ClientMapperTest extends AbstractControllerTest {
@Autowired
private BasicOAuth2ClientMapper basicOAuth2ClientMapper;
@Autowired
private UserService userService;
@Test
public void testShouldFindUserOfOwnTenant() throws Exception {
loginTenantAdmin();
OAuth2Client tenantClient = doPost("/api/oauth2/client", createOauth2Client(tenantId, "tenant client"), OAuth2Client.class);
OAuth2User oAuth2User = new OAuth2User();
oAuth2User.setEmail(TENANT_ADMIN_EMAIL);
SecurityUser securityUser = basicOAuth2ClientMapper.getOrCreateSecurityUserFromOAuth2User(oAuth2User, tenantClient);
assertThat(securityUser.getTenantId()).isEqualTo(tenantId);
assertThat(securityUser.getAuthority()).isEqualTo(Authority.TENANT_ADMIN);
}
@Test
public void testShouldNotFindUserOfAnotherTenant() throws Exception {
loginDifferentTenant();
loginTenantAdmin();
OAuth2Client tenantClient = doPost("/api/oauth2/client", createOauth2Client(tenantId, "tenant client"), OAuth2Client.class);
// the email attribute is controlled by the identity provider behind the client
OAuth2User oAuth2User = new OAuth2User();
oAuth2User.setEmail(DIFFERENT_TENANT_ADMIN_EMAIL);
UsernameNotFoundException exception = assertThrows(
UsernameNotFoundException.class,
() -> basicOAuth2ClientMapper.getOrCreateSecurityUserFromOAuth2User(oAuth2User, tenantClient));
assertThat(exception.getMessage()).isEqualTo("User not found: " + DIFFERENT_TENANT_ADMIN_EMAIL);
User differentTenantAdmin = userService.findUserByEmail(TenantId.SYS_TENANT_ID, DIFFERENT_TENANT_ADMIN_EMAIL);
assertThat(differentTenantAdmin.getTenantId()).isEqualTo(differentTenantId);
loginSysAdmin();
deleteDifferentTenant();
}
@Test
public void testShouldNotFindSysAdmin() throws Exception {
loginTenantAdmin();
OAuth2Client tenantClient = doPost("/api/oauth2/client", createOauth2Client(tenantId, "tenant client"), OAuth2Client.class);
OAuth2User oAuth2User = new OAuth2User();
oAuth2User.setEmail(SYS_ADMIN_EMAIL);
UsernameNotFoundException exception = assertThrows(
UsernameNotFoundException.class,
() -> basicOAuth2ClientMapper.getOrCreateSecurityUserFromOAuth2User(oAuth2User, tenantClient));
assertThat(exception.getMessage()).isEqualTo("User not found: " + SYS_ADMIN_EMAIL);
User sysAdmin = userService.findUserByEmail(TenantId.SYS_TENANT_ID, SYS_ADMIN_EMAIL);
assertThat(sysAdmin.getAuthority()).isEqualTo(Authority.SYS_ADMIN);
}
@Test
public void testShouldCreateUserInClientTenant() throws Exception {
loginDifferentTenant();
loginTenantAdmin();
OAuth2Client tenantClient = doPost("/api/oauth2/client", createOauth2Client(tenantId, "tenant client"), OAuth2Client.class);
// a custom mapper endpoint may return any tenant id; the client's own tenant must win
String email = "userA@corporation.gmail.com";
OAuth2User oAuth2User = new OAuth2User();
oAuth2User.setEmail(email);
oAuth2User.setTenantId(differentTenantId);
basicOAuth2ClientMapper.getOrCreateSecurityUserFromOAuth2User(oAuth2User, tenantClient);
User created = userService.findUserByEmail(TenantId.SYS_TENANT_ID, email);
assertThat(created.getTenantId()).isEqualTo(tenantId);
loginSysAdmin();
deleteDifferentTenant();
}
}
Loading…
Cancel
Save