Browse Source

Merge pull request #10881 from YevhenBondarenko/feature/app-secret-upgrade

[Mobile App] added upgrade script for update mobile secretApp
pull/10893/head
Viacheslav Klimov 2 years ago
committed by GitHub
parent
commit
f8c0414d56
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  2. 51
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  3. 2
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  4. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java
  5. 17
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html
  6. 23
      ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts
  7. 2
      ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts
  8. 4
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -136,7 +136,7 @@ public class ThingsboardInstallService {
dataUpdateService.updateData("3.6.4");
entityDatabaseSchemaService.createCustomerTitleUniqueConstraintIfNotExists();
systemDataLoaderService.updateDefaultNotificationConfigs(false);
systemDataLoaderService.updateJwtSettings();
systemDataLoaderService.updateSecuritySettings();
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
break;
default:

51
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -26,6 +26,7 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@ -68,6 +69,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.oauth2.OAuth2Mobile;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
@ -98,6 +100,7 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.oauth2.OAuth2MobileDao;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
@ -120,7 +123,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE;
import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.isSigningKeyDefault;
import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.validateTokenSigningKeyLength;
import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.validateKeyLength;
@Service
@Profile("install")
@ -146,6 +149,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
private final DeviceConnectivityConfiguration connectivityConfiguration;
private final QueueService queueService;
private final JwtSettingsService jwtSettingsService;
private final OAuth2MobileDao oAuth2MobileDao;
private final NotificationSettingsService notificationSettingsService;
private final NotificationTargetService notificationTargetService;
@ -269,21 +273,20 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Override
public void createRandomJwtSettings() throws Exception {
if (jwtSettingsService.getJwtSettings() == null) {
log.info("Creating JWT admin settings...");
var jwtSettings = new JwtSettings(this.tokenExpirationTime, this.refreshTokenExpTime, this.tokenIssuer, this.tokenSigningKey);
if (isSigningKeyDefault(jwtSettings) || !validateTokenSigningKeyLength(jwtSettings)) {
jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(
RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8)));
}
jwtSettingsService.saveJwtSettings(jwtSettings);
} else {
log.info("Skip creating JWT admin settings because they already exist.");
if (jwtSettingsService.getJwtSettings() == null) {
log.info("Creating JWT admin settings...");
var jwtSettings = new JwtSettings(this.tokenExpirationTime, this.refreshTokenExpTime, this.tokenIssuer, this.tokenSigningKey);
if (isSigningKeyDefault(jwtSettings) || !validateKeyLength(jwtSettings.getTokenSigningKey())) {
jwtSettings.setTokenSigningKey(generateRandomKey());
}
jwtSettingsService.saveJwtSettings(jwtSettings);
} else {
log.info("Skip creating JWT admin settings because they already exist.");
}
}
@Override
public void updateJwtSettings() {
public void updateSecuritySettings() {
JwtSettings jwtSettings = jwtSettingsService.getJwtSettings();
boolean invalidSignKey = false;
String warningMessage = null;
@ -291,7 +294,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
if (isSigningKeyDefault(jwtSettings)) {
warningMessage = "The platform is using the default JWT Signing Key, which is a security risk.";
invalidSignKey = true;
} else if (!validateTokenSigningKeyLength(jwtSettings)) {
} else if (!validateKeyLength(jwtSettings.getTokenSigningKey())) {
warningMessage = "The JWT Signing Key is shorter than 512 bits, which is a security risk.";
invalidSignKey = true;
}
@ -301,10 +304,28 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
"You can change the JWT Signing Key using the Web UI: " +
"Navigate to \"System settings -> Security settings\" while logged in as a System Administrator.", warningMessage);
jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(
RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8)));
jwtSettings.setTokenSigningKey(generateRandomKey());
jwtSettingsService.saveJwtSettings(jwtSettings);
}
List<OAuth2Mobile> mobiles = oAuth2MobileDao.find(TenantId.SYS_TENANT_ID);
if (CollectionUtils.isNotEmpty(mobiles)) {
mobiles.stream()
.filter(config -> !validateKeyLength(config.getAppSecret()))
.forEach(config -> {
log.warn("WARNING: The App secret is shorter than 512 bits, which is a security risk. " +
"A new Application Secret has been added automatically for Mobile Application [{}]. " +
"You can change the Application Secret using the Web UI: " +
"Navigate to \"Security settings -> OAuth2 -> Mobile applications\" while logged in as a System Administrator.", config.getPkgName());
config.setAppSecret(generateRandomKey());
oAuth2MobileDao.save(TenantId.SYS_TENANT_ID, config);
});
}
}
private String generateRandomKey() {
return Base64.getEncoder().encodeToString(
RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8));
}
@Override

