diff --git a/build-profile.json5 b/build-profile.json5 index 772addd5..4935f180 100644 --- a/build-profile.json5 +++ b/build-profile.json5 @@ -4,7 +4,7 @@ { "name": "default", "signingConfig": "release", - "compileSdkVersion": 11, + "compileSdkVersion": 12, "compatibleSdkVersion": 10 } ] diff --git a/product/phone/src/main/ets/MainAbility/MainAbility.ts b/product/phone/src/main/ets/MainAbility/MainAbility.ts index 9bb5fbbe..a7ecafe3 100644 --- a/product/phone/src/main/ets/MainAbility/MainAbility.ts +++ b/product/phone/src/main/ets/MainAbility/MainAbility.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 Huawei Device Co., Ltd. + * Copyright (c) 2021-2024 Huawei Device Co., Ltd. * 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 @@ -38,6 +38,14 @@ export default class MainAbility extends Ability { let url = 'pages/settingList'; if (this.funcAbilityWant?.parameters?.router && this.funcAbilityWant.parameters.router === 'volumeControl') { url = 'pages/volumeControl'; + } else if (this.funcAbilityWant?.uri === "wifi") { + url = 'pages/wifi'; + } else if (this.funcAbilityWant?.uri === "bluetooth") { + url = 'pages/bluetooth'; + } else if (this.funcAbilityWant?.uri === "volumeControl") { + url = 'pages/volumeControl'; + } else if (this.funcAbilityWant?.uri === "locationServices") { + url = 'pages/locationServices'; } windowStage.setUIContent(this.context, url, null); GlobalContext.getContext().setObject(GlobalContext.globalKeySettingsAbilityContext, this.context); diff --git a/product/phone/src/main/ets/model/vpnImpl/SwanCtlModel.ts b/product/phone/src/main/ets/model/vpnImpl/SwanCtlModel.ts new file mode 100644 index 00000000..e092b96d --- /dev/null +++ b/product/phone/src/main/ets/model/vpnImpl/SwanCtlModel.ts @@ -0,0 +1,250 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import common from '@ohos.app.ability.common'; +import HashMap from '@ohos.util.HashMap'; +import { util } from '@kit.ArkTS'; +import { IpsecVpnConfig } from './VpnConfig'; +import { VpnConfigModel } from './VpnConfigModel'; +import { VpnTypeModel } from '../../model/vpnImpl/VpnTypeModel'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; + +const MODULE_TAG: string = 'setting_vpn:SwanCtlModel:'; +//param +const KEY_VPN_ADDRESS: string = 'vpn_address_value'; +const KEY_VPN_USERNAME: string = 'vpn_username_value'; +const KEY_VPN_IPSEC_IDENTIFIER: string = 'vpn_ipsec_identifier_value'; +const KEY_VPN_PASSWORD: string = 'vpn_password_value'; +const KEY_VPN_IPSEC_SHAREDKEY: string = 'vpn_ipsec_sharedKey_value'; + +//file path +const SWANCTL_IKE2_IPSEC_MSCHAPV2_PATH: string = 'vpn/ike2_ipsec_mschapv2/swanctl.conf'; +const STRONGSWAN_IKE2_IPSEC_MSCHAPV2_PATH: string = 'vpn/ike2_ipsec_mschapv2/strongswan.conf'; +const SWANCTL_IKE2_IPSEC_RSA_PATH: string = 'vpn/ike2_ipsec_rsa/swanctl.conf'; +const STRONGSWAN_IKE2_IPSEC_RSA_PATH: string = 'vpn/ike2_ipsec_rsa/strongswan.conf'; +const SWANCTL_IKE2_IPSEC_PSK_PATH: string = 'vpn/ike2_ipsec_psk/swanctl.conf'; +const STRONGSWAN_IKE2_IPSEC_PSK_PATH: string = 'vpn/ike2_ipsec_psk/strongswan.conf'; + +const SWANCTL_IPSEC_XAUTH_PSK_PATH: string = 'vpn/ipsec_xauth_psk/swanctl.conf'; +const STRONGSWAN_IPSEC_XAUTH_PSK_PATH: string = 'vpn/ipsec_xauth_psk/strongswan.conf'; +const SWANCTL_IPSEC_XAUTH_RSA_PATH: string = 'vpn/ipsec_xauth_rsa/swanctl.conf'; +const STRONGSWAN_IPSEC_XAUTH_RSA_PATH: string = 'vpn/ipsec_xauth_rsa/strongswan.conf'; + +const STRONGSWAN_L2TP_IPSEC_RSA_PATH: string = 'vpn/l2tp_ipsec_rsa/strongswan.conf'; +const STRONGSWAN_L2TP_IPSEC_PSK_PATH: string = 'vpn/l2tp_ipsec_psk/strongswan.conf'; + +const CLIENT_L2TP_IPSEC_PSK_PATH: string = 'vpn/l2tp_ipsec_psk/options.l2tpd.client.conf'; +const CLIENT_L2TP_IPSEC_RSA_PATH: string = 'vpn/l2tp_ipsec_rsa/options.l2tpd.client.conf'; +const XL2TPD_L2TP_IPSEC_PSK_PATH: string = 'vpn/l2tp_ipsec_psk/xl2tpd.conf'; +const XL2TPD_L2TP_IPSEC_RSA_PATH: string = 'vpn/l2tp_ipsec_rsa/xl2tpd.conf'; + +const IPSECCONF_L2TP_IPSEC_PSK_PATH: string = 'vpn/l2tp_ipsec_psk/ipsec.conf'; +const IPSECCONF_L2TP_IPSEC_RSA_PATH: string = 'vpn/l2tp_ipsec_rsa/ipsec.conf'; + +const IPSEC_SECRETS_L2TP_IPSEC_PSK_PATH: string = 'vpn/l2tp_ipsec_psk/ipsec.secrets.conf'; +const IPSEC_SECRETS_L2TP_IPSEC_RSA_PATH: string = 'vpn/l2tp_ipsec_rsa/ipsec.secrets.conf'; + +const SWANCTL_IPSEC_HYBRID_RSA_PATH: string = 'vpn/ipsec_hybrid_rsa/swanctl.conf'; +const STRONGSWAN_IPSEC_HYBRID_RSA_PATH: string = 'vpn/ipsec_hybrid_rsa/strongswan.conf'; + +export class SwanCtlModel { + private context: common.UIAbilityContext = undefined; + private swanCtlMap = new HashMap(); + private strongSwanMap = new HashMap(); + private l2tpdClientMap = new HashMap(); + private xl2tpdMap = new HashMap(); + private ipsecConfMap = new HashMap(); + private ipsecSecretsMap = new HashMap(); + + private static instance: SwanCtlModel; + + public static getInstance(): SwanCtlModel { + if (!this.instance) { + this.instance = new SwanCtlModel(); + } + return this.instance; + } + + public async init(context: common.UIAbilityContext): Promise { + this.context = context; + this.readTemplate(); + } + + public buildConfig(ipsec: IpsecVpnConfig): IpsecVpnConfig { + let helper: util.Base64Helper = new util.Base64Helper(); + if (ipsec.vpnType !== VpnTypeModel.TYPE_L2TP_IPSEC_PSK && ipsec.vpnType !== VpnTypeModel.TYPE_L2TP_IPSEC_RSA) { + ipsec.swanctlConfig = helper.encodeToStringSync(this.ipsec2swanCtl(ipsec)); + } + ipsec.strongSwanConfig = helper.encodeToStringSync(this.ipsec2strongSwan(ipsec)); + if (ipsec.vpnType === VpnTypeModel.TYPE_L2TP_IPSEC_PSK || ipsec.vpnType === VpnTypeModel.TYPE_L2TP_IPSEC_RSA) { + ipsec.optionsL2tpdClient = helper.encodeToStringSync(this.ipsec2L2tpdClient(ipsec)); + ipsec.xl2tpdConfig = helper.encodeToStringSync(this.ipsec2Xl2tpd(ipsec)); + ipsec.ipsecConfig = helper.encodeToStringSync(this.ipsec2IpsecConf(ipsec)); + ipsec.ipsecSecrets = helper.encodeToStringSync(this.ipsec2IpsecSecrets(ipsec)); + } + return ipsec; + } + + private ipsec2swanCtl(ipsec: IpsecVpnConfig): Uint8Array { + if (this.swanCtlMap.isEmpty()) { + this.readTemplate(); + } + let template = this.swanCtlMap.get(ipsec.vpnType); + let vpnTemplate = String(template); + vpnTemplate = this.replaceConfigParam(vpnTemplate, KEY_VPN_ADDRESS, VpnConfigModel.getInstance().getAddress(ipsec)); + vpnTemplate = this.replaceConfigParam(vpnTemplate, KEY_VPN_IPSEC_IDENTIFIER, ipsec.ipsecIdentifier); + vpnTemplate = this.replaceConfigParam(vpnTemplate, KEY_VPN_IPSEC_SHAREDKEY, ipsec.ipsecPreSharedKey); + vpnTemplate = this.replaceConfigParam(vpnTemplate, KEY_VPN_USERNAME, ipsec.userName); + vpnTemplate = this.replaceConfigParam(vpnTemplate, KEY_VPN_PASSWORD, ipsec.password); + + let swanCtl = this.stringToUint8Array(vpnTemplate); + return swanCtl; + } + + private ipsec2strongSwan(ipsec: IpsecVpnConfig): Uint8Array { + if (this.strongSwanMap.isEmpty()) { + this.readTemplate(); + } + let template = this.strongSwanMap.get(ipsec.vpnType); + let strongSwan = this.stringToUint8Array(String(template)); + return strongSwan; + } + + private ipsec2Xl2tpd(ipsec: IpsecVpnConfig): Uint8Array { + if (this.xl2tpdMap.isEmpty()) { + this.readTemplate(); + } + let template = this.xl2tpdMap.get(ipsec.vpnType); + let vpnTemplate = String(template); + vpnTemplate = this.replaceConfigParam(vpnTemplate, KEY_VPN_ADDRESS, VpnConfigModel.getInstance().getAddress(ipsec)); + let xl2tpd = this.stringToUint8Array(vpnTemplate); + return xl2tpd; + } + + private ipsec2L2tpdClient(ipsec: IpsecVpnConfig): Uint8Array { + if (this.l2tpdClientMap.isEmpty()) { + this.readTemplate(); + } + let template = this.l2tpdClientMap.get(ipsec.vpnType); + let vpnTemplate = String(template); + vpnTemplate = this.replaceConfigParam(vpnTemplate, KEY_VPN_USERNAME, ipsec.userName); + vpnTemplate = this.replaceConfigParam(vpnTemplate, KEY_VPN_PASSWORD, ipsec.password); + let xl2tpd = this.stringToUint8Array(vpnTemplate); + return xl2tpd; + } + + private ipsec2IpsecConf(ipsec: IpsecVpnConfig): Uint8Array { + if (this.ipsecConfMap.isEmpty()) { + this.readTemplate(); + } + let template = this.ipsecConfMap.get(ipsec.vpnType); + let vpnTemplate = String(template); + vpnTemplate = this.replaceConfigParam(vpnTemplate, KEY_VPN_ADDRESS, VpnConfigModel.getInstance().getAddress(ipsec)); + let ipsecConf = this.stringToUint8Array(vpnTemplate); + return ipsecConf; + } + + private ipsec2IpsecSecrets(ipsec: IpsecVpnConfig): Uint8Array { + if (this.ipsecSecretsMap.isEmpty()) { + this.readTemplate(); + } + let template = this.ipsecSecretsMap.get(ipsec.vpnType); + let vpnTemplate = String(template); + vpnTemplate = this.replaceConfigParam(vpnTemplate, KEY_VPN_IPSEC_SHAREDKEY, ipsec.ipsecPreSharedKey); + let ipsecSecrets = this.stringToUint8Array(vpnTemplate); + return ipsecSecrets; + } + + private readTemplate(): void { + //swanctl.config + this.swanCtlMap.set(VpnTypeModel.TYPE_IKEV2_IPSEC_MSCHAPv2, SWANCTL_IKE2_IPSEC_MSCHAPV2_PATH); + this.swanCtlMap.set(VpnTypeModel.TYPE_IKEV2_IPSEC_RSA, SWANCTL_IKE2_IPSEC_RSA_PATH); + this.swanCtlMap.set(VpnTypeModel.TYPE_IKEV2_IPSEC_PSK, SWANCTL_IKE2_IPSEC_PSK_PATH); + this.swanCtlMap.set(VpnTypeModel.TYPE_IPSEC_XAUTH_PSK, SWANCTL_IPSEC_XAUTH_PSK_PATH); + this.swanCtlMap.set(VpnTypeModel.TYPE_IPSEC_XAUTH_RSA, SWANCTL_IPSEC_XAUTH_RSA_PATH); + this.swanCtlMap.set(VpnTypeModel.TYPE_IPSEC_HYBRID_RSA, SWANCTL_IPSEC_HYBRID_RSA_PATH); + this.swanCtlMap = this.readRawFile(this.swanCtlMap); + //strongSwan.config + this.strongSwanMap.set(VpnTypeModel.TYPE_IKEV2_IPSEC_MSCHAPv2, STRONGSWAN_IKE2_IPSEC_MSCHAPV2_PATH); + this.strongSwanMap.set(VpnTypeModel.TYPE_IKEV2_IPSEC_RSA, STRONGSWAN_IKE2_IPSEC_RSA_PATH); + this.strongSwanMap.set(VpnTypeModel.TYPE_IKEV2_IPSEC_PSK, STRONGSWAN_IKE2_IPSEC_PSK_PATH); + this.strongSwanMap.set(VpnTypeModel.TYPE_IPSEC_XAUTH_PSK, STRONGSWAN_IPSEC_XAUTH_PSK_PATH); + this.strongSwanMap.set(VpnTypeModel.TYPE_IPSEC_XAUTH_RSA, STRONGSWAN_IPSEC_XAUTH_RSA_PATH); + this.strongSwanMap.set(VpnTypeModel.TYPE_IPSEC_HYBRID_RSA, STRONGSWAN_IPSEC_HYBRID_RSA_PATH); + this.strongSwanMap.set(VpnTypeModel.TYPE_L2TP_IPSEC_PSK, STRONGSWAN_L2TP_IPSEC_PSK_PATH); + this.strongSwanMap.set(VpnTypeModel.TYPE_L2TP_IPSEC_RSA, STRONGSWAN_L2TP_IPSEC_RSA_PATH); + this.strongSwanMap = this.readRawFile(this.strongSwanMap); + //xl2tpd.conf + this.xl2tpdMap.set(VpnTypeModel.TYPE_L2TP_IPSEC_PSK, XL2TPD_L2TP_IPSEC_PSK_PATH); + this.xl2tpdMap.set(VpnTypeModel.TYPE_L2TP_IPSEC_RSA, XL2TPD_L2TP_IPSEC_RSA_PATH); + this.xl2tpdMap = this.readRawFile(this.xl2tpdMap); + //options.l2tpd.client.conf + this.l2tpdClientMap.set(VpnTypeModel.TYPE_L2TP_IPSEC_PSK, CLIENT_L2TP_IPSEC_PSK_PATH); + this.l2tpdClientMap.set(VpnTypeModel.TYPE_L2TP_IPSEC_RSA, CLIENT_L2TP_IPSEC_RSA_PATH); + this.l2tpdClientMap = this.readRawFile(this.l2tpdClientMap); + //ipsec.conf + this.ipsecConfMap.set(VpnTypeModel.TYPE_L2TP_IPSEC_PSK, IPSECCONF_L2TP_IPSEC_PSK_PATH); + this.ipsecConfMap.set(VpnTypeModel.TYPE_L2TP_IPSEC_RSA, IPSECCONF_L2TP_IPSEC_RSA_PATH); + this.ipsecConfMap = this.readRawFile(this.ipsecConfMap); + //ipsec.secrets.conf + this.ipsecSecretsMap.set(VpnTypeModel.TYPE_L2TP_IPSEC_PSK, IPSEC_SECRETS_L2TP_IPSEC_PSK_PATH); + this.ipsecSecretsMap.set(VpnTypeModel.TYPE_L2TP_IPSEC_RSA, IPSEC_SECRETS_L2TP_IPSEC_RSA_PATH); + this.ipsecSecretsMap = this.readRawFile(this.ipsecSecretsMap); + } + + private readRawFile(pathMap): HashMap { + try { + pathMap.forEach((path, key) => { + this.context.resourceManager.getRawFileContent(path as string, (error, value) => { + if (error !== null && error !== undefined) { + LogUtil.log(MODULE_TAG + 'readRawFile faile, error:' + error); + } else { + pathMap.set(key, this.uint8ArrayToString(value)); + } + }); + }); + } catch (error) { + LogUtil.error(MODULE_TAG + 'callback getRawfileContent failed, error:' + error); + } + return pathMap; + } + + private replaceConfigParam(vpnTemplate: string, key: string, value: string): string { + if (!value || !key) { + LogUtil.warn(MODULE_TAG + 'replaceConfigParam failed, params is null or undefined'); + return vpnTemplate; + } + while (vpnTemplate.indexOf(key) !== -1) { + vpnTemplate = vpnTemplate.replace(key, value.trim()); + } + return vpnTemplate; + } + + private uint8ArrayToString(fileData): string { + let dataString = ''; + for (let i = 0; i < fileData.length; i++) { + dataString += String.fromCharCode(fileData[i]); + } + return dataString; + } + + private stringToUint8Array(str: string): Uint8Array { + let arr: number[] = []; + for (let i = 0, j = str.length; i < j; ++i) { + arr.push(str.charCodeAt(i)); + } + return new Uint8Array(arr); + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/model/vpnImpl/VpnConfig.ts b/product/phone/src/main/ets/model/vpnImpl/VpnConfig.ts new file mode 100644 index 00000000..abcea85c --- /dev/null +++ b/product/phone/src/main/ets/model/vpnImpl/VpnConfig.ts @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import vpn from '@ohos.net.vpn'; +import { VpnTypeModel } from './VpnTypeModel'; + +/** + * extend system VpnConig + */ +export default class VpnConfig implements vpn.SysVpnConfig { + // vpnConfig + addresses: Array = []; + routes?: Array; + dnsAddresses?: Array; + searchDomains?: Array; + isLegacy: boolean = true; + + // sysVpnConfig + vpnId: string = ''; + vpnName: string = ''; + vpnType: vpn.SysVpnType = VpnTypeModel.TYPE_IKEV2_IPSEC_MSCHAPv2; + userName?: string; + password?: string; + saveLogin: boolean = false; + userId?: number; + forwardingRoutes?: string; +} + +export class OpenVpnConfig extends VpnConfig implements vpn.OpenVpnConfig { + ovpnConfigFilePath?: string // openVpn file name + ovpnConfigContent?: string // openVpn config + ovpnConfig?: string // openVpn config base64 + ovpnAuthType: number = 0 // openvpn auth type + ovpnProtocolFileRaw?: string // Protocol + ovpnProtocol: number = 0 // 0:tcp 1:udp + ovpnAddressPortFileRaw?: string // port + ovpnPort?: string // openvpn port + askpass: string // private key password + ovpnCaCertFilePath?: string // openVpn CA FilePath + ovpnUserCertFilePath?: string // openVpn USER FilePath + ovpnPrivateKeyFilePath?: string // openVpn private key FilePath + ovpnCaCertFileRaw?: string // ca raw data + ovpnCaCert?: string // CA data + ovpnUserCertFileRaw?: string // + ovpnUserCert?: string // + ovpnPrivateKeyFileRaw?: string // private key raw data + ovpnPrivateKey?: string // private key data + ovpnUserPassFileRaw?: string // userpass raw d + ovpnProxyHostFileRaw?: string // host raw data + ovpnProxyHost?: string //ovpn host + ovpnProxyPort?: string //ovpn port + ovpnProxyUserPassFileRaw?: string // userpass data + ovpnProxyUser?: string //ovpn user + ovpnProxyPass?: string //ovpn pass +} + +export class IpsecVpnConfig extends VpnConfig implements vpn.IpsecVpnConfig { + ipsecIdentifier?: string // ipsec identifier + ipsecPreSharedKey?: string // ipsec pre sharedKey + l2tpSharedKey?: string // L2TP secret + ipsecPublicUserCertConfig?: string // public userCert config + ipsecPublicUserCertFilePath?: string // public userCert FilePath + ipsecPrivateUserCertConfig?: string // private userCert config + ipsecPrivateUserCertFilePath?: string // private userCert FilePath + ipsecCaCertConfig?: string // ca config + ipsecCaCertFilePath?: string // ca FilePath + ipsecPublicServerCertConfig?: string // public serverCert config + ipsecPublicServerCertFilePath?: string // public serverCert FilePath + ipsecPrivateServerCertConfig?: string // private serverCert config + ipsecPrivateServerCertFilePath?: string // private serverCert FilePath + swanctlConfig?: string // swanctl config base64 + strongSwanConfig?: string // strongswan config base64 + optionsL2tpdClient?: string // optionsL2tpd Client base64 + xl2tpdConfig?: string // xl2tpd config base64 + ipsecConfig?: string // swanctl config base64 + ipsecSecrets?: string // swanctl config base64 +} + +export class VpnListItem { + vpnName: string //vpnName + vpnId: string //UUID +} \ No newline at end of file diff --git a/product/phone/src/main/ets/model/vpnImpl/VpnConfigModel.ts b/product/phone/src/main/ets/model/vpnImpl/VpnConfigModel.ts new file mode 100644 index 00000000..92cf231a --- /dev/null +++ b/product/phone/src/main/ets/model/vpnImpl/VpnConfigModel.ts @@ -0,0 +1,336 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import util from '@ohos.util'; +import { promptAction } from '@kit.ArkUI'; +import VpnConfig, { IpsecVpnConfig, OpenVpnConfig } from './VpnConfig'; +import VpnConstant from './VpnConstant'; +import { VpnTypeModel } from './VpnTypeModel'; +import { SwanCtlModel } from './SwanCtlModel'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; + +const MODULE_TAG: string = 'setting_vpn:VpnConnectModel:'; +const OVPN_PROTOCOL_TCP: number = 0; +const OVPN_PROTOCOL_UDP: number = 1; + +/** + * system vpn config model + */ +export class VpnConfigModel { + private static instance: VpnConfigModel; + + private isNeedUpdateVpnList: boolean = false; + + public static getInstance(): VpnConfigModel { + if (!this.instance) { + this.instance = new VpnConfigModel(); + } + return this.instance; + } + + isUpdateVpnList(): boolean { + return this.isNeedUpdateVpnList; + } + + setNeedUpdateVpnList(isUpdate: boolean): void { + this.isNeedUpdateVpnList = isUpdate; + } + + setAddress(vpnConfig: VpnConfig, vpnAddress: string): void { + if (vpnConfig === undefined || vpnConfig === null) { + LogUtil.error(MODULE_TAG + `setAddress failed, invalid config`); + return; + } + vpnConfig.addresses = [{ + address: { address: vpnAddress }, + prefixLength: 1, + }] + } + + getAddress(vpnConfig: VpnConfig): string | undefined { + if (vpnConfig === undefined || vpnConfig === null) { + LogUtil.error(MODULE_TAG + `setRoutes failed, invalid config`); + return undefined; + } + if (vpnConfig.addresses?.length > 0) { + return vpnConfig.addresses[0].address?.address; + } + LogUtil.error(MODULE_TAG + `getAddress invalid vpnConfig`); + return undefined; + } + + stringToUint8Array(str: string): Uint8Array { + let arr: number[] = []; + for (let i = 0, j = str.length; i < j; ++i) { + arr.push(str.charCodeAt(i)); + } + return new Uint8Array(arr); + } + + uint8ArrayToString(data: Uint8Array): string { + let resultStr: string = ''; + const charArray = data.map(value => Number(value)); + charArray.forEach((value) => { + resultStr += String.fromCharCode(value); + }); + return resultStr; + } + + showToast(msg: string | Resource): void { + if (!msg) { + LogUtil.error(MODULE_TAG + `showToast failed, invalid msg`); + return; + } + promptAction.showToast({ + message: msg, + duration: 2000, + }); + } + + inflateConfigFromFileData(config: OpenVpnConfig, data: string[]): void { + config.ovpnConfigFilePath = data[0]; + let content = data[1]; + config.ovpnConfigContent = content; + + let regex = /proto\s+(tcp|udp)/; + let match = regex.exec(content); + if (match && match.length >= 1) { + config.ovpnProtocolFileRaw = match[0]; + config.ovpnProtocol = match[1] === 'udp' ? OVPN_PROTOCOL_UDP : OVPN_PROTOCOL_TCP; + } + + regex = /remote\s+([^\s]+)\s+(\d+)/; + match = regex.exec(content); + if (match && match.length >= 2) { + config.ovpnAddressPortFileRaw = match[0]; + this.setAddress(config, match[1]); + config.ovpnPort = match[2]; + } + + regex = /([\s\S]*?)<\/ca>/ + match = regex.exec(content); + if (match) { + config.ovpnCaCertFileRaw = match[0]; + } + + regex = /([\s\S]*?)<\/cert>/ + match = regex.exec(content); + if (match && match.length >= 1) { + config.ovpnUserCertFileRaw = match[0]; + } + + regex = /([\s\S]*?)<\/key>/ + match = regex.exec(content); + if (match && match.length >= 1) { + config.ovpnPrivateKeyFileRaw = match[0]; + } + + regex = /\s*([\s\S]+?)\s*(\r?\n|\r)\s*([\s\S]+?)\s*<\/auth-user-pass>/ + match = regex.exec(content); + if (match && match.length >= 4) { + config.ovpnUserPassFileRaw = match[0]; + config.userName = match[1]; + config.password = match[3]; + } + + regex = /http-proxy\s+([^\s]+)\s+(\d+)/; + match = regex.exec(content); + if (match && match.length >= 3) { + config.ovpnProxyHostFileRaw = match[0]; + config.ovpnProxyHost = match[1]; + config.ovpnProxyPort = match[2]; + } + + regex = /\s*([\s\S]+?)\s*(\r?\n|\r)\s*([\s\S]+?)\s*<\/http-proxy-user-pass>/; + match = regex.exec(content); + if (match && match.length >= 4) { + config.ovpnProxyUserPassFileRaw = match[0]; + config.ovpnProxyUser = match[1]; + config.ovpnProxyPass = match[3]; + } + } + + prepareAddSysVpnConfig(config: VpnConfig): void { + switch (config?.vpnType) { + case VpnTypeModel.TYPE_OPENVPN: + this.prepareOvpnConfig(config as OpenVpnConfig); + break; + default: + this.prepareIpsecConfig(config as IpsecVpnConfig); + break; + } + } + + prepareIpsecConfig(config: IpsecVpnConfig): void { + if (config === undefined || config === null) { + LogUtil.error(MODULE_TAG + "prepareIpsecConfig faild, invalid param.") + return; + } + SwanCtlModel.getInstance().buildConfig(config); + } + + prepareOvpnConfig(config: OpenVpnConfig): void { + if (config === undefined || config === null) { + LogUtil.error(MODULE_TAG + "prepareOvpnConfig faild, invalid param.") + return; + } + let that = new util.Base64Helper(); + let content = ''; + if (config.ovpnConfig) { + let data = that.decodeSync(config.ovpnConfig); + content = this.uint8ArrayToString(data); + } else { + content = config.ovpnConfigContent ?? 'client\ndev tun'; + } + + let protocolReplace: string = `proto ${config.ovpnProtocol === OVPN_PROTOCOL_TCP ? 'tcp' : 'udp'}`; + if (protocolReplace === config.ovpnProtocolFileRaw) { + content = content.replace(config.ovpnProtocolFileRaw, '\n' + protocolReplace); + } else { + content += '\n'; + content += protocolReplace; + } + + let vpnAddress: string = this.getAddress(config); + if (vpnAddress && config.ovpnPort) { + let addressPortReplace: string = `remote ${vpnAddress} ${config.ovpnPort}`; + if (addressPortReplace === config.ovpnAddressPortFileRaw) { + content = content.replace(config.ovpnAddressPortFileRaw, '\n' + addressPortReplace); + } else { + content += '\n'; + content += addressPortReplace; + content += '\nlog \/data\/service\/el1\/public\/netmanager\/config.log\ndev-node \/dev\/tun' + + '\nresolv-retry infinite\nnobind\npersist-key\npersist-tun\nverb 7\n'; + } + } + + if (config.ovpnCaCert) { + let caCert: string = config.ovpnCaCert; + if (config.ovpnCaCertFileRaw) { + if (config.ovpnCaCertFileRaw !== caCert) { + content = content.replace(config.ovpnCaCertFileRaw, '\n' + caCert); + } + } else { + content += '\n'; + content += caCert; + } + } + + if (config.ovpnAuthType === VpnConstant.OVPN_AUTH_TYPE_PWD) { + config.ovpnUserCert = undefined; + if (config.ovpnUserCertFileRaw) { + content.replace(config.ovpnUserCertFileRaw, ''); + } + config.ovpnUserCertFileRaw = undefined; + config.ovpnUserCertFilePath = undefined; + } + + if (config.ovpnUserCert) { + let userCert: string = config.ovpnUserCert; + if (config.ovpnUserCertFileRaw) { + if (config.ovpnUserCertFileRaw !== userCert) { + content = content.replace(config.ovpnUserCertFileRaw, '\n' + userCert); + } + } else { + content += '\n'; + content += userCert; + } + } + + if (config.ovpnAuthType === VpnConstant.OVPN_AUTH_TYPE_PWD) { + config.ovpnPrivateKey = undefined; + if (config.ovpnPrivateKeyFileRaw) { + content.replace(config.ovpnPrivateKeyFileRaw, ''); + } + config.ovpnPrivateKeyFileRaw = undefined; + config.ovpnPrivateKeyFilePath = undefined; + } + + if (config.ovpnPrivateKey) { + let privateKey: string = config.ovpnPrivateKey; + if (config.ovpnPrivateKeyFileRaw) { + if (config.ovpnPrivateKeyFileRaw !== privateKey) { + content = content.replace(config.ovpnPrivateKeyFileRaw, '\n' + privateKey); + } + } else { + content += '\n'; + content += privateKey; + } + } + + if (config.ovpnAuthType === VpnConstant.OVPN_AUTH_TYPE_TLS) { + config.userName = undefined; + config.password = undefined; + if (config.ovpnUserPassFileRaw) { + content = content.replace(config.ovpnUserPassFileRaw, ''); + } + } + + if (config.userName) { + let userPass: string = ''; + userPass += '\n'; + userPass += config.userName; + userPass += '\n'; + userPass += config.password; + userPass += '\n'; + userPass += '' + + if (config.userName) { + if (config.ovpnUserPassFileRaw) { + if (config.ovpnUserPassFileRaw !== userPass) { + content = content.replace(config.ovpnUserPassFileRaw, '\n' + userPass); + } + } else { + content += '\n'; + content += userPass; + } + } + } + + if (config.ovpnProxyHost) { + let proxyHostPort: string = `http-proxy ${config.ovpnProxyHost} ${config.ovpnProxyPort ?? ''}`; + if (config.ovpnProxyHostFileRaw) { + if (config.ovpnProxyHostFileRaw !== proxyHostPort) { + content = content.replace(config.ovpnProxyHostFileRaw, '\n' + proxyHostPort); + } + } else { + content += '\n'; + content += proxyHostPort; + } + } + + if (config.ovpnProxyUser) { + let proxyUserPass: string = ''; + proxyUserPass += '\n'; + proxyUserPass += config.ovpnProxyUser; + proxyUserPass += '\n'; + proxyUserPass += config.ovpnProxyPass; + proxyUserPass += '\n'; + proxyUserPass += '' + if (config.ovpnProxyUserPassFileRaw) { + if (config.ovpnProxyUserPassFileRaw !== proxyUserPass) { + content = content.replace(config.ovpnProxyUserPassFileRaw, '\n' + proxyUserPass); + } + } else { + content += '\n'; + content += proxyUserPass; + } + } + + let regex = /^\s*$\n/gm; + content = content.replace(regex, ''); + config.ovpnConfig = that.encodeToStringSync(this.stringToUint8Array(content)); + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/model/vpnImpl/VpnConnectModel.ts b/product/phone/src/main/ets/model/vpnImpl/VpnConnectModel.ts new file mode 100644 index 00000000..eeebffab --- /dev/null +++ b/product/phone/src/main/ets/model/vpnImpl/VpnConnectModel.ts @@ -0,0 +1,198 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import vpn from '@ohos.net.vpn'; +import { BusinessError } from '@kit.BasicServicesKit'; +import type common from '@ohos.app.ability.common'; +import VpnConfig from './VpnConfig'; +import VpnConstant from './VpnConstant'; +import { VpnConfigModel } from '../../model/vpnImpl/VpnConfigModel'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; + +const MODULE_TAG: string = 'setting_vpn:VpnConnectModel:'; +AppStorage.setOrCreate(VpnConstant.STORAGE_KEY_CONNECT_STATE, VpnConstant.VPN_STATE_NONE); + +/** + * app management service class + */ +export class VpnConnectModel { + private static instance: VpnConnectModel; + + private connection: vpn.VpnConnection | undefined = undefined; + private timeoutId: number = undefined; + private connectedVpnId: string = undefined; + private connectState: number = VpnConstant.VPN_STATE_NONE; + private replaceConnectVpnConfig: VpnConfig = undefined; + + public static getInstance(): VpnConnectModel { + if (!this.instance) { + this.instance = new VpnConnectModel(); + } + return this.instance; + } + + setReplaceConnectVpn(vpnConfig: VpnConfig): void { + this.replaceConnectVpnConfig = vpnConfig; + } + + setConnectState(state: number, id?: string): void { + if (id) { + this.connectedVpnId = id; + } + LogUtil.info(MODULE_TAG + `setConnectState: ${this.connectState} -> ${state} id:${this.connectedVpnId}`); + this.connectState = state; + AppStorage.setOrCreate(VpnConstant.STORAGE_KEY_CONNECT_STATE, state); + } + + getConnectedVpnId(): string { + return this.connectedVpnId; + } + + isConnecting(vpnId: string): boolean { + return (vpnId === this.connectedVpnId) && + (this.connectState === VpnConstant.VPN_STATE_CONNECTING); + } + + isConnectedOrConnecting(vpnId: string): boolean { + let connectState = this.connectState; + return (vpnId === this.connectedVpnId) && + ((connectState === VpnConstant.VPN_STATE_CONNECTING) || + (connectState === VpnConstant.VPN_STATE_CONNECTED)); + } + + onConnectStateChange(isConnected: boolean): void { + LogUtil.info(MODULE_TAG + `onConnectStateChange isConnected=` + isConnected); + this.removeTimeout(); + if (isConnected) { + this.setConnectState(VpnConstant.VPN_STATE_CONNECTED); + return; + } + if (this.replaceConnectVpnConfig) { + let config = this.replaceConnectVpnConfig; + this.replaceConnectVpnConfig = undefined; + this.setUp(config); + return; + } + if (this.connectState === VpnConstant.VPN_STATE_CONNECTING) { + this.setConnectState(VpnConstant.VPN_STATE_CONNECT_FAILED); + return; + } + if (this.connectState === VpnConstant.VPN_STATE_DISCONNECTING) { + this.setConnectState(VpnConstant.VPN_STATE_DISCONNECTED); + } + } + + init(context: common.UIAbilityContext): void { + this.connection = vpn.createVpnConnection(context); + try { + LogUtil.info(MODULE_TAG + `vpn on start`); + vpn.on('connect', (data) => { + this.onConnectStateChange(data?.isConnected); + }) + } catch (error) { + LogUtil.error(MODULE_TAG + `vpn on error = ${JSON.stringify(error)}`); + } + this.getConnectedVpn(); + } + + release(): void { + try { + LogUtil.info(MODULE_TAG + `vpnConnection on subscribe off start`); + vpn.off('connect', (data) => { + LogUtil.info(MODULE_TAG + `vpnConnection off data = ${data}`); + }) + } catch (error) { + LogUtil.error(MODULE_TAG + `vpnConnection off error = ${JSON.stringify(error)}`); + } + } + + removeTimeout(): void { + if (this.timeoutId !== undefined) { + LogUtil.info(MODULE_TAG + `removeTimeout timeoutId = ${this.timeoutId}`); + clearTimeout(this.timeoutId); + } + } + + async setUp(vpnConfig: VpnConfig): Promise { + if (vpnConfig === undefined || vpnConfig === null) { + LogUtil.info(MODULE_TAG + `setUp failed, invalid param.`); + return; + } + LogUtil.info(MODULE_TAG + `setUp start`); + this.setConnectState(VpnConstant.VPN_STATE_CONNECTING, vpnConfig.vpnId); + this.removeTimeout(); + this.timeoutId = setTimeout(() => { + LogUtil.info(MODULE_TAG + `setUp timeout vpnId=` + vpnConfig.vpnId); + this.setConnectState(VpnConstant.VPN_STATE_DISCONNECTING, vpnConfig.vpnId); + this.destroy((error: string) => { + if (error) { + LogUtil.error(MODULE_TAG + `vpn destroy failed, error:` + error); + } + this.setConnectState(VpnConstant.VPN_STATE_CONNECT_FAILED, vpnConfig.vpnId); + }); + }, VpnConstant.VPN_CONNECT_TIME_OUT_DURATION); + try { + await this.connection.setUp(vpnConfig); + } catch (err) { + LogUtil.error(MODULE_TAG + `setUp error = ${JSON.stringify(err)}`); + this.removeTimeout(); + VpnConfigModel.getInstance().showToast($r('app.string.vpn_error_operation_failed') + ' error:' + err); + this.setConnectState(VpnConstant.VPN_STATE_CONNECT_FAILED, vpnConfig.vpnId); + // destroy connection + this.destroy((error: string) => { + if (error) { + LogUtil.info(MODULE_TAG + `vpn destroy failed, error:` + error); + } + }); + } + } + + getConnectedVpn(): void { + try { + LogUtil.info(MODULE_TAG + `getConnectedVpn start`); + vpn.getConnectedSysVpnConfig().then((data) => { + if (data && data.addresses && data.vpnId) { + this.setConnectState(VpnConstant.VPN_STATE_CONNECTED, data.vpnId); + } else { + this.setConnectState(VpnConstant.VPN_STATE_NONE); + } + }); + } catch (error) { + LogUtil.error(MODULE_TAG + `getConnectedVpn error: ${JSON.stringify(error)}`); + this.setConnectState(VpnConstant.VPN_STATE_NONE); + } + } + + isHapAvailable(): boolean { + return this.timeoutId !== undefined; + } + + destroy(callback): void { + this.connection?.destroy((error: BusinessError) => { + if (error) { + LogUtil.info(MODULE_TAG + `destroy error = ${JSON.stringify(error)}`); + } + callback(error?.message); + }); + } + + uint8ArrayToString(u8a: Uint8Array): string { + let dataStr = ""; + for (let i = 0; i < u8a.length; i++) { + dataStr += String.fromCharCode(u8a[i]) + } + return dataStr; + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/model/vpnImpl/VpnConstant.ts b/product/phone/src/main/ets/model/vpnImpl/VpnConstant.ts new file mode 100644 index 00000000..254f928b --- /dev/null +++ b/product/phone/src/main/ets/model/vpnImpl/VpnConstant.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +export default class VpnConstant { + static readonly VPN_NAME_MAX_LENGTH: number = 30; + static readonly VPN_USER_NAME_MAX_LENGTH: number = 30; + static readonly VPN_PASSWORD_MAX_LENGTH: number = 30; + static readonly INPUT_MAX_LENGTH: number = 100; + + static readonly STORAGE_KEY_CONNECT_STATE: string = 'vpnConnectState'; + + static readonly VPN_STATE_NONE: number = 0; + static readonly VPN_STATE_CONNECTING: number = 1; // connecting + static readonly VPN_STATE_CONNECTED: number = 2; // connect success + static readonly VPN_STATE_DISCONNECTING: number = 3; // not connect + static readonly VPN_STATE_DISCONNECTED: number = 4; // not connect + static readonly VPN_STATE_CONNECT_FAILED: number = 5; // connect failed + + static readonly VPN_CONNECT_TIME_OUT_DURATION: number = 60000; + + static readonly REGEX_IP: RegExp = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + static readonly REGEX_PORT: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; + + static readonly OVPN_AUTH_TYPE_TLS: number = 0; + static readonly OVPN_AUTH_TYPE_PWD: number = 1; + static readonly OVPN_AUTH_TYPE_TLS_PWD: number = 2; +} \ No newline at end of file diff --git a/product/phone/src/main/ets/model/vpnImpl/VpnTypeModel.ts b/product/phone/src/main/ets/model/vpnImpl/VpnTypeModel.ts new file mode 100644 index 00000000..ce6f82b8 --- /dev/null +++ b/product/phone/src/main/ets/model/vpnImpl/VpnTypeModel.ts @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ +import parameter from '@ohos.systemParameterEnhance'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; + +const MODULE_TAG: string = 'setting_vpn:VpnTypeModel:'; + +/** + * support vpn type & displayname + */ +export class VpnTypeModel { + static readonly TYPE_IKEV2_IPSEC_MSCHAPv2: number = 1; // vpn.SysVpnType.IKEV2_IPSEC_MSCHAPv2; + static readonly TYPE_IKEV2_IPSEC_PSK: number = 2; // vpn.SysVpnType.IKEV2_IPSEC_PSK; + static readonly TYPE_IKEV2_IPSEC_RSA: number = 3; // vpn.SysVpnType.IKEV2_IPSEC_RSA; + static readonly TYPE_L2TP_IPSEC_PSK: number = 4; // vpn.SysVpnType.L2TP_IPSEC_PSK; + static readonly TYPE_L2TP_IPSEC_RSA: number = 5; // vpn.SysVpnType.L2TP_IPSEC_RSA; + static readonly TYPE_IPSEC_XAUTH_PSK: number = 6; // vpn.SysVpnType.IPSEC_XAUTH_PSK; + static readonly TYPE_IPSEC_XAUTH_RSA: number = 7; // vpn.SysVpnType.IPSEC_XAUTH_RSA; + static readonly TYPE_IPSEC_HYBRID_RSA: number = 8; // vpn.SysVpnType.IPSEC_HYBRID_RSA; + static readonly TYPE_OPENVPN: number = 9; // vpn.SysVpnType.OPENVPN; + + private supportVpnTypes: number[] = []; + private static instance: VpnTypeModel; + + public static getInstance(): VpnTypeModel { + if (!this.instance) { + this.instance = new VpnTypeModel(); + } + return this.instance; + } + + constructor() { + let supportStr: string = parameter.getSync("const.product.supportVpn", ""); + supportStr.split(',').forEach((vpnTypeStr) => { + let vpnType: number = Number(vpnTypeStr); + switch (vpnType) { + case VpnTypeModel.TYPE_IKEV2_IPSEC_MSCHAPv2: + case VpnTypeModel.TYPE_IKEV2_IPSEC_PSK: + case VpnTypeModel.TYPE_IKEV2_IPSEC_RSA: + case VpnTypeModel.TYPE_L2TP_IPSEC_PSK: + case VpnTypeModel.TYPE_L2TP_IPSEC_RSA: + case VpnTypeModel.TYPE_IPSEC_XAUTH_PSK: + case VpnTypeModel.TYPE_IPSEC_XAUTH_RSA: + case VpnTypeModel.TYPE_IPSEC_HYBRID_RSA: + case VpnTypeModel.TYPE_OPENVPN: + this.supportVpnTypes.push(vpnType); + break; + default : + LogUtil.info(MODULE_TAG + supportStr + ` has unknown vpnType:` + vpnType); + break; + } + }) + LogUtil.info(MODULE_TAG + `supportVpn ${this.supportVpnTypes}`); + } + + isSupportVpn(): boolean { + return this.supportVpnTypes.length > 0; + } + + getSupportVpnTypes(): number[] { + return this.supportVpnTypes; + } + + getSupportVpnTypeStrs(): string[] { + let types: string[] = []; + this.supportVpnTypes.forEach(type => { + types.push(this.getVpnTypeStr(type)) + }); + return types; + } + + getVpnTypeStr(vpnType: number): string { + switch (vpnType) { + case VpnTypeModel.TYPE_IKEV2_IPSEC_MSCHAPv2: return 'IKEv2/IPSec MSCHAPv2'; + case VpnTypeModel.TYPE_IKEV2_IPSEC_PSK: return 'IKEv2/IPSec PSK'; + case VpnTypeModel.TYPE_IKEV2_IPSEC_RSA: return 'IKEv2/IPSec RSA'; + case VpnTypeModel.TYPE_L2TP_IPSEC_PSK: return 'L2TP/IPSec PSK'; + case VpnTypeModel.TYPE_L2TP_IPSEC_RSA: return 'L2TP/IPSec RSA'; + case VpnTypeModel.TYPE_IPSEC_XAUTH_PSK: return 'IPSec Xauth PSK'; + case VpnTypeModel.TYPE_IPSEC_XAUTH_RSA: return 'IPSec Xauth RSA'; + case VpnTypeModel.TYPE_IPSEC_HYBRID_RSA: return 'IPSec Hybrid RSA'; + case VpnTypeModel.TYPE_OPENVPN: return 'OpenVpn'; + default : + LogUtil.warn(MODULE_TAG + 'getVpnTypeStr unknown vpnType:' + vpnType); + return ''; + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/moreConnections.ets b/product/phone/src/main/ets/pages/moreConnections.ets index bb0f0a09..6e5b10fa 100644 --- a/product/phone/src/main/ets/pages/moreConnections.ets +++ b/product/phone/src/main/ets/pages/moreConnections.ets @@ -16,12 +16,15 @@ import display from '@ohos.display'; import deviceInfo from '@ohos.deviceInfo'; import NfcModel from '../model/moreConnectionsImpl/NfcModel'; +import parameter from '@ohos.systemparameter'; import LogUtil from '../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; import ConfigData from '../../../../../../common/utils/src/main/ets/default/baseUtil/ConfigData'; import HeadComponent from '../../../../../../common/component/src/main/ets/default/headComponent'; import { + SubEntryComponent, SubEntryComponentWithEndText } from '../../../../../../common/component/src/main/ets/default/subEntryComponent'; +import { VpnTypeModel } from '../model/vpnImpl/VpnTypeModel'; const TAG = ConfigData.TAG + 'MoreConnections: '; const deviceTypeInfo = deviceInfo.deviceType; @@ -38,6 +41,7 @@ struct MoreConnections { private maxScreenHeight: number = 0; private nfcImageWidth: number = 0; private nfcImageHeight: number = 0; + private nfcSupport: string = 'false'; nfcStatusChange() { AppStorage.SetOrCreate("nfcStatusInfo", this.nfcStatus ? $r("app.string.enabled") : $r("app.string.disabled")) @@ -48,13 +52,43 @@ struct MoreConnections { GridContainer({ gutter: ConfigData.GRID_CONTAINER_GUTTER_24, margin: ConfigData.GRID_CONTAINER_MARGIN_24 }) { Column() { HeadComponent({ headName: $r('app.string.moreConnectionsTab'), isActive: true }); - - SubEntryComponentWithEndText({ - targetPage: 'pages/nfc', - title: $r('app.string.NFC'), - endText: $nfcStatusInfo + Column() { + List() { + // NFC + if (this.nfcSupport === 'true') { + ListItem() { + SubEntryComponentWithEndText({ + targetPage: 'pages/nfc', + title: $r('app.string.NFC'), + endText: $nfcStatusInfo + }) + } + } + // vpn + if (VpnTypeModel.getInstance().isSupportVpn()) { + ListItem() { + SubEntryComponent({ + targetPage: 'pages/vpn/vpnList', + title: $r("app.string.VPN"), + }) + } + } + } + .width(ConfigData.WH_100_100) + .divider({ + strokeWidth: $r('app.float.divider_wh'), + color: $r('sys.color.ohos_id_color_list_separator'), + startMargin: $r('app.float.wh_value_15'), + endMargin: $r('app.float.wh_value_15') }) + } + .borderRadius($r("app.float.radius_16")) + .backgroundColor($r("app.color.white_bg_color")) + .width(ConfigData.WH_100_100) + .margin({ top: $r("app.float.distance_12") }) + .padding({ + top: $r("app.float.distance_4"), + bottom: $r("app.float.distance_4") }) - .margin({ top: $r("app.float.distance_8") }) } .useSizeType({ sm: { span: 4, offset: 0 }, @@ -71,6 +105,15 @@ struct MoreConnections { } aboutToAppear() { + this.nfcSupport = parameter.getSync('const.SystemCapability.Communication.NFC.Core', 'false'); + LogUtil.info(TAG + `nfcSupport ${this.nfcSupport}`); + + if (this.nfcSupport === 'true') { + this.nfcInit(); + } + } + + nfcInit(){ LogUtil.info(TAG + 'aboutToAppear in'); try { @@ -106,8 +149,7 @@ struct MoreConnections { this.nfcImageWidth = (this.maxScreenHeight / 2) * 0.8; this.nfcImageHeight = (this.maxScreenHeight / 2) * 0.8; } - } - else { + } else { if (this.maxScreenHeight > this.maxScreenWidth / 2) { this.nfcImageWidth = (this.maxScreenWidth / 2) * 0.7; this.nfcImageHeight = (this.maxScreenWidth / 2) * 0.7; diff --git a/product/phone/src/main/ets/pages/settingList.ets b/product/phone/src/main/ets/pages/settingList.ets index 3fa3afd4..88b0a74e 100644 --- a/product/phone/src/main/ets/pages/settingList.ets +++ b/product/phone/src/main/ets/pages/settingList.ets @@ -25,6 +25,7 @@ import ConfigData from '../../../../../../common/utils/src/main/ets/default/base import ResourceUtil from '../../../../../../common/search/src/main/ets/default/common/ResourceUtil'; import {SettingItemComponent} from '../../../../../../common/component/src/main/ets/default/settingItemComponent'; import GlobalResourceManager from '../../../../../../common/utils/src/main/ets/default/baseUtil/GlobalResourceManager'; +import { VpnTypeModel } from '../model/vpnImpl/VpnTypeModel'; const deviceTypeInfo = deviceInfo.deviceType; @@ -120,22 +121,6 @@ struct SettingList { } LogUtil.info('CCCC settings SettingList aboutToAppear end'); } - - onPageShow() { - LogUtil.info('CCCC settings SettingList onPageShow enter'); - FeatureAbility.getWant().then((want) => { - if (want.uri === "wifi") { - Router.replace({ uri: "pages/wifi" }); - } else if (want.uri === "bluetooth") { - Router.replace({ uri: "pages/bluetooth" }); - } else if (want.uri === "volumeControl") { - Router.replace({ uri: "pages/volumeControl" }); - } else if (want.uri === "locationServices") { - Router.replace({ uri: "pages/locationServices" }); - } - }) - LogUtil.info('CCCC settings SettingList onPageShow end'); - } } @Component @@ -272,13 +257,13 @@ struct EntryComponent { }) } - if (this.info === 'true') { - ListItem(){ + if (this.info === 'true' || VpnTypeModel.getInstance().isSupportVpn()) { + ListItem() { // moreConnections SettingItemComponent({ targetPage: "pages/moreConnections", settingTitle: $r("app.string.moreConnectionsTab"), - settingEndText:$endTextEmpty, + settingEndText: $endTextEmpty, settingIcon: "/res/image/ic_settings_more_connections.svg", }) } diff --git a/product/phone/src/main/ets/pages/vpn/customComponent.ets b/product/phone/src/main/ets/pages/vpn/customComponent.ets new file mode 100644 index 00000000..fe15f06c --- /dev/null +++ b/product/phone/src/main/ets/pages/vpn/customComponent.ets @@ -0,0 +1,224 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import deviceInfo from '@ohos.deviceInfo'; +import fileIo from '@ohos.file.fs'; +import picker from '@ohos.file.picker'; +import util from '@ohos.util'; +import { BusinessError } from '@ohos.base'; +import { SelectDialog } from '@ohos.arkui.advanced.Dialog'; +import VpnConfig, { OpenVpnConfig } from '../../model/vpnImpl/VpnConfig'; +import { VpnTypeModel } from '../../model/vpnImpl/VpnTypeModel'; +import VpnConstant from '../../model/vpnImpl/VpnConstant'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; + +export const SELECTER_VPNTYPE: number = 1; +export const SELECTER_OVPN_PROTOCOL: number = 2; +export const SELECTER_OVPN_AUTH: number = 3; +export const deviceTypeInfo = deviceInfo.deviceType; + +export const INPUT_TYPE_IP: number = 1; +export const INPUT_TYPE_PORT: number = 2; + +const MODULE_TAG: string = 'setting_vpn:custom_component:'; + +@Component +export struct TextWithInput { + title: Resource | string = ''; + inputType: number | undefined = undefined; + inputPlaceholder: Resource | string = ''; + maxLength: number = VpnConstant.INPUT_MAX_LENGTH; + @Prop inputText: string = ''; + @State fontColor: ResourceColor = $r('app.color.font_color_182431'); + onChange: (value: string) => void = () => {}; + + build() { + Column() { + Text(this.title) + .fontSize($r('app.float.font_16')) + .fontColor($r('app.color.font_color_182431')) + .fontWeight(FontWeight.Medium) + .textAlign(TextAlign.Start) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .textAlign(TextAlign.Start) + .padding({ left: 8 }) + .margin({ top: 10 }); + TextInput({ placeholder: this.inputPlaceholder, text: this.inputText }).onChange((value: string) => { + if (this.inputType === INPUT_TYPE_IP) { + if (VpnConstant.REGEX_IP.test(value)) { + this.fontColor = $r('app.color.font_color_182431'); + } else { + this.fontColor = $r('sys.color.ohos_id_color_warning'); + } + } + if (this.inputType === INPUT_TYPE_PORT) { + if (VpnConstant.REGEX_PORT.test(value)) { + this.fontColor = $r('app.color.font_color_182431'); + } else { + this.fontColor = $r('sys.color.ohos_id_color_warning'); + } + } + this.onChange(value); + }).padding({ left: 5 }) + .fontSize($r('app.float.font_16')) + .showUnderline(true) + .height(45) + .margin({ top: 5 }) + .fontColor(this.fontColor) + .maxLength(this.maxLength); + }.alignItems(HorizontalAlign.Start) + } +} + +@Component +export struct Selector { + title: string | Resource = ''; + @Link vpnConfig: VpnConfig; + type: number = 0; + sheetTitles: string[] = []; + sheetInfos: SheetInfo[] = []; + selectDialogController: CustomDialogController | undefined = undefined; + + build() { + Row() { + Text(this.title) + .fontSize($r('app.float.font_16')) + .fontColor($r('app.color.font_color_182431')) + .fontWeight(FontWeight.Medium) + .textAlign(TextAlign.Start) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .textAlign(TextAlign.Start) + Blank() + Text(this.sheetTitles[this.getSelectIndex(this.vpnConfig, this.type)]) + .padding({ right: 4 }) + Image($r('app.media.ic_settings_arrow')) + .width($r('app.float.wh_value_12')) + .height($r('app.float.wh_value_24')) + .fillColor($r('sys.color.ohos_id_color_primary')) + .opacity($r('app.float.opacity_0_2')) + } + .borderRadius($r('app.float.radius_16')) + .backgroundColor('#ffffff') + .width('100%') + .height($r('app.float.wh_value_56')) + .borderRadius($r('app.float.radius_16')) + .padding({ left: 8, right: 8 }) + .onClick(() => { + this.showSelectDialog(); + }) + } + + getSelectIndex(vpnConfig: VpnConfig, selecter: number): number { + if (selecter === SELECTER_VPNTYPE) { + return VpnTypeModel.getInstance().getSupportVpnTypes().indexOf(vpnConfig.vpnType); + } else if (selecter === SELECTER_OVPN_PROTOCOL) { + let openvpn : OpenVpnConfig = vpnConfig as OpenVpnConfig; + return openvpn.ovpnProtocol; + } else if (selecter === SELECTER_OVPN_AUTH) { + let openvpn : OpenVpnConfig = vpnConfig as OpenVpnConfig; + return openvpn.ovpnAuthType; + } else { + return 0; + } + } + + showSelectDialog() { + this.selectDialogController = new CustomDialogController({ + builder: SelectDialog({ + title: this.title, + selectedIndex: this.getSelectIndex(this.vpnConfig, this.type), + confirm: { + value: $r('app.string.cancel'), + action: () => { + this.selectDialogController?.close(); + }, + }, + radioContent: this.sheetInfos + }), + autoCancel: true, + alignment: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? DialogAlignment.Bottom : DialogAlignment.Center, + offset: ({ dx: 0, dy: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? '-24dp' : 0 }) + }); + this.selectDialogController?.open(); + } +} + +@Component +export struct SelectFile { + title: ResourceStr = ''; + @Prop vpnConfig: VpnConfig; + @State fileName: string | undefined = undefined; + realFilePath: string | undefined = undefined; + callback?: (result: string[]) => void; + + build() { + Row() { + Text(this.title) + .fontSize($r('app.float.font_16')) + .fontColor($r('app.color.font_color_182431')) + .fontWeight(FontWeight.Medium) + .textAlign(TextAlign.Start) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .textAlign(TextAlign.Start) + Blank() + Text(this.fileName ?? $r('app.string.vpn_edit_ovpn_click_select')) + .padding({ right: 4 }) + Image($r('sys.media.ohos_ic_public_arrow_right')).width($r('app.float.wh_value_12')) + .height($r('app.float.wh_value_24')) + .fillColor($r('sys.color.ohos_id_color_primary')) + .opacity($r('app.float.opacity_0_2')) + } + .borderRadius($r('app.float.radius_16')) + .backgroundColor('#ffffff') + .width('100%') + .height($r('app.float.wh_value_56')) + .borderRadius($r('app.float.radius_16')) + .padding({ left: 8, right: 8 }) + .onClick(() => { + this.pickFile((result: string[]) => { + if (result) { + this.fileName = result[0]; + this.callback!(result); + } + }) + }) + } + + pickFile(callback: (result: string[]) => void): void { + try { + let selectOption = new picker.DocumentSelectOptions(); + let documentPicker = new picker.DocumentViewPicker(); + documentPicker.select(selectOption).then((result: Array) => { + if (result.length > 0) { + let uri = result[0]; + let fileName = uri.substring(uri.lastIndexOf('/') + 1); + let file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY); + let fileStat = fileIo.statSync(file.fd); + let buf = new ArrayBuffer(fileStat.size); + fileIo.readSync(file.fd, buf); + fileIo.close(file.fd) + let unit8 = new Uint8Array(buf); + let content = new util.TextDecoder().decodeWithStream(unit8); + callback([fileName, content]); + } + }).catch((err: BusinessError) => { + LogUtil.error(MODULE_TAG + 'DocumentViewPicker select failed with err: ' + JSON.stringify(err)); + }); + } catch (error) { + let err: BusinessError = error as BusinessError; + LogUtil.error(MODULE_TAG + 'DocumentViewPicker failed with err: ' + JSON.stringify(err)); + } + } +} diff --git a/product/phone/src/main/ets/pages/vpn/ipsecVpnEdit.ets b/product/phone/src/main/ets/pages/vpn/ipsecVpnEdit.ets new file mode 100644 index 00000000..70770b5e --- /dev/null +++ b/product/phone/src/main/ets/pages/vpn/ipsecVpnEdit.ets @@ -0,0 +1,301 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import { util } from '@kit.ArkTS'; +import { TextWithInput, SelectFile, INPUT_TYPE_IP } from './customComponent'; +import { IpsecVpnConfig } from '../../model/vpnImpl/VpnConfig'; +import { VpnTypeModel } from '../../model/vpnImpl/VpnTypeModel'; +import { VpnConfigModel } from '../../model/vpnImpl/VpnConfigModel'; +import VpnConstant from '../../model/vpnImpl/VpnConstant'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; + +@Component +export default struct IpsecVpnEdit { + onSaveBtnEnableChange: Function = (enabled: boolean) => {}; + @State @Watch('onVpnConfigChange') vpnConfig: IpsecVpnConfig = new IpsecVpnConfig(); + @State ipsecIdentifierVisibility: Visibility = Visibility.None; + @State l2tpSecretVisibility: Visibility = Visibility.None; + @State ipsecSecretVisibility: Visibility = Visibility.None; + @State userCertVisibility: Visibility = Visibility.None; + @State caServerCertVisibility: Visibility = Visibility.None; + @State advanceChecked: boolean = false; + @State vpnAddress: string = ''; + @State dnsAddress: string = ''; + vpnType: number = 0; + base64Util = new util.Base64Helper(); + + aboutToAppear() { + this.vpnAddress = VpnConfigModel.getInstance().getAddress(this.vpnConfig) ?? ''; + let dnsAddresses = this.vpnConfig.dnsAddresses; + if (dnsAddresses) { + this.dnsAddress = dnsAddresses[0]; + } + this.updateUI(); + } + + updateUI() { + this.updateIpsecIdentifierVisibility(); + this.updateL2tpSecretVisibility(); + this.updateIpsecSecretVisibility(); + this.updateUserCertVisibility(); + this.updateCaServerCertVisibility(); + } + + onVpnConfigChange() { + if (this.vpnType !== this.vpnConfig.vpnType) { + LogUtil.info('onVpnConfigChange ' + this.vpnType + '->' + this.vpnConfig.vpnType); + this.vpnType = this.vpnConfig.vpnType; + this.updateUI(); + } + } + + onNonNullableParamChange(): void { + let isNonNullParamReady = true; + if (!VpnConstant.REGEX_IP.test(this.vpnAddress)) { + isNonNullParamReady = false; + } + if (this.dnsAddress.length >0 && !VpnConstant.REGEX_IP.test(this.dnsAddress)) { + isNonNullParamReady = false; + } + if (this.vpnConfig.vpnType !== VpnTypeModel.TYPE_L2TP_IPSEC_PSK) { + if (this.ipsecIdentifierVisibility === Visibility.Visible && !this.vpnConfig.ipsecIdentifier) { + isNonNullParamReady = false; + } + if (this.l2tpSecretVisibility === Visibility.Visible && !this.vpnConfig.l2tpSharedKey) { + isNonNullParamReady = false; + } + } + if (this.ipsecSecretVisibility === Visibility.Visible && !this.vpnConfig.ipsecPreSharedKey) { + isNonNullParamReady = false; + } + this.onSaveBtnEnableChange(isNonNullParamReady); + } + + updateIpsecIdentifierVisibility(): void { + switch (this.vpnConfig.vpnType) { + case VpnTypeModel.TYPE_IKEV2_IPSEC_MSCHAPv2: + case VpnTypeModel.TYPE_IKEV2_IPSEC_PSK: + case VpnTypeModel.TYPE_IKEV2_IPSEC_RSA: + case VpnTypeModel.TYPE_L2TP_IPSEC_PSK: + case VpnTypeModel.TYPE_IPSEC_XAUTH_PSK: + this.ipsecIdentifierVisibility = Visibility.Visible; + break; + default: + this.ipsecIdentifierVisibility = Visibility.None; + break; + } + } + + updateL2tpSecretVisibility(): void { + switch (this.vpnConfig.vpnType) { + case VpnTypeModel.TYPE_L2TP_IPSEC_PSK: + case VpnTypeModel.TYPE_L2TP_IPSEC_RSA: + this.l2tpSecretVisibility = Visibility.Visible; + break; + default: + this.l2tpSecretVisibility = Visibility.None; + break; + } + } + + updateIpsecSecretVisibility(): void { + switch (this.vpnConfig.vpnType) { + case VpnTypeModel.TYPE_IKEV2_IPSEC_PSK: + case VpnTypeModel.TYPE_L2TP_IPSEC_PSK: + case VpnTypeModel.TYPE_IPSEC_XAUTH_PSK: + this.ipsecSecretVisibility = Visibility.Visible; + break; + default: + this.ipsecSecretVisibility = Visibility.None; + break; + } + } + + updateUserCertVisibility(): void { + switch (this.vpnConfig.vpnType) { + case VpnTypeModel.TYPE_IKEV2_IPSEC_RSA: + case VpnTypeModel.TYPE_L2TP_IPSEC_RSA: + case VpnTypeModel.TYPE_IPSEC_XAUTH_RSA: + this.userCertVisibility = Visibility.Visible; + break; + default: + this.userCertVisibility = Visibility.None; + break; + } + } + + updateCaServerCertVisibility(): void { + switch (this.vpnConfig.vpnType) { + case VpnTypeModel.TYPE_IKEV2_IPSEC_MSCHAPv2: + case VpnTypeModel.TYPE_IKEV2_IPSEC_RSA: + case VpnTypeModel.TYPE_L2TP_IPSEC_RSA: + case VpnTypeModel.TYPE_IPSEC_XAUTH_RSA: + case VpnTypeModel.TYPE_IPSEC_HYBRID_RSA: + this.caServerCertVisibility = Visibility.Visible; + break; + default: + this.caServerCertVisibility = Visibility.None; + break; + } + } + + build() { + Column() { + TextWithInput({ + title: $r('app.string.vpn_edit_serveraddress'), + inputPlaceholder: '', + inputText: this.vpnAddress, + inputType: INPUT_TYPE_IP, + onChange: (value: string) => { + this.vpnAddress = value.trim(); + VpnConfigModel.getInstance().setAddress(this.vpnConfig, this.vpnAddress); + this.onNonNullableParamChange(); + } + }) + TextWithInput({ + title: $r('app.string.vpn_edit_ipsecidentifier'), + inputPlaceholder: $r('app.string.vpn_edit_unuse'), + inputText: this.vpnConfig.ipsecIdentifier, + onChange: (value: string) => { + this.vpnConfig.ipsecIdentifier = value.trim(); + this.onNonNullableParamChange(); + } + }).visibility(this.ipsecIdentifierVisibility) + TextWithInput({ + title: $r('app.string.vpn_edit_l2tpsecret'), + inputPlaceholder: $r('app.string.vpn_edit_unuse'), + inputText: this.vpnConfig.l2tpSharedKey, + onChange: (value: string) => { + this.vpnConfig.l2tpSharedKey = value.trim(); + this.onNonNullableParamChange(); + } + }).visibility(this.l2tpSecretVisibility) + TextWithInput({ + title: $r('app.string.vpn_edit_ipsecsecret'), + inputPlaceholder: $r('app.string.vpn_edit_unuse'), + inputText: this.vpnConfig.ipsecPreSharedKey, + onChange: (value: string) => { + this.vpnConfig.ipsecPreSharedKey = value.trim(); + this.onNonNullableParamChange(); + } + }).visibility(this.ipsecSecretVisibility) + SelectFile({ + title: $r('app.string.vpn_edit_usercert'), + fileName: this.vpnConfig.ipsecPublicUserCertFilePath, + vpnConfig: this.vpnConfig, + callback: (result: string[]) => { + this.vpnConfig.ipsecPublicUserCertFilePath = result[0]; + this.vpnConfig. + ipsecPublicUserCertConfig = this.base64Util.encodeToStringSync( + VpnConfigModel.getInstance().stringToUint8Array(result[1])); + this.onNonNullableParamChange(); + } + }).margin({ top: 15 }) + .visibility(this.userCertVisibility) + SelectFile({ + title: $r('app.string.vpn_edit_user_private_cert'), + fileName: this.vpnConfig.ipsecPrivateUserCertFilePath, + vpnConfig: this.vpnConfig, + callback: (result: string[]) => { + this.vpnConfig.ipsecPrivateUserCertFilePath = result[0]; + this.vpnConfig. + ipsecPrivateUserCertConfig = this.base64Util.encodeToStringSync( + VpnConfigModel.getInstance().stringToUint8Array(result[1])); + this.onNonNullableParamChange(); + } + }).margin({ top: 15 }) + .visibility(this.userCertVisibility) + + SelectFile({ + title: $r('app.string.vpn_edit_cacert'), + fileName: this.vpnConfig.ipsecCaCertFilePath, + vpnConfig: this.vpnConfig, + callback: (result: string[]) => { + this.vpnConfig.ipsecCaCertFilePath = result[0]; + this.vpnConfig.ipsecCaCertConfig = this.base64Util.encodeToStringSync( + VpnConfigModel.getInstance().stringToUint8Array(result[1])); + this.onNonNullableParamChange(); + } + }).margin({ top: 10 }) + .visibility(this.caServerCertVisibility) + SelectFile({ + title: $r('app.string.vpn_edit_servercert'), + fileName: this.vpnConfig.ipsecPublicServerCertFilePath, + vpnConfig: this.vpnConfig, + callback: (result: string[]) => { + this.vpnConfig.ipsecPublicServerCertFilePath = result[0]; + this.vpnConfig.ipsecPublicServerCertConfig = this.base64Util.encodeToStringSync( + VpnConfigModel.getInstance().stringToUint8Array(result[1])); + this.onNonNullableParamChange(); + } + }).margin({ top: 10 }) + .visibility(this.caServerCertVisibility) + SelectFile({ + title: $r('app.string.vpn_edit_server_private_cert'), + fileName: this.vpnConfig.ipsecPrivateServerCertFilePath, + vpnConfig: this.vpnConfig, + callback: (result: string[]) => { + this.vpnConfig.ipsecPrivateServerCertFilePath = result[0]; + this.vpnConfig.ipsecPrivateServerCertConfig = this.base64Util.encodeToStringSync( + VpnConfigModel.getInstance().stringToUint8Array(result[1])); + this.onNonNullableParamChange(); + } + }).margin({ top: 10, bottom: 10 }) + .visibility(this.caServerCertVisibility) + Row({ space: 10 }) { + Checkbox().shape(CheckBoxShape.ROUNDED_SQUARE).onChange((value) => { + this.advanceChecked = value; + }) + Text($r('app.string.vpn_edit_advanced')) + }.width('100%').margin({ left: 4 }) + Column({ space: 10 }) { + TextWithInput({ + title: $r('app.string.vpn_edit_searchdomains'), + inputPlaceholder: $r('app.string.vpn_edit_unuse'), + inputText: this.vpnConfig.searchDomains?.[0], + onChange: (value: string) => { + let searchDomain = value.trim(); + if (searchDomain) { + this.vpnConfig.searchDomains = [searchDomain]; + } + } + }) + TextWithInput({ + title: $r('app.string.vpn_edit_dnsaddresses'), + inputPlaceholder: $r('app.string.vpn_edit_unuse'), + inputText: this.dnsAddress, + inputType: INPUT_TYPE_IP, + onChange: (value: string) => { + this.dnsAddress = value.trim(); + if (this.dnsAddress && this.dnsAddress.length > 0) { + this.vpnConfig.dnsAddresses = [this.dnsAddress]; + } + this.onNonNullableParamChange(); + } + }) + TextWithInput({ + title: $r('app.string.vpn_edit_router'), + inputPlaceholder: $r('app.string.vpn_edit_unuse'), + inputText: this.vpnConfig.forwardingRoutes, + onChange: (value: string) => { + this.vpnConfig.forwardingRoutes = value.trim(); + } + }) + }.width('100%') + .alignItems(HorizontalAlign.Start) + .visibility(this.advanceChecked ? Visibility.Visible : Visibility.None) + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/vpn/openVpnEdit.ets b/product/phone/src/main/ets/pages/vpn/openVpnEdit.ets new file mode 100644 index 00000000..1b55da40 --- /dev/null +++ b/product/phone/src/main/ets/pages/vpn/openVpnEdit.ets @@ -0,0 +1,274 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import ResourceManager from '@ohos.resourceManager'; +import { + TextWithInput, + Selector, + SelectFile, + SELECTER_OVPN_PROTOCOL, + SELECTER_OVPN_AUTH, + INPUT_TYPE_IP, + INPUT_TYPE_PORT +} from './customComponent'; +import { OpenVpnConfig } from '../../model/vpnImpl/VpnConfig'; +import { VpnConfigModel } from '../../model/vpnImpl/VpnConfigModel'; +import VpnConstant from '../../model/vpnImpl/VpnConstant'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; +import { GlobalContext } from '../../../../../../../common/utils/src/main/ets/default/baseUtil/GlobalContext'; + +@Component +export default struct OpenVpnEdit { + onSaveBtnEnableChange: Function = (enabled: boolean) => {}; + @State vpnConfig: OpenVpnConfig = new OpenVpnConfig(); + @State vpnAddress: string = ''; + @State vpnPort: string = ''; + @State ovpnAdvanceChecked: boolean = false; + @State ovpnProxyChecked: boolean = false; + @State ovpnProxyHost: string = ''; + @State ovpnProxyPort: string = ''; + + ovpnProtocolSheetTitles: string[] = []; + ovpnProtocolSheets: SheetInfo[] = []; + ovpnAuthSheetTitles: string[] = []; + ovpnAuthSheets: SheetInfo[] = []; + + aboutToAppear() { + try { + let context = GlobalContext.getContext().getObject(GlobalContext.globalKeySettingsAbilityContext) as Context; + let resMgr: ResourceManager.ResourceManager = context.resourceManager; + this.vpnConfig.ovpnProtocol = this.vpnConfig.ovpnProtocol ?? 0; + this.ovpnProtocolSheetTitles = resMgr.getStringArrayValueSync($r('app.strarray.ovpn_protocol').id); + this.ovpnProtocolSheets = this.ovpnProtocolSheetTitles.map((title, index) => { + let sheetInfo: SheetInfo = { + title: title, + action: () => { + this.vpnConfig.ovpnProtocol = index; + } + } + return sheetInfo; + }) + this.vpnConfig.ovpnAuthType = this.vpnConfig.ovpnAuthType ?? 0; + this.ovpnAuthSheetTitles = resMgr.getStringArrayValueSync($r('app.strarray.ovpn_auth_type').id); + this.ovpnAuthSheets = this.ovpnAuthSheetTitles.map((title, index) => { + let sheetInfo: SheetInfo = { + title: title, + action: () => { + this.vpnConfig.ovpnAuthType = index; + } + } + return sheetInfo; + }) + } catch (error) { + LogUtil.error(`getStringArrayValueSync failed, error: ${JSON.stringify(error)}.`); + } + + this.updateUI(); + } + + updateUI() { + this.vpnAddress = VpnConfigModel.getInstance().getAddress(this.vpnConfig) ?? ''; + this.vpnPort = this.vpnConfig.ovpnPort ?? ''; + } + + onNonNullableParamChange(): void { + let isNonNullParamReady = true; + if (!VpnConstant.REGEX_IP.test(this.vpnAddress) || !VpnConstant.REGEX_PORT.test(this.vpnPort)) { + isNonNullParamReady = false; + } + if (this.ovpnProxyHost.length > 0 && !VpnConstant.REGEX_IP.test(this.ovpnProxyHost)) { + isNonNullParamReady = false; + } + if (this.ovpnProxyPort.length > 0 && !VpnConstant.REGEX_PORT.test(this.ovpnProxyPort)) { + isNonNullParamReady = false; + } + this.onSaveBtnEnableChange(isNonNullParamReady); + } + + build() { + Column() { + SelectFile({ + title: $r('app.string.vpn_edit_ovpn_configfile'), + fileName: this.vpnConfig.ovpnConfigFilePath, + vpnConfig: this.vpnConfig, + callback: (result: string[]) => { + VpnConfigModel.getInstance().inflateConfigFromFileData(this.vpnConfig, result); + this.updateUI(); + } + }); + + TextWithInput({ + title: $r('app.string.vpn_edit_ovpn_serveraddress'), + inputPlaceholder: '', + inputText: this.vpnAddress, + inputType: INPUT_TYPE_IP, + onChange: (value: string) => { + this.vpnAddress = value.trim(); + VpnConfigModel.getInstance().setAddress(this.vpnConfig, this.vpnAddress); + this.onNonNullableParamChange(); + } + }).margin({ top: 5 }) + + TextWithInput({ + title: $r('app.string.vpn_edit_ovpn_port'), + inputPlaceholder: '', + inputText: this.vpnPort, + inputType: INPUT_TYPE_PORT, + onChange: (value: string) => { + this.vpnPort = value.trim() + this.vpnConfig.ovpnPort = this.vpnPort; + this.onNonNullableParamChange(); + } + }) + + Selector({ + title: $r('app.string.vpn_edit_ovpn_protocol'), + vpnConfig: this.vpnConfig, + type: SELECTER_OVPN_PROTOCOL, + sheetTitles: this.ovpnProtocolSheetTitles, + sheetInfos: this.ovpnProtocolSheets + }).margin({ top: 10, bottom: 10 }); + + Row({ space: 10 }) { + Checkbox().shape(CheckBoxShape.ROUNDED_SQUARE).onChange((value) => { + this.ovpnAdvanceChecked = value; + }) + Text($r('app.string.vpn_edit_advanced')) + }.width('100%').margin({ left: 4, bottom: 10 }) + + Column({ space: 10 }) { + Selector({ + title: $r('app.string.vpn_edit_auth_type'), + vpnConfig: this.vpnConfig, + type: SELECTER_OVPN_AUTH, + sheetTitles: this.ovpnAuthSheetTitles, + sheetInfos: this.ovpnAuthSheets + }) + + SelectFile({ + title: $r('app.string.vpn_edit_ovpn_ca_cert_file'), + fileName: this.vpnConfig.ovpnCaCertFilePath, + vpnConfig: this.vpnConfig, + callback: (result: string[]) => { + this.vpnConfig.ovpnCaCertFilePath = result[0]; + this.vpnConfig.ovpnCaCert = '\n' + result[1] + '\n'; + } + }); + SelectFile({ + title: $r('app.string.vpn_edit_ovpn_user_cert_file'), + fileName: this.vpnConfig.ovpnUserCertFilePath, + vpnConfig: this.vpnConfig, + callback: (result: string[]) => { + this.vpnConfig.ovpnUserCertFilePath = result[0]; + this.vpnConfig.ovpnUserCert = '\n' + result[1] + '\n'; + } + }).visibility(this.vpnConfig.ovpnAuthType !== VpnConstant.OVPN_AUTH_TYPE_PWD ? + Visibility.Visible : Visibility.None); + + SelectFile({ + title: $r('app.string.vpn_edit_ovpn_private_key'), + fileName: this.vpnConfig.ovpnPrivateKeyFilePath, + vpnConfig: this.vpnConfig, + callback: (result: string[]) => { + this.vpnConfig.ovpnPrivateKeyFilePath = result[0]; + this.vpnConfig.ovpnPrivateKey = '\n' + result[1] + '\n'; + } + }).visibility(this.vpnConfig.ovpnAuthType !== VpnConstant.OVPN_AUTH_TYPE_PWD ? + Visibility.Visible : Visibility.None); + + TextWithInput({ + title: $r('app.string.vpn_edit_ovpn_private_key_psd'), + inputPlaceholder: '', + inputText: this.vpnConfig.askpass, + onChange: (value: string) => { + this.vpnConfig.askpass = value.trim(); + } + }).visibility(this.vpnConfig.ovpnAuthType !== VpnConstant.OVPN_AUTH_TYPE_PWD ? + Visibility.Visible : Visibility.None); + + TextWithInput({ + title: $r('app.string.vpn_edit_ovpn_username'), + inputPlaceholder: '', + inputText: this.vpnConfig.userName, + maxLength: VpnConstant.VPN_USER_NAME_MAX_LENGTH, + onChange: (value: string) => { + this.vpnConfig.userName = value.trim(); + } + }).visibility(this.vpnConfig.ovpnAuthType !== VpnConstant.OVPN_AUTH_TYPE_TLS ? + Visibility.Visible : Visibility.None); + + TextWithInput({ + title: $r('app.string.vpn_edit_ovpn_password'), + inputPlaceholder: '', + inputText: this.vpnConfig.password, + maxLength: VpnConstant.VPN_PASSWORD_MAX_LENGTH, + onChange: (value: string) => { + this.vpnConfig.password = value.trim(); + } + }).visibility(this.vpnConfig.ovpnAuthType !== VpnConstant.OVPN_AUTH_TYPE_TLS ? + Visibility.Visible : Visibility.None); + + Row({ space: 10 }) { + Checkbox().shape(CheckBoxShape.ROUNDED_SQUARE).onChange((value) => { + this.ovpnProxyChecked = value; + }) + Text($r('app.string.vpn_edit_proxy_advanced')) + }.width('100%').margin({ left: 4 }) + + Column({ space: 10 }) { + TextWithInput({ + title: $r('app.string.vpn_edit_ovpn_proxy_host'), + inputPlaceholder: $r('app.string.vpn_edit_unuse'), + inputText: this.ovpnProxyHost, + inputType: INPUT_TYPE_IP, + onChange: (value: string) => { + this.ovpnProxyHost = value.trim(); + this.vpnConfig.ovpnProxyHost = this.ovpnProxyHost; + this.onNonNullableParamChange(); + } + }) + + TextWithInput({ + title: $r('app.string.vpn_edit_ovpn_proxy_port'), + inputPlaceholder: $r('app.string.vpn_edit_unuse'), + inputText: this.ovpnProxyPort, + inputType: INPUT_TYPE_PORT, + onChange: (value: string) => { + this.ovpnProxyPort = value.trim(); + this.vpnConfig.ovpnProxyPort = this.ovpnProxyPort; + this.onNonNullableParamChange(); + } + }) + TextWithInput({ + title: $r('app.string.vpn_edit_ovpn_proxy_username'), + inputPlaceholder: $r('app.string.vpn_edit_unuse'), + inputText: this.vpnConfig.ovpnProxyUser, + onChange: (value: string) => { + this.vpnConfig.ovpnProxyUser = value.trim(); + } + }) + TextWithInput({ + title: $r('app.string.vpn_edit_ovpn_proxy_password'), + inputPlaceholder: $r('app.string.vpn_edit_unuse'), + inputText: this.vpnConfig.ovpnProxyPass, + onChange: (value: string) => { + this.vpnConfig.ovpnProxyPass = value.trim(); + } + }) + }.visibility(this.ovpnProxyChecked ? Visibility.Visible : Visibility.None) + }.visibility(this.ovpnAdvanceChecked ? Visibility.Visible : Visibility.None) + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/ets/pages/vpn/vpnConnect.ets b/product/phone/src/main/ets/pages/vpn/vpnConnect.ets new file mode 100644 index 00000000..4be0ea3e --- /dev/null +++ b/product/phone/src/main/ets/pages/vpn/vpnConnect.ets @@ -0,0 +1,230 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import { BusinessError } from '@ohos.base'; +import deviceInfo from '@ohos.deviceInfo'; +import router from '@ohos.router'; +import util from '@ohos.util'; +import vpn from '@ohos.net.vpn'; +import { VpnConnectModel } from '../../model/vpnImpl/VpnConnectModel'; +import VpnConfig, { VpnListItem } from '../../model/vpnImpl/VpnConfig'; +import { SwanCtlModel } from '../../model/vpnImpl/SwanCtlModel'; +import VpnConstant from '../../model/vpnImpl/VpnConstant'; +import { VpnConfigModel } from '../../model/vpnImpl/VpnConfigModel'; +import { ResourceUtils } from '../../model/accessibilityImpl/resourceUtils'; +import HeadComponent from '../../../../../../../common/component/src/main/ets/default/headComponent'; +import ConfigData from '../../../../../../../common/utils/src/main/ets/default/baseUtil/ConfigData'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; + +const deviceTypeInfo = deviceInfo.deviceType; +const MODULE_TAG: string = 'setting_vpn:VpnConnect:'; + +@Entry +@Component +export struct VpnConnect { + @StorageLink('connectState') connectState: number = VpnConstant.VPN_STATE_NONE; + @State vpnConfig: VpnConfig = new VpnConfig(); + @State vpnUserName: string = ''; + @State vpnPassword: string = ''; + @State isSetupBtnEnabled: boolean = false; + + async aboutToAppear() { + let state: number | undefined = AppStorage.get(VpnConstant.STORAGE_KEY_CONNECT_STATE); + if (state) { + this.connectState = state; + } + let vpnItem: VpnListItem = router.getParams() as VpnListItem; + if (vpnItem && vpnItem.vpnId) { + LogUtil.info(MODULE_TAG + `vpnid = ${JSON.stringify(vpnItem.vpnId)}`) + try { + let data: vpn.SysVpnConfig = await vpn.getSysVpnConfig(vpnItem.vpnId); + if (data) { + this.vpnConfig = data as VpnConfig; + if (this.vpnConfig.saveLogin) { + this.vpnUserName = this.vpnConfig.userName ?? ''; + this.vpnPassword = this.vpnConfig.password ?? ''; + } + this.setConnectBtnEnabled(); + } else { + VpnConfigModel.getInstance().showToast($r('app.string.vpn_error_operation_failed')); + } + } catch (err) { + let message = (err as BusinessError).message; + VpnConfigModel.getInstance().showToast( + ResourceUtils.getStringSync($r('app.string.vpn_error_operation_failed')) + ': ' + message); + } + } else { + LogUtil.error(MODULE_TAG + `getParams is error`) + } + } + + build() { + Column() { + HeadComponent({ + headName: $r('app.string.vpn_connect_title', this.vpnConfig.vpnName), + isActive: true + }).padding({ right: 35 }) + + Column() { + Column() { + Text($r('app.string.vpn_connect_username')) + .width('100%') + TextInput({ text: this.vpnUserName }) + .padding({ left: 1 }) + .fontSize($r('app.float.font_16')) + .maxLength(VpnConstant.VPN_USER_NAME_MAX_LENGTH) + .backgroundColor('#00000000') + .height(40) + .onChange((value) => { + value = value.replace(/\s+/g, ''); + this.vpnUserName = value.trim(); + this.setConnectBtnEnabled(); + }) + Divider() + .color($r('sys.color.ohos_id_color_list_separator')) + Text($r('app.string.vpn_connect_password')) + .width('100%') + .margin({ top: 15 }) + TextInput({ text: this.vpnPassword }) + .padding({ left: 1 }) + .type(InputType.Password) + .maxLength(VpnConstant.VPN_PASSWORD_MAX_LENGTH) + .fontSize($r('app.float.font_16')) + .backgroundColor('#00000000') + .height(40) + .onChange((value) => { + value = value.replace(/\s+/g, ''); + this.vpnPassword = value.trim(); + this.setConnectBtnEnabled(); + }) + Divider() + .color($r('sys.color.ohos_id_color_list_separator')) + + Row({ space: 8 }) { + Checkbox().select(this.vpnConfig.saveLogin ?? false).onChange((value) => { + this.vpnConfig.saveLogin = value; + }).shape(CheckBoxShape.ROUNDED_SQUARE) + Text($r('app.string.vpn_connect_save')) + }.width('100%').margin({ top: 15 }) + } + + Blank().layoutWeight(1) + Row() { + Button($r('app.string.cancel')) + .fontColor('#0A59F7') + .fontSize(16) + .height(45) + .fontWeight(FontWeight.Bold) + .padding({ top: 10, left: 60, right: 60, bottom: 10 }) + .borderRadius($r('app.float.radius_20')) + .backgroundColor('#E6E8E9') + .layoutWeight(1) + .onClick(() => { + router.back(); + }) + Blank().width(30) + Button($r('app.string.vpn_connect_confirm')) + .fontColor(this.isSetupBtnEnabled ? '#0A59F7' : '#92B3F3') + .fontSize(16) + .height(45) + .fontWeight(FontWeight.Bold) + .padding({ top: 10, left: 60, right: 60, bottom: 10 }) + .borderRadius($r('app.float.radius_20')) + .backgroundColor('#E6E8E9') + .layoutWeight(1) + .enabled(this.isSetupBtnEnabled) + .onClick(() => { + this.onSetupBtnClick(); + }) + }.margin({ bottom: 65 }) + } + .padding($r('app.float.distance_4')) + } + .width(ConfigData.WH_100_100) + .height(ConfigData.WH_100_100) + .padding({ left: ConfigData.GRID_CONTAINER_MARGIN_24, right: ConfigData.GRID_CONTAINER_MARGIN_24, bottom: 20 }) + .backgroundColor($r('sys.color.ohos_id_color_sub_background')) + } + + async onSetupBtnClick() { + LogUtil.info(MODULE_TAG + `onSetupBtnClick current connect = ${JSON.stringify(this.connectState)}`); + if (this.connectState === VpnConstant.VPN_STATE_CONNECTED || + this.connectState === VpnConstant.VPN_STATE_CONNECTING) { + this.showVpnChangeDialog(); + return; + } + if ((!this.vpnConfig.userName || !this.vpnConfig.password) || + this.vpnUserName !== this.vpnConfig.userName || this.vpnPassword !== this.vpnConfig.password) { + this.vpnConfig.userName = this.vpnUserName; + this.vpnConfig.password = this.vpnPassword; + // name and pwd changed, need to rebuild config + this.vpnConfig = SwanCtlModel.getInstance().buildConfig(this.vpnConfig); + if (!this.vpnConfig.vpnId || this.vpnConfig.vpnId === '') { + this.vpnConfig.vpnId = util.generateRandomUUID(); + } + try { + await vpn.addSysVpnConfig(this.vpnConfig); + VpnConnectModel.getInstance().setUp(this.vpnConfig); + } catch (err) { + let message = (err as BusinessError).message; + VpnConfigModel.getInstance().showToast(ResourceUtils.getStringSync($r('app.string.vpn_error_operation_failed')) + ': ' + message); + } + router.back(); + return; + } + VpnConnectModel.getInstance().setUp(this.vpnConfig); + router.back(); + } + + setConnectBtnEnabled() { + if (this.vpnUserName === '' || this.vpnPassword === '') { + this.isSetupBtnEnabled = false; + } else { + this.isSetupBtnEnabled = true; + } + } + + showVpnChangeDialog(): void { + AlertDialog.show({ + title: $r('app.string.vpn_change_alert_title'), + message: $r('app.string.vpn_change_alert_content'), + primaryButton: { + value: $r('app.string.cancel'), + action: () => { + LogUtil.info('dialog cancel callbacks'); + } + }, + secondaryButton: { + value: $r('app.string.vpn_change_alert_confirm'), + action: () => { + VpnConnectModel.getInstance().setConnectState(VpnConstant.VPN_STATE_DISCONNECTING); + VpnConnectModel.getInstance().setReplaceConnectVpn(this.vpnConfig); + VpnConnectModel.getInstance().destroy((error: string) => { + if (error) { + LogUtil.info(MODULE_TAG + `vpn destroy failed, error:` + error); + } + }); + router.back(); + } + }, + alignment: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? + DialogAlignment.Bottom : DialogAlignment.Center, + offset: ({ dx: 0, dy: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? '-24dp' : 0 }) + }) + } +} + + + diff --git a/product/phone/src/main/ets/pages/vpn/vpnEdit.ets b/product/phone/src/main/ets/pages/vpn/vpnEdit.ets new file mode 100644 index 00000000..3f974559 --- /dev/null +++ b/product/phone/src/main/ets/pages/vpn/vpnEdit.ets @@ -0,0 +1,263 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import { BusinessError } from '@ohos.base'; +import router from '@ohos.router'; +import vpn from '@ohos.net.vpn'; +import util from '@ohos.util'; +import window from '@ohos.window'; +import OpenVpnEdit from './openVpnEdit'; +import IpsecVpnEdit from './ipsecVpnEdit'; +import { TextWithInput, Selector, SELECTER_VPNTYPE, deviceTypeInfo } from './customComponent'; +import { VpnTypeModel } from '../../model/vpnImpl/VpnTypeModel'; +import { VpnConnectModel } from '../../model/vpnImpl/VpnConnectModel'; +import VpnConstant from '../../model/vpnImpl/VpnConstant'; +import { VpnConfigModel } from '../../model/vpnImpl/VpnConfigModel'; +import { ResourceUtils } from '../../model/accessibilityImpl/resourceUtils'; +import VpnConfig, { OpenVpnConfig, IpsecVpnConfig } from '../../model/vpnImpl/VpnConfig'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; +import ConfigData from '../../../../../../../common/utils/src/main/ets/default/baseUtil/ConfigData'; +import HeadComponent from '../../../../../../../common/component/src/main/ets/default/headComponent'; + +const MODULE_TAG: string = 'setting_vpn:VpnEdit:'; + +@Entry +@Component +export struct VpnEdit { + @State vpnConfig: VpnConfig = new VpnConfig(); + @State isSaveBtnEnabled: boolean = false; + @State isDelBtnVisibility: Visibility = Visibility.None; + protocolSheets: SheetInfo[] = []; + + aboutToAppear() { + window.getLastWindow(getContext(this), (err, w) => { + w.setWindowSystemBarProperties({ + statusBarColor: '#ffffff' + }) + }) + let param = router.getParams(); + if (param) { + this.vpnConfig = param as VpnConfig; + } + this.isDelBtnVisibility = this.vpnConfig.vpnName ? Visibility.Visible : Visibility.None; + + this.protocolSheets = VpnTypeModel.getInstance().getSupportVpnTypeStrs().map((str, index) => { + let s: SheetInfo = { + title: str, + action: () => { + this.vpnConfig.vpnType = (index < VpnTypeModel.getInstance().getSupportVpnTypes().length) ? + VpnTypeModel.getInstance().getSupportVpnTypes()[index] : 0; + } + } + return s; + }) + } + + onSaveBtnEnableChange: Function = (enabled: boolean) => { + this.isSaveBtnEnabled = enabled && this.vpnConfig.vpnName.length > 0; + } + + async onSaveClick() { + if (!this.vpnConfig.vpnName || this.vpnConfig.vpnName.length < 1) { + VpnConfigModel.getInstance().showToast($r('app.string.vpn_error_invalid_param')); + return; + } + // destroy connect when vpn connected + if (VpnConnectModel.getInstance().isConnectedOrConnecting(this.vpnConfig.vpnId)) { + VpnConfigModel.getInstance().showToast($r('app.string.vpn_error_disconnect_first')) + return; + } + + VpnConfigModel.getInstance().prepareAddSysVpnConfig(this.vpnConfig); + if (!this.vpnConfig.vpnId || this.vpnConfig.vpnId === '') { + this.vpnConfig.vpnId = util.generateRandomUUID(); + } + try { + await vpn.addSysVpnConfig(this.vpnConfig); + VpnConfigModel.getInstance().setNeedUpdateVpnList(true); + this.backToListPage(); + } catch (err) { + let message = (err as BusinessError).message; + VpnConfigModel.getInstance().showToast( + ResourceUtils.getStringSync($r('app.string.vpn_error_operation_failed')) + ': ' + message); + } + } + + backToListPage() { + router.back(); + } + + async onDeleteClick() { + if (!this.vpnConfig) { + LogUtil.error(MODULE_TAG + 'onDeleteClick failed, vpn config is error'); + return; + } + if (!this.vpnConfig.vpnId || this.vpnConfig.vpnId === '') { + LogUtil.error(MODULE_TAG + 'onDeleteClick failed, vpnId is error'); + return; + } + try { + await vpn.deleteSysVpnConfig(this.vpnConfig.vpnId); + VpnConfigModel.getInstance().setNeedUpdateVpnList(true); + this.backToListPage(); + }catch (err){ + let message = (err as BusinessError).message; + VpnConfigModel.getInstance().showToast( + ResourceUtils.getStringSync($r('app.string.vpn_error_operation_failed')) + ': ' + message); + } + } + + showDeleteDialog(): void { + AlertDialog.show({ + message: $r('app.string.vpn_line_remove_alert_title', this.vpnConfig.vpnName), + primaryButton: { + value: $r('app.string.cancel'), + action: () => { + LogUtil.info('dialog cancel callbacks'); + } + }, + secondaryButton: { + fontColor: $r('sys.color.ohos_id_color_warning'), + value: $r('app.string.vpn_line_remove_alert_confirm'), + action: () => { + this.onDeleteClick(); + } + }, + alignment: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? + DialogAlignment.Bottom : DialogAlignment.Center, + offset: ({ dx: 0, dy: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? '-24dp' : 0 }) + }) + } + + build() { + Column() { + GridContainer({ gutter: ConfigData.GRID_CONTAINER_GUTTER_24, margin: ConfigData.GRID_CONTAINER_MARGIN_24 }) { + RelativeContainer() { + HeadComponent({ headName: $r('app.string.vpn_edit_title'), isActive: true }).alignRules({ + top: { anchor: '__container__', align: VerticalAlign.Top }, + left: { anchor: '__container__', align: HorizontalAlign.Start } + }).id('head') + + Scroll() { + Column({ space: 10 }) { + TextWithInput({ + title: $r('app.string.vpn_edit_alias'), + inputPlaceholder: '', + inputText: this.vpnConfig.vpnName, + maxLength: VpnConstant.VPN_NAME_MAX_LENGTH, + onChange: (value: string) => { + this.vpnConfig.vpnName = value.trim(); + this.onSaveBtnEnableChange(this.vpnConfig.vpnName.length > 0 ? true : false); + } + }) + Text($r('app.string.vpn_line_disconnect_operation_remove')) + .fontColor($r('sys.color.ohos_id_color_badge_red')) + .fontWeight(FontWeight.Bold) + .width(ConfigData.WH_100_100) + .textAlign(TextAlign.Center) + .onClick(() => { + if (VpnConnectModel.getInstance().isConnectedOrConnecting(this.vpnConfig.vpnId)) { + VpnConfigModel.getInstance().showToast($r('app.string.vpn_error_disconnect_first')) + } else { + this.showDeleteDialog(); + } + }) + .borderRadius($r('app.float.radius_16')) + .backgroundColor('#ffffff') + .width('100%') + .height($r('app.float.wh_value_52')) + .borderRadius($r('app.float.radius_16')) + .padding({ left: 8, right: 8 }) + .visibility(this.isDelBtnVisibility) + + Selector({ + title: $r('app.string.vpn_edit_type'), + vpnConfig: this.vpnConfig, + type: SELECTER_VPNTYPE, + sheetTitles: VpnTypeModel.getInstance().getSupportVpnTypeStrs(), + sheetInfos: this.protocolSheets, + }).enabled(this.isDelBtnVisibility !== Visibility.Visible) + + if (this.vpnConfig.vpnType === VpnTypeModel.TYPE_OPENVPN) { + OpenVpnEdit({ + vpnConfig: this.vpnConfig as OpenVpnConfig, + onSaveBtnEnableChange: this.onSaveBtnEnableChange, + }) + } else { + IpsecVpnEdit({ + vpnConfig: this.vpnConfig as IpsecVpnConfig, + onSaveBtnEnableChange: this.onSaveBtnEnableChange, + }) + } + Blank().layoutWeight(1) + }.alignItems(HorizontalAlign.Start).width(ConfigData.WH_100_100) + }.alignRules({ + top: { anchor: 'head', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start }, + right: { anchor: '__container__', align: HorizontalAlign.End }, + bottom: { anchor: 'bottom', align: VerticalAlign.Top } + }).id('content') + + Row() { + Button($r('app.string.cancel')) + .fontColor('#0A59F7') + .fontSize(16) + .height(45) + .layoutWeight(1) + .fontWeight(FontWeight.Bold) + .padding({ top: 10, left: 60, right: 60, bottom: 10 }) + .borderRadius($r('app.float.radius_20')) + .backgroundColor('#E6E8E9') + .onClick(() => { + this.backToListPage(); + }) + Blank().width(30) + Button($r('app.string.vpn_edit_save')) + .fontColor(this.isSaveBtnEnabled ? '#0A59F7' : '#92B3F3') + .fontSize(16) + .height(45) + .layoutWeight(1) + .fontWeight(FontWeight.Bold) + .padding({ top: 10, left: 60, right: 60, bottom: 10 }) + .borderRadius($r('app.float.radius_20')) + .backgroundColor('#E6E8E9') + .enabled(this.isSaveBtnEnabled) + .background() + .onClick(() => { + this.onSaveClick(); + }) + }.margin(15).alignRules({ + bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, + left: { anchor: '__container__', align: HorizontalAlign.Start } + }).id('bottom') + + }.width(ConfigData.WH_100_100).height(ConfigData.WH_100_100) + .useSizeType({ + sm: { span: 4, offset: 0 }, + md: { span: 6, offset: 1 }, + lg: { span: 8, offset: 2 } + }) + } + .width(ConfigData.WH_100_100) + .height(ConfigData.WH_100_100) + } + .backgroundColor($r('sys.color.ohos_id_color_sub_background')) + .width(ConfigData.WH_100_100) + .height(ConfigData.WH_100_100) + } +} + + + diff --git a/product/phone/src/main/ets/pages/vpn/vpnLine.ets b/product/phone/src/main/ets/pages/vpn/vpnLine.ets new file mode 100644 index 00000000..86fe412d --- /dev/null +++ b/product/phone/src/main/ets/pages/vpn/vpnLine.ets @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import { BusinessError } from '@ohos.base'; +import deviceInfo from '@ohos.deviceInfo'; +import vpn from '@ohos.net.vpn'; +import router from '@ohos.router'; +import { VpnConnectModel } from '../../model/vpnImpl/VpnConnectModel'; +import VpnConfig, { VpnListItem } from '../../model/vpnImpl/VpnConfig'; +import { VpnConfigModel } from '../../model/vpnImpl/VpnConfigModel'; +import VpnConstant from '../../model/vpnImpl/VpnConstant'; +import { ResourceUtils } from '../../model/accessibilityImpl/resourceUtils'; +import ComponentConfig from '../../../../../../../common/component/src/main/ets/default/ComponentConfig'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; + +const MODULE_TAG: string = 'setting_vpn:VpnLine:'; +const deviceTypeInfo = deviceInfo.deviceType; + +/** + * item custom component + */ +@Entry +@Component +export default struct VpnLine { + @StorageLink(VpnConstant.STORAGE_KEY_CONNECT_STATE) @Watch('onConnectStateChange') connectState: number = + VpnConstant.VPN_STATE_NONE; + @State connectStateLabel: string | Resource = ''; + @State vpnListItem: VpnListItem | undefined = undefined; + @State titleFontColor: Resource = $r('sys.color.ohos_id_color_text_primary'); + @State isTouched: boolean = false; + @State heights: Resource = $r('app.float.wh_value_72'); + @State fontSize: Resource = $r('sys.float.ohos_id_text_size_body1'); + @State valueFontSize: Resource = $r('sys.float.ohos_id_text_size_body2'); + private isEnabled: boolean = true; + @State editPopupVisibility: boolean = false + + showDisconnectDialog(): void { + AlertDialog.show({ + message: $r('app.string.vpn_line_disconnect_alert_title', this.vpnListItem!.vpnName), + primaryButton: { + value: $r('app.string.cancel'), + action: () => { + LogUtil.info('dialog cancel callbacks'); + } + }, + secondaryButton: { + fontColor: $r('sys.color.ohos_id_color_warning'), + value: $r('app.string.vpn_line_disconnect_alert_confirm'), + action: () => { + VpnConnectModel.getInstance().setConnectState(VpnConstant.VPN_STATE_DISCONNECTING); + VpnConnectModel.getInstance().destroy((error: string) => { + if (error) { + LogUtil.info(MODULE_TAG + `vpn destroy failed, error:` + error); + } + }); + } + }, + alignment: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? + DialogAlignment.Bottom : DialogAlignment.Center, + offset: ({ dx: 0, dy: deviceTypeInfo === 'phone' || deviceTypeInfo === 'default' ? '-24dp' : 0 }) + }) + } + + aboutToAppear(): void { + let state: number | undefined = AppStorage.get(VpnConstant.STORAGE_KEY_CONNECT_STATE); + if (state) { + this.connectState = state; + if (this.connectState === VpnConstant.VPN_STATE_CONNECTED && + this.vpnListItem!.vpnId === VpnConnectModel.getInstance().getConnectedVpnId()) { + this.connectStateLabel = $r('app.string.vpn_state_connec_success'); + } + } + } + + async onEditBtnClick() { + if (VpnConnectModel.getInstance().isConnecting(this.vpnListItem!.vpnId)) { + this.showDisconnectDialog(); + return; + } + try { + let data: vpn.SysVpnConfig = await vpn.getSysVpnConfig(this.vpnListItem?.vpnId); + if (data) { + router.pushUrl({ + url: 'pages/vpn/vpnEdit', + params: data as VpnConfig + }); + } + } catch (err) { + let message = (err as BusinessError).message; + VpnConfigModel.getInstance().showToast(ResourceUtils.getStringSync($r('app.string.vpn_error_operation_failed')) + ': ' + message); + } + } + + onListItemClick() { + if (VpnConnectModel.getInstance().isConnectedOrConnecting(this.vpnListItem!.vpnId)) { + this.showDisconnectDialog(); + } else { + router.pushUrl({ + url: 'pages/vpn/vpnConnect', + params: this.vpnListItem + }); + } + } + + onConnectStateChange(): void { + if (this.vpnListItem!.vpnId === VpnConnectModel.getInstance().getConnectedVpnId()) { + LogUtil.info(MODULE_TAG + 'onConnectStateChange state=' + this.connectState); + switch (this.connectState) { + case VpnConstant.VPN_STATE_CONNECTING: + this.connectStateLabel = $r('app.string.vpn_state_connect_ing'); + return; + case VpnConstant.VPN_STATE_CONNECTED: + this.connectStateLabel = $r('app.string.vpn_state_connec_success'); + return; + case VpnConstant.VPN_STATE_DISCONNECTING: + this.connectStateLabel = $r('app.string.vpn_state_disconnecting'); + return; + case VpnConstant.VPN_STATE_DISCONNECTED: + this.connectStateLabel = $r('app.string.vpn_state_disconnected'); + return; + case VpnConstant.VPN_STATE_CONNECT_FAILED: + this.connectStateLabel = $r('app.string.vpn_state_connec_failed'); + return; + default: + this.connectStateLabel = ''; + return; + } + } + this.connectStateLabel = ''; + } + + build() { + Row() { + Row() { + Image($r('app.media.ic_vpn')) + .width(30) + .height(16) + .margin({ + right: $r('app.float.wh_value_16') + }) + Column() { + Text(this.vpnListItem?.vpnName ?? '') + .fontColor(this.isEnabled ? this.titleFontColor : $r('sys.color.ohos_id_color_primary')) + .fontSize(this.fontSize) + .fontWeight(FontWeight.Medium) + .align(Alignment.Start) + .maxLines(ComponentConfig.MAX_LINES_1) + .width(ComponentConfig.WH_100_100) + .ellipsisMode(EllipsisMode.END) + .padding({ right: 50 }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + + Text(this.connectStateLabel) + .fontColor($r('sys.color.ohos_id_color_mask_light')) + .fontSize(this.valueFontSize) + .textAlign(TextAlign.Start) + .fontWeight(FontWeight.Regular) + .maxLines(ComponentConfig.MAX_LINES_1) + .width(ComponentConfig.WH_100_100) + .ellipsisMode(EllipsisMode.END) + .padding({ right: 50 }) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + .margin({ top: 8 }) + .visibility(this.connectStateLabel.toString().length > 0 ? Visibility.Visible : Visibility.None) + } + .alignItems(HorizontalAlign.Start) + .width('80%') + + Row() { + Image($r('app.media.ic_public_detail')) + .width(24) + .height(24) + } + .width($r('app.float.wh_value_50')) + .height(ComponentConfig.WH_100_100) + .alignItems(VerticalAlign.Center) + .justifyContent(FlexAlign.Center) + .margin({ + right: $r('app.float.wh_value_16') + }) + .onClick(() => { + this.onEditBtnClick(); + }) + } + .flexShrink(0) + .alignItems(VerticalAlign.Center) + .align(Alignment.Start) + + Blank() + } + .width(ComponentConfig.WH_100_100) + .padding({ + left: $r('sys.float.ohos_id_card_margin_end'), + right: $r('sys.float.ohos_id_card_margin_end'), + top: $r('sys.float.ohos_id_elements_margin_vertical_m'), + bottom: $r('sys.float.ohos_id_elements_margin_vertical_m') + }) + .alignItems(VerticalAlign.Center) + .height('62vp') + .borderRadius($r('app.float.radius_16')) + .linearGradient(this.isTouched ? { + angle: 90, + direction: GradientDirection.Right, + colors: [[$r('app.color.DCEAF9'), 0.0], [$r('app.color.FAFAFA'), 1.0]] + } : { + angle: 90, + direction: GradientDirection.Right, + colors: [[$r('sys.color.ohos_id_color_foreground_contrary'), 1], + [$r('sys.color.ohos_id_color_foreground_contrary'), 1]] + }) + .onTouch((event?: TouchEvent | undefined) => { + if (event?.type === TouchType.Down) { + this.isTouched = true; + } + if (event?.type === TouchType.Up) { + this.isTouched = false; + } + }) + .onClick(() => { + this.onListItemClick(); + }) + } +} diff --git a/product/phone/src/main/ets/pages/vpn/vpnList.ets b/product/phone/src/main/ets/pages/vpn/vpnList.ets new file mode 100644 index 00000000..026cc4e6 --- /dev/null +++ b/product/phone/src/main/ets/pages/vpn/vpnList.ets @@ -0,0 +1,190 @@ +/** + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +import { BusinessError } from '@ohos.base'; +import common from '@ohos.app.ability.common'; +import router from '@ohos.router'; +import vpn from '@ohos.net.vpn'; +import VpnLine from './vpnLine'; +import VpnConfig, { VpnListItem } from '../../model/vpnImpl/VpnConfig'; +import { VpnConnectModel } from '../../model/vpnImpl/VpnConnectModel'; +import { SwanCtlModel } from '../../model/vpnImpl/SwanCtlModel'; +import { VpnConfigModel } from '../../model/vpnImpl/VpnConfigModel'; +import { ResourceUtils } from '../../model/accessibilityImpl/resourceUtils'; +import ConfigData from '../../../../../../../common/utils/src/main/ets/default/baseUtil/ConfigData'; +import { BaseData } from '../../../../../../../common/utils/src/main/ets/default/bean/BaseData'; +import HeadComponent from '../../../../../../../common/component/src/main/ets/default/headComponent'; +import LogUtil from '../../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; + +const MODULE_TAG: string = 'setting_vpn:VPNList:'; + +/** + * VPN list + */ +@Entry +@Component +struct VPNList { + @State vpnList: VpnListItem[] = []; + @State isLoading: boolean = false; + + build() { + Column() { + GridContainer({ gutter: ConfigData.GRID_CONTAINER_GUTTER_24, margin: ConfigData.GRID_CONTAINER_MARGIN_24 }) { + Column() { + HeadComponent({ headName: $r('app.string.VPN'), isActive: true }) + if (this.isLoading) { + Column() { + LoadingProgress() + .color(Color.White) + .width(80) + }.width('100%').height('100%').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center) + } else { + if (this.vpnList!.length > 0 ) { + Column() { + List() { + ForEach(this.vpnList, (item: VpnListItem, index: number) => { + ListItem() { + VpnLine({ + vpnListItem: item, + fontSize: $r('sys.float.ohos_id_text_size_body1'), + valueFontSize: $r('sys.float.ohos_id_text_size_body2'), + }) + }.padding(3) + }, (item: BaseData) => JSON.stringify(item)); + } + .margin({ top: $r('app.float.distance_8') }) + .align(Alignment.Top) + .borderRadius($r('app.float.radius_16')) + .backgroundColor($r('sys.color.ohos_id_color_foreground_contrary')) + .divider({ + strokeWidth: $r('app.float.divider_wh'), + color: $r('sys.color.ohos_id_color_list_separator'), + startMargin: $r('app.float.wh_value_48'), + endMargin: $r('app.float.wh_value_8') + }) + } + .width(ConfigData.WH_100_100) + .height(ConfigData.WH_100_100) + .layoutWeight(1) + + Column({ space: 5 }) { + Image($r('app.media.ic_public_add')) + .width(20) + .height(20) + Text($r('app.string.vpn_list_add')) + .fontColor('#333333') + .fontSize(14) + .fontWeight(FontWeight.Medium) + } + .margin({ bottom: 25, top: 25 }) + .onClick(() => { + this.onAddBtnClick(); + }) + } else { + Column() { + Image($r('app.media.empty')) + .width(200) + .height(200) + Text($r('app.string.vpn_list_empty')) + .fontSize(15) + .fontColor('#999999') + } + .justifyContent(FlexAlign.Center) + .width(ConfigData.WH_100_100) + .height(ConfigData.WH_100_100) + .layoutWeight(1) + + Column({ space: 5 }) { + Button($r('app.string.vpn_list_add')) + .fontColor('#0A59F7') + .fontSize(16) + .fontWeight(FontWeight.Bold) + .height(45) + .padding({ top: 10, left: 60, right: 60, bottom: 10 }) + .borderRadius($r('app.float.radius_24')) + .backgroundColor('#E6E8E9') + .onClick(() => { + this.onAddBtnClick(); + }) + }.margin({ bottom: 25, top: 25 }) + } + } + } + .useSizeType({ + sm: { span: 4, offset: 0 }, + md: { span: 6, offset: 1 }, + lg: { span: 8, offset: 2 } + }) + } + .width(ConfigData.WH_100_100) + .height(ConfigData.WH_100_100) + } + .backgroundColor($r('sys.color.ohos_id_color_sub_background')) + .width(ConfigData.WH_100_100) + .height(ConfigData.WH_100_100) + } + + onAddBtnClick() { + router.pushUrl({ + url: 'pages/vpn/vpnEdit', + }); + } + + async updateVpnList() { + this.isLoading = true; + try { + let data: vpn.SysVpnConfig[] = await vpn.getSysVpnConfigList(); + if (data) { + let list = data as VpnListItem[]; + let currentVpnId = VpnConnectModel.getInstance().getConnectedVpnId(); + list.sort((item1, item2) => { + if (currentVpnId! && item1.vpnId === currentVpnId) { + return -1; + } else if (currentVpnId! && item2.vpnId === currentVpnId) { + return 1; + } else { + return item1.vpnName.localeCompare(item2.vpnName, 'zh'); + } + }); + this.vpnList = list; + } else { + this.vpnList = []; + } + } catch (err) { + let message = (err as BusinessError).message; + VpnConfigModel.getInstance().showToast(ResourceUtils.getStringSync($r('app.string.vpn_error_operation_failed')) + ': ' + message); + } + this.isLoading = false; + } + + aboutToAppear(): void { + LogUtil.info(MODULE_TAG + 'aboutToAppear'); + VpnConfigModel.getInstance().setNeedUpdateVpnList(true); + VpnConnectModel.getInstance().init(getContext() as common.UIAbilityContext); + SwanCtlModel.getInstance().init(getContext() as common.UIAbilityContext); + } + + onPageShow(): void { + if (VpnConfigModel.getInstance().isUpdateVpnList()) { + this.updateVpnList(); + VpnConfigModel.getInstance().setNeedUpdateVpnList(false); + } + } + + aboutToDisappear(): void { + LogUtil.info(MODULE_TAG + 'aboutToDisappear'); + VpnConnectModel.getInstance().release(); + } +} \ No newline at end of file diff --git a/product/phone/src/main/module.json5 b/product/phone/src/main/module.json5 index 8795a84a..18fb3033 100644 --- a/product/phone/src/main/module.json5 +++ b/product/phone/src/main/module.json5 @@ -2,7 +2,7 @@ "module": { "name": "phone", "type": "entry", - "srcEntrance": "./ets/Application/AbilityStage.ts", + "srcEntry": "./ets/Application/AbilityStage.ts", "description": "$string:mainability_description", "mainElement": "com.ohos.settings.MainAbility", "deviceTypes": [ @@ -22,7 +22,7 @@ "abilities": [ { "name": "com.ohos.settings.MainAbility", - "srcEntrance": "./ets/MainAbility/MainAbility.ts", + "srcEntry": "./ets/MainAbility/MainAbility.ts", "description": "$string:mainability_description", "icon": "$media:app_icon", "label": "$string:entry_MainAbility", @@ -43,7 +43,7 @@ }, { "name": "com.ohos.settings.AppInfoAbility", - "srcEntrance": "./ets/MainAbility/AppInfoAbility.ts", + "srcEntry": "./ets/MainAbility/AppInfoAbility.ts", "description": "$string:applicationInfo", "icon": "$media:app_icon", "label": "$string:applicationInfo", @@ -227,7 +227,10 @@ { "name": "ohos.permission.sec.ACCESS_UDID", "reason": "$string:GET_ACCESS_UDID" + }, + { + "name": "ohos.permission.MANAGE_VPN" } ] } -} \ No newline at end of file +} diff --git a/product/phone/src/main/resources/base/element/color.json b/product/phone/src/main/resources/base/element/color.json index 38f7c308..1e50f237 100644 --- a/product/phone/src/main/resources/base/element/color.json +++ b/product/phone/src/main/resources/base/element/color.json @@ -83,6 +83,10 @@ { "name": "white", "value": "#FFFFFF" + }, + { + "name": "FA2A2D", + "value": "#FA2A2D" } ] } \ No newline at end of file diff --git a/product/phone/src/main/resources/base/element/strarray.json b/product/phone/src/main/resources/base/element/strarray.json new file mode 100644 index 00000000..425407b4 --- /dev/null +++ b/product/phone/src/main/resources/base/element/strarray.json @@ -0,0 +1,29 @@ +{ + "strarray": [ + { + "name": "ovpn_auth_type", + "value": [ + { + "value": "证书(TLS)" + }, + { + "value": "密码" + }, + { + "value": "证书和密码(TLS)" + } + ] + }, + { + "name": "ovpn_protocol", + "value": [ + { + "value": "TCP" + }, + { + "value": "UDP" + } + ] + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/base/element/string.json b/product/phone/src/main/resources/base/element/string.json index 1b87ab1c..a96abb74 100644 --- a/product/phone/src/main/resources/base/element/string.json +++ b/product/phone/src/main/resources/base/element/string.json @@ -1491,6 +1491,282 @@ { "name": "GET_ACCESS_UDID", "value": "获取UDID的权限" + }, + { + "name": "vpn_change_alert_title", + "value": "要替换现有VPN吗?" + }, + { + "name": "vpn_change_alert_content", + "value": "您已连接到VPN,如果您要连接到其他VPN,则系统替换现有VPN." + }, + + { + "name": "vpn_change_alert_confirm", + "value": "替换" + }, + + { + "name": "vpn_connect_title", + "value": "连接到%s" + }, + { + "name": "vpn_connect_username", + "value": "用户名" + }, + { + "name": "vpn_connect_password", + "value": "密码" + }, + { + "name": "vpn_connect_save", + "value": "保存账户信息" + }, + { + "name": "vpn_connect_confirm", + "value": "连接" + }, + { + "name": "vpn_edit_title", + "value": "编辑VPN网络" + }, + { + "name": "vpn_edit_alias", + "value": "名称" + }, + { + "name": "vpn_edit_type", + "value": "类型" + }, + { + "name": "vpn_edit_ovpn_configfile", + "value": "OpenVPN 配置文件" + }, + { + "name": "vpn_edit_ovpn_ca_cert_file", + "value": "CA 证书" + }, + { + "name": "vpn_edit_ovpn_user_cert_file", + "value": "用户证书" + }, + { + "name": "vpn_edit_ovpn_private_key", + "value": "私钥" + }, + { + "name": "vpn_edit_ovpn_private_key_psd", + "value": "私钥密码" + }, + { + "name": "vpn_edit_ovpn_username", + "value": "用户名" + }, + { + "name": "vpn_edit_ovpn_password", + "value": "密码" + }, + { + "name": "vpn_edit_ovpn_click_select", + "value": "点击选择" + }, + { + "name": "vpn_edit_ovpn_protocol", + "value": "OpenVPN 协议" + }, + { + "name": "vpn_edit_auth_type", + "value": "OpenVPN 认证类型" + }, + { + "name": "vpn_edit_serveraddress", + "value": "服务器地址" + }, + { + "name": "vpn_edit_ovpn_serveraddress", + "value": "OpenVPN 服务器地址" + }, + { + "name": "vpn_edit_ovpn_port", + "value": "OpenVPN 服务器端口" + }, + { + "name": "vpn_edit_ipsecidentifier", + "value": "IPSec 标识符" + }, + + { + "name": "vpn_edit_l2tpsecret", + "value": "L2TP 密钥" + }, + { + "name": "vpn_edit_ipsecsecret", + "value": "IPSec 预共享密钥" + }, + { + "name": "vpn_edit_usercert", + "value": "IPSec 用户证书公钥" + }, + { + "name": "vpn_edit_user_private_cert", + "value": "IPSec 用户证书私钥" + }, + { + "name": "vpn_edit_cacert", + "value": "IPSec CA 证书" + }, + { + "name": "vpn_edit_servercert", + "value": "IPSec 服务器证书公钥" + }, + { + "name": "vpn_edit_server_private_cert", + "value": "IPSec 服务器秘钥证书私钥" + }, + { + "name": "vpn_edit_unuse", + "value": "未使用" + }, + { + "name": "vpn_edit_advanced", + "value": "显示高级选项" + }, + { + "name": "vpn_edit_proxy_advanced", + "value": "显示代理" + }, + { + "name": "vpn_edit_searchdomains", + "value": "DNS 搜索域" + }, + { + "name": "vpn_edit_dnsaddresses", + "value": "DNS 服务器(例如 8.8.8.8)" + }, + { + "name": "vpn_edit_router", + "value": "转发路线(例如 10.0.0.0/8)" + }, + { + "name": "vpn_edit_ovpn_proxy_host", + "value": "OpenVPN 代理主机" + }, + { + "name": "vpn_edit_ovpn_proxy_port", + "value": "OpenVPN 代理端口" + }, + { + "name": "vpn_edit_ovpn_proxy_username", + "value": "OpenVPN 代理用户名" + }, + { + "name": "vpn_edit_ovpn_proxy_password", + "value": "OpenVPN 代理密码" + }, + { + "name": "vpn_edit_ovpn_useauth", + "value": "使用基本身份验证" + }, + { + "name": "vpn_edit_save", + "value": "保存" + }, + { + "name": "vpn_edit_cert_not_specified", + "value": "(未指定)" + }, + { + "name": "vpn_edit_cert_not_verify_server", + "value": "(不验证服务器)" + }, + { + "name": "vpn_edit_cert_from_server", + "value": "(来自服务器)" + }, + { + "name": "vpn_line_remove_alert_title", + "value": "是否删除%s网络?" + }, + { + "name": "vpn_line_remove_alert_confirm", + "value": "删除" + }, + { + "name": "vpn_line_disconnect_alert_title", + "value": "是否断开%s网络?" + }, + { + "name": "vpn_line_disconnect_alert_confirm", + "value": "断开" + }, + + { + "name": "vpn_line_disconnect_operation_edit", + "value": "编辑VPN网络" + }, + { + "name": "vpn_line_disconnect_operation_remove", + "value": "删除VPN网络" + }, + + { + "name": "vpn_state_connect_ing", + "value": "连接中..." + }, + { + "name": "vpn_state_connec_success", + "value": "连接成功" + }, + { + "name": "vpn_state_connec_failed", + "value": "连接失败" + }, + { + "name": "vpn_state_disconnecting", + "value": "断开中..." + }, + { + "name": "vpn_state_disconnected", + "value": "已断开" + }, + { + "name": "vpn_list_empty", + "value": "没有VPN" + }, + { + "name": "vpn_list_add", + "value": "添加VPN网络" + }, { + "name": "ovpn_auth_type_cert", + "value": "证书( TLS )" + }, + { + "name": "ovpn_auth_type_pass", + "value": "密码" + }, + { + "name": "ovpn_auth_type_certpass", + "value": "证书和密码( TLS )" + }, + { + "name": "VPN", + "value": "VPN" + }, + { + "name": "vpn_error_invalid_param", + "value": "参数错误" + }, + { + "name": "vpn_error_operation_failed", + "value": "API调用失败" + }, + { + "name": "vpn_error_system_internal", + "value": "系统内部错误" + }, + { + "name": "vpn_error_disconnect_first", + "value": "请先断开VPN,再操作" } ] } \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/empty.svg b/product/phone/src/main/resources/base/media/empty.svg new file mode 100644 index 00000000..cd135941 --- /dev/null +++ b/product/phone/src/main/resources/base/media/empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/ic_public_add.svg b/product/phone/src/main/resources/base/media/ic_public_add.svg new file mode 100644 index 00000000..614328d3 --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_public_add.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_add + + + + + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/ic_public_detail.svg b/product/phone/src/main/resources/base/media/ic_public_detail.svg new file mode 100644 index 00000000..b1222dcf --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_public_detail.svg @@ -0,0 +1,9 @@ + + + ic_public_detail + + + + + + \ No newline at end of file diff --git a/product/phone/src/main/resources/base/media/ic_vpn.svg b/product/phone/src/main/resources/base/media/ic_vpn.svg new file mode 100644 index 00000000..bb15201f --- /dev/null +++ b/product/phone/src/main/resources/base/media/ic_vpn.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/product/phone/src/main/resources/base/profile/main_pages.json b/product/phone/src/main/resources/base/profile/main_pages.json index 750303ae..f858e822 100644 --- a/product/phone/src/main/resources/base/profile/main_pages.json +++ b/product/phone/src/main/resources/base/profile/main_pages.json @@ -43,6 +43,9 @@ "pages/accessibilityShortKey", "pages/accessibilityColorCorrection", "pages/accessibilityScreenTouch", - "pages/abilityDialog/accessibilityWindowShortKeyDialog" + "pages/abilityDialog/accessibilityWindowShortKeyDialog", + "pages/vpn/vpnList", + "pages/vpn/vpnEdit", + "pages/vpn/vpnConnect" ] } diff --git a/product/phone/src/main/resources/en_US/element/strarray.json b/product/phone/src/main/resources/en_US/element/strarray.json new file mode 100644 index 00000000..ce6f49ba --- /dev/null +++ b/product/phone/src/main/resources/en_US/element/strarray.json @@ -0,0 +1,29 @@ +{ + "strarray": [ + { + "name": "ovpn_auth_type", + "value": [ + { + "value": "Certificate(TLS)" + }, + { + "value": "Password" + }, + { + "value": "Certificate and Password(TLS)" + } + ] + }, + { + "name": "ovpn_protocol", + "value": [ + { + "value": "TCP" + }, + { + "value": "UDP" + } + ] + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/en_US/element/string.json b/product/phone/src/main/resources/en_US/element/string.json index 02ff26e0..f2337a53 100644 --- a/product/phone/src/main/resources/en_US/element/string.json +++ b/product/phone/src/main/resources/en_US/element/string.json @@ -1487,6 +1487,272 @@ { "name": "GET_ACCESS_UDID", "value": "Permission to obtain a UDID" + }, + { + "name": "vpn_change_alert_title", + "value": "Replace the existing VPN?" + }, + { + "name": "vpn_change_alert_content", + "value": "You have connected to the VPN. If you want to connect to another VPN, the system will replace the existing VPN." + }, + { + "name": "vpn_change_alert_confirm", + "value": "Replace" + }, + { + "name": "vpn_connect_title", + "value": "Connection to %s" + }, + { + "name": "vpn_connect_username", + "value": "Username" + }, + { + "name": "vpn_connect_password", + "value": "Password" + }, + { + "name": "vpn_connect_save", + "value": "Save Account Information" + }, + { + "name": "vpn_connect_confirm", + "value": "Connect" + }, + { + "name": "vpn_edit_title", + "value": "Edit VPN Network" + }, + { + "name": "vpn_edit_alias", + "value": "Alias" + }, + { + "name": "vpn_edit_type", + "value": "Protocol Type" + }, + { + "name": "vpn_edit_ovpn_configfile", + "value": "OpenVPN Config File" + }, + { + "name": "vpn_edit_ovpn_click_select", + "value": "Click Select" + }, + { + "name": "vpn_edit_ovpn_ca_cert_file", + "value": "CA Certificate" + }, + { + "name": "vpn_edit_ovpn_user_cert_file", + "value": "User Certificate" + }, + { + "name": "vpn_edit_ovpn_private_key", + "value": "Private key" + }, + { + "name": "vpn_edit_ovpn_private_key_psd", + "value": "Private key password" + }, + { + "name": "vpn_edit_ovpn_protocol", + "value": "OpenVPN Protocol" + }, + { + "name": "vpn_edit_auth_type", + "value": "OpenVPN Auth Type" + }, + { + "name": "vpn_edit_serveraddress", + "value": "Server Address" + }, + { + "name": "vpn_edit_ovpn_serveraddress", + "value": "OpenVpn Server Address" + }, + { + "name": "vpn_edit_ovpn_port", + "value": "OpenVpn Server Port" + }, + { + "name": "vpn_edit_ipsecidentifier", + "value": "IPSec Identifier" + }, + + { + "name": "vpn_edit_l2tpsecret", + "value": "L2TP Secret" + }, + { + "name": "vpn_edit_ipsecsecret", + "value": "IPSec Secret" + }, + { + "name": "vpn_edit_usercert", + "value": "IPSec User Certificate Public Key" + }, + { + "name": "vpn_edit_user_private_cert", + "value": "IPSec User Certificate Private Key" + }, + { + "name": "vpn_edit_cacert", + "value": "IPSec CA Certificate" + }, + { + "name": "vpn_edit_servercert", + "value": "IPSec Server Certificate Public Key" + }, + { + "name": "vpn_edit_server_private_cert", + "value": "IPSec Server Certificate Private Key" + }, + { + "name": "vpn_edit_unuse", + "value": "not used" + }, + { + "name": "vpn_edit_advanced", + "value": "Show Advanced Options" + }, + { + "name": "vpn_edit_proxy_advanced", + "value": "Show Proxy Advanced Options" + }, + { + "name": "vpn_edit_searchdomains", + "value": "DNS Search Domain" + }, + { + "name": "vpn_edit_dnsaddresses", + "value": "DNS Server (e.g. 8.8.8.8)" + }, + { + "name": "vpn_edit_router", + "value": "Forwarding Route (e.g. 10.0.0.0/8)" + }, + { + "name": "vpn_edit_ovpn_proxy_host", + "value": "OpenVpn Proxy Host" + }, + { + "name": "vpn_edit_ovpn_proxy_port", + "value": "OpenVpn Proxy Port" + }, + { + "name": "vpn_edit_ovpn_proxy_username", + "value": "OpenVpn Proxy Username" + }, + { + "name": "vpn_edit_ovpn_proxy_password", + "value": "OpenVpn Proxy Password" + }, + { + "name": "vpn_edit_ovpn_useauth", + "value": "Using Basic Authentication" + }, + { + "name": "vpn_edit_save", + "value": "Save" + }, + { + "name": "vpn_edit_cert_not_specified", + "value": "(unspecified)" + }, + { + "name": "vpn_edit_cert_not_verify_server", + "value": "(Do not verify server)" + }, + { + "name": "vpn_edit_cert_from_server", + "value": "(From server)" + }, + { + "name": "vpn_line_remove_alert_title", + "value": "Delete the %s vpn?" + }, + { + "name": "vpn_line_remove_alert_confirm", + "value": "Delete" + }, + { + "name": "vpn_line_disconnect_alert_title", + "value": "Disconnect the %s VPN?" + }, + { + "name": "vpn_line_disconnect_alert_confirm", + "value": "Disconnect" + }, + + { + "name": "vpn_line_disconnect_operation_edit", + "value": "Edit VPN Network" + }, + { + "name": "vpn_line_disconnect_operation_remove", + "value": "Delete VPN Network" + }, + + { + "name": "vpn_state_connect_ing", + "value": "Connecting..." + }, + { + "name": "vpn_state_connec_success", + "value": "Connection successful" + }, + { + "name": "vpn_state_connec_failed", + "value": "Connection failed" + }, + { + "name": "vpn_state_disconnecting", + "value": "Disconnecting..." + }, + { + "name": "vpn_state_disconnected", + "value": "DisConnected" + }, + { + "name": "vpn_list_empty", + "value": "No VPN Network" + }, + { + "name": "vpn_list_add", + "value": "Add VPN Network" + }, { + "name": "ovpn_auth_type_cert", + "value": "Certificate(TLS)" + }, + { + "name": "ovpn_auth_type_pass", + "value": "Password" + }, + { + "name": "ovpn_auth_type_certpass", + "value": "Certificate and Password(TLS)" + }, + { + "name": "VPN", + "value": "VPN" + }, + { + "name": "vpn_error_invalid_param", + "value": "Invalid parameter value" + }, + { + "name": "vpn_error_operation_failed", + "value": "Operation failed" + }, + { + "name": "vpn_error_system_internal", + "value": "System internal error" + }, + { + "name": "vpn_error_disconnect_first", + "value": "Disconnect the VPN before operating" } ] } \ No newline at end of file diff --git a/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_mschapv2/strongswan.conf b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_mschapv2/strongswan.conf new file mode 100644 index 00000000..335288f5 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_mschapv2/strongswan.conf @@ -0,0 +1,35 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 x509 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce aes des md4 sha1 sha2 fips-prf pem pkcs1 curve25519 gmp x509 curl revocation hmac kdf vici kernel-netlink socket-default eap-identity eap-mschapv2 updown +} + +charon { + +filelog { + charon { + # path to the log file, specify this as section name in versions prior to 5.7.0 + path = /data/service/el1/public/netmanager/charon.log + # add a timestamp prefix + time_format = %b %e %T + # prepend connection name, simplifies grepping + ike_name = yes + # overwrite existing files + append = no + # increase default loglevel for all daemon subsystems + default = 4 + # flush each line to disk + flush_line = yes + } + stderr { + # more detailed loglevel for a specific subsystem, overriding the + # default loglevel. + ike = 4 + knl = 4 + } + } +} diff --git a/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_mschapv2/swanctl.conf b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_mschapv2/swanctl.conf new file mode 100644 index 00000000..7f26bef6 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_mschapv2/swanctl.conf @@ -0,0 +1,31 @@ +connections { + home { + remote_addrs = vpn_address_value + vips = 0.0.0.0 + local { + auth = eap-mschapv2 + eap_id = vpn_username_value + } + remote { + auth = pubkey + } + children { + home { + remote_ts=0.0.0.0/0 + esp_proposals = aes128gcm128-x25519 + } + } + version = 2 + proposals = aes128-sha256-x25519 + } +} +secrets { + eap-carol { + id = ipsec_identifier_value + secret = password_value + } + eap-dave { + id = vpn_username_value + secret = vpn_password_value + } +} diff --git a/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_psk/strongswan.conf b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_psk/strongswan.conf new file mode 100644 index 00000000..400a524e --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_psk/strongswan.conf @@ -0,0 +1,39 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = random openssl +} + +charon-systemd { + load = random nonce aes sha1 sha2 hmac kdf curve25519 kernel-netlink socket-default updown vici kernel-libipsec +} +charon { + plugins { + kernel-libipsec { + allow_peer_ts = yes + } +} + +filelog { + charon { + # path to the log file, specify this as section name in versions prior to 5.7.0 + path = /data/service/el1/public/netmanager/charon.log + # add a timestamp prefix + time_format = %b %e %T + # prepend connection name, simplifies grepping + ike_name = yes + # overwrite existing files + append = no + # increase default loglevel for all daemon subsystems + default = 4 + # flush each line to disk + flush_line = yes + } + stderr { + # more detailed loglevel for a specific subsystem, overriding the + # default loglevel. + ike = 4 + knl = 4 + } + } +} diff --git a/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_psk/swanctl.conf b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_psk/swanctl.conf new file mode 100644 index 00000000..c3dac8cf --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_psk/swanctl.conf @@ -0,0 +1,30 @@ +connections { + + home { + remote_addrs = vpn_address_value + vips = 0.0.0.0 + local { + auth = psk + } + remote { + auth = psk + id = vpn_ipsec_identifier_value + } + children { + home { + remote_ts=0.0.0.0/0 + esp_proposals = aes128gcm128-x25519 + } + } + version = 2 + proposals = aes128-sha256-x25519 + } +} + +secrets { + + ike-moon { + id = vpn_ipsec_identifier_value + secret = vpn_ipsec_sharedKey_value + } +} diff --git a/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_rsa/strongswan.conf b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_rsa/strongswan.conf new file mode 100644 index 00000000..335288f5 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_rsa/strongswan.conf @@ -0,0 +1,35 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 x509 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce aes des md4 sha1 sha2 fips-prf pem pkcs1 curve25519 gmp x509 curl revocation hmac kdf vici kernel-netlink socket-default eap-identity eap-mschapv2 updown +} + +charon { + +filelog { + charon { + # path to the log file, specify this as section name in versions prior to 5.7.0 + path = /data/service/el1/public/netmanager/charon.log + # add a timestamp prefix + time_format = %b %e %T + # prepend connection name, simplifies grepping + ike_name = yes + # overwrite existing files + append = no + # increase default loglevel for all daemon subsystems + default = 4 + # flush each line to disk + flush_line = yes + } + stderr { + # more detailed loglevel for a specific subsystem, overriding the + # default loglevel. + ike = 4 + knl = 4 + } + } +} diff --git a/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_rsa/swanctl.conf b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_rsa/swanctl.conf new file mode 100644 index 00000000..edde5679 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ike2_ipsec_rsa/swanctl.conf @@ -0,0 +1,22 @@ +connections { + home { + remote_addrs = vpn_address_value + vips = 0.0.0.0 + local { + auth = pubkey + certs = /data/service/el1/public/netmanager/client.cert.pem + id = vpn_ipsec_identifier_value + } + remote { + auth = pubkey + } + children { + home { + remote_ts=0.0.0.0/0 + esp_proposals = aes128gcm128-x25519 + } + } + version = 2 + proposals = aes128-sha256-x25519 + } +} diff --git a/product/phone/src/main/resources/rawfile/vpn/ipsec_hybrid_rsa/strongswan.conf b/product/phone/src/main/resources/rawfile/vpn/ipsec_hybrid_rsa/strongswan.conf new file mode 100644 index 00000000..d91c6302 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ipsec_hybrid_rsa/strongswan.conf @@ -0,0 +1,35 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 x509 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce aes des md4 sha1 sha2 fips-prf pem pkcs1 curve25519 gmp x509 curl revocation hmac kdf vici kernel-netlink socket-default eap-identity eap-mschapv2 updown +} + +charon { + +filelog { + charon { + # path to the log file, specify this as section name in versions prior to 5.7.0 + path = /data/service/el1/public/netmanager/charon.log + # add a timestamp prefix + time_format = %b %e %T + # prepend connection name, simplifies grepping + ike_name = yes + # overwrite existing files + append = no + # increase default loglevel for all daemon subsystems + default = 4 + # flush each line to disk + flush_line = yes + } + stderr { + # more detailed loglevel for a specific subsystem, overriding the + # default loglevel. + ike = 4 + knl = 4 + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/resources/rawfile/vpn/ipsec_hybrid_rsa/swanctl.conf b/product/phone/src/main/resources/rawfile/vpn/ipsec_hybrid_rsa/swanctl.conf new file mode 100644 index 00000000..11b1d8cd --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ipsec_hybrid_rsa/swanctl.conf @@ -0,0 +1,28 @@ +connections { + home { + remote_addrs = vpn_address_value + vips = 0.0.0.0 + local { + auth = xauth + xauth_id = vpn_username_value + } + remote { + auth = pubkey + } + children { + home { + remote_ts=0.0.0.0/0 + esp_proposals = aes256-sha2_384 + } + } + version = 1 + proposals = aes256-sha2_384-modp1024 + } +} + +secrets { + xauth { + id = vpn_username_value + secret = vpn_password_value + } +} diff --git a/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_psk/strongswan.conf b/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_psk/strongswan.conf new file mode 100644 index 00000000..69760fb0 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_psk/strongswan.conf @@ -0,0 +1,43 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 x509 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce aes des md4 sha1 sha2 fips-prf pem pkcs1 curve25519 gmp x509 curl revocation hmac kdf vici kernel-netlink socket-default eap-identity eap-mschapv2 updown +} + +charon { + +i_dont_care_about_security_and_use_aggressive_mode_psk = yes + +plugins { +kernel-netlink { +install_routes_xfrmi = yes +} +} + +filelog { + charon { + # path to the log file, specify this as section name in versions prior to 5.7.0 + path = /data/service/el1/public/netmanager/charon.log + # add a timestamp prefix + time_format = %b %e %T + # prepend connection name, simplifies grepping + ike_name = yes + # overwrite existing files + append = no + # increase default loglevel for all daemon subsystems + default = 4 + # flush each line to disk + flush_line = yes + } + stderr { + # more detailed loglevel for a specific subsystem, overriding the + # default loglevel. + ike = 2 + knl = 3 + } + } +} \ No newline at end of file diff --git a/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_psk/swanctl.conf b/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_psk/swanctl.conf new file mode 100644 index 00000000..28360428 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_psk/swanctl.conf @@ -0,0 +1,34 @@ +connections { + home { + remote_addrs = vpn_address_value + vips = 0.0.0.0 + local { + auth = psk + } + local-xauth { + auth = xauth + xauth_id = vpn_username_value + } + remote { + auth = psk + } + children { + home { + remote_ts=0.0.0.0/0 + esp_proposals = aes256-sha2_384 + } + } + version = 1 + proposals = aes256-sha2_384-modp1024 + aggressive=yes + } +} +secrets { + ike-moon { + secret = vpn_ipsec_sharedKey_value + } + xauth{ + id = vpn_username_value + secret = vpn_password_value + } +} diff --git a/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_rsa/strongswan.conf b/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_rsa/strongswan.conf new file mode 100644 index 00000000..afc2ea7a --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_rsa/strongswan.conf @@ -0,0 +1,37 @@ +# /etc/strongswan.conf - strongSwan configuration file + +swanctl { + load = pem pkcs1 x509 revocation constraints pubkey openssl random +} + +charon-systemd { + load = random nonce aes des md4 sha1 sha2 fips-prf pem pkcs1 curve25519 gmp x509 curl revocation hmac kdf vici kernel-netlink socket-default eap-identity eap-mschapv2 updown +} + +charon { + +i_dont_care_about_security_and_use_aggressive_mode_psk = yes + +filelog { + charon { + # path to the log file, specify this as section name in versions prior to 5.7.0 + path = /data/service/el1/public/netmanager/charon.log + # add a timestamp prefix + time_format = %b %e %T + # prepend connection name, simplifies grepping + ike_name = yes + # overwrite existing files + append = no + # increase default loglevel for all daemon subsystems + default = 4 + # flush each line to disk + flush_line = yes + } + stderr { + # more detailed loglevel for a specific subsystem, overriding the + # default loglevel. + ike = 4 + knl = 4 + } + } +} diff --git a/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_rsa/swanctl.conf b/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_rsa/swanctl.conf new file mode 100644 index 00000000..64663de9 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/ipsec_xauth_rsa/swanctl.conf @@ -0,0 +1,31 @@ +connections { + home { + remote_addrs = vpn_address_value + vips = 0.0.0.0 + local { + auth = pubkey + certs = /data/service/el1/public/netmanager/client.cert.pem + id = vpn_username_value + } + local-xauth { + auth = xauth + } + remote { + auth = pubkey + } + children { + home { + remote_ts=0.0.0.0/0 + esp_proposals = aes256-sha2_384 + } + } + version = 1 + proposals = aes256-sha2_384-modp1024 + } +} +secrets { + xauth-carol { + id = vpn_username_value + secret = vpn_password_value + } +} diff --git a/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/ipsec.conf b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/ipsec.conf new file mode 100644 index 00000000..4502aa7d --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/ipsec.conf @@ -0,0 +1,23 @@ +# ipsec.conf - strongSwan IPsec configuration file + +config setup + +conn %default + ikelifetime=60m + keylife=20m + rekeymargin=3m + keyingtries=1 + keyexchange=ikev1 + authby=secret + ike=aes128-sha1-modp1024,3des-sha1-modp1024! + esp=aes128-sha1-modp1024,3des-sha1-modp1024! + +conn home + keyexchange=ikev1 + left=%defaultroute + auto=add + authby=secret + type=transport + leftprotoport=17/1701 + rightprotoport=17/1701 + right=vpn_address_value diff --git a/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/ipsec.secrets.conf b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/ipsec.secrets.conf new file mode 100644 index 00000000..512bbc07 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/ipsec.secrets.conf @@ -0,0 +1,2 @@ +# ipsec.secrets.conf - strongSwan IPsec secrets file +: PSK vpn_ipsec_sharedKey_value diff --git a/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/options.l2tpd.client.conf b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/options.l2tpd.client.conf new file mode 100644 index 00000000..ef637258 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/options.l2tpd.client.conf @@ -0,0 +1,16 @@ +ipcp-accept-local +ipcp-accept-remote +refuse-eap +require-mschap-v2 +noccp +noauth +logfile /data/service/el1/public/netmanager/xl2tpd.log +idle 1800 +mtu 1410 +mru 1410 +defaultroute +usepeerdns +debug +connect-delay 5000 +name vpn_username_value +password vpn_password_value diff --git a/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/strongswan.conf b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/strongswan.conf new file mode 100644 index 00000000..2c2f729d --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/strongswan.conf @@ -0,0 +1,39 @@ +# strongswan.conf - strongSwan configuration file +# +# Refer to the strongswan.conf(5) manpage for details +# +# Configuration changes should be made in the included files + +charon { + load_modular = yes + plugins { + include /system/etc/strongswan/strongswan.d/charon/*.conf + kernel-libipsec { + load = no + } + } + filelog { + charon { + # path to the log file, specify this as section name in versions prior to 5.7.0 + path = /data/service/el1/public/netmanager/charon.log + # add a timestamp prefix + time_format = %b %e %T + # prepend connection name, simplifies grepping + ike_name = yes + # overwrite existing files + append = no + # increase default loglevel for all daemon subsystems + default = 4 + # flush each line to disk + flush_line = yes + } + stderr { + # more detailed loglevel for a specific subsystem, overriding the + # default loglevel. + ike = 4 + knl = 4 + } + } +} + +include /system/etc/strongswan/strongswan.d/*.conf diff --git a/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/xl2tpd.conf b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/xl2tpd.conf new file mode 100644 index 00000000..c9af858e --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_psk/xl2tpd.conf @@ -0,0 +1,6 @@ +[lac myVPN] +; set this to the ip address of your vpn server +lns = vpn_address_value +ppp debug = yes +pppoptfile = /data/service/el1/public/netmanager/options.l2tpd.client.conf +length bit = yes diff --git a/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/ipsec.conf b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/ipsec.conf new file mode 100644 index 00000000..e9736b65 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/ipsec.conf @@ -0,0 +1,23 @@ +# ipsec.conf - strongSwan IPsec configuration file + +config setup + +conn %default + ikelifetime=60m + keylife=20m + rekeymargin=3m + keyingtries=1 + keyexchange=ikev1 + authby=secret + ike=aes128-sha1-modp1024,3des-sha1-modp1024! + esp=aes128-sha1-modp1024,3des-sha1-modp1024! + +conn home + keyexchange=ikev1 + left=%defaultroute + auto=add + authby=secret + type=transport + leftprotoport=17/1701 + rightprotoport=17/1701 + right= vpn_address_value diff --git a/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/ipsec.secrets.conf b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/ipsec.secrets.conf new file mode 100644 index 00000000..512bbc07 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/ipsec.secrets.conf @@ -0,0 +1,2 @@ +# ipsec.secrets.conf - strongSwan IPsec secrets file +: PSK vpn_ipsec_sharedKey_value diff --git a/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/options.l2tpd.client.conf b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/options.l2tpd.client.conf new file mode 100644 index 00000000..8c37934c --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/options.l2tpd.client.conf @@ -0,0 +1,16 @@ +ipcp-accept-local +ipcp-accept-remote +refuse-eap +require-mschap-v2 +noccp +noauth +logfile /data/service/el1/public/netmanager/xl2tpd.log +idle 1800 +mtu 1410 +mru 1410 +defaultroute +usepeerdns +debug +connect-delay 5000 +name USERNAME_VALUE +password PASSWORD_VALUE diff --git a/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/strongswan.conf b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/strongswan.conf new file mode 100644 index 00000000..aeb59a90 --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/strongswan.conf @@ -0,0 +1,40 @@ +# strongswan.conf - strongSwan configuration file +# +# Refer to the strongswan.conf(5) manpage for details +# +# Configuration changes should be made in the included files + + +charon { + load_modular = yes + plugins { + include /system/etc/strongswan/strongswan.d/charon/*.conf + kernel-libipsec { + load = no + } + } + filelog { + charon { + # path to the log file, specify this as section name in versions prior to 5.7.0 + path = /data/service/el1/public/netmanager/charon.log + # add a timestamp prefix + time_format = %b %e %T + # prepend connection name, simplifies grepping + ike_name = yes + # overwrite existing files + append = no + # increase default loglevel for all daemon subsystems + default = 4 + # flush each line to disk + flush_line = yes + } + stderr { + # more detailed loglevel for a specific subsystem, overriding the + # default loglevel. + ike = 4 + knl = 4 + } + } +} + +include /system/etc/strongswan/strongswan.d/*.conf diff --git a/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/xl2tpd.conf b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/xl2tpd.conf new file mode 100644 index 00000000..c9af858e --- /dev/null +++ b/product/phone/src/main/resources/rawfile/vpn/l2tp_ipsec_rsa/xl2tpd.conf @@ -0,0 +1,6 @@ +[lac myVPN] +; set this to the ip address of your vpn server +lns = vpn_address_value +ppp debug = yes +pppoptfile = /data/service/el1/public/netmanager/options.l2tpd.client.conf +length bit = yes diff --git a/product/phone/src/main/resources/zh_CN/element/strarray.json b/product/phone/src/main/resources/zh_CN/element/strarray.json new file mode 100644 index 00000000..425407b4 --- /dev/null +++ b/product/phone/src/main/resources/zh_CN/element/strarray.json @@ -0,0 +1,29 @@ +{ + "strarray": [ + { + "name": "ovpn_auth_type", + "value": [ + { + "value": "证书(TLS)" + }, + { + "value": "密码" + }, + { + "value": "证书和密码(TLS)" + } + ] + }, + { + "name": "ovpn_protocol", + "value": [ + { + "value": "TCP" + }, + { + "value": "UDP" + } + ] + } + ] +} \ No newline at end of file diff --git a/product/phone/src/main/resources/zh_CN/element/string.json b/product/phone/src/main/resources/zh_CN/element/string.json index d20992ac..2a51fea9 100644 --- a/product/phone/src/main/resources/zh_CN/element/string.json +++ b/product/phone/src/main/resources/zh_CN/element/string.json @@ -1491,6 +1491,270 @@ { "name": "GET_ACCESS_UDID", "value": "获取UDID的权限" + }, + { + "name": "vpn_change_alert_title", + "value": "要替换现有VPN吗?" + }, + { + "name": "vpn_change_alert_content", + "value": "您已连接到VPN,如果您要连接到其他VPN,则系统替换现有VPN." + }, + { + "name": "vpn_change_alert_confirm", + "value": "替换" + }, + { + "name": "vpn_connect_title", + "value": "连接到%s" + }, + { + "name": "vpn_connect_username", + "value": "用户名" + }, + { + "name": "vpn_connect_password", + "value": "密码" + }, + { + "name": "vpn_connect_save", + "value": "保存账户信息" + }, + { + "name": "vpn_connect_confirm", + "value": "连接" + }, + { + "name": "vpn_edit_title", + "value": "编辑VPN网络" + }, + { + "name": "vpn_edit_alias", + "value": "名称" + }, + { + "name": "vpn_edit_type", + "value": "类型" + }, + { + "name": "vpn_edit_ovpn_configfile", + "value": "OpenVPN 配置文件" + }, + { + "name": "vpn_edit_ovpn_ca_cert_file", + "value": "CA 证书" + }, + { + "name": "vpn_edit_ovpn_user_cert_file", + "value": "用户证书" + }, + { + "name": "vpn_edit_ovpn_private_key", + "value": "私钥" + }, + { + "name": "vpn_edit_ovpn_private_key_psd", + "value": "私钥密码" + }, + { + "name": "vpn_edit_ovpn_click_select", + "value": "点击选择" + }, + { + "name": "vpn_edit_ovpn_protocol", + "value": "OpenVPN 协议" + }, + { + "name": "vpn_edit_auth_type", + "value": "OpenVPN 认证类型" + }, + { + "name": "vpn_edit_serveraddress", + "value": "服务器地址" + }, + { + "name": "vpn_edit_ovpn_serveraddress", + "value": "OpenVPN 服务器地址" + }, + { + "name": "vpn_edit_ovpn_port", + "value": "OpenVPN 服务器端口" + }, + { + "name": "vpn_edit_ipsecidentifier", + "value": "IPSec 标识符" + }, + { + "name": "vpn_edit_l2tpsecret", + "value": "L2TP 密钥" + }, + { + "name": "vpn_edit_ipsecsecret", + "value": "IPSec 预共享密钥" + }, + { + "name": "vpn_edit_usercert", + "value": "IPSec 用户证书公钥" + }, + { + "name": "vpn_edit_user_private_cert", + "value": "IPSec 用户证书私钥" + }, + { + "name": "vpn_edit_cacert", + "value": "IPSec CA 证书" + }, + { + "name": "vpn_edit_servercert", + "value": "IPSec 服务器证书公钥" + }, + { + "name": "vpn_edit_server_private_cert", + "value": "IPSec 服务器证书私钥" + }, + { + "name": "vpn_edit_unuse", + "value": "未使用" + }, + { + "name": "vpn_edit_advanced", + "value": "显示高级选项" + }, + { + "name": "vpn_edit_proxy_advanced", + "value": "显示代理" + }, + { + "name": "vpn_edit_searchdomains", + "value": "DNS 搜索域" + }, + { + "name": "vpn_edit_dnsaddresses", + "value": "DNS 服务器(例如 8.8.8.8)" + }, + { + "name": "vpn_edit_router", + "value": "转发路线(例如 10.0.0.0/8)" + }, + { + "name": "vpn_edit_ovpn_proxy_host", + "value": "OpenVPN 代理主机" + }, + { + "name": "vpn_edit_ovpn_proxy_port", + "value": "OpenVPN 代理端口" + }, + { + "name": "vpn_edit_ovpn_proxy_username", + "value": "OpenVPN 代理用户名" + }, + { + "name": "vpn_edit_ovpn_proxy_password", + "value": "OpenVPN 代理密码" + }, + { + "name": "vpn_edit_ovpn_useauth", + "value": "使用基本身份验证" + }, + { + "name": "vpn_edit_save", + "value": "保存" + }, + { + "name": "vpn_edit_cert_not_specified", + "value": "(未指定)" + }, + { + "name": "vpn_edit_cert_not_verify_server", + "value": "(不验证服务器)" + }, + { + "name": "vpn_edit_cert_from_server", + "value": "(来自服务器)" + }, + { + "name": "vpn_line_remove_alert_title", + "value": "是否删除%s网络?" + }, + { + "name": "vpn_line_remove_alert_confirm", + "value": "删除" + }, + { + "name": "vpn_line_disconnect_alert_title", + "value": "是否断开%s网络?" + }, + { + "name": "vpn_line_disconnect_alert_confirm", + "value": "断开" + }, + { + "name": "vpn_line_disconnect_operation_edit", + "value": "编辑VPN网络" + }, + { + "name": "vpn_line_disconnect_operation_remove", + "value": "删除VPN网络" + }, + { + "name": "vpn_state_connect_ing", + "value": "连接中..." + }, + { + "name": "vpn_state_connec_success", + "value": "连接成功" + }, + { + "name": "vpn_state_connec_failed", + "value": "连接失败" + }, + { + "name": "vpn_state_disconnecting", + "value": "断开中..." + }, + { + "name": "vpn_state_disconnected", + "value": "已断开" + }, + { + "name": "vpn_list_empty", + "value": "没有VPN" + }, + { + "name": "vpn_list_add", + "value": "添加VPN网络" + }, + { + "name": "ovpn_auth_type_cert", + "value": "证书( TLS )" + }, + { + "name": "ovpn_auth_type_pass", + "value": "密码" + }, + { + "name": "ovpn_auth_type_certpass", + "value": "证书和密码( TLS )" + }, + { + "name": "VPN", + "value": "VPN" + }, + { + "name": "vpn_error_invalid_param", + "value": "参数错误" + }, + { + "name": "vpn_error_operation_failed", + "value": "API调用失败" + }, + { + "name": "vpn_error_system_internal", + "value": "系统内部错误" + }, + { + "name": "vpn_error_disconnect_first", + "value": "请先断开VPN,再操作" } ] } \ No newline at end of file