diff --git a/UserAuthentication/.gitignore b/UserAuthentication/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/UserAuthentication/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/UserAuthentication/AppScope/app.json5 b/UserAuthentication/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c79cddcb5762a72797c66c3233a2ab19b95f1e41 --- /dev/null +++ b/UserAuthentication/AppScope/app.json5 @@ -0,0 +1,25 @@ +/* +* Copyright (c) 2025 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. +*/ + +{ + "app": { + "bundleName": "com.samples.userauthentication", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/UserAuthentication/README.md b/UserAuthentication/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b995722a700d0678b4ec1428e81f3b5e886c9204 --- /dev/null +++ b/UserAuthentication/README.md @@ -0,0 +1,87 @@ +# 用户认证服务 + +### 介绍 + +此示例演示了用户认证服务的部分功能,包括查询用户认证功能和凭证,调用用户认证界面等。 + +### 效果预览 + +| 首页 | 实例1 | 用户验证窗口 | +| -------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------ | +| | | | + +使用说明: + +1. 在使用前先在“设置-安全与隐私”中设置PIN码以及人脸或指纹验证。 +2. 完成设置后在查询能力页面点击查询按钮,如果按钮下方文本显示“Pass”则表示设置成功可以进行接下来的操作。 +3. 通过点击上方的其他标签来切换不同的页面,点击按钮可触发验证页面。 + +### 工程目录 + +``` +\entry\src\main + │ module.json5 // 配置文件,用于配置权限等信息 + ├─ets + │ ├─common + │ │ Logger.ts // 打印工具类 + │ ├─entryability + │ ├─entrybackupability + │ └─pages + │ Index.ets // 主页面,代码参考页面 + │ testPage.ets // 测试用页面具体使用方式见'约束与限制' + └─resources + ├─base // 资源文件,内容为英文 + │ ├─element + │ ├─media + │ └─profile + └─zh_CN + └─element // 资源文件,内容为中文 + +``` + +### 具体实现 + +- 在开发板的设置中配置用户认证方式,然后在`\entry\src\main\module5`文件中添加用户认证权限`ohos.permission.ACCESS_BIOMETRIC`。 +- 将示例代码中的内容封装成函数。 +- 设置多个`TabContent`组件来区分不同功能,将不同的函数功能绑定到 各个`TabContent `页面的各个`Button`上。 +- 在每个按钮的上方设置一个 `Text` 文本,用于说明按钮的功能。 +- 在按钮的下方设置另一个 `Text` 文本,在点击按钮后显示功能是否正常。 + +### 相关权限 + +[ohos.permission.ACCESS_BIOMETRIC](https://gitee.com/openharmony/docs/blob/OpenHarmony-5.0.0-Release/zh-cn/application-dev/security/AccessToken/permissions-for-all.md#ohospermissionaccess_biometric) + +### 依赖 + +不涉及。 + +### 约束与限制 + +1.本示例仅支持标准系统上运行,支持设备:华为手机。 + +2.HarmonyOS系统:HarmonyOS 5.1.0 Release及以上。 + +3.HarmonyOS SDK版本:HarmonyOS 5.1.0 Release及以上。 + +4.本示例为Stage模型,支持API18版本SDK。 + +5.本示例需要使用DevEco Studio Release(5.0.5.306)及以上版本才可编译运行。 + +6.HH-SCDAYU200开发套件不支持生物特征识别,建议将认证方式更改为个人识别码(PIN),并确保在切换自定义功能时采用至少两种身份验证方法。 + +7.自动化测试中非自动化的相关测试点需要手动输入密码或手动录入人脸或指纹。 + +8.在运行测试前需要修改启动页面为`testPage`,具体修改方式为:将`src/main/ets/entryability/EntryAbility.ets`中的`windowStage.loadContent('pages/Index', ...)`修改为`windowStage.loadContent('pages/testPage', ...)` 。 + +### 下载 + +如需单独下载本工程,执行如下命令: + +``` +git init +git config core.sparsecheckout true +echo UserAuthentication > .git/info/sparse-checkout +git remote add origin https://gitee.com/harmonyos_samples/guide-snippets.git +git pull origin master +``` + diff --git a/UserAuthentication/build-profile.json5 b/UserAuthentication/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..b9ca6cfaece6017d1b3945c88414b5ba694ade39 --- /dev/null +++ b/UserAuthentication/build-profile.json5 @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2025 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. +*/ + +{ + "app": { + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.1.0(18)", + "targetSdkVersion": "5.1.0(18)", + "runtimeOS": "HarmonyOS" + } + ], + "buildModeSet": [ + { + "name": "debug" + }, + { + "name": "release" + } + ], + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/UserAuthentication/entry/.gitignore b/UserAuthentication/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/UserAuthentication/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/UserAuthentication/entry/build-profile.json5 b/UserAuthentication/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..eed6635f664fa08b992d84590bf62be7e0bb5381 --- /dev/null +++ b/UserAuthentication/entry/build-profile.json5 @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 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. + */ + +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/UserAuthentication/entry/hvigorfile.ts b/UserAuthentication/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..98d52319cb1dee60511b5716dba03b76e68a6d8b --- /dev/null +++ b/UserAuthentication/entry/hvigorfile.ts @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2025 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 { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/UserAuthentication/entry/obfuscation-rules.txt b/UserAuthentication/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..fdbb5b9852d7dd5f39bddaeb21ab5ee1f3346749 --- /dev/null +++ b/UserAuthentication/entry/obfuscation-rules.txt @@ -0,0 +1,22 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/UserAuthentication/entry/oh-package.json5 b/UserAuthentication/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..8492e57daac4f46472628635499c31b3799c3468 --- /dev/null +++ b/UserAuthentication/entry/oh-package.json5 @@ -0,0 +1,25 @@ +/* +* Copyright (c) 2025 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. +*/ + +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {} +} + diff --git a/UserAuthentication/entry/src/main/ets/common/Logger.ts b/UserAuthentication/entry/src/main/ets/common/Logger.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc4abffaa1348add20acba4eade3dd05c3e0a203 --- /dev/null +++ b/UserAuthentication/entry/src/main/ets/common/Logger.ts @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025 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 { hilog } from '@kit.PerformanceAnalysisKit'; + +const DOMAIN: number = 0x0000; // 此处使用自行申请的应用代码号 +const PREFIX: string = '[Sample_UserAuthentication]'; // 应用log标签 + +export class Logger { + private domain: number; + private prefix: string; + private format: string = '%{public}s'; + + constructor(prefix: string) { + this.prefix = prefix; + this.domain = DOMAIN; + } + + getDomain() { + return this.domain; + } + + getPrefix() { + return this.prefix; + } + + getFormat() { + return this.format; + } + + debug(...args: string[]) { + hilog.debug(this.domain, this.prefix, this.format, args); + } + + info(...args: string[]) { + hilog.info(this.domain, this.prefix, this.format, args); + } + + warn(...args: string[]) { + hilog.warn(this.domain, this.prefix, this.format, args); + } + + error(...args: string[]) { + hilog.error(this.domain, this.prefix, this.format, args); + } + + fatal(...args: string[]) { + hilog.fatal(this.domain, this.prefix, this.format, args); + } + + isLoggable(level: number) { + hilog.isLoggable(this.domain, this.prefix, level); + } +} + +export default new Logger(PREFIX); diff --git a/UserAuthentication/entry/src/main/ets/entryability/EntryAbility.ets b/UserAuthentication/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..22a41871d06219f7465d4893c9679b2aa376b6e5 --- /dev/null +++ b/UserAuthentication/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2025 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 { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { JSON } from '@kit.ArkTS'; +import { window } from '@kit.ArkUI'; +import Logger from '../common/Logger'; + +const DOMAIN: number = Logger.getDomain(); +const PREFIX: string = Logger.getPrefix(); +const FORMAT: string = Logger.getFormat(); + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(DOMAIN, PREFIX, FORMAT, 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, PREFIX, FORMAT, 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, PREFIX, FORMAT, 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, PREFIX, `Failed to load the content. Cause: ${FORMAT}`, + JSON.stringify(err.message) ?? ''); + return; + } + hilog.info(DOMAIN, PREFIX, FORMAT, 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, PREFIX, FORMAT, 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, PREFIX, FORMAT, 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, PREFIX, FORMAT, 'Ability onBackground'); + } +} diff --git a/UserAuthentication/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/UserAuthentication/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..b503d146ec83987789d0f0e40abf88dc85cbdf61 --- /dev/null +++ b/UserAuthentication/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,32 @@ +/* +* Copyright (c) 2025 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 { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; +import Logger from '../common/Logger'; + +const DOMAIN: number = Logger.getDomain(); +const PREFIX: string = Logger.getPrefix(); +const FORMAT: string = Logger.getFormat(); + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, PREFIX, FORMAT, 'onBackup ok'); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, PREFIX, `onRestore ok ${FORMAT}`, JSON.stringify(bundleVersion)); + } +} \ No newline at end of file diff --git a/UserAuthentication/entry/src/main/ets/pages/Index.ets b/UserAuthentication/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..47e8c89bfd3ada82303e99e8281bdee17a3ee245 --- /dev/null +++ b/UserAuthentication/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,740 @@ +/* + * Copyright (c) 2025 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. + */ + +// 本页代码为标准实例,没有使用同步语法,不涉及测试,更多关于测试信息,详见readme。 +import { BusinessError } from '@kit.BasicServicesKit'; +import { cryptoFramework } from '@kit.CryptoArchitectureKit'; +import { JSON } from '@kit.ArkTS'; +import { promptAction } from '@kit.ArkUI'; +import { userAuth } from '@kit.UserAuthenticationKit'; +import Logger from '../common/Logger'; + +// 用于获取资源字符串 +function resourceToString(resource: Resource): string { + let result: string = ''; + try { + result = getContext().resourceManager.getStringSync(resource); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`catch error. Code is ${err?.code}, message is ${err?.message}`); + } + if (result === '' || result === undefined) { + return 'undefined'; + } else { + return result; + } +} + +// 用于异步等待 +async function waiting(times: number): Promise { + // 返回一个 Promise,表示等待 times 毫秒 + return new Promise(resolve => setTimeout(resolve, times)); +} + + +enum PageIndex { + QUERY_CAPABILITY = 0, // 能力查询页面的索引值 + EXAMPLE_TAB_1 = 1, // 实例1页面的索引值 + EXAMPLE_TAB_2 = 2, // 实例2页面的索引值 + EXAMPLE_TAB_3 = 3, // 实例3页面的索引值 + EXAMPLE_TAB_4 = 4, // 实例4页面的索引值 + CREDENTIAL_QUERIES = 5, // 能力查询页面的索引值 + SIMULATION_VALIDATION = 6 // 模拟认证页面的索引值 +}; + +enum ResultIndex { + WHETHER_SUPPORTED = 0, // 能力查询结果的索引值 + EXAMPLE_1 = 1, // 实例1结果的索引值 + EXAMPLE_2 = 2, // 实例2结果的索引值 + EXAMPLE_3 = 3, // 实例3结果的索引值 + EXAMPLE_4 = 4, // 实例4结果的索引值 + CANCEL = 5, // 实例5结果的索引值 + CUSTOMIZE = 6, // 实例6结果的索引值 + QUERY_CREDENTIALS = 7 // 能力查询结果的索引值 +}; + +@Entry +@Component +struct Index { + @State currentIndex: number = 0; // 最开始索引页面为第一页 + @State result: string[] = new Array(8).fill('wait'); + @State credentialDigest: string = ''; + @State whetherSupport: Resource = $r('app.string.waitQuery'); + @State credentialValue: string = ''; + SPACE_GAP: number = 5; // Row组件间隙 + + /* + * obtain-supported-authentication-capabilities.md + * 以查询设备是否支持认证可信等级≥ATL1的PIN认证功能为例 + */ + // [Start obtain_supported_capabilities] + obtainingSupported() { + try { + // 查询认证能力是否支持 + userAuth.getAvailableStatus(userAuth.UserAuthType.PIN, userAuth.AuthTrustLevel.ATL1); + Logger.info('current auth trust level is supported'); + return true; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`current auth trust level is not supported. Code is ${err?.code}, message is ${err?.message}`); + return false; + } + } + + // [End obtain_supported_capabilities] + // 用于处理示例1,示例2,示例3和示例4的部分操作 + // [Start authentication_example1] + // [Start authentication_example2] + // [Start authentication_example3] + // [Start authentication_example4] + handleAuthResult(userAuthInstance: userAuth.UserAuthInstance, exampleNumber: number) { + // [StartExclude authentication_example1] + // [StartExclude authentication_example2] + // [StartExclude authentication_example3] + // [StartExclude authentication_example4] + try { + // [EndExclude authentication_example1] + // [EndExclude authentication_example2] + // [EndExclude authentication_example3] + // [EndExclude authentication_example4] + // userAuthInstance.on异常抛出层 + userAuthInstance.on('result', { + onResult: (result: userAuth.UserAuthResult) => { + // [StartExclude authentication_example1] + // [StartExclude authentication_example2] + // [StartExclude authentication_example3] + // [StartExclude authentication_example4] + this.result[exampleNumber] = (`${result.result}`); + try { + // onResult异常抛出层 + try { + // JSON.stringify异常抛出层 + // [EndExclude authentication_example1] + // [EndExclude authentication_example2] + // [EndExclude authentication_example3] + // [EndExclude authentication_example4] + Logger.info(`userAuthInstance callback result: ${JSON.stringify(result)}`); + // [StartExclude authentication_example1] + // [StartExclude authentication_example2] + // [StartExclude authentication_example3] + // [StartExclude authentication_example4] + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`JSON stringify catch error. Code is ${err?.code}, message is ${err?.message}`); + } + // [EndExclude authentication_example1] + // [EndExclude authentication_example2] + // [EndExclude authentication_example3] + // [EndExclude authentication_example4] + userAuthInstance.off('result'); // 认证完成后取消订阅 + // [StartExclude authentication_example1] + // [StartExclude authentication_example2] + // [StartExclude authentication_example3] + // [StartExclude authentication_example4] + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`onResult catch error. Code: ${err?.code}, Message: ${err?.message}`); + } + // [EndExclude authentication_example1] + // [EndExclude authentication_example2] + // [EndExclude authentication_example3] + // [EndExclude authentication_example4] + } + }); + // 启动认证 + userAuthInstance.start(); + Logger.info('auth start success'); + // [StartExclude authentication_example1] + // [StartExclude authentication_example2] + // [StartExclude authentication_example3] + // [StartExclude authentication_example4] + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`userAuthInstance.on catch error. Code: ${err?.code}, Message: ${err?.message}`); + } + // [EndExclude authentication_example1] + // [EndExclude authentication_example2] + // [EndExclude authentication_example3] + // [EndExclude authentication_example4] + } + + /* + * start-authentication.md + * 示例1: + * 发起用户认证,采用认证可信等级≥ATL3的人脸 + 锁屏密码 + 指纹认证,获取认证结果 + * */ + // [StartExclude authentication_example2] + // [StartExclude authentication_example3] + // [StartExclude authentication_example4] + initiatingUserAuthentication1() { + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; // 设置认证参数 + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.PIN, userAuth.UserAuthType.FACE, userAuth.UserAuthType.FINGERPRINT], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + }; + // 配置认证界面 + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + this.handleAuthResult(userAuthInstance, ResultIndex.EXAMPLE_1); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + } + } + + // [EndExclude authentication_example2] + // [End authentication_example1] + /* + * start-authentication.md + * 示例2: + * 发起用户认证,采用认证可信等级≥ATL3的人脸 + 锁屏密码 + 指纹认证 + 认证类型相关 + 复用设备解锁最大有效时长认证,获取认证结果 + * */ + initiatingUserAuthentication2() { + // 设置认证参数 + let reuseUnlockResult: userAuth.ReuseUnlockResult = { + reuseMode: userAuth.ReuseMode.AUTH_TYPE_RELEVANT, + reuseDuration: userAuth.MAX_ALLOWABLE_REUSE_DURATION, + }; + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.PIN, userAuth.UserAuthType.FACE, userAuth.UserAuthType.FINGERPRINT], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + reuseUnlockResult: reuseUnlockResult, + }; + // 配置认证界面 + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + this.handleAuthResult(userAuthInstance, ResultIndex.EXAMPLE_2); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + } + } + + // [EndExclude authentication_example3] + // [End authentication_example2] + /* + * start-authentication.md + * 示例3: + * 发起用户认证,采用认证可信等级≥ATL3的人脸 + 锁屏密码 + 指纹认证 + 任意应用认证类型相关 + 复用任意应用最大有效时长认证,获取认证结果: + * */ + initiatingUserAuthentication3() { + // 设置认证参数 + let reuseUnlockResult: userAuth.ReuseUnlockResult = { + reuseMode: userAuth.ReuseMode.CALLER_IRRELEVANT_AUTH_TYPE_RELEVANT, + reuseDuration: userAuth.MAX_ALLOWABLE_REUSE_DURATION, + }; + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.PIN, userAuth.UserAuthType.FACE, userAuth.UserAuthType.FINGERPRINT], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + reuseUnlockResult: reuseUnlockResult, + }; + // 配置认证界面 + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + this.handleAuthResult(userAuthInstance, ResultIndex.EXAMPLE_3); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + } + } + + // [EndExclude authentication_example4] + // [End authentication_example3] + /* + * start-authentication.md + * 示例4: + * 以模应用方式进行用户身份认证 + * */ + initiatingUserAuthentication4() { + // 设置认证参数 + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.PIN, userAuth.UserAuthType.FACE, userAuth.UserAuthType.FINGERPRINT], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + }; + // 配置认证界面 + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + uiContext: getContext(), + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + this.handleAuthResult(userAuthInstance, ResultIndex.EXAMPLE_4); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + } + } + + // [End authentication_example4] + // [Start cancel_authentication] + handleAuthResultAndCanceling(userAuthInstance: userAuth.UserAuthInstance, exampleNumber: number) { + // [StartExclude cancel_authentication] + try { + // userAuthInstance.on异常抛出层 + userAuthInstance.on('result', { + onResult: (result: userAuth.UserAuthResult) => { + this.result[exampleNumber] = (`${result.result}`); + try { + // onResult异常抛出层 + try { + // JSON.stringify异常抛出层 + Logger.info(`userAuthInstance callback result: ${JSON.stringify(result)}`); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`JSON stringify catch error. Code is ${err?.code}, message is ${err?.message}`); + } + userAuthInstance.off('result'); // 认证完成后取消订阅 + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`onResult catch error. Code: ${err?.code}, Message: ${err?.message}`); + } + } + }); + // [EndExclude cancel_authentication] + // 启动认证 + userAuthInstance.start(); + Logger.info('auth start success'); + // [StartExclude cancel_authentication] + // 使用等待函数模拟等待用户认证 + let DELAY_TIME: number = 3000; // 停止3秒 + waiting(DELAY_TIME).then(() => { + // 延迟后取消认证 + // [EndExclude cancel_authentication] + userAuthInstance.cancel(); + Logger.info('auth cancel success'); + // [StartExclude cancel_authentication] + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`userAuthInstance.on catch error. Code: ${err?.code}, Message: ${err?.message}`); + } + // [EndExclude cancel_authentication] + } + + /* + * cancel-authentication.md + * 发起认证可信等级≥ATL3的人脸+锁屏密码认证后,取消认证请求 + * */ + cancelingUserAuthentication() { + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + // 设置认证参数 + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.PIN, userAuth.UserAuthType.FACE, userAuth.UserAuthType.FINGERPRINT], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + }; + // 配置认证界面 + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 使用 Promise 来监听认证结果 + this.handleAuthResultAndCanceling(userAuthInstance, ResultIndex.CANCEL); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code as number}, message is ${err?.message}`); + } + } + + // [End cancel_authentication] + // [Start custom_authentication] + handleCustomAuthResult(userAuthInstance: userAuth.UserAuthInstance, exampleNumber: number) { + // [StartExclude custom_authentication] + try { + // [EndExclude custom_authentication] + // userAuthInstance.on异常抛出层 + userAuthInstance.on('result', { + onResult: (result: userAuth.UserAuthResult) => { + // [StartExclude custom_authentication] + this.result[exampleNumber] = (`${result.result}`); + try { + // onResult异常抛出层 + try { + // JSON.stringify异常抛出层 + // [EndExclude custom_authentication] + Logger.info(`userAuthInstance callback result: ${JSON.stringify(result)}`); + // [StartExclude custom_authentication] + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`JSON stringify catch error. Code is ${err?.code}, message is ${err?.message}`); + } + userAuthInstance.off('result'); // 认证完成后取消订阅 + // [EndExclude custom_authentication] + if (result.result == userAuth.UserAuthResultCode.CANCELED_FROM_WIDGET || + result.result == userAuth.UserAuthResultCode.NOT_ENROLLED) { + // 请开发者自行完成拉起自定义认证界面的实现 + // 此处拉起类似于支付宝输入密码支付的界面 + // [StartExclude custom_authentication] + promptAction.showToast({ + message: resourceToString($r('app.string.dialogOfCustom')), // 显示文本 + duration: 3000, // 显示时长3000ms + bottom: 300 // 距离底部的距离300vp + }); + // [EndExclude custom_authentication] + } + // [StartExclude custom_authentication] + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`onResult catch error. Code: ${err?.code}, Message: ${err?.message}`); + } + // [EndExclude custom_authentication] + } + }); + // 启动认证 + userAuthInstance.start(); + Logger.info('auth start success'); + // [StartExclude custom_authentication] + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`userAuthInstance.on catch error. Code: ${err?.code}, Message: ${err?.message}`); + } + // [EndExclude custom_authentication] + } + + /* + * apply-custom-authentication.md + * 当前示例仅展示如何配置界面、选择切换到自定义认证界面,具体拉起的页面及对应页面的实现,请开发者自行实现 + * */ + applyingCustomAuthentication() { + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.FACE], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + }; // 配置认证界面需设置navigationButtonText + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + navigationButtonText: resourceToString($r('app.string.navigationButtonText')), + // 该参数仅在单指纹、单人脸场景下支持,该密码并非系统密码,而是应用自己设置的密码 + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + this.handleCustomAuthResult(userAuthInstance, ResultIndex.CUSTOMIZE); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + } + } + // [End custom_authentication] + + /* + * obtain-enrolled-state-capabilities.md + * 以查询用户人脸注册凭据的状态为例 + * */ + // [Start obtain_enrolled_capabilities] + obtainingEnrolledCredentialInformation() { + try { + let enrolledState = userAuth.getEnrolledState(userAuth.UserAuthType.PIN); + Logger.info(`get current enrolled state success, enrolledState: ${JSON.stringify(enrolledState)}`); + return enrolledState.credentialDigest; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`get current enrolled state failed, Code is ${err?.code}, message is ${err?.message}`); + return false; + } + } + + // [End obtain_enrolled_capabilities] + comprehensiveFeatures() { + let reuseUnlockResult: userAuth.ReuseUnlockResult = { + reuseMode: userAuth.ReuseMode.AUTH_TYPE_RELEVANT, + reuseDuration: userAuth.MAX_ALLOWABLE_REUSE_DURATION, + }; + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.FACE], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + reuseUnlockResult: reuseUnlockResult + }; // 配置认证界面需设置navigationButtonText + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + navigationButtonText: resourceToString($r('app.string.navigationButtonText')), + // 该参数仅在单指纹、单人脸场景下支持,该密码并非系统密码,而是应用自己设置的密码 + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + this.handleCustomAuthResult(userAuthInstance, ResultIndex.CUSTOMIZE); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + } + } + + @Builder + tabBuilder(title: Resource, targetIndex: number, tabId: string) { + Column() { + Text(title) + .fontColor(this.currentIndex === targetIndex ? $r('app.color.tab_selected_color') : + $r('app.color.tab_not_selected_color')) + .fontSize($r('app.float.size_13')) + } + .width('100%') + .height($r('app.float.size_50')) + .justifyContent(FlexAlign.Center) + .id(tabId) + } + + build() { + Column() { + Tabs() { + TabContent() { + Column() { + Text($r('app.string.whetherSupported')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.query')) + .onClick(() => { + if (this.obtainingSupported()) { + this.result[ResultIndex.WHETHER_SUPPORTED] = 'Support'; + } else { + this.result[ResultIndex.WHETHER_SUPPORTED] = 'Not Support'; + } + }) + Text(this.result[ResultIndex.WHETHER_SUPPORTED]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + } + }.tabBar(this.tabBuilder($r('app.string.queryCapability'), PageIndex.QUERY_CAPABILITY, 'queryCapability')) + + TabContent() { + Column() { + Text($r('app.string.descriptionContent1')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example1')) + .onClick(async () => { + this.initiatingUserAuthentication1(); + }) + Text(this.result[ResultIndex.EXAMPLE_1]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + Blank() + .height('5%') + Text($r('app.string.descriptionContent2')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example2')) + .onClick(async () => { + this.initiatingUserAuthentication2(); + }) + Text(this.result[ResultIndex.EXAMPLE_2]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + } + }.tabBar(this.tabBuilder($r('app.string.exampleTab1'), PageIndex.EXAMPLE_TAB_1, 'exampleTab1')) + + TabContent() { + Column() { + Text($r('app.string.descriptionContent3')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example3')) + .onClick(() => { + this.initiatingUserAuthentication3(); + }) + Text(this.result[ResultIndex.EXAMPLE_3]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + Blank() + .height('5%') + Text($r('app.string.descriptionContent4')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example4')) + .onClick(() => { + this.initiatingUserAuthentication4(); + }) + Text(this.result[ResultIndex.EXAMPLE_4]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + } + }.tabBar(this.tabBuilder($r('app.string.exampleTab2'), PageIndex.EXAMPLE_TAB_2, 'exampleTab2')) + + TabContent() { + Column() { + Text($r('app.string.descriptionContent5')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example5')) + .onClick(async () => { + this.cancelingUserAuthentication(); + }) + Text(this.result[ResultIndex.CANCEL]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + } + }.tabBar(this.tabBuilder($r('app.string.exampleTab3'), PageIndex.EXAMPLE_TAB_3, 'exampleTab3')) + + TabContent() { + Column() { + Text($r('app.string.descriptionContent6')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example6')) + .onClick(() => { + this.applyingCustomAuthentication(); + }) + Text(this.result[ResultIndex.CUSTOMIZE]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + } + }.tabBar(this.tabBuilder($r('app.string.exampleTab4'), PageIndex.EXAMPLE_TAB_4, 'exampleTab4')) + + TabContent() { + Column() { + Text($r('app.string.queryCredentials')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.query')) + .onClick(() => { + if (this.obtainingEnrolledCredentialInformation()) { + this.credentialDigest = 'credentialDigest is ' + this.obtainingEnrolledCredentialInformation(); + } else { + this.credentialDigest = 'Error'; + } + }) + Text(this.credentialDigest) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + } + }.tabBar(this.tabBuilder($r('app.string.credentialQueries'), PageIndex.CREDENTIAL_QUERIES, 'credentialQueries')) + + TabContent() { + Column() { + Button($r('app.string.refreshSupported')) + .onClick(() => { + if (this.obtainingSupported()) { + this.whetherSupport = $r('app.string.queryPass'); + } else { + this.whetherSupport = $r('app.string.queryError'); + } + }) + .margin($r('app.float.size_10')) + Text(this.whetherSupport) + .fontSize($r('app.float.size_20')) + .textAlign(TextAlign.Center) + Blank() + .height('10%') + Row({ space: this.SPACE_GAP }) { + Button($r('app.string.customLogin')) + .onClick(() => { + this.comprehensiveFeatures(); + }) + Button($r('app.string.simpleLogin')) + .onClick(() => { + this.initiatingUserAuthentication2(); + }) + } + .margin($r('app.float.size_10')) + + Blank() + .height('10%') + Text($r('app.string.credentialDescription')) + .width('80%') + .fontSize($r('app.float.size_20')) + .textAlign(TextAlign.Center) + Button($r('app.string.refreshTheCredentials')) + .onClick(() => { + try { + let enrolledState = userAuth.getEnrolledState(userAuth.UserAuthType.PIN); + Logger.info(`get current enrolled state success, enrolledState: ${JSON.stringify(enrolledState)}`); + this.credentialValue = enrolledState.credentialDigest + ''; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`get current enrolled state failed, Code is ${err?.code}, message is ${err?.message}`); + } + }) + .margin($r('app.float.size_10')) + Text(this.credentialValue) + .fontSize($r('app.float.size_20')) + .textAlign(TextAlign.Center) + } + } + .tabBar(this.tabBuilder($r('app.string.simulationValidation'), PageIndex.SIMULATION_VALIDATION, + 'simulationValidation')) + } + .vertical(false) + .barMode(BarMode.Fixed) + .barWidth('90%') + .barHeight('5%') + .onChange((index: number) => { + this.currentIndex = index; + }) + .width('100%') + .height('100%') + .scrollable(true) + }.height('100%') + } +} diff --git a/UserAuthentication/entry/src/main/ets/pages/testPage.ets b/UserAuthentication/entry/src/main/ets/pages/testPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..c5aea7ee936c9fee96c9301dfadd4c8a9d9e0a83 --- /dev/null +++ b/UserAuthentication/entry/src/main/ets/pages/testPage.ets @@ -0,0 +1,747 @@ +/* + * Copyright (c) 2025 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. + */ + +/* 此页面仅用于测试,使用了同步语法,但实际不建议使用同步语法, + * 请在运行测试时将src/main/ets/entryability/EntryAbility.ets中的 + * windowStage.loadContent('pages/Index', ...) + * 修改为windowStage.loadContent('pages/testPage', ...) + */ + +import { BusinessError } from '@kit.BasicServicesKit'; +import { cryptoFramework } from '@kit.CryptoArchitectureKit'; +import { JSON } from '@kit.ArkTS'; +import { promptAction } from '@kit.ArkUI'; +import { userAuth } from '@kit.UserAuthenticationKit'; +import Logger from '../common/Logger'; + +// 用于获取资源字符串 +function resourceToString(resource: Resource): string { + let result: string = ''; + try { + result = getContext().resourceManager.getStringSync(resource); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`catch error. Code is ${err?.code}, message is ${err?.message}`); + } + if (result === '' || result === undefined) { + return 'undefined'; + } else { + return result; + } +} + +// 用于异步等待 +async function waiting(times: number): Promise { + // 返回一个 Promise,表示等待 times 毫秒 + return new Promise(resolve => setTimeout(resolve, times)); +} + +/* + * obtain-supported-authentication-capabilities.md + * 以查询设备是否支持认证可信等级≥ATL1的PIN认证功能为例 + */ +export function obtainingSupported() { + try { + // 查询认证能力是否支持 + userAuth.getAvailableStatus(userAuth.UserAuthType.PIN, userAuth.AuthTrustLevel.ATL1); + Logger.info('current auth trust level is supported'); + return true; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`current auth trust level is not supported. Code is ${err?.code}, message is ${err?.message}`); + return false; + } +} + +// 用于处理示例1,示例2,示例3和示例4的部分操作 +function handleAuthResult(userAuthInstance: userAuth.UserAuthInstance): Promise { + return new Promise((resolve, reject) => { + try { + // userAuthInstance.on异常抛出层 + userAuthInstance.on('result', { + onResult(result) { + try { + // onResult异常抛出层 + try { + // JSON.stringify异常抛出层 + Logger.info(`userAuthInstance callback result: ${JSON.stringify(result)}`); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`JSON stringify catch error. Code is ${err?.code}, message is ${err?.message}`); + reject(err); + } + userAuthInstance.off('result'); // 认证完成后取消订阅 + resolve(result.result); // 返回认证结果 + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`onResult catch error. Code: ${err?.code}, Message: ${err?.message}`); + reject(err); + } + } + }); + // 启动认证 + userAuthInstance.start(); + Logger.info('auth start success'); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`userAuthInstance.on catch error. Code: ${err?.code}, Message: ${err?.message}`); + reject(err); + } + }); +} + +/* + * start-authentication.md + * 示例1: + * 发起用户认证,采用认证可信等级≥ATL3的人脸 + 锁屏密码 + 指纹认证,获取认证结果 + * */ +export async function initiatingUserAuthentication1() { + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; // 设置认证参数 + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.PIN, userAuth.UserAuthType.FACE, userAuth.UserAuthType.FINGERPRINT], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + }; + // 配置认证界面 + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + const result = await handleAuthResult(userAuthInstance); + return result; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + return err.code; + } +} + +/* + * start-authentication.md + * 示例2: + * 发起用户认证,采用认证可信等级≥ATL3的人脸 + 锁屏密码 + 指纹认证 + 认证类型相关 + 复用设备解锁最大有效时长认证,获取认证结果 + * */ +export async function initiatingUserAuthentication2() { + // 设置认证参数 + let reuseUnlockResult: userAuth.ReuseUnlockResult = { + reuseMode: userAuth.ReuseMode.AUTH_TYPE_RELEVANT, + reuseDuration: userAuth.MAX_ALLOWABLE_REUSE_DURATION, + }; + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.PIN, userAuth.UserAuthType.FACE, userAuth.UserAuthType.FINGERPRINT], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + reuseUnlockResult: reuseUnlockResult, + }; + // 配置认证界面 + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + const result = await handleAuthResult(userAuthInstance); + return result; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + return err.code; + } +} + +/* + * start-authentication.md + * 示例3: + * 发起用户认证,采用认证可信等级≥ATL3的人脸 + 锁屏密码 + 指纹认证 + 任意应用认证类型相关 + 复用任意应用最大有效时长认证,获取认证结果: + * */ +export async function initiatingUserAuthentication3() { + // 设置认证参数 + let reuseUnlockResult: userAuth.ReuseUnlockResult = { + reuseMode: userAuth.ReuseMode.CALLER_IRRELEVANT_AUTH_TYPE_RELEVANT, + reuseDuration: userAuth.MAX_ALLOWABLE_REUSE_DURATION, + } + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.PIN, userAuth.UserAuthType.FACE, userAuth.UserAuthType.FINGERPRINT], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + reuseUnlockResult: reuseUnlockResult, + }; + // 配置认证界面 + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + const result = await handleAuthResult(userAuthInstance); + return result; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + return err.code; + } +} + +/* + * start-authentication.md + * 示例4: + * 以模应用方式进行用户身份认证 + * */ +export async function initiatingUserAuthentication4() { + // 设置认证参数 + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.PIN, userAuth.UserAuthType.FACE, userAuth.UserAuthType.FINGERPRINT], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + }; + // 配置认证界面 + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + uiContext: getContext(), + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + const result = await handleAuthResult(userAuthInstance); + return result; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + return err.code; + } +} + +function handleAuthResultAndCanceling(userAuthInstance: userAuth.UserAuthInstance): Promise { + return new Promise((resolve, reject) => { + try { + // userAuthInstance.on异常抛出层 + userAuthInstance.on('result', { + onResult(result) { + try { + // onResult异常抛出层 + try { + // JSON.stringify异常抛出层 + Logger.info(`userAuthInstance callback result: ${JSON.stringify(result)}`); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`JSON stringify catch error. Code is ${err?.code}, message is ${err?.message}`); + reject(err); + } + userAuthInstance.off('result'); // 认证完成后取消订阅 + resolve(result.result); // 返回认证结果 + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`onResult catch error. Code: ${err?.code}, Message: ${err?.message}`); + reject(err); + } + } + }); + // 启动认证 + userAuthInstance.start(); + Logger.info('auth start success'); + // 使用等待函数模拟等待用户认证 + let DELAY_TIME: number = 3000; // 停止3秒 + waiting(DELAY_TIME).then(() => { + // 延迟后取消认证 + userAuthInstance.cancel(); + Logger.info('auth cancel success'); + }); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`userAuthInstance.on catch error. Code: ${err?.code}, Message: ${err?.message}`); + reject(err); + } + }); +} + +/* + * cancel-authentication.md + * 发起认证可信等级≥ATL3的人脸+锁屏密码认证后,取消认证请求 + * */ +export async function cancelingUserAuthentication() { + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + // 设置认证参数 + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.PIN, userAuth.UserAuthType.FACE, userAuth.UserAuthType.FINGERPRINT], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + }; + // 配置认证界面 + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 使用 Promise 来监听认证结果 + const result = await handleAuthResultAndCanceling(userAuthInstance); + return result; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code as number}, message is ${err?.message}`); + if (err?.message === 'Authentication cancelled after waiting') { + err.code = userAuth.UserAuthResultCode.CANCELED; + } + return err.code; + } +} + +function handleCustomAuthResult(userAuthInstance: userAuth.UserAuthInstance): Promise { + return new Promise((resolve, reject) => { + try { + // userAuthInstance.on异常抛出层 + userAuthInstance.on('result', { + onResult(result) { + try { + // onResult异常抛出层 + try { + // JSON.stringify异常抛出层 + Logger.info(`userAuthInstance callback result: ${JSON.stringify(result)}`); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`JSON stringify catch error. Code is ${err?.code}, message is ${err?.message}`); + reject(err); + } + userAuthInstance.off('result'); // 认证完成后取消订阅 + if (result.result == userAuth.UserAuthResultCode.CANCELED_FROM_WIDGET || + result.result == userAuth.UserAuthResultCode.NOT_ENROLLED) { + // 请开发者自行完成拉起自定义认证界面的实现 + // 此处拉起类似于支付宝输入密码支付的界面 + promptAction.showToast({ + message: resourceToString($r('app.string.dialogOfCustom')), // 显示文本 + duration: 3000, // 显示时长 + bottom: 300 // 距离底部的距离 + }); + } + resolve(result.result); // 返回认证结果 + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`onResult catch error. Code: ${err?.code}, Message: ${err?.message}`); + reject(err); + } + } + }); + // 启动认证 + userAuthInstance.start(); + Logger.info('auth start success'); + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`userAuthInstance.on catch error. Code: ${err?.code}, Message: ${err?.message}`); + reject(err); + } + }); +} + +/* + * apply-custom-authentication.md + * 当前示例仅展示如何配置界面、选择切换到自定义认证界面,具体拉起的页面及对应页面的实现,请开发者自行实现 + * */ +async function applyingCustomAuthentication() { + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.FACE], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + }; // 配置认证界面需设置navigationButtonText + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + navigationButtonText: resourceToString($r('app.string.navigationButtonText')), + // 该参数仅在单指纹、单人脸场景下支持,该密码并非系统密码,而是应用自己设置的密码 + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + const result = await handleCustomAuthResult(userAuthInstance); + return result; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + return err.code; + } +} + +/* + * obtain-enrolled-state-capabilities.md + * 以查询用户人脸注册凭据的状态为例 + * */ +function obtainingEnrolledCredentialInformation() { + try { + let enrolledState = userAuth.getEnrolledState(userAuth.UserAuthType.PIN); + Logger.info(`get current enrolled state success, enrolledState: ${JSON.stringify(enrolledState)}`); + return enrolledState.credentialDigest; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`get current enrolled state failed, Code is ${err?.code}, message is ${err?.message}`); + return false; + } +} + +async function comprehensiveFeatures() { + let reuseUnlockResult: userAuth.ReuseUnlockResult = { + reuseMode: userAuth.ReuseMode.AUTH_TYPE_RELEVANT, + reuseDuration: userAuth.MAX_ALLOWABLE_REUSE_DURATION, + }; + try { + const rand = cryptoFramework.createRandom(); + const len: number = 16; + const randData: Uint8Array = rand?.generateRandomSync(len)?.data; + const authParam: userAuth.AuthParam = { + challenge: randData, + authType: [userAuth.UserAuthType.FACE], + authTrustLevel: userAuth.AuthTrustLevel.ATL3, + reuseUnlockResult: reuseUnlockResult + }; // 配置认证界面需设置navigationButtonText + const widgetParam: userAuth.WidgetParam = { + title: resourceToString($r('app.string.title')), + navigationButtonText: resourceToString($r('app.string.navigationButtonText')), + // 该参数仅在单指纹、单人脸场景下支持,该密码并非系统密码,而是应用自己设置的密码 + }; + // 获取认证对象 + const userAuthInstance = userAuth.getUserAuthInstance(authParam, widgetParam); + Logger.info('get userAuth instance success'); + // 订阅认证结果 + const result = await handleCustomAuthResult(userAuthInstance); + return result; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`); + return err.code; + } +} + +enum PageIndex { + QUERY_CAPABILITY = 0, // 能力查询页面的索引值 + EXAMPLE_TAB_1 = 1, // 实例1页面的索引值 + EXAMPLE_TAB_2 = 2, // 实例2页面的索引值 + EXAMPLE_TAB_3 = 3, // 实例3页面的索引值 + EXAMPLE_TAB_4 = 4, // 实例4页面的索引值 + CREDENTIAL_QUERIES = 5, // 能力查询页面的索引值 + SIMULATION_VALIDATION = 6 // 模拟认证页面的索引值 +} + +enum TextIdIndex { + WHETHER_SUPPORTED = 0, // 能力查询结果的索引值 + EXAMPLE_1 = 1, // 实例1结果的索引值 + EXAMPLE_2 = 2, // 实例2结果的索引值 + EXAMPLE_3 = 3, // 实例3结果的索引值 + EXAMPLE_4 = 4, // 实例4结果的索引值 + CANCEL = 5, // 实例5结果的索引值 + CUSTOMIZE = 6, // 实例6结果的索引值 + QUERY_CREDENTIALS = 7 // 能力查询结果的索引值 +}; + +@Entry +@Component +struct Index { + @State currentIndex: number = 0; // 最开始索引页面为第一页 + @State test: string[] = new Array(8).fill('wait'); // 一共9个后续测试需要判断的值所以设置数组大小为9 + @State credentialDigest: string = ''; + @State whetherSupport: Resource = $r('app.string.waitQuery'); + @State credentialValue: string = ''; + SPACE_GAP: number = 5; + + @Builder + tabBuilder(title: Resource, targetIndex: number, tabId: string) { + Column() { + Text(title) + .fontColor(this.currentIndex === targetIndex ? $r('app.color.tab_selected_color') : + $r('app.color.tab_not_selected_color')) + .fontSize($r('app.float.size_13')) + } + .width('100%') + .height($r('app.float.size_50')) + .justifyContent(FlexAlign.Center) + .id(tabId) + } + + build() { + Column() { + Tabs() { + TabContent() { + Column() { + Text($r('app.string.whetherSupported')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.query')) + .onClick(() => { + if (obtainingSupported()) { + this.test[TextIdIndex.WHETHER_SUPPORTED] = 'Pass'; + } else { + this.test[TextIdIndex.WHETHER_SUPPORTED] = 'Error'; + } + }) + Text(this.test[TextIdIndex.WHETHER_SUPPORTED]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + .id('text' + TextIdIndex.WHETHER_SUPPORTED) + } + }.tabBar(this.tabBuilder($r('app.string.queryCapability'), PageIndex.QUERY_CAPABILITY, 'queryCapability')) + + TabContent() { + Column() { + Text($r('app.string.descriptionContent1')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example1')) + .onClick(async () => { + let msg = initiatingUserAuthentication1(); + if (await msg === userAuth.UserAuthResultCode.SUCCESS) { + this.test[TextIdIndex.EXAMPLE_1] = 'Pass'; + } else { + this.test[TextIdIndex.EXAMPLE_1] = 'Error'; + } + }) + .id('button1') + Text(this.test[TextIdIndex.EXAMPLE_1]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + .id('text' + TextIdIndex.EXAMPLE_1) + Blank() + .height('5%') + Text($r('app.string.descriptionContent2')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example2')) + .onClick(async () => { + let msg = await initiatingUserAuthentication2(); + if (msg === userAuth.UserAuthResultCode.SUCCESS) { + this.test[TextIdIndex.EXAMPLE_2] = 'Pass'; + } else { + this.test[TextIdIndex.EXAMPLE_2] = 'Error'; + } + }) + .id('button2') + Text(this.test[TextIdIndex.EXAMPLE_2]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + .id('text' + TextIdIndex.EXAMPLE_2) + } + }.tabBar(this.tabBuilder($r('app.string.exampleTab1'), PageIndex.EXAMPLE_TAB_1, 'exampleTab1')) + + TabContent() { + Column() { + Text($r('app.string.descriptionContent3')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example3')) + .onClick(async () => { + let msg = initiatingUserAuthentication3(); + if (await msg === userAuth.UserAuthResultCode.SUCCESS) { + this.test[TextIdIndex.EXAMPLE_3] = 'Pass'; + } else { + this.test[TextIdIndex.EXAMPLE_3] = 'Error'; + } + }) + .id('button3') + Text(this.test[TextIdIndex.EXAMPLE_3]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + .id('text' + TextIdIndex.EXAMPLE_3) + Blank() + .height('5%') + Text($r('app.string.descriptionContent4')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example4')) + .onClick(async () => { + let msg = await initiatingUserAuthentication4(); + if (msg === userAuth.UserAuthResultCode.SUCCESS) { + this.test[TextIdIndex.EXAMPLE_4] = 'Pass'; + } else { + this.test[TextIdIndex.EXAMPLE_4] = 'Error'; + } + }) + .id('button4') + Text(this.test[TextIdIndex.EXAMPLE_4]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + .id('text' + TextIdIndex.EXAMPLE_4) + } + }.tabBar(this.tabBuilder($r('app.string.exampleTab2'), PageIndex.EXAMPLE_TAB_2, 'exampleTab2')) + + TabContent() { + Column() { + Text($r('app.string.descriptionContent5')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example5')) + .onClick(async () => { + let msg = cancelingUserAuthentication(); + if (await msg === userAuth.UserAuthResultCode.CANCELED) { + this.test[TextIdIndex.CANCEL] = 'Pass'; + } else { + this.test[TextIdIndex.CANCEL] = 'Error'; + } + }) + Text(this.test[TextIdIndex.CANCEL]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + .id('text' + TextIdIndex.CANCEL) + } + }.tabBar(this.tabBuilder($r('app.string.exampleTab3'), PageIndex.EXAMPLE_TAB_3, 'exampleTab3')) + + TabContent() { + Column() { + Text($r('app.string.descriptionContent6')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.example6')) + .onClick(async () => { + let msg = await applyingCustomAuthentication(); + if (msg == userAuth.UserAuthResultCode.CANCELED_FROM_WIDGET || + msg == userAuth.UserAuthResultCode.NOT_ENROLLED) { + this.test[TextIdIndex.CUSTOMIZE] = 'Pass'; + } else { + this.test[TextIdIndex.CUSTOMIZE] = 'Error'; + } + }) + Text(this.test[TextIdIndex.CUSTOMIZE]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + .id('text' + TextIdIndex.CUSTOMIZE) + } + }.tabBar(this.tabBuilder($r('app.string.exampleTab4'), PageIndex.EXAMPLE_TAB_4, 'exampleTab4')) + + TabContent() { + Column() { + Text($r('app.string.queryCredentials')) + .width('70%') + .textAlign(TextAlign.Center) + .fontSize($r('app.float.size_20')) + Button($r('app.string.query')) + .onClick(() => { + if (obtainingEnrolledCredentialInformation()) { + this.test[TextIdIndex.QUERY_CREDENTIALS] = 'Pass'; + this.credentialDigest = 'credentialDigest is ' + obtainingEnrolledCredentialInformation(); + } else { + this.test[TextIdIndex.QUERY_CREDENTIALS] = 'Error'; + } + }) + Text(this.test[TextIdIndex.QUERY_CREDENTIALS]) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + .id('text' + TextIdIndex.QUERY_CREDENTIALS) + Text(this.credentialDigest) + .fontSize($r('app.float.size_40')) + .textAlign(TextAlign.Center) + } + }.tabBar(this.tabBuilder($r('app.string.credentialQueries'), PageIndex.CREDENTIAL_QUERIES, 'credentialQueries')) + + TabContent() { + Column() { + Button($r('app.string.refreshSupported')) + .onClick(() => { + if (obtainingSupported()) { + this.whetherSupport = $r('app.string.queryPass'); + } else { + this.whetherSupport = $r('app.string.queryError'); + } + }) + .id('button1') + .margin($r('app.float.size_10')) + Text(this.whetherSupport) + .fontSize($r('app.float.size_20')) + .textAlign(TextAlign.Center) + Blank() + .height('10%') + Row({ space: this.SPACE_GAP }) { + Button($r('app.string.customLogin')) + .onClick(() => { + comprehensiveFeatures(); + }) + .id('button2') + Button($r('app.string.simpleLogin')) + .onClick(() => { + initiatingUserAuthentication2(); + }) + .id('button3') + } + .margin($r('app.float.size_10')) + + Blank() + .height('10%') + Text($r('app.string.credentialDescription')) + .width('80%') + .fontSize($r('app.float.size_20')) + .textAlign(TextAlign.Center) + Button($r('app.string.refreshTheCredentials')) + .onClick(() => { + try { + let enrolledState = userAuth.getEnrolledState(userAuth.UserAuthType.PIN); + Logger.info(`get current enrolled state success, enrolledState: ${JSON.stringify(enrolledState)}`); + this.credentialValue = enrolledState.credentialDigest + ''; + } catch (error) { + const err: BusinessError = error as BusinessError; + Logger.error(`get current enrolled state failed, Code is ${err?.code}, message is ${err?.message}`); + } + }) + .id('button4') + .margin($r('app.float.size_10')) + Text(this.credentialValue) + .fontSize($r('app.float.size_20')) + .textAlign(TextAlign.Center) + } + } + .tabBar(this.tabBuilder($r('app.string.simulationValidation'), PageIndex.SIMULATION_VALIDATION, + 'simulationValidation')) + } + .vertical(false) + .barMode(BarMode.Fixed) + .barWidth('90%') + .barHeight('5%') + .onChange((index: number) => { + this.currentIndex = index; + }) + .width('100%') + .height('100%') + .scrollable(true) + }.height('100%') + } +} diff --git a/UserAuthentication/entry/src/main/module.json5 b/UserAuthentication/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2f0beaf027106d0be03eab937781b86293cbc576 --- /dev/null +++ b/UserAuthentication/entry/src/main/module.json5 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025 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. + */ + +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "default", + "tablet", + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "definePermissions": [ + { + "name": "ohos.access.permission.SECURITY_RESTRICTIONS", + "grantMode": "system_grant", + "availableLevel": "normal", + "provisionEnable": true, + "distributedSceneEnable": false, + "label": "$string:EntryAbility_label" + } + ], + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ], + "permissions": [ + "ohos.access.permission.SECURITY_RESTRICTIONS" + ] + } + ], + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.ACCESS_BIOMETRIC", + "reason": "$string:reason", + "usedScene": { + "when": "always" + } + }, + { + "name": "ohos.access.permission.SECURITY_RESTRICTIONS" + } + ] + } +} \ No newline at end of file diff --git a/UserAuthentication/entry/src/main/resources/base/element/color.json b/UserAuthentication/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3a54e33c8f20710c81384aa0de054afa2377f49d --- /dev/null +++ b/UserAuthentication/entry/src/main/resources/base/element/color.json @@ -0,0 +1,16 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + }, + { + "name": "tab_selected_color", + "value": "#1698CE" + }, + { + "name": "tab_not_selected_color", + "value": "#6B6B6B" + } + ] +} \ No newline at end of file diff --git a/UserAuthentication/entry/src/main/resources/base/element/float.json b/UserAuthentication/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..f10bacdc37bbae4ebe5ba9f7b0f12d500d920a30 --- /dev/null +++ b/UserAuthentication/entry/src/main/resources/base/element/float.json @@ -0,0 +1,28 @@ +{ + "float": [ + { + "name": "size_5", + "value": "10" + }, + { + "name": "size_10", + "value": "10" + }, + { + "name": "size_13", + "value": "13" + }, + { + "name": "size_20", + "value": "20" + }, + { + "name": "size_40", + "value": "40" + }, + { + "name": "size_50", + "value": "50" + } + ] +} \ No newline at end of file diff --git a/UserAuthentication/entry/src/main/resources/base/element/string.json b/UserAuthentication/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..db59ec44cf8c34b854e930f3d5176fea82af120f --- /dev/null +++ b/UserAuthentication/entry/src/main/resources/base/element/string.json @@ -0,0 +1,156 @@ +{ + "string": [ + { + "name": "app_name", + "value": "User Authentication" + }, + { + "name": "module_desc", + "value": "User Authentication" + }, + { + "name": "EntryAbility_desc", + "value": "User Authentication" + }, + { + "name": "EntryAbility_label", + "value": "User Authentication" + }, + { + "name": "reason", + "value": "Used to verify user permissions" + }, + { + "name": "whetherSupported", + "value": "Whether the certification capability is supported" + }, + { + "name": "query", + "value": "query" + }, + { + "name": "descriptionContent1", + "value": "Use the authentication credibility level ≥ATL3 face + lock screen password + fingerprint authentication to obtain the authentication result" + }, + { + "name": "example1", + "value": "Example 1 of initiating user authentication" + }, + { + "name": "descriptionContent2", + "value": "Use the authentication level ≥ATL3 face + lock screen password + fingerprint authentication + authentication type + multiplexing device unlocking maximum validity period authentication to obtain the authentication result" + }, + { + "name": "example2", + "value": "Example 2 for initiating user authentication" + }, + { + "name": "descriptionContent4", + "value": "Perform user identity authentication in the form of a modular application" + }, + { + "name": "example3", + "value": "Example 3 for initiating user authentication" + }, + { + "name": "descriptionContent3", + "value": "Initiate user authentication and use the authentication credibility level ≥ATL3 face + lock screen password + fingerprint authentication + any application authentication type + reuse the maximum validity duration authentication of any application to obtain the authentication result" + }, + { + "name": "example4", + "value": "Example 3 for initiating user authentication" + }, + { + "name": "queryCredentials", + "value": "Query the status of the user's registration credentials, and when the authentication method is disabled and then enabled, the query credentials will change again" + }, + { + "name": "customLogin", + "value": "Impersonate a custom login" + }, + { + "name": "simpleLogin", + "value": "User authentication login" + }, + { + "name": "refreshTheCredentials", + "value": "Refresh the credentials" + }, + { + "name": "credentialDescription", + "value": "After the authentication method is disabled and then enabled, click \"Refresh Credential Information\" to change the credential code" + }, + { + "name": "refreshSupported", + "value": "Refreshes the support status of the certification competency" + }, + { + "name": "queryCapability", + "value": "Query capability" + }, + { + "name": "exampleTab1", + "value": "Example 1、2" + }, + { + "name": "exampleTab2", + "value": "Example 3、4" + }, + { + "name": "exampleTab3", + "value": "Cancel" + }, + { + "name": "exampleTab4", + "value": "Customize" + }, + { + "name": "credentialQueries", + "value": "Query credentials" + }, + { + "name": "waitQuery", + "value": "Wait for the query" + }, + { + "name": "queryPass", + "value": "Support authentication capabilities" + }, + { + "name": "queryError", + "value": "No, please go to Settings -> Biometrics & Passwords to set the authentication capability" + }, + { + "name": "simulationValidation", + "value": "Simulation validation" + }, + { + "name": "title", + "value": "Please verify your identity" + }, + { + "name": "navigationButtonText", + "value": "Use app passwords" + }, + { + "name": "dialogOfCustom", + "value": "Here pull up the application password entry interface" + }, + { + "name": "descriptionContent5", + "value": "After initiating the authentication of the face + lock screen password + fingerprint authentication with the authentication level ≥ATL3), cancel the authentication request" + }, + { + "name": "example5", + "value": "Cancellation of certification during the certification process" + }, + { + "name": "descriptionContent6", + "value": "Toggle custom authentication" + }, + { + "name": "example6", + "value": "Pull up the certification" + } + ] +} \ No newline at end of file diff --git a/UserAuthentication/entry/src/main/resources/base/media/app_icon.png b/UserAuthentication/entry/src/main/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/UserAuthentication/entry/src/main/resources/base/media/app_icon.png differ diff --git a/UserAuthentication/entry/src/main/resources/base/media/background.png b/UserAuthentication/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..f939c9fa8cc8914832e602198745f592a0dfa34d Binary files /dev/null and b/UserAuthentication/entry/src/main/resources/base/media/background.png differ diff --git a/UserAuthentication/entry/src/main/resources/base/media/foreground.png b/UserAuthentication/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902 Binary files /dev/null and b/UserAuthentication/entry/src/main/resources/base/media/foreground.png differ diff --git a/UserAuthentication/entry/src/main/resources/base/media/layered_image.json b/UserAuthentication/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/UserAuthentication/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/UserAuthentication/entry/src/main/resources/base/media/startIcon.png b/UserAuthentication/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/UserAuthentication/entry/src/main/resources/base/media/startIcon.png differ diff --git a/UserAuthentication/entry/src/main/resources/base/profile/backup_config.json b/UserAuthentication/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/UserAuthentication/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/UserAuthentication/entry/src/main/resources/base/profile/main_pages.json b/UserAuthentication/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..5182871ad89025863b21914fd92762e44c56a4f6 --- /dev/null +++ b/UserAuthentication/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,6 @@ +{ + "src": [ + "pages/testPage", + "pages/Index" + ] +} diff --git a/UserAuthentication/entry/src/main/resources/zh_CN/element/string.json b/UserAuthentication/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..0e5a51139df0a11a3a3f742b2b7294b2430ea84a --- /dev/null +++ b/UserAuthentication/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,156 @@ +{ + "string": [ + { + "name": "app_name", + "value": "User Authentication" + }, + { + "name": "module_desc", + "value": "UserAuthentication工程化示例代码" + }, + { + "name": "EntryAbility_desc", + "value": "UserAuthentication工程化示例代码" + }, + { + "name": "EntryAbility_label", + "value": "User Authentication" + }, + { + "name": "reason", + "value": "用于验证用户权限" + }, + { + "name": "whetherSupported", + "value": "认证能力是否支持" + }, + { + "name": "query", + "value": "查询" + }, + { + "name": "descriptionContent1", + "value": "采用认证可信等级≥ATL3的人脸 + 锁屏密码 + 指纹认证,获取认证结果" + }, + { + "name": "example1", + "value": "发起用户认证示例1" + }, + { + "name": "descriptionContent2", + "value": "采用认证可信等级≥ATL3的人脸 + 锁屏密码 + 指纹认证 + 认证类型相关 + 复用设备解锁最大有效时长认证,获取认证结果" + }, + { + "name": "example2", + "value": "发起用户认证示例2" + }, + { + "name": "descriptionContent3", + "value": "发起用户认证,采用认证可信等级≥ATL3的人脸 + 锁屏密码 + 指纹认证 + 任意应用认证类型相关 + 复用任意应用最大有效时长认证,获取认证结果" + }, + { + "name": "example3", + "value": "发起用户认证示例3" + }, + { + "name": "descriptionContent4", + "value": "以模应用方式进行用户身份认证" + }, + { + "name": "example4", + "value": "发起用户认证示例4" + }, + { + "name": "queryCredentials", + "value": "查询用户注册凭据的状态,当认证方式被关闭再开启后,再次查询凭据会变化" + }, + { + "name": "customLogin", + "value": "模拟自定义登录" + }, + { + "name": "simpleLogin", + "value": "用户验证登录" + }, + { + "name": "refreshTheCredentials", + "value": "刷新凭证信息" + }, + { + "name": "credentialDescription", + "value": "当认证方式被关闭再开启后,点击“刷新凭证信息”下方凭据码会发生变化" + }, + { + "name": "refreshSupported", + "value": "刷新认证能力支持状态" + }, + { + "name": "queryCapability", + "value": "查询能力" + }, + { + "name": "exampleTab1", + "value": "实例1、2" + }, + { + "name": "exampleTab2", + "value": "实例3、4" + }, + { + "name": "exampleTab3", + "value": "取消实例" + }, + { + "name": "exampleTab4", + "value": "自定义" + }, + { + "name": "credentialQueries", + "value": "查询凭据" + }, + { + "name": "waitQuery", + "value": "等待查询" + }, + { + "name": "queryPass", + "value": "支持认证能力" + }, + { + "name": "queryError", + "value": "不支持,请前往 设置->生物识别和密码 设置认证能力" + }, + { + "name": "simulationValidation", + "value": "模拟验证" + }, + { + "name": "title", + "value": "请进行身份认证" + }, + { + "name": "navigationButtonText", + "value": "使用应用密码" + }, + { + "name": "dialogOfCustom", + "value": "此处拉起应用密码输入界面" + }, + { + "name": "descriptionContent5", + "value": "发起认证可信等级≥ATL3的人脸 + 锁屏密码 + 指纹认证认证后,取消认证请求" + }, + { + "name": "example5", + "value": "认证过程中取消认证" + }, + { + "name": "descriptionContent6", + "value": "切换自定义认证" + }, + { + "name": "example6", + "value": "拉起认证" + } + ] +} \ No newline at end of file diff --git a/UserAuthentication/entry/src/ohosTest/ets/test/Index.test.ets b/UserAuthentication/entry/src/ohosTest/ets/test/Index.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..9f609ca1a8789e51a1f1aa0404c9759c9541d6a9 --- /dev/null +++ b/UserAuthentication/entry/src/ohosTest/ets/test/Index.test.ets @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2025 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 { describe, expect, it } from '@ohos/hypium'; +import { abilityDelegatorRegistry, Component, Driver, ON } from '@kit.TestKit'; +import { UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; + +const TAG = '[Sample_UserAuthentication]'; +const DOMAIN = 0xF811; +const BUNDLE = 'UserAuthentication_'; +const DELAY_TIME_500MS = 500; +const DELAY_TIME_1S = 1000; +const DELAY_TIME_5S = 5000; +const DELAY_TIME_10S = 10000; +const delegator = abilityDelegatorRegistry.getAbilityDelegator(); +const bundleName = abilityDelegatorRegistry.getArguments().bundleName; + +export default function IndexTest() { + describe('IndexTest', () => { + /** + * @tc.number IAM_StartAbility_001 + * @tc.name IAM_StartAbility_001 + * @tc.desc 启动Ability + */ + it(BUNDLE + 'StartAbility_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'StartAbility_001,begin'); + //start tested ability + const want: Want = { + bundleName: bundleName, + abilityName: 'EntryAbility' + }; + await delegator.startAbility(want); + let driver: Driver = Driver.create(); + await driver.delayMs(DELAY_TIME_500MS); + // check top display ability + const ability: UIAbility = await delegator.getCurrentTopAbility(); + hilog.info(DOMAIN, TAG, BUNDLE + 'StartAbility_001,get top ability'); + expect(ability.context.abilityInfo.name).assertEqual('EntryAbility'); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'StartAbility_001,end'); + }) + + /** + * @tc.number IAM_UiTest_001 + * @tc.name IAM_UiTest_001 + * @tc.desc 测试查询能力功能点 + */ + it(BUNDLE + 'UiTest_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_001,begin'); + let driver: Driver = Driver.create(); + let tabBar: Component = await driver.findComponent(ON.id('queryCapability')); + await tabBar.click(); + let button: Component = await driver.findComponent(ON.type('Button')); + await button.click(); + await driver.delayMs(DELAY_TIME_1S); + let textComponent: Component = await driver.findComponent(ON.id('text0')); + let text = await textComponent.getText(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_001,test:' + text); + expect('Pass').assertEqual(text); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_001,end'); + }) + + /** + * @tc.number IAM_UiTest_002 + * @tc.name IAM_UiTest_002 + * @tc.desc 测试实例1功能点 + */ + it(BUNDLE + 'UiTest_002', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_002,begin'); + let driver: Driver = Driver.create(); + let tabBar: Component = await driver.findComponent(ON.id('exampleTab1')); + await tabBar.click(); + await driver.delayMs(DELAY_TIME_500MS); + let button: Component = await driver.findComponent(ON.type('Button').id('button1')); + await button.click(); + await driver.delayMs(DELAY_TIME_10S); + let textComponent: Component = await driver.findComponent(ON.id('text1')); + let text = await textComponent.getText(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_002,test:' + text); + expect('Pass').assertEqual(text); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_002,end'); + }) + + /** + * @tc.number IAM_UiTest_003 + * @tc.name IAM_UiTest_003 + * @tc.desc 测试实例2功能点 + */ + it(BUNDLE + 'UiTest_003', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_003,begin'); + let driver: Driver = Driver.create(); + let button: Component = await driver.findComponent(ON.type('Button').id('button2')); + await button.click(); + await driver.delayMs(DELAY_TIME_10S); + let textComponent: Component = await driver.findComponent(ON.id('text2')); + let text = await textComponent.getText(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_003,test:' + text); + expect('Pass').assertEqual(text); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_003,end'); + }) + + /** + * @tc.number IAM_UiTest_004 + * @tc.name IAM_UiTest_004 + * @tc.desc 测试实例3功能点 + */ + it(BUNDLE + 'UiTest_004', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_003,begin'); + let driver: Driver = Driver.create(); + let tabBar: Component = await driver.findComponent(ON.id('exampleTab2')); + await tabBar.click(); + await driver.delayMs(DELAY_TIME_500MS); + let button: Component = await driver.findComponent(ON.type('Button').id('button3')); + await button.click(); + await driver.delayMs(DELAY_TIME_10S); + let textComponent: Component = await driver.findComponent(ON.id('text3')); + let text = await textComponent.getText(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_003,test:' + text); + expect('Pass').assertEqual(text); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_003,end'); + }) + /** + * @tc.number IAM_UiTest_005 + * @tc.name IAM_UiTest_005 + * @tc.desc 测试实例4功能点 + */ + it(BUNDLE + 'UiTest_005', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_003,begin'); + let driver: Driver = Driver.create(); + let button: Component = await driver.findComponent(ON.type('Button').id('button4')); + await button.click(); + await driver.delayMs(DELAY_TIME_10S); + let textComponent: Component = await driver.findComponent(ON.id('text4')); + let text = await textComponent.getText(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_003,test:' + text); + expect('Pass').assertEqual(text); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_003,end'); + }) + + /** + * @tc.number IAM_UiTest_006 + * @tc.name IAM_UiTest_006 + * @tc.desc 测试"自定义"功能点 + */ + it(BUNDLE + 'UiTest_006', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_004,begin'); + let driver: Driver = Driver.create(); + let tabBar: Component = await driver.findComponent(ON.id('exampleTab4')); + await tabBar.click(); + await driver.delayMs(DELAY_TIME_500MS); + let button: Component = await driver.findComponent(ON.type('Button')); + await button.click(); + await driver.delayMs(DELAY_TIME_10S); + let textComponent: Component = await driver.findComponent(ON.id('text6')); + let text = await textComponent.getText(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_004,test:' + text); + expect('Pass').assertEqual(text); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_004,end'); + }) + + /** + * @tc.number IAM_UiTest_007 + * @tc.name IAM_UiTest_007 + * @tc.desc 测试"取消实例"功能点 + */ + it(BUNDLE + 'UiTest_007', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_005,begin'); + let driver: Driver = Driver.create(); + let tabBar: Component = await driver.findComponent(ON.id('exampleTab3')); + await tabBar.click(); + await driver.delayMs(DELAY_TIME_500MS); + let button: Component = await driver.findComponent(ON.type('Button')); + await button.click(); + await driver.delayMs(DELAY_TIME_5S); + let textComponent: Component = await driver.findComponent(ON.id('text5')); + let text = await textComponent.getText(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_005,test:' + text); + expect('Pass').assertEqual(text); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_005,end'); + }) + + /** + * @tc.number IAM_UiTest_008 + * @tc.name IAM_UiTest_008 + * @tc.desc 测试查询凭据功能点 + */ + it(BUNDLE + 'UiTest_008', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_006,begin'); + let driver: Driver = Driver.create(); + let tabBar: Component = await driver.findComponent(ON.id('credentialQueries')); + await tabBar.click(); + await driver.delayMs(DELAY_TIME_500MS); + let button: Component = await driver.findComponent(ON.type('Button')); + await button.click(); + await driver.delayMs(DELAY_TIME_1S); + let textComponent: Component = await driver.findComponent(ON.id('text7')); + let text = await textComponent.getText(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_006,test:' + text); + expect('Pass').assertEqual(text); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_006,end'); + }) + /** + * @tc.number IAM_UiTest_009 + * @tc.name IAM_UiTest_009 + * @tc.desc 测试模拟验证按钮功能 + */ + it(BUNDLE + 'UiTest_009', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_007,begin'); + let driver: Driver = Driver.create(); + let tabBar: Component = await driver.findComponent(ON.id('simulationValidation')); + await tabBar.click(); + await driver.delayMs(DELAY_TIME_500MS); + let button1: Component = await driver.findComponent(ON.id('button1')); + await button1.click(); + await driver.delayMs(DELAY_TIME_1S); + let button2: Component = await driver.findComponent(ON.id('button2')); + await button2.click(); + await driver.delayMs(DELAY_TIME_5S); + let button3: Component = await driver.findComponent(ON.id('button3')); + await button3.click(); + await driver.delayMs(DELAY_TIME_5S); + let button4: Component = await driver.findComponent(ON.id('button4')); + await button4.click(); + await driver.delayMs(DELAY_TIME_1S); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'UiTest_007,end'); + }) + }) +} diff --git a/UserAuthentication/entry/src/ohosTest/ets/test/List.test.ets b/UserAuthentication/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..86a02e4b0ded5515d6c50a4639184852f0abed07 --- /dev/null +++ b/UserAuthentication/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 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 IndexTest from './Index.test'; + +export default function testsuite() { + IndexTest(); +} \ No newline at end of file diff --git a/UserAuthentication/entry/src/ohosTest/module.json5 b/UserAuthentication/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c3fd9dda3040d888d9d8b0b62bcb5d3b6fbeb614 --- /dev/null +++ b/UserAuthentication/entry/src/ohosTest/module.json5 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 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. + */ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/UserAuthentication/hvigor/hvigor-config.json5 b/UserAuthentication/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..8fe2d29d5ae9a871a25cc0948fcb01235c927a26 --- /dev/null +++ b/UserAuthentication/hvigor/hvigor-config.json5 @@ -0,0 +1,37 @@ +/* +* Copyright (c) 2025 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. +*/ + +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/UserAuthentication/hvigorfile.ts b/UserAuthentication/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..1307c03303328d961439d1f6983b09e728b5e8ce --- /dev/null +++ b/UserAuthentication/hvigorfile.ts @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2025 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 { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/UserAuthentication/oh-package.json5 b/UserAuthentication/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..b2df9895b2dd058847cf588052f3268db9c2ecba --- /dev/null +++ b/UserAuthentication/oh-package.json5 @@ -0,0 +1,25 @@ +/* +* Copyright (c) 2025 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. +*/ + +{ + "modelVersion": "5.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.18", + "@ohos/hamock": "1.0.0" + } +} diff --git a/UserAuthentication/ohosTest.md b/UserAuthentication/ohosTest.md new file mode 100644 index 0000000000000000000000000000000000000000..6845d66f50a3b41a4d95eb4ac4f464964e84e00a --- /dev/null +++ b/UserAuthentication/ohosTest.md @@ -0,0 +1,16 @@ +# 测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期显示 | 是否自动 | 测试结果 | +| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -------- | -------- | +| 启动应用 | 设备正常运行 | 1.开启开发板
2.在运行测试前需要修改启动页面为`testPage`,具体修改方式为:将`src/main/ets/entryability/EntryAbility.ets`中的`windowStage.loadContent('pages/Index', ...)`修改为`windowStage.loadContent('pages/testPage', ...)`
3.编译hap包并将hap包及环境烧录进开发板,运行测试用例
| 成功拉起应用。 | 是 | 验证通过 | +| 查询认证能力 | 开发板需提前设置用户认证信息,即密码认证,人脸认证或指纹认证 | | 1.按钮下方文本从Wait变为Pass | 是 | 验证通过 | +| 测试实例1 | 开发板需提前设置用户认证信息,即密码认证,人脸认证或指纹认证 | 1.输入用户认证信息 | 1.上方tab切换到“实例1、2”
2.自动点击第一个按钮后拉起用户认证页面,在十秒内输入用户认证信息后,第一个按钮下方的文本从Wait变为Pass | 否 | 验证通过 | +| 测试实例2 | 开发板需提前设置用户认证信息,即密码认证,人脸认证或指纹认证 | 1.输入用户认证信息 | 1.自动点击第二个按钮后拉起用户认证页面,在十秒内输入用户认证信息后,第一个按钮下方的文本从Wait变为Pass | 否 | 验证通过 | +| 测试实例3 | 开发板需提前设置用户认证信息,即密码认证,人脸认证或指纹认证 | 1.输入用户认证信息 | 1.上方tab切换到“实例3、4”
2.自动点击第一个按钮后拉起用户认证页面,在十秒内输入用户认证信息后,第一个按钮下方的文本从Wait变为Pass | 否 | 验证通过 | +| 测试实例4 | 开发板需提前设置用户认证信息,即密码认证,人脸认证或指纹认证 | 1.输入用户认证信息 | 1.自动点击第二个按钮后拉起用户认证页面,在十秒内输入用户认证信息后,第一个按钮下方的文本从Wait变为Pass | 否 | 验证通过 | +| 切换自定义认证 | 开发板需提前设置用户认证信息,即密码认证,人脸认证或指纹认证 | 1.在用户认证界面点击“使用应用密码”按钮 | 1.上方tab切换到“自定义”
2.自动点击按钮后拉起用户认证页面,在用户认证界面点击“使用应用密码”按钮后,按钮下方的文本从Wait变为Pass
3.如果测试设备不支持人脸认证和指纹认证,那么自动点击按钮后下方Wait变为Pass | 否 | 验证通过 | +| 认证过程中取消认证 | 开发板需提前设置用户认证信息,即密码认证,人脸认证或指纹认证 | | 1.上方tab切换到“取消实例”
2.自动点击按钮后拉起用户认证页面,3秒后用户认证页面被关闭,按钮下方的文本从Wait变为Pass | 是 | 验证通过 | +| 查询用户注册凭据的状态 | 开发板需提前设置用户认证信息,即密码认证,人脸认证或指纹认证 | | 1.上方tab切换到“查询凭据”
2.自动点击按钮后按钮下方的文本从Wait变为Pass,并在Pass下方输出凭据号 | 是 | 验证通过 | +| 模拟场景 | 开发板需提前设置用户认证信息,即密码认证,人脸认证或指纹认证 | 1.输入用户认证信息
2.在用户认证界面点击“使用应用密码”按钮 | 1.上方tab切换到“模拟验证”
2.自动点击第一个按钮后按钮下方的文本输出“支持认证能力”
3.自动点击第二个按钮后拉起用户认证界面
4.自动点击第三个按钮,拉起用户认证界面
5.自动点击第四个按钮,在第四个按钮下方输出凭据号 | 否 | 验证通过 | \ No newline at end of file diff --git a/UserAuthentication/screenshots/example1.jpeg b/UserAuthentication/screenshots/example1.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c48e7742daecb5e0c250af72090dc5f0da72a837 Binary files /dev/null and b/UserAuthentication/screenshots/example1.jpeg differ diff --git a/UserAuthentication/screenshots/mockLogin.jpeg b/UserAuthentication/screenshots/mockLogin.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..4807ed2742e0cd4ba26178e3e3ed3e504d1d9d2c Binary files /dev/null and b/UserAuthentication/screenshots/mockLogin.jpeg differ diff --git a/UserAuthentication/screenshots/query.jpeg b/UserAuthentication/screenshots/query.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..edc48d3264f54c4df0cbc087e9b23263c525f590 Binary files /dev/null and b/UserAuthentication/screenshots/query.jpeg differ