Browse Source
* Added base impl for OAuth-2 * Added basic and custom OAuth2 user mappers * Removed comment line * Refactoring to review. Added tenantId and customerId. Added email tenant name strategy * Revert debug logger * Fixed compilation * Test fixed * Create UI for OAuthService * Revert package-lock.json * Add translate login es_ES Co-authored-by: Vladyslav_Prykhodko <vprykhodko@thingsboard.io>pull/2729/head
committed by
GitHub
48 changed files with 1117 additions and 54 deletions
@ -0,0 +1,128 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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 lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException; |
||||
|
import org.springframework.util.StringUtils; |
||||
|
import org.thingsboard.server.common.data.Customer; |
||||
|
import org.thingsboard.server.common.data.Tenant; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.common.data.security.Authority; |
||||
|
import org.thingsboard.server.dao.customer.CustomerService; |
||||
|
import org.thingsboard.server.dao.oauth2.OAuth2User; |
||||
|
import org.thingsboard.server.dao.tenant.TenantService; |
||||
|
import org.thingsboard.server.dao.user.UserService; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
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 AbstractOAuth2ClientMapper { |
||||
|
|
||||
|
@Autowired |
||||
|
private UserService userService; |
||||
|
|
||||
|
@Autowired |
||||
|
private TenantService tenantService; |
||||
|
|
||||
|
@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()); |
||||
|
|
||||
|
User user = userService.findUserByEmail(TenantId.SYS_TENANT_ID, oauth2User.getEmail()); |
||||
|
|
||||
|
if (user == null && !allowUserCreation) { |
||||
|
throw new UsernameNotFoundException("User not found: " + oauth2User.getEmail()); |
||||
|
} |
||||
|
|
||||
|
if (user == null) { |
||||
|
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(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
SecurityUser securityUser = new SecurityUser(user, true, principal); |
||||
|
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 new RuntimeException("Can't get or create security user from oauth2 user", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private TenantId getTenantId(String tenantName) { |
||||
|
List<Tenant> tenants = tenantService.findTenants(new PageLink(1, 0, tenantName)).getData(); |
||||
|
Tenant tenant; |
||||
|
if (tenants == null || tenants.isEmpty()) { |
||||
|
tenant = new Tenant(); |
||||
|
tenant.setTitle(tenantName); |
||||
|
tenant = tenantService.saveTenant(tenant); |
||||
|
} else { |
||||
|
tenant = tenants.get(0); |
||||
|
} |
||||
|
return tenant.getTenantId(); |
||||
|
} |
||||
|
|
||||
|
private CustomerId getCustomerId(TenantId tenantId, String customerName) { |
||||
|
if (StringUtils.isEmpty(customerName)) { |
||||
|
return null; |
||||
|
} |
||||
|
Optional<Customer> customerOpt = customerService.findCustomerByTenantIdAndTitle(tenantId, customerName); |
||||
|
if (customerOpt.isPresent()) { |
||||
|
return customerOpt.get().getId(); |
||||
|
} else { |
||||
|
Customer customer = new Customer(); |
||||
|
customer.setTenantId(tenantId); |
||||
|
customer.setTitle(customerName); |
||||
|
return customerService.saveCustomer(customer).getId(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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 lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.text.StrSubstitutor; |
||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.springframework.util.StringUtils; |
||||
|
import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig; |
||||
|
import org.thingsboard.server.dao.oauth2.OAuth2User; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Service(value = "basicOAuth2ClientMapper") |
||||
|
@Slf4j |
||||
|
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) { |
||||
|
OAuth2User oauth2User = new OAuth2User(); |
||||
|
Map<String, Object> attributes = token.getPrincipal().getAttributes(); |
||||
|
String email = getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); |
||||
|
oauth2User.setEmail(email); |
||||
|
oauth2User.setTenantName(getTenantName(attributes, config)); |
||||
|
if (!StringUtils.isEmpty(config.getBasic().getLastNameAttributeKey())) { |
||||
|
String lastName = getStringAttributeByKey(attributes, config.getBasic().getLastNameAttributeKey()); |
||||
|
oauth2User.setLastName(lastName); |
||||
|
} |
||||
|
if (!StringUtils.isEmpty(config.getBasic().getFirstNameAttributeKey())) { |
||||
|
String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey()); |
||||
|
oauth2User.setFirstName(firstName); |
||||
|
} |
||||
|
if (!StringUtils.isEmpty(config.getBasic().getCustomerNameStrategyPattern())) { |
||||
|
StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); |
||||
|
String customerName = sub.replace(config.getBasic().getCustomerNameStrategyPattern()); |
||||
|
oauth2User.setCustomerName(customerName); |
||||
|
} |
||||
|
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.getBasic().isAllowUserCreation()); |
||||
|
} |
||||
|
|
||||
|
private String getTenantName(Map<String, Object> attributes, OAuth2ClientMapperConfig config) { |
||||
|
switch (config.getBasic().getTenantNameStrategy()) { |
||||
|
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_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!"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private String getStringAttributeByKey(Map<String, Object> attributes, String key) { |
||||
|
String result = null; |
||||
|
try { |
||||
|
result = (String) attributes.get(key); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
log.warn("Can't convert attribute to String by key " + key); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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 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; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.springframework.util.StringUtils; |
||||
|
import org.springframework.web.client.RestTemplate; |
||||
|
import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig; |
||||
|
import org.thingsboard.server.dao.oauth2.OAuth2User; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
|
||||
|
@Service(value = "customOAuth2ClientMapper") |
||||
|
@Slf4j |
||||
|
public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { |
||||
|
|
||||
|
private static final ObjectMapper json = new ObjectMapper(); |
||||
|
|
||||
|
private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); |
||||
|
|
||||
|
@Override |
||||
|
public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config) { |
||||
|
OAuth2User oauth2User = getOAuth2User(token, config.getCustom()); |
||||
|
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.getBasic().isAllowUserCreation()); |
||||
|
} |
||||
|
|
||||
|
public OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig custom) { |
||||
|
if (!StringUtils.isEmpty(custom.getUsername()) && !StringUtils.isEmpty(custom.getPassword())) { |
||||
|
restTemplateBuilder = restTemplateBuilder.basicAuthentication(custom.getUsername(), custom.getPassword()); |
||||
|
} |
||||
|
RestTemplate restTemplate = restTemplateBuilder.build(); |
||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
||||
|
import org.thingsboard.server.dao.oauth2.OAuth2ClientMapperConfig; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
|
||||
|
public interface OAuth2ClientMapper { |
||||
|
SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig config); |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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 lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Qualifier; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
@Component |
||||
|
@Slf4j |
||||
|
public class OAuth2ClientMapperProvider { |
||||
|
|
||||
|
@Autowired |
||||
|
@Qualifier("basicOAuth2ClientMapper") |
||||
|
private OAuth2ClientMapper basicOAuth2ClientMapper; |
||||
|
|
||||
|
@Autowired |
||||
|
@Qualifier("customOAuth2ClientMapper") |
||||
|
private OAuth2ClientMapper customOAuth2ClientMapper; |
||||
|
|
||||
|
public OAuth2ClientMapper getOAuth2ClientMapperByType(String oauth2ClientType) { |
||||
|
switch (oauth2ClientType) { |
||||
|
case "custom": |
||||
|
return customOAuth2ClientMapper; |
||||
|
case "basic": |
||||
|
return basicOAuth2ClientMapper; |
||||
|
default: |
||||
|
throw new RuntimeException("OAuth2ClientMapper with type " + oauth2ClientType + " is not supported!"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
import org.springframework.security.core.Authentication; |
||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; |
||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.dao.oauth2.OAuth2Client; |
||||
|
import org.thingsboard.server.dao.oauth2.OAuth2Configuration; |
||||
|
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
import org.thingsboard.server.service.security.model.token.JwtToken; |
||||
|
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
import javax.servlet.http.HttpServletResponse; |
||||
|
import java.io.IOException; |
||||
|
|
||||
|
@Component(value = "oauth2AuthenticationSuccessHandler") |
||||
|
@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") |
||||
|
public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { |
||||
|
|
||||
|
private final JwtTokenFactory tokenFactory; |
||||
|
private final RefreshTokenRepository refreshTokenRepository; |
||||
|
private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; |
||||
|
private final OAuth2Configuration oauth2Configuration; |
||||
|
|
||||
|
@Autowired |
||||
|
public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, |
||||
|
final RefreshTokenRepository refreshTokenRepository, |
||||
|
final OAuth2ClientMapperProvider oauth2ClientMapperProvider, |
||||
|
final OAuth2Configuration oauth2Configuration) { |
||||
|
this.tokenFactory = tokenFactory; |
||||
|
this.refreshTokenRepository = refreshTokenRepository; |
||||
|
this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; |
||||
|
this.oauth2Configuration = oauth2Configuration; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onAuthenticationSuccess(HttpServletRequest request, |
||||
|
HttpServletResponse response, |
||||
|
Authentication authentication) throws IOException { |
||||
|
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; |
||||
|
|
||||
|
OAuth2Client oauth2Client = oauth2Configuration.getClientByRegistrationId(token.getAuthorizedClientRegistrationId()); |
||||
|
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(oauth2Client.getMapperConfig().getType()); |
||||
|
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oauth2Client.getMapperConfig()); |
||||
|
|
||||
|
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); |
||||
|
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); |
||||
|
|
||||
|
getRedirectStrategy().sendRedirect(request, response, "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.dao.oauth2; |
||||
|
|
||||
|
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public interface OAuth2Service { |
||||
|
|
||||
|
List<OAuth2ClientInfo> getOAuth2Clients(); |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.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; |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.common.data.id; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonCreator; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
|
||||
|
public class OAuth2IntegrationId extends UUIDBased { |
||||
|
|
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
@JsonCreator |
||||
|
public OAuth2IntegrationId(@JsonProperty("id") UUID id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
public static OAuth2IntegrationId fromString(String oauth2IntegrationId) { |
||||
|
return new OAuth2IntegrationId(UUID.fromString(oauth2IntegrationId)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.common.data.oauth2; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import org.thingsboard.server.common.data.BaseData; |
||||
|
import org.thingsboard.server.common.data.id.OAuth2IntegrationId; |
||||
|
|
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@Data |
||||
|
public class OAuth2ClientInfo extends BaseData<OAuth2IntegrationId> { |
||||
|
|
||||
|
private String name; |
||||
|
private String icon; |
||||
|
private String url; |
||||
|
|
||||
|
public OAuth2ClientInfo() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public OAuth2ClientInfo(OAuth2IntegrationId id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
public OAuth2ClientInfo(OAuth2ClientInfo oauth2ClientInfo) { |
||||
|
super(oauth2ClientInfo); |
||||
|
this.name = oauth2ClientInfo.getName(); |
||||
|
this.icon = oauth2ClientInfo.getIcon(); |
||||
|
this.url = oauth2ClientInfo.getUrl(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.dao.oauth2; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class OAuth2Client { |
||||
|
|
||||
|
private String loginButtonLabel; |
||||
|
private String loginButtonIcon; |
||||
|
private String clientName; |
||||
|
private String clientId; |
||||
|
private String clientSecret; |
||||
|
private String accessTokenUri; |
||||
|
private String authorizationUri; |
||||
|
private String scope; |
||||
|
private String redirectUriTemplate; |
||||
|
private String jwkSetUri; |
||||
|
private String authorizationGrantType; |
||||
|
private String clientAuthenticationMethod; |
||||
|
private String userInfoUri; |
||||
|
private String userNameAttributeName; |
||||
|
private OAuth2ClientMapperConfig mapperConfig; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.dao.oauth2; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class OAuth2ClientMapperConfig { |
||||
|
|
||||
|
private String type; |
||||
|
private BasicOAuth2ClientMapperConfig basic; |
||||
|
private CustomOAuth2ClientMapperConfig custom; |
||||
|
|
||||
|
@Data |
||||
|
public static class BasicOAuth2ClientMapperConfig { |
||||
|
private boolean allowUserCreation; |
||||
|
private String emailAttributeKey; |
||||
|
private String firstNameAttributeKey; |
||||
|
private String lastNameAttributeKey; |
||||
|
private String tenantNameStrategy; |
||||
|
private String tenantNameStrategyPattern; |
||||
|
private String customerNameStrategyPattern; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class CustomOAuth2ClientMapperConfig { |
||||
|
private String url; |
||||
|
private String username; |
||||
|
private String password; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.dao.oauth2; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration; |
||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; |
||||
|
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; |
||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Configuration |
||||
|
@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") |
||||
|
@ConfigurationProperties(prefix = "security.oauth2") |
||||
|
@Data |
||||
|
@Slf4j |
||||
|
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 (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()) |
||||
|
.tokenUri(client.getAccessTokenUri()) |
||||
|
.redirectUriTemplate(client.getRedirectUriTemplate()) |
||||
|
.scope(client.getScope().split(",")) |
||||
|
.clientName(client.getClientName()) |
||||
|
.authorizationGrantType(new AuthorizationGrantType(client.getAuthorizationGrantType())) |
||||
|
.userInfoUri(client.getUserInfoUri()) |
||||
|
.userNameAttributeName(client.getUserNameAttributeName()) |
||||
|
.jwkSetUri(client.getJwkSetUri()) |
||||
|
.clientAuthenticationMethod(new ClientAuthenticationMethod(client.getClientAuthenticationMethod())) |
||||
|
.build(); |
||||
|
result.add(registration); |
||||
|
} |
||||
|
return new InMemoryClientRegistrationRepository(result); |
||||
|
} |
||||
|
|
||||
|
public OAuth2Client getClientByRegistrationId(String registrationId) { |
||||
|
OAuth2Client result = null; |
||||
|
if (clients != null && !clients.isEmpty()) { |
||||
|
for (String key : clients.keySet()) { |
||||
|
if (key.equals(registrationId)) { |
||||
|
result = clients.get(key); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 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.dao.oauth2; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
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 |
||||
|
public class OAuth2ServiceImpl implements OAuth2Service { |
||||
|
|
||||
|
@Autowired(required = false) |
||||
|
OAuth2Configuration oauth2Configuration; |
||||
|
|
||||
|
@Override |
||||
|
public List<OAuth2ClientInfo> getOAuth2Clients() { |
||||
|
if (oauth2Configuration == null || !oauth2Configuration.isEnabled()) { |
||||
|
return Collections.emptyList(); |
||||
|
} |
||||
|
List<OAuth2ClientInfo> result = new ArrayList<>(); |
||||
|
for (Map.Entry<String, OAuth2Client> entry : oauth2Configuration.getClients().entrySet()) { |
||||
|
OAuth2ClientInfo client = new OAuth2ClientInfo(); |
||||
|
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…
Reference in new issue