Add platform type filter for oauth2 registrations.

This commit is contained in:
Igor Kulikov 2021-06-10 17:01:16 +03:00
parent 8e4f8594f7
commit cc531daaee
22 changed files with 187 additions and 44 deletions

View File

@ -96,6 +96,7 @@ CREATE TABLE IF NOT EXISTS oauth2_registration (
authorization_uri varchar(255), authorization_uri varchar(255),
token_uri varchar(255), token_uri varchar(255),
scope varchar(255), scope varchar(255),
platforms varchar(255),
user_info_uri varchar(255), user_info_uri varchar(255),
user_name_attribute_name varchar(255), user_name_attribute_name varchar(255),
jwk_set_uri varchar(255), jwk_set_uri varchar(255),

View File

@ -26,9 +26,11 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2Info; import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
@ -51,7 +53,8 @@ public class OAuth2Controller extends BaseController {
@RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request, public List<OAuth2ClientInfo> getOAuth2Clients(HttpServletRequest request,
@RequestParam(required = false) String pkgName) throws ThingsboardException { @RequestParam(required = false) String pkgName,
@RequestParam(required = false) String platform) throws ThingsboardException {
try { try {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort());
@ -61,7 +64,13 @@ public class OAuth2Controller extends BaseController {
log.debug("Header: {} {}", header, request.getHeader(header)); log.debug("Header: {} {}", header, request.getHeader(header));
} }
} }
return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName); PlatformType platformType = null;
if (StringUtils.isNotEmpty(platform)) {
try {
platformType = PlatformType.valueOf(platform);
} catch (Exception e) {}
}
return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName, platformType);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

View File

@ -199,6 +199,7 @@ public class ThingsboardInstallService {
databaseEntitiesUpgradeService.upgradeDatabase("3.2.2"); databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
dataUpdateService.updateData("3.2.2"); dataUpdateService.updateData("3.2.2");
systemDataLoaderService.createOAuth2Templates();
log.info("Updating system data..."); log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.updateSystemWidgets();

View File

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.oauth2;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2Info; import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams; import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams;
@ -25,7 +26,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
public interface OAuth2Service { public interface OAuth2Service {
List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName, String pkgName); List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName, String pkgName, PlatformType platformType);
@Deprecated @Deprecated
void saveOAuth2Params(OAuth2ClientsParams oauth2Params); void saveOAuth2Params(OAuth2ClientsParams oauth2Params);

View File

@ -46,6 +46,7 @@ public class OAuth2Registration extends SearchTextBasedWithAdditionalInfo<OAuth2
private String clientAuthenticationMethod; private String clientAuthenticationMethod;
private String loginButtonLabel; private String loginButtonLabel;
private String loginButtonIcon; private String loginButtonIcon;
private List<PlatformType> platforms;
public OAuth2Registration(OAuth2Registration registration) { public OAuth2Registration(OAuth2Registration registration) {
super(registration); super(registration);
@ -62,6 +63,7 @@ public class OAuth2Registration extends SearchTextBasedWithAdditionalInfo<OAuth2
this.clientAuthenticationMethod = registration.clientAuthenticationMethod; this.clientAuthenticationMethod = registration.clientAuthenticationMethod;
this.loginButtonLabel = registration.loginButtonLabel; this.loginButtonLabel = registration.loginButtonLabel;
this.loginButtonIcon = registration.loginButtonIcon; this.loginButtonIcon = registration.loginButtonIcon;
this.platforms = registration.platforms;
} }
@Override @Override

View File

@ -39,5 +39,6 @@ public class OAuth2RegistrationInfo {
private String clientAuthenticationMethod; private String clientAuthenticationMethod;
private String loginButtonLabel; private String loginButtonLabel;
private String loginButtonIcon; private String loginButtonIcon;
private List<PlatformType> platforms;
private JsonNode additionalInfo; private JsonNode additionalInfo;
} }

View File