2
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -25,7 +25,7 @@ public interface SystemDataLoaderService {
void createRandomJwtSettings() throws Exception;
void updateJwtSettings() throws Exception;
void updateSecuritySettings() throws Exception;
void createOAuth2Templates() throws Exception;

6
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java

@ -17,8 +17,6 @@ package org.thingsboard.server.service.security.auth.jwt.settings;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
@ -111,8 +109,8 @@ public class DefaultJwtSettingsService implements JwtSettingsService {
return TOKEN_SIGNING_KEY_DEFAULT.equals(settings.getTokenSigningKey());
}
public static boolean validateTokenSigningKeyLength(JwtSettings settings) {
return Base64.getDecoder().decode(settings.getTokenSigningKey()).length * Byte.SIZE >= KEY_LENGTH;
public static boolean validateKeyLength(String key) {
return Base64.getDecoder().decode(key).length * Byte.SIZE >= KEY_LENGTH;
}
}

17
ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html

@ -156,7 +156,7 @@
<div [formGroupName]="n" fxLayout="row" fxLayoutGap="8px">
<div fxFlex fxLayout="row" fxLayout.xs="column" fxLayoutGap="8px">
<div fxFlex fxLayout="column">
<mat-form-field fxFlex class="mat-block" floatLabel="always">
<mat-form-field fxFlex class="mat-block" floatLabel="always" subscriptSizing="dynamic">
<mat-label translate>admin.oauth2.mobile-package</mat-label>
<input matInput formControlName="pkgName" placeholder="{{ 'admin.oauth2.mobile-package-placeholder' | translate }}" required>
<mat-hint translate>admin.oauth2.mobile-package-hint</mat-hint>
@ -166,9 +166,9 @@
</mat-error>
</div>
<div fxFlex fxLayout="row">
<mat-form-field fxFlex class="mat-block">
<mat-form-field fxFlex class="mat-block" subscriptSizing="dynamic">
<mat-label translate>admin.oauth2.mobile-app-secret</mat-label>
<textarea matInput formControlName="appSecret" rows="1" required></textarea>
<input matInput formControlName="appSecret" required>
<tb-copy-button
matSuffix
miniButton="false"
@ -178,8 +178,15 @@
tooltipPosition="above"
icon="mdi:clipboard-arrow-left">
</tb-copy-button>
<mat-error *ngIf="mobileInfo.get('appSecret').invalid">
{{ 'admin.oauth2.invalid-mobile-app-secret' | translate }}
<mat-hint translate>admin.oauth2.mobile-app-secret-hint</mat-hint>
<mat-error *ngIf="mobileInfo.get('appSecret').hasError('required')">
{{ 'admin.oauth2.mobile-app-secret-required' | translate }}
</mat-error>
<mat-error *ngIf="mobileInfo.get('appSecret').hasError('base64')">
{{ 'admin.oauth2.mobile-app-secret-min-length' | translate }}
</mat-error>
<mat-error *ngIf="mobileInfo.get('appSecret').hasError('minLength')">
{{ 'admin.oauth2.mobile-app-secret-base64' | translate }}
</mat-error>
</mat-form-field>
</div>

23
ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts

@ -17,6 +17,7 @@
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import {
AbstractControl,
FormControl,
UntypedFormArray,
UntypedFormBuilder,
UntypedFormGroup,
@ -215,7 +216,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
this.oauth2SettingsForm.get('edgeEnabled').patchValue(false);
this.oauth2SettingsForm.get('edgeEnabled').disable();
}
}))
}));
}
private initOAuth2Settings(oauth2Info: OAuth2Info): void {
@ -302,11 +303,25 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
private buildMobileInfoForm(mobileInfo?: OAuth2MobileInfo): UntypedFormGroup {
return this.fb.group({
pkgName: [mobileInfo?.pkgName, [Validators.required]],
appSecret: [mobileInfo?.appSecret, [Validators.required, Validators.minLength(16), Validators.maxLength(2048),
Validators.pattern(/^[A-Za-z0-9]+$/)]],
appSecret: [mobileInfo?.appSecret, [Validators.required, this.base64Format]],
}, {validators: this.uniquePkgNameValidator});
}
private base64Format(control: FormControl): { [key: string]: boolean } | null {
if (control.value === '') {
return null;
}
try {
const value = atob(control.value);
if (value.length < 64) {
return {minLength: true};
}
return null;
} catch (e) {
return {base64: true};
}
}
private buildRegistrationForm(registration?: OAuth2RegistrationInfo): UntypedFormGroup {
let additionalInfo = null;
if (isDefinedAndNotNull(registration?.additionalInfo)) {
@ -556,7 +571,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
addMobileInfo(control: AbstractControl): void {
this.mobileInfos(control).push(this.buildMobileInfoForm({
pkgName: '',
appSecret: randomAlphanumeric(24)
appSecret: btoa(randomAlphanumeric(64))
}));
}

2
ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts

@ -201,7 +201,7 @@ export class SecuritySettingsComponent extends PageComponent implements HasConfi
}
try {
const value = atob(control.value);
if (value.length < 32) {
if (value.length < 64) {
return {minLength: true};
}
return null;

4
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -292,6 +292,10 @@
"mobile-package-hint": "For Android: your own unique Application ID. For iOS: Product bundle identifier.",
"mobile-package-unique": "Application package must be unique.",
"mobile-app-secret": "Application secret",
"mobile-app-secret-hint": "Base64 encoded string representing at least 512 bits of data.",
"mobile-app-secret-required": "Application secret is required.",
"mobile-app-secret-min-length": "Application secret must be at least 512 bits of data.",
"mobile-app-secret-base64": "Application secret must be base64 format.",
"invalid-mobile-app-secret": "Application secret must contain only alphanumeric characters and must be between 16 and 2048 characters long.",
"copy-mobile-app-secret": "Copy application secret",
"add-mobile-app": "Add application",

Loading…
Cancel
Save