Browse Source

Refactoring to review. Added tenantId and customerId. Added email tenant name strategy

pull/2717/head
Volodymyr Babak 6 years ago
committed by Andrew Shvayka
parent
commit
8dbc550fd3
  1. 4
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  2. 42
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  3. 18
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java
  4. 20
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  5. 90
      application/src/main/resources/thingsboard.yml
  6. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2User.java
  7. 2
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Client.java
  8. 2
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java
  9. 12
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java
  10. 11
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java

4
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -200,10 +200,10 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
.addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
if (oauth2Configuration.isEnabled()) {
if (oauth2Configuration != null && oauth2Configuration.isEnabled()) {
http.oauth2Login()
.loginPage("/oauth2Login")
.loginProcessingUrl(oauth2Configuration.getClients().values().iterator().next().getLoginProcessingUrl())
.loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())
.successHandler(oauth2AuthenticationSuccessHandler);
}
}

42
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BaseOAuth2ClientMapper.java → application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java

@ -36,9 +36,11 @@ import org.thingsboard.server.service.security.model.UserPrincipal;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public abstract class BaseOAuth2ClientMapper {
public abstract class AbstractOAuth2ClientMapper {
@Autowired
private UserService userService;
@ -49,6 +51,8 @@ public abstract class BaseOAuth2ClientMapper {
@Autowired
private CustomerService customerService;
private final Lock userCreationLock = new ReentrantLock();
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, boolean allowUserCreation) {
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail());
@ -59,18 +63,30 @@ public abstract class BaseOAuth2ClientMapper {
}
if (user == null) {
user = new User();
if (StringUtils.isEmpty(oauth2User.getCustomerName())) {
user.setAuthority(Authority.TENANT_ADMIN);
} else {
user.setAuthority(Authority.CUSTOMER_USER);
userCreationLock.lock();
try {
user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail());
if (user == null) {
user = new User();
if (oauth2User.getCustomerId() == null && StringUtils.isEmpty(oauth2User.getCustomerName())) {
user.setAuthority(Authority.TENANT_ADMIN);
} else {
user.setAuthority(Authority.CUSTOMER_USER);
}
TenantId tenantId = oauth2User.getTenantId() != null ?
oauth2User.getTenantId() : getTenantId(oauth2User.getTenantName());
user.setTenantId(tenantId);
CustomerId customerId = oauth2User.getCustomerId() != null ?
oauth2User.getCustomerId() : getCustomerId(user.getTenantId(), oauth2User.getCustomerName());
user.setCustomerId(customerId);
user.setEmail(oauth2User.getEmail());
user.setFirstName(oauth2User.getFirstName());
user.setLastName(oauth2User.getLastName());
user = userService.saveUser(user);
}
} finally {
userCreationLock.unlock();
}
user.setTenantId(getTenantId(oauth2User.getTenantName()));
user.setCustomerId(getCustomerId(user.getTenantId(), oauth2User.getCustomerName()));
user.setEmail(oauth2User.getEmail());
user.setFirstName(oauth2User.getFirstName());
user.setLastName(oauth2User.getLastName());
user = userService.saveUser(user);
}
try {
@ -78,7 +94,7 @@ public abstract class BaseOAuth2ClientMapper {
return (SecurityUser) new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities()).getPrincipal();
} catch (Exception e) {
log.error("Can't get or create security user from oauth2 user", e);
throw e;
throw new RuntimeException("Can't get or create security user from oauth2 user", e);
}
}

18
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java

@ -28,7 +28,13 @@ import java.util.Map;
@Service(value = "basicOAuth2ClientMapper")
@Slf4j
public class BasicOAuth2ClientMapper extends BaseOAuth2ClientMapper implements OAuth2ClientMapper {
public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
private static final String START_PLACEHOLDER_PREFIX = "%{";
private static final String END_PLACEHOLDER_PREFIX = "}";
private static final String EMAIL_TENANT_STRATEGY = "email";
private static final String DOMAIN_TENANT_STRATEGY = "domain";
private static final String CUSTOM_TENANT_STRATEGY = "custom";
@Override
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config) {
@ -46,7 +52,7 @@ public class BasicOAuth2ClientMapper extends BaseOAuth2ClientMapper implements O
oauth2User.setFirstName(firstName);
}
if (!StringUtils.isEmpty(config.getBasic().getCustomerNameStrategyPattern())) {
StrSubstitutor sub = new StrSubstitutor(attributes, "${", "}");
StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
String customerName = sub.replace(config.getBasic().getCustomerNameStrategyPattern());
oauth2User.setCustomerName(customerName);
}
@ -55,11 +61,13 @@ public class BasicOAuth2ClientMapper extends BaseOAuth2ClientMapper implements O
private String getTenantName(Map<String, Object> attributes, OAuth2ClientMapperConfig config) {
switch (config.getBasic().getTenantNameStrategy()) {
case "domain":
case EMAIL_TENANT_STRATEGY:
return getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
case DOMAIN_TENANT_STRATEGY:
String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
return email.substring(email .indexOf("@") + 1);
case "custom":
StrSubstitutor sub = new StrSubstitutor(attributes, "${", "}");
case CUSTOM_TENANT_STRATEGY:
StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
return sub.replace(config.getBasic().getTenantNameStrategyPattern());
default:
throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!");

20
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.server.service.security.auth.oauth2;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
@ -27,7 +29,9 @@ import org.thingsboard.server.service.security.model.SecurityUser;
@Service(value = "customOAuth2ClientMapper")
@Slf4j
public class CustomOAuth2ClientMapper extends BaseOAuth2ClientMapper implements OAuth2ClientMapper {
public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
private static final ObjectMapper json = new ObjectMapper();
private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
@ -42,6 +46,18 @@ public class CustomOAuth2ClientMapper extends BaseOAuth2ClientMapper implements
restTemplateBuilder = restTemplateBuilder.basicAuthentication(custom.getUsername(), custom.getPassword());
}
RestTemplate restTemplate = restTemplateBuilder.build();
return restTemplate.postForEntity(custom.getUrl(), token.getPrincipal(), OAuth2User.class).getBody();
String request;
try {
request = json.writeValueAsString(token.getPrincipal());
} catch (JsonProcessingException e) {
log.error("Can't convert principal to JSON string", e);
throw new RuntimeException("Can't convert principal to JSON string", e);
}
try {
return restTemplate.postForEntity(custom.getUrl(), request, OAuth2User.class).getBody();
} catch (Exception e) {
log.error("Can't connect to custom mapper endpoint", e);
throw new RuntimeException("Can't connect to custom mapper endpoint", e);
}
}
}

90
application/src/main/resources/thingsboard.yml

@ -98,72 +98,40 @@ security:
# Time allowed to claim the device in milliseconds
duration: "${SECURITY_CLAIM_DURATION:60000}" # 1 minute, note this value must equal claimDevices.timeToLiveInMinutes value
basic:
enabled: false
enabled: "${SECURITY_BASIC_ENABLED:false}"
oauth2:
enabled: true
enabled: "${SECURITY_OAUTH2_ENABLED:false}"
loginProcessingUrl: "${SECURITY_OAUTH2_LOGIN_PROCESSING_URL:/login/oauth2/code/}"
clients:
schwarz:
registrationId: A
loginButtonLabel: Auth0 #
loginButtonIcon:
clientName: Test app
clientId: dVH9reqyqiXIG7M2wmamb0ySue8zaM4g
clientSecret: EYAfAGxwkwoeYnb2o2cDgaWZB5k97OStpZQPPvcMMD-SVH2BuughTGeBazXtF5I6
accessTokenUri: https://dev-r9m8ht0k.auth0.com/oauth/token
authorizationUri: https://dev-r9m8ht0k.auth0.com/authorize
scope: openid,profile,email
redirectUriTemplate: http://localhost:8080/login/oauth2/code/
loginProcessingUrl: /login/oauth2/code/
jwkSetUri: https://dev-r9m8ht0k.auth0.com/.well-known/jwks.json
authorizationGrantType: authorization_code # authorization_code, implicit, refresh_token, client_credentials
clientAuthenticationMethod: post # basic, post
userInfoUri: https://dev-r9m8ht0k.auth0.com/userinfo
userNameAttributeName: email
default:
loginButtonLabel: "${SECURITY_OAUTH2_DEFAULT_LOGIN_BUTTON_LABEL:Default}" # Label that going to be show on login screen
loginButtonIcon: "${SECURITY_OAUTH2_DEFAULT_LOGIN_BUTTON_ICON:}" # Icon that going to be show on login screen. Material design icon ID (https://material.angularjs.org/latest/api/directive/mdIcon)
clientName: "${SECURITY_OAUTH2_DEFAULT_CLIENT_NAME:ClientName}"
clientId: "${SECURITY_OAUTH2_DEFAULT_CLIENT_ID:}"
clientSecret: "${SECURITY_OAUTH2_DEFAULT_CLIENT_SECRET:}"
accessTokenUri: "${SECURITY_OAUTH2_DEFAULT_ACCESS_TOKEN_URI:}"
authorizationUri: "${SECURITY_OAUTH2_DEFAULT_AUTHORIZATION_URI:}"
scope: "${SECURITY_OAUTH2_DEFAULT_SCOPE:}"
redirectUriTemplate: "${SECURITY_OAUTH2_DEFAULT_REDIRECT_URI_TEMPLATE:http://localhost:8080/login/oauth2/code/}" # Must be in sync with security.oauth2.loginProcessingUrl
jwkSetUri: "${SECURITY_OAUTH2_DEFAULT_JWK_SET_URI:}"
authorizationGrantType: "${SECURITY_OAUTH2_DEFAULT_AUTHORIZATION_GRANT_TYPE:authorization_code}" # authorization_code, implicit, refresh_token or client_credentials
clientAuthenticationMethod: "${SECURITY_OAUTH2_DEFAULT_CLIENT_AUTHENTICATION_METHOD:post}" # basic or post
userInfoUri: "${SECURITY_OAUTH2_DEFAULT_USER_INFO_URI:}"
userNameAttributeName: "${SECURITY_OAUTH2_DEFAULT_USER_NAME_ATTRIBUTE_NAME:email}"
mapperConfig:
type: custom # basic or custom
type: "${SECURITY_OAUTH2_DEFAULT_MAPPER_TYPE:basic}" # basic or custom
basic:
allowUserCreation: true # required
emailAttributeKey: email # required
firstNameAttributeKey:
lastNameAttributeKey:
tenantNameStrategy: domain # domain or custom
tenantNameStrategyPattern:
customerNameStrategyPattern:
allowUserCreation: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_ALLOW_USER_CREATION:true}" # Allows to create user if it not exists
emailAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_EMAIL_ATTRIBUTE_KEY:email}" # Attribute key to use as email for the user
firstNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_FIRST_NAME_ATTRIBUTE_KEY:}"
lastNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_LAST_NAME_ATTRIBUTE_KEY:}"
tenantNameStrategy: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY:domain}" # domain, email or custom
tenantNameStrategyPattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY_PATTERN:}"
customerNameStrategyPattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_STRATEGY_PATTERN:}"
custom:
url: http://localhost:9090/oauth2/mapper
username: admin
password: bababa
auth0:
registrationId: B
loginButtonLabel: Schwarz #
loginButtonIcon: mdi:google
clientName: Thingsboard Dev Test Q
clientId: 5f5c0998-1d9b-4679-9610-6108fb91af2a
clientSecret: h_kXVb7Ee1LgDDinix_nkAh_owWX7YCO783NNteF9AIOqlTWu2L03YoFjv5KL8yRVyx4uYAE-r_N3tFbupE8Kw
accessTokenUri: https://federation-q.auth.schwarz/nidp/oauth/nam/token
authorizationUri: https://federation-q.auth.schwarz/nidp/oauth/nam/authz
scope: openid,profile,email,siam
redirectUriTemplate: http://localhost:8080/login/oauth2/code/
loginProcessingUrl: /login/oauth2/code/
jwkSetUri: https://federation-q.auth.schwarz/nidp/oauth/nam/keys
authorizationGrantType: authorization_code # authorization_code, implicit, refresh_token, client_credentials
clientAuthenticationMethod: post # basic, post
userInfoUri: https://federation-q.auth.schwarz/nidp/oauth/nam/userinfo
userNameAttributeName: mail
mapperConfig:
type: basic # simple or custom
basic:
allowUserCreation: true # required
emailAttributeKey: CloudLoginName # required
firstNameAttributeKey: givenName
lastNameAttributeKey: sn
tenantNameStrategy: custom # domain or custom
tenantNameStrategyPattern: LOL ${region}
customerNameStrategyPattern: GGG ${countrycode}
custom:
url: http://localhost:9090/oauth2/mapper
username: test
password: test
url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}"
username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}"
password: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_PASSWORD:}"
# Dashboard parameters
dashboard:

4
common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2User.java

@ -16,11 +16,15 @@
package org.thingsboard.server.dao.oauth2;
import lombok.Data;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@Data
public class OAuth2User {
private String tenantName;
private TenantId tenantId;
private String customerName;
private CustomerId customerId;
private String email;
private String firstName;
private String lastName;

2
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Client.java

@ -20,7 +20,6 @@ import lombok.Data;
@Data
public class OAuth2Client {
private String registrationId;
private String loginButtonLabel;
private String loginButtonIcon;
private String clientName;
@ -31,7 +30,6 @@ public class OAuth2Client {
private String scope;
private String redirectUriTemplate;
private String jwkSetUri;
private String loginProcessingUrl;
private String authorizationGrantType;
private String clientAuthenticationMethod;
private String userInfoUri;

2
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java

@ -21,8 +21,8 @@ import lombok.Data;
public class OAuth2ClientMapperConfig {
private String type;
private CustomOAuth2ClientMapperConfig custom;
private BasicOAuth2ClientMapperConfig basic;
private CustomOAuth2ClientMapperConfig custom;
@Data
public static class BasicOAuth2ClientMapperConfig {

12
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Configuration.java

@ -40,13 +40,15 @@ import java.util.Map;
public class OAuth2Configuration {
private boolean enabled;
private String loginProcessingUrl;
private Map<String, OAuth2Client> clients = new HashMap<>();
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
List<ClientRegistration> result = new ArrayList<>();
for (OAuth2Client client : clients.values()) {
ClientRegistration registration = ClientRegistration.withRegistrationId(client.getRegistrationId())
for (Map.Entry<String, OAuth2Client> entry : clients.entrySet()) {
OAuth2Client client = entry.getValue();
ClientRegistration registration = ClientRegistration.withRegistrationId(entry.getKey())
.clientId(client.getClientId())
.authorizationUri(client.getAuthorizationUri())
.clientSecret(client.getClientSecret())
@ -68,9 +70,9 @@ public class OAuth2Configuration {
public OAuth2Client getClientByRegistrationId(String registrationId) {
OAuth2Client result = null;
if (clients != null && !clients.isEmpty()) {
for (OAuth2Client client : clients.values()) {
if (client.getRegistrationId().equals(registrationId)) {
result = client;
for (String key : clients.keySet()) {
if (key.equals(registrationId)) {
result = clients.get(key);
break;
}
}

11
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@ -33,15 +34,15 @@ public class OAuth2ServiceImpl implements OAuth2Service {
@Override
public List<OAuth2ClientInfo> getOAuth2Clients() {
if (!oauth2Configuration.isEnabled()) {
if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) {
return Collections.emptyList();
}
List<OAuth2ClientInfo> result = new ArrayList<>();
for (OAuth2Client c : oauth2Configuration.getClients().values()) {
for (Map.Entry<String, OAuth2Client> entry : oauth2Configuration.getClients().entrySet()) {
OAuth2ClientInfo client = new OAuth2ClientInfo();
client.setName(c.getLoginButtonLabel());
client.setUrl(String.format("/oauth2/authorization/%s", c.getRegistrationId()));
client.setIcon(c.getLoginButtonIcon());
client.setName(entry.getValue().getLoginButtonLabel());
client.setUrl(String.format("/oauth2/authorization/%s", entry.getKey()));
client.setIcon(entry.getValue().getLoginButtonIcon());
result.add(client);
}
return result;

Loading…
Cancel
Save