@ -0,0 +1,20 @@
/**
* Copyright © 2016-2021 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;
public enum PlatformType {
WEB, ANDROID, IOS
}

View File

@ -433,6 +433,7 @@ public class ModelConstants {
public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri"; public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri";
public static final String OAUTH2_TOKEN_URI_PROPERTY = "token_uri"; public static final String OAUTH2_TOKEN_URI_PROPERTY = "token_uri";
public static final String OAUTH2_SCOPE_PROPERTY = "scope"; public static final String OAUTH2_SCOPE_PROPERTY = "scope";
public static final String OAUTH2_PLATFORMS_PROPERTY = "platforms";
public static final String OAUTH2_USER_INFO_URI_PROPERTY = "user_info_uri"; public static final String OAUTH2_USER_INFO_URI_PROPERTY = "user_info_uri";
public static final String OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY = "user_name_attribute_name"; public static final String OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY = "user_name_attribute_name";
public static final String OAUTH2_JWK_SET_URI_PROPERTY = "jwk_set_uri"; public static final String OAUTH2_JWK_SET_URI_PROPERTY = "jwk_set_uri";

View File

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.model.sql; package org.thingsboard.server.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.oauth2.TenantNameStrategyType; import org.thingsboard.server.common.data.oauth2.TenantNameStrategyType;
import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
@ -38,7 +40,9 @@ import javax.persistence.EnumType;
import javax.persistence.Enumerated; import javax.persistence.Enumerated;
import javax.persistence.Table; import javax.persistence.Table;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -59,6 +63,8 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity<OAuth2Registration>
private String tokenUri; private String tokenUri;
@Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY) @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY)
private String scope; private String scope;
@Column(name = ModelConstants.OAUTH2_PLATFORMS_PROPERTY)
private String platforms;
@Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY) @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY)
private String userInfoUri; private String userInfoUri;
@Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY) @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY)
@ -125,6 +131,7 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity<OAuth2Registration>
this.authorizationUri = registration.getAuthorizationUri(); this.authorizationUri = registration.getAuthorizationUri();
this.tokenUri = registration.getAccessTokenUri(); this.tokenUri = registration.getAccessTokenUri();
this.scope = registration.getScope().stream().reduce((result, element) -> result + "," + element).orElse(""); this.scope = registration.getScope().stream().reduce((result, element) -> result + "," + element).orElse("");
this.platforms = registration.getPlatforms() != null ? registration.getPlatforms().stream().map(Enum::name).reduce((result, element) -> result + "," + element).orElse("") : "";
this.userInfoUri = registration.getUserInfoUri(); this.userInfoUri = registration.getUserInfoUri();
this.userNameAttributeName = registration.getUserNameAttributeName(); this.userNameAttributeName = registration.getUserNameAttributeName();
this.jwkSetUri = registration.getJwkSetUri(); this.jwkSetUri = registration.getJwkSetUri();
@ -201,6 +208,8 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity<OAuth2Registration>
registration.setAuthorizationUri(authorizationUri); registration.setAuthorizationUri(authorizationUri);
registration.setAccessTokenUri(tokenUri); registration.setAccessTokenUri(tokenUri);
registration.setScope(Arrays.asList(scope.split(","))); registration.setScope(Arrays.asList(scope.split(",")));
registration.setPlatforms(StringUtils.isNotEmpty(platforms) ? Arrays.stream(platforms.split(","))
.map(str -> PlatformType.valueOf(str)).collect(Collectors.toList()) : Collections.emptyList());
registration.setUserInfoUri(userInfoUri); registration.setUserInfoUri(userInfoUri);
registration.setUserNameAttributeName(userNameAttributeName); registration.setUserNameAttributeName(userNameAttributeName);
registration.setJwkSetUri(jwkSetUri); registration.setJwkSetUri(jwkSetUri);

View File

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.oauth2; package org.thingsboard.server.dao.oauth2;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.common.data.oauth2.SchemeType;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
@ -24,7 +25,7 @@ import java.util.UUID;
public interface OAuth2RegistrationDao extends Dao<OAuth2Registration> { public interface OAuth2RegistrationDao extends Dao<OAuth2Registration> {
List<OAuth2Registration> findEnabledByDomainSchemesDomainNameAndPkgName(List<SchemeType> domainSchemes, String domainName, String pkgName); List<OAuth2Registration> findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(List<SchemeType> domainSchemes, String domainName, String pkgName, PlatformType platformType);
List<OAuth2Registration> findByOAuth2ParamsId(UUID oauth2ParamsId); List<OAuth2Registration> findByOAuth2ParamsId(UUID oauth2ParamsId);

View File

@ -65,8 +65,8 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se
private OAuth2MobileDao oauth2MobileDao; private OAuth2MobileDao oauth2MobileDao;
@Override @Override
public List<OAuth2ClientInfo> getOAuth2Clients(String domainSchemeStr, String domainName, String pkgName) { public List<OAuth2ClientInfo> getOAuth2Clients(String domainSchemeStr, String domainName, String pkgName, PlatformType platformType) {
log.trace("Executing getOAuth2Clients [{}://{}]", domainSchemeStr, domainName); log.trace("Executing getOAuth2Clients [{}://{}] pkgName=[{}] platformType=[{}]", domainSchemeStr, domainName, pkgName, platformType);
if (domainSchemeStr == null) { if (domainSchemeStr == null) {
throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME); throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME);
} }
@ -77,7 +77,9 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se
throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME); throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME);
} }
validateString(domainName, INCORRECT_DOMAIN_NAME + domainName); validateString(domainName, INCORRECT_DOMAIN_NAME + domainName);
return oauth2RegistrationDao.findEnabledByDomainSchemesDomainNameAndPkgName(Arrays.asList(domainScheme, SchemeType.MIXED), domainName, pkgName).stream() return oauth2RegistrationDao.findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(
Arrays.asList(domainScheme, SchemeType.MIXED), domainName, pkgName, platformType)
.stream()
.map(OAuth2Utils::toClientInfo) .map(OAuth2Utils::toClientInfo)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View File

@ -127,6 +127,7 @@ public class OAuth2Utils {
.authorizationUri(registration.getAuthorizationUri()) .authorizationUri(registration.getAuthorizationUri())
.accessTokenUri(registration.getAccessTokenUri()) .accessTokenUri(registration.getAccessTokenUri())
.scope(registration.getScope()) .scope(registration.getScope())
.platforms(registration.getPlatforms())
.userInfoUri(registration.getUserInfoUri()) .userInfoUri(registration.getUserInfoUri())
.userNameAttributeName(registration.getUserNameAttributeName()) .userNameAttributeName(registration.getUserNameAttributeName())
.jwkSetUri(registration.getJwkSetUri()) .jwkSetUri(registration.getJwkSetUri())
@ -167,6 +168,7 @@ public class OAuth2Utils {
registration.setAuthorizationUri(registrationInfo.getAuthorizationUri()); registration.setAuthorizationUri(registrationInfo.getAuthorizationUri());
registration.setAccessTokenUri(registrationInfo.getAccessTokenUri()); registration.setAccessTokenUri(registrationInfo.getAccessTokenUri());
registration.setScope(registrationInfo.getScope()); registration.setScope(registrationInfo.getScope());
registration.setPlatforms(registrationInfo.getPlatforms());
registration.setUserInfoUri(registrationInfo.getUserInfoUri()); registration.setUserInfoUri(registrationInfo.getUserInfoUri());
registration.setUserNameAttributeName(registrationInfo.getUserNameAttributeName()); registration.setUserNameAttributeName(registrationInfo.getUserNameAttributeName());
registration.setJwkSetUri(registrationInfo.getJwkSetUri()); registration.setJwkSetUri(registrationInfo.getJwkSetUri());
@ -224,6 +226,7 @@ public class OAuth2Utils {
.loginButtonLabel(clientRegistrationDto.getLoginButtonLabel()) .loginButtonLabel(clientRegistrationDto.getLoginButtonLabel())
.loginButtonIcon(clientRegistrationDto.getLoginButtonIcon()) .loginButtonIcon(clientRegistrationDto.getLoginButtonIcon())
.additionalInfo(clientRegistrationDto.getAdditionalInfo()) .additionalInfo(clientRegistrationDto.getAdditionalInfo())
.platforms(Collections.emptyList())
.build(); .build();
} }

View File

@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.common.data.oauth2.SchemeType;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.sql.OAuth2RegistrationEntity; import org.thingsboard.server.dao.model.sql.OAuth2RegistrationEntity;
@ -45,8 +46,9 @@ public class JpaOAuth2RegistrationDao extends JpaAbstractDao<OAuth2RegistrationE
} }
@Override @Override
public List<OAuth2Registration> findEnabledByDomainSchemesDomainNameAndPkgName(List<SchemeType> domainSchemes, String domainName, String pkgName) { public List<OAuth2Registration> findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(List<SchemeType> domainSchemes, String domainName, String pkgName, PlatformType platformType) {
return DaoUtil.convertDataList(repository.findAllEnabledByDomainSchemesNameAndPkgName(domainSchemes, domainName, pkgName)); return DaoUtil.convertDataList(repository.findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(domainSchemes, domainName, pkgName,
platformType != null ? "%" + platformType.name() + "%" : null));
} }
@Override @Override

View File

@ -30,14 +30,15 @@ public interface OAuth2RegistrationRepository extends CrudRepository<OAuth2Regis
"FROM OAuth2RegistrationEntity reg " + "FROM OAuth2RegistrationEntity reg " +
"LEFT JOIN OAuth2ParamsEntity params on reg.oauth2ParamsId = params.id " + "LEFT JOIN OAuth2ParamsEntity params on reg.oauth2ParamsId = params.id " +
"LEFT JOIN OAuth2DomainEntity domain on reg.oauth2ParamsId = domain.oauth2ParamsId " + "LEFT JOIN OAuth2DomainEntity domain on reg.oauth2ParamsId = domain.oauth2ParamsId " +
"LEFT JOIN OAuth2MobileEntity mobile on reg.oauth2ParamsId = mobile.oauth2ParamsId " +
"WHERE params.enabled = true " + "WHERE params.enabled = true " +
"AND domain.domainName = :domainName " + "AND domain.domainName = :domainName " +
"AND domain.domainScheme IN (:domainSchemes) " + "AND domain.domainScheme IN (:domainSchemes) " +
"AND (:pkgName IS NULL OR mobile.pkgName = :pkgName)") "AND (:pkgName IS NULL OR EXISTS (SELECT mobile FROM OAuth2MobileEntity mobile WHERE mobile.oauth2ParamsId = reg.oauth2ParamsId AND mobile.pkgName = :pkgName)) " +
List<OAuth2RegistrationEntity> findAllEnabledByDomainSchemesNameAndPkgName(@Param("domainSchemes") List<SchemeType> domainSchemes, "AND (:platformFilter IS NULL OR reg.platforms IS NULL OR reg.platforms = '' OR reg.platforms LIKE :platformFilter)")
@Param("domainName") String domainName, List<OAuth2RegistrationEntity> findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(@Param("domainSchemes") List<SchemeType> domainSchemes,
@Param("pkgName") String pkgName); @Param("domainName") String domainName,
@Param("pkgName") String pkgName,
@Param("platformFilter") String platformFilter);
List<OAuth2RegistrationEntity> findByOauth2ParamsId(UUID oauth2ParamsId); List<OAuth2RegistrationEntity> findByOauth2ParamsId(UUID oauth2ParamsId);

View File

@ -391,6 +391,7 @@ CREATE TABLE IF NOT EXISTS oauth2_registration (
authorization_uri varchar(255), authorization_uri varchar(255),
token_uri varchar(255), token_uri varchar(255),
scope varchar(255), scope varchar(255),
platforms varchar(255),
user_info_uri varchar(255), user_info_uri varchar(255),
user_name_attribute_name varchar(255), user_name_attribute_name varchar(255),
jwk_set_uri varchar(255), jwk_set_uri varchar(255),

View File

@ -428,6 +428,7 @@ CREATE TABLE IF NOT EXISTS oauth2_registration (
authorization_uri varchar(255), authorization_uri varchar(255),
token_uri varchar(255), token_uri varchar(255),
scope varchar(255), scope varchar(255),
platforms varchar(255),
user_info_uri varchar(255), user_info_uri varchar(255),
user_name_attribute_name varchar(255), user_name_attribute_name varchar(255),
jwk_set_uri varchar(255), jwk_set_uri varchar(255),

View File

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.service; package org.thingsboard.server.dao.service;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2MobileInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ParamsInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ParamsInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.common.data.oauth2.SchemeType;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Service;
@ -231,10 +233,10 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null))
.collect(Collectors.toList()); .collect(Collectors.toList());
List<OAuth2ClientInfo> nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain", null); List<OAuth2ClientInfo> nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain", null, null);
Assert.assertTrue(nonExistentDomainClients.isEmpty()); Assert.assertTrue(nonExistentDomainClients.isEmpty());
List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null); List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null);
Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size());
firstGroupClientInfos.forEach(firstGroupClientInfo -> { firstGroupClientInfos.forEach(firstGroupClientInfo -> {
Assert.assertTrue( Assert.assertTrue(
@ -244,10 +246,10 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
); );
}); });
List<OAuth2ClientInfo> firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null); List<OAuth2ClientInfo> firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null, null);
Assert.assertTrue(firstDomainHttpsClients.isEmpty()); Assert.assertTrue(firstDomainHttpsClients.isEmpty());
List<OAuth2ClientInfo> fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain", null); List<OAuth2ClientInfo> fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain", null, null);
Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpClients.size()); Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpClients.size());
secondGroupClientInfos.forEach(secondGroupClientInfo -> { secondGroupClientInfos.forEach(secondGroupClientInfo -> {
Assert.assertTrue( Assert.assertTrue(
@ -256,7 +258,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
&& clientInfo.getName().equals(secondGroupClientInfo.getName())) && clientInfo.getName().equals(secondGroupClientInfo.getName()))
); );
}); });
List<OAuth2ClientInfo> fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain", null); List<OAuth2ClientInfo> fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain", null, null);
Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpsClients.size()); Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpsClients.size());
secondGroupClientInfos.forEach(secondGroupClientInfo -> { secondGroupClientInfos.forEach(secondGroupClientInfo -> {
Assert.assertTrue( Assert.assertTrue(
@ -266,7 +268,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
); );
}); });
List<OAuth2ClientInfo> secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null); List<OAuth2ClientInfo> secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null);
Assert.assertEquals(firstGroupClientInfos.size() + secondGroupClientInfos.size(), secondDomainHttpClients.size()); Assert.assertEquals(firstGroupClientInfos.size() + secondGroupClientInfos.size(), secondDomainHttpClients.size());
firstGroupClientInfos.forEach(firstGroupClientInfo -> { firstGroupClientInfos.forEach(firstGroupClientInfo -> {
Assert.assertTrue( Assert.assertTrue(
@ -283,7 +285,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
); );
}); });
List<OAuth2ClientInfo> secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain", null); List<OAuth2ClientInfo> secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain", null, null);
Assert.assertEquals(firstGroupClientInfos.size() + thirdGroupClientInfos.size(), secondDomainHttpsClients.size()); Assert.assertEquals(firstGroupClientInfos.size() + thirdGroupClientInfos.size(), secondDomainHttpsClients.size());
firstGroupClientInfos.forEach(firstGroupClientInfo -> { firstGroupClientInfos.forEach(firstGroupClientInfo -> {
Assert.assertTrue( Assert.assertTrue(
@ -331,7 +333,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null))
.collect(Collectors.toList()); .collect(Collectors.toList());
List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null); List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null);
Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size());
firstGroupClientInfos.forEach(firstGroupClientInfo -> { firstGroupClientInfos.forEach(firstGroupClientInfo -> {
Assert.assertTrue( Assert.assertTrue(
@ -341,7 +343,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
); );
}); });
List<OAuth2ClientInfo> firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null); List<OAuth2ClientInfo> firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null, null);
Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpsClients.size()); Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpsClients.size());
firstGroupClientInfos.forEach(firstGroupClientInfo -> { firstGroupClientInfos.forEach(firstGroupClientInfo -> {
Assert.assertTrue( Assert.assertTrue(
@ -381,13 +383,13 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
oAuth2Service.saveOAuth2Info(oAuth2Info); oAuth2Service.saveOAuth2Info(oAuth2Info);
List<OAuth2ClientInfo> secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null); List<OAuth2ClientInfo> secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null);
Assert.assertEquals(5, secondDomainHttpClients.size()); Assert.assertEquals(5, secondDomainHttpClients.size());
oAuth2Info.setEnabled(false); oAuth2Info.setEnabled(false);
oAuth2Service.saveOAuth2Info(oAuth2Info); oAuth2Service.saveOAuth2Info(oAuth2Info);
List<OAuth2ClientInfo> secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null); List<OAuth2ClientInfo> secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null);
Assert.assertEquals(0, secondDomainHttpDisabledClients.size()); Assert.assertEquals(0, secondDomainHttpDisabledClients.size());
} }
@ -520,7 +522,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info();
Assert.assertEquals(oAuth2Info, foundOAuth2Info); Assert.assertEquals(oAuth2Info, foundOAuth2Info);
List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1"); List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", null);
Assert.assertEquals(3, firstDomainHttpClients.size()); Assert.assertEquals(3, firstDomainHttpClients.size());
for (OAuth2ClientInfo clientInfo : firstDomainHttpClients) { for (OAuth2ClientInfo clientInfo : firstDomainHttpClients) {
String[] segments = clientInfo.getUrl().split("/"); String[] segments = clientInfo.getUrl().split("/");
@ -536,6 +538,56 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
} }
} }
@Test
public void testFindClientsByPackageAndPlatform() {
OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(),
OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build()
))
.mobileInfos(Lists.newArrayList(
OAuth2MobileInfo.builder().pkgName("com.test.pkg1").callbackUrlScheme("testPkg1Callback").build(),
OAuth2MobileInfo.builder().pkgName("com.test.pkg2").callbackUrlScheme("testPkg2Callback").build()
))
.clientRegistrations(Lists.newArrayList(
validRegistrationInfo("Google", Arrays.asList(PlatformType.WEB, PlatformType.ANDROID)),
validRegistrationInfo("Facebook", Arrays.asList(PlatformType.IOS)),
validRegistrationInfo("GitHub", Collections.emptyList())
))
.build(),
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(),
OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build()
))
.mobileInfos(Collections.emptyList())
.clientRegistrations(Lists.newArrayList(
validRegistrationInfo(),
validRegistrationInfo()
))
.build()
));
oAuth2Service.saveOAuth2Info(oAuth2Info);
OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info();
Assert.assertEquals(oAuth2Info, foundOAuth2Info);
List<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null);
Assert.assertEquals(3, firstDomainHttpClients.size());
List<OAuth2ClientInfo> pkg1Clients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", null);
Assert.assertEquals(3, pkg1Clients.size());
List<OAuth2ClientInfo> pkg1AndroidClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", PlatformType.ANDROID);
Assert.assertEquals(2, pkg1AndroidClients.size());
Assert.assertTrue(pkg1AndroidClients.stream().anyMatch(client -> client.getName().equals("Google")));
Assert.assertTrue(pkg1AndroidClients.stream().anyMatch(client -> client.getName().equals("GitHub")));
List<OAuth2ClientInfo> pkg1IOSClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", PlatformType.IOS);
Assert.assertEquals(2, pkg1IOSClients.size());
Assert.assertTrue(pkg1IOSClients.stream().anyMatch(client -> client.getName().equals("Facebook")));
Assert.assertTrue(pkg1IOSClients.stream().anyMatch(client -> client.getName().equals("GitHub")));
}
private OAuth2Info createDefaultOAuth2Info() { private OAuth2Info createDefaultOAuth2Info() {
return new OAuth2Info(true, Lists.newArrayList( return new OAuth2Info(true, Lists.newArrayList(
OAuth2ParamsInfo.builder() OAuth2ParamsInfo.builder()
@ -567,17 +619,22 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest {
} }
private OAuth2RegistrationInfo validRegistrationInfo() { private OAuth2RegistrationInfo validRegistrationInfo() {
return validRegistrationInfo(null, Collections.emptyList());
}
private OAuth2RegistrationInfo validRegistrationInfo(String label, List<PlatformType> platforms) {
return OAuth2RegistrationInfo.builder() return OAuth2RegistrationInfo.builder()
.clientId(UUID.randomUUID().toString()) .clientId(UUID.randomUUID().toString())
.clientSecret(UUID.randomUUID().toString()) .clientSecret(UUID.randomUUID().toString())
.authorizationUri(UUID.randomUUID().toString()) .authorizationUri(UUID.randomUUID().toString())
.accessTokenUri(UUID.randomUUID().toString()) .accessTokenUri(UUID.randomUUID().toString())
.scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())) .scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()))
.platforms(platforms == null ? Collections.emptyList() : platforms)
.userInfoUri(UUID.randomUUID().toString()) .userInfoUri(UUID.randomUUID().toString())
.userNameAttributeName(UUID.randomUUID().toString()) .userNameAttributeName(UUID.randomUUID().toString())
.jwkSetUri(UUID.randomUUID().toString()) .jwkSetUri(UUID.randomUUID().toString())
.clientAuthenticationMethod(UUID.randomUUID().toString()) .clientAuthenticationMethod(UUID.randomUUID().toString())
.loginButtonLabel(UUID.randomUUID().toString()) .loginButtonLabel(label != null ? label : UUID.randomUUID().toString())
.loginButtonIcon(UUID.randomUUID().toString()) .loginButtonIcon(UUID.randomUUID().toString())
.additionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())) .additionalInfo(mapper.createObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString()))
.mapperConfig( .mapperConfig(

View File

@ -44,7 +44,7 @@ import { AdminService } from '@core/http/admin.service';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { OAuth2ClientInfo } from '@shared/models/oauth2.models'; import { OAuth2ClientInfo, PlatformType } from '@shared/models/oauth2.models';
import { isDefinedAndNotNull, isMobileApp } from '@core/utils'; import { isDefinedAndNotNull, isMobileApp } from '@core/utils';
@Injectable({ @Injectable({
@ -204,11 +204,8 @@ export class AuthService {
} }
} }
public loadOAuth2Clients(pkgName?: string): Observable<Array<OAuth2ClientInfo>> { public loadOAuth2Clients(): Observable<Array<OAuth2ClientInfo>> {
let url = '/api/noauth/oauth2Clients'; const url = '/api/noauth/oauth2Clients?platform=' + PlatformType.WEB;
if (isDefinedAndNotNull(pkgName)) {
url += `?pkgName=${pkgName}`;
}
return this.http.post<Array<OAuth2ClientInfo>>(url, return this.http.post<Array<OAuth2ClientInfo>>(url,
null, defaultHttpOptions()).pipe( null, defaultHttpOptions()).pipe(
catchError(err => of([])), catchError(err => of([])),

View File

@ -201,18 +201,27 @@
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<section [formGroupName]="j"> <section [formGroupName]="j">
<section formGroupName="additionalInfo" fxLayout="row"> <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block"> <section fxFlex formGroupName="additionalInfo" fxLayout="row">
<mat-label translate>admin.oauth2.login-provider</mat-label> <mat-form-field fxFlex class="mat-block">
<mat-select formControlName="providerName"> <mat-label translate>admin.oauth2.login-provider</mat-label>
<mat-option *ngFor="let provider of templateProvider" [value]="provider"> <mat-select formControlName="providerName">
{{ provider }} <mat-option *ngFor="let provider of templateProvider" [value]="provider">
{{ provider }}
</mat-option>
</mat-select>
</mat-form-field>
<div [tb-help]="getHelpLink(registration)" *ngIf="!isCustomProvider(registration)"></div>
</section>
<mat-form-field floatLabel="always" fxFlex class="mat-block">
<mat-label translate>admin.oauth2.allowed-platforms</mat-label>
<mat-select formControlName="platforms" multiple placeholder="{{ 'admin.oauth2.all-platforms' | translate }}">
<mat-option *ngFor="let platform of platformTypes" [value]="platform">
{{ platformTypeTranslations.get(platform) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div [tb-help]="getHelpLink(registration)" *ngIf="!isCustomProvider(registration)"></div> </div>
</section>
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex class="mat-block"> <mat-form-field fxFlex class="mat-block">
<mat-label translate>admin.oauth2.client-id</mat-label> <mat-label translate>admin.oauth2.client-id</mat-label>

View File

@ -28,7 +28,8 @@ import {
OAuth2DomainInfo, OAuth2DomainInfo,
OAuth2Info, OAuth2MobileInfo, OAuth2Info, OAuth2MobileInfo,
OAuth2ParamsInfo, OAuth2ParamsInfo,
OAuth2RegistrationInfo, OAuth2RegistrationInfo, PlatformType,
platformTypeTranslations,
TenantNameStrategy TenantNameStrategy
} from '@shared/models/oauth2.models'; } from '@shared/models/oauth2.models';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@ -99,6 +100,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
tenantNameStrategies = Object.keys(TenantNameStrategy); tenantNameStrategies = Object.keys(TenantNameStrategy);
protocols = Object.keys(DomainSchema); protocols = Object.keys(DomainSchema);
domainSchemaTranslations = domainSchemaTranslations; domainSchemaTranslations = domainSchemaTranslations;
platformTypes = Object.keys(PlatformType);
platformTypeTranslations = platformTypeTranslations;
templateProvider = ['Custom']; templateProvider = ['Custom'];
@ -293,6 +296,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
additionalInfo: this.fb.group({ additionalInfo: this.fb.group({
providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required] providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required]
}), }),
platforms: [registration?.platforms ? registration.platforms : []],
loginButtonLabel: [registration?.loginButtonLabel ? registration.loginButtonLabel : null, Validators.required], loginButtonLabel: [registration?.loginButtonLabel ? registration.loginButtonLabel : null, Validators.required],
loginButtonIcon: [registration?.loginButtonIcon ? registration.loginButtonIcon : null], loginButtonIcon: [registration?.loginButtonIcon ? registration.loginButtonIcon : null],
clientId: [registration?.clientId ? registration.clientId : '', Validators.required], clientId: [registration?.clientId ? registration.clientId : '', Validators.required],

View File

@ -63,6 +63,20 @@ export enum TenantNameStrategy{
CUSTOM = 'CUSTOM' CUSTOM = 'CUSTOM'
} }
export enum PlatformType {
WEB = 'WEB',
ANDROID = 'ANDROID',
IOS = 'IOS'
}
export const platformTypeTranslations = new Map<PlatformType, string>(
[
[PlatformType.WEB, 'admin.oauth2.platform-web'],
[PlatformType.ANDROID, 'admin.oauth2.platform-android'],
[PlatformType.IOS, 'admin.oauth2.platform-ios']
]
);
export interface OAuth2ClientRegistrationTemplate extends OAuth2RegistrationInfo{ export interface OAuth2ClientRegistrationTemplate extends OAuth2RegistrationInfo{
comment: string; comment: string;
createdTime: number; createdTime: number;
@ -80,6 +94,7 @@ export interface OAuth2RegistrationInfo {
accessTokenUri: string; accessTokenUri: string;
authorizationUri: string; authorizationUri: string;
scope: string[]; scope: string[];
platforms: PlatformType[];
jwkSetUri?: string; jwkSetUri?: string;
userInfoUri: string; userInfoUri: string;
clientAuthenticationMethod: ClientAuthenticationMethod; clientAuthenticationMethod: ClientAuthenticationMethod;

View File

@ -228,7 +228,12 @@
"mobile-callback-url-scheme": "Callback URL scheme", "mobile-callback-url-scheme": "Callback URL scheme",
"add-mobile-app": "Add application", "add-mobile-app": "Add application",
"delete-mobile-app": "Delete application info", "delete-mobile-app": "Delete application info",
"providers": "Providers" "providers": "Providers",
"platform-web": "Web",
"platform-android": "Android",
"platform-ios": "iOS",
"all-platforms": "All platforms",
"allowed-platforms": "Allowed platforms"
} }
}, },
"alarm": { "alarm": {