diff --git a/debian/dde-session-shell.install b/debian/dde-session-shell.install index 61b7dc97..a1609f1e 100644 --- a/debian/dde-session-shell.install +++ b/debian/dde-session-shell.install @@ -2,7 +2,8 @@ etc usr/bin usr/share/applications usr/share/deepin-debug-config -usr/share/dsg +usr/share/dsg/configs/org.deepin.dde.lightdm-deepin-greeter/org.deepin.dde.lightdm-deepin-greeter.json +usr/share/dsg/configs/org.deepin.dde.lock/org.deepin.dde.lock.json usr/share/glib-2.0 usr/share/xgreeters usr/share/dbus-1 diff --git a/docs/development-guide.md b/docs/development-guide.md index a25a41a0..49b4ad9e 100644 --- a/docs/development-guide.md +++ b/docs/development-guide.md @@ -1,26 +1,68 @@ +# 版本变更记录 + +| 版本 | 修订说明 | 修订人 | 修订时间 | +|:------|:---------|:-------|:-----------| +| V1.0 | 首次提交 | 殷杰 | 2022-05-17 | +| V1.1 | 1. 认证插件版本号更新为 2.0.0
2. 增加基于 JSON 数据的双向通讯方式
3. 认证插件可以自定义登录器的显示内容 | 殷杰 | 2022-05-17 | +| V1.2 | 补充缺失的接口函数和消息协议,优化文档格式和内容 | 殷杰 | 2023-07-22 | + # 概述 -本文档是针对 UOS 登录器插件给出开发指南,目的是为了让开发人员了解如何在 UOS 登录器上增加一种自定义认证方式,对插件接口做了详细说明以及实战练习。 +本文档是针对 UOS 登录器插件的开发指南,旨在指导开发人员如何在 UOS 登录器上增加自定义认证方式。插件必须基于 Qt 框架进行开发。 + +**适用读者:** +- 产品经理 +- 设计人员 +- 开发人员 +- 测试人员 -# 认证插件可以做什么? +# 认证插件功能介绍 -UOS 提供了丰富了登录方式:密码、UKey、指纹、人脸、虹膜等,这些登录方式是在登录器内部处理的,想要实现其它的登录方式(比如二维码)又不想与 UOS 的登录器有过多的耦合,那么就可以使用认证插件的方式来实现。只需根据认证插件的接口来开发一个动态库,即可在登录器上增加一种自定义登录方式。 +UOS 系统内置了多种登录认证方式: +- 密码认证 +- UKey 认证 +- 指纹识别 +- 人脸识别 +- 虹膜识别 + +这些认证方式都是在登录器内部实现的。如果需要添加新的认证方式(如二维码登录),又不想与 UOS 登录器代码产生强耦合,可以通过开发认证插件来实现。只需开发一个符合接口规范的动态库,即可为登录器增加新的认证方式,无需修改 UOS 登录器的源代码。 ## 认证流程 -登录器在开机/注销/锁屏后会加载认证插件,并发起认证,然后等待插件返回认证结果。用户鉴权通过后,插件发送认证成功给登录器,后面的如何进入系统由登录器来处理。在整个过程中,插件只需要关注自身的认证逻辑,把认证结果发送到登录器后插件的工作就完成了。 +认证插件的工作流程如下: + +1. **插件加载**:登录器在开机/注销/锁屏时加载认证插件 +2. **等待输入**:插件等待用户提供认证信息 +3. **验证处理**:插件验证用户提供的信息 +4. **结果返回**:将认证结果返回给登录器 +5. **完成退出**:登录器处理后续的系统登录流程 + +插件只需专注于认证逻辑,无需关心系统登录的具体实现。 ```plantuml @startuml 认证流程时序图 +skinparam handwritten false +skinparam monochrome true +skinparam shadowing false +skinparam defaultFontName sans-serif +skinparam sequenceMessageAlign center + title 认证流程时序图 + +participant 用户 +participant 登录器 +participant 认证插件 + autonumber 用户 -> 登录器: 开机、注销、锁屏 登录器 -> 认证插件: 加载认证插件 +登录器 -> 认证插件: 开始认证 认证插件 -> 认证插件: 等待用户输入 -用户 -> 认证插件: 提交鉴权信息(输入密码、扫码、生物信息等) -认证插件 -> 认证插件: 校验用户提交信息(本地校验、远程服务器校验等) +用户 -> 认证插件: 提交鉴权信息\n(密码/扫码/生物信息等) +认证插件 -> 认证插件: 校验用户信息\n(本地/远程验证) 认证插件 --> 登录器: 鉴权成功 登录器 -> 登录器: 进入系统 + @enduml ``` @@ -28,136 +70,238 @@ autonumber 登录器:即 UOS 系统中的登录/锁屏软件。 -登录是指用户开机、重启、注销后看到的 UI 界面,进程名称为 lightdm-deepin-greeter。 +登录界面:是指用户开机、重启、注销后看到的 UI 界面,进程名称为 lightdm-deepin-greeter。 + +锁屏界面:是用户按下 Meta+L 快捷键(没有修改快捷键的情况下),或者在电源界面选择"锁定"后看到的 UI 界面,进程名为 dde-lock。 -锁屏是用户按下 Meta+L 快捷键(没有修改快捷键的情况下),或者在电源界面选择“锁定”后看到的 UI 界面,进程名为 dde-lock。 +登录界面和锁屏界面是两个不同的进程,UI界面非常相似,但是功能和认证流程有很大的区别,在开发和调试的时候需要区分清楚。通过UI界面的`解锁按钮`可以轻松区分:登录界面解锁按钮是`箭头`图标,锁屏界面解锁按钮是`锁型`图标。 # 接口使用 -## 环境配置 +## 开发环境配置 -安装开发包 dde-session-shell-dev,安装完成后在 /usr/include/dde-session-shell/ 路径会有三个插件相关的头文件:base_module_interface.h 、login_module_interface.h、login_module_interface_v2.h,开发时使用 login_module_interface_v2.h 即可,有更完备的接口和功能。 +### 必需组件 -认证插件需要使用 Qt 框架,根据实际需求搭建 Qt 开发环境即可,这里不再赘述。 +1. **开发包安装** + ```bash + sudo apt install dde-session-shell-dev # 版本要求 ≥ 5.5.18 + ``` -## 数据结构说明 +### 头文件说明 -下面是开发认证插件时可能会用到的数据结构,可以在开发时作为工具文档使用。 +安装完成后,在 `/usr/include/dde-session-shell/` 目录下会有几个关键头文件: +- `base_module_interface.h`:基础接口定义 +- `login_module_interface.h`:登录相关接口定义 +- `login_module_interface_v2.h`:登录相关接口定义(2.x.x版本) -### ModuleType +> **注意**:开发时只需包含 `login_module_interface_v2.h` 即可,它已经包含了基础接口的定义。 -**说明:** 插件类型 +## 数据结构参考 + +本节介绍认证插件开发中使用的核心数据结构。 + +### 插件类型相关 + +#### ModuleType + +**说明:** 定义插件的基本类型和功能范围 **类型:** 枚举 -| 字段 | 说明 | 备注 | -| :-------- | :------- | :--------------------------------------------------------------------- | -| LoginType | 认证插件 | 认证插件默认使用此字段 | -| TrayType | 托盘插件 | 展示在登录器右下方控制区域的插件(例如网络,认证插件不用关注这个类型) | +**使用场景:** 在插件初始化时指定类型 + +| 字段 | 值 | 说明 | 使用建议 | +|:-----|:---|:-----|:---------| +| LoginType | 0 | 标准认证插件 | 🟢 默认推荐,适用于大多数认证场景 | +| TrayType | 1 | 托盘插件 | ⚪ 认证插件无需关注 | +| FullManagedLoginType | 2 | 全托管插件 | 🟡 需要完全控制登录流程时使用 | +| IpcAssistLoginType | 3 | IPC辅助登录插件 | 🟡 用于厂商密码插件集成 | +| PasswordExtendLoginType | 4 | 密码扩展插件 | 🟡 需要双重认证时使用 | -### AuthCallbackData +> **注意**:除非有特殊需求,默认使用 `LoginType`。其他类型建议先与 UOS 开发团队确认。 -**说明:** 认证插件需要传回的数据 +### 认证数据相关 +#### AuthCallbackData + +**说明:** 认证结果回传数据结构 **类型:** 结构体 +**使用场景:** 在认证完成时向登录器返回结果 -| 字段 | 数据类型 | 说明 | 必填 | 备注 | -| :------ | :------- | :------- | :--- | :--------------- | -| result | int | 认证结果 | 是 | 1 成功,其他失败 | -| account | string | 账户 | 否 | 用户账号 | -| token | string | 通行令牌 | 否 | 用户密码等 | -| message | string | 提示消息 | 否 | | -| json | string | 冗余字段 | 否 | 暂时没有使用 | +| 字段 | 类型 | 必填 | 说明 | 示例值 | +|:-----|:-----|:-----|:-----|:-------| +| result | int | 是 | 认证结果 | 1: 成功
其他: 失败 | +| account | string | 是 | 用户账号 | "user123" | +| token | string | 否 | 认证凭据 | "password123" | +| message | string | 否 | 提示信息 | "认证成功" | +| json | string | 否 | 预留的扩展数据字段,暂未使用 | | -### LoginCallBack +> **安全提示**: +> - 认证失败时应该在message中提供友好的错误提示 +> - 避免在message中包含敏感信息,该字段会在日志中打印出来 -**说明:** 回调函数以及登录器的回传指针 +#### AuthResult -**类型:** 结构体 +**说明:** 认证结果状态码 +**类型:** 枚举 +**使用场景:** 在 `AuthCallbackData` 的 result 字段中使用 -| 字段 | 类型 | 说明 | 备注 | -| :------------------ | :----------------- | :--------------- | :------------------------------------- | -| app_data | void\* | 登录器的回传指针 | 插件无需关注,只需在回调函数中传入即可 | -| authCallbackFun | AuthCallbackFun | 认证回调函数 | 用于认证完成后通知登录器 | -| messageCallbackFunc | MessageCallbackFun | 消息回调函数 | 用于主动与登录器交互 | +| 状态码 | 值 | 说明 | 使用场景 | +|:-------|:---|:-----|:---------| +| None | 0 | 未知状态 | 初始化或异常情况 | +| Success | 1 | 认证成功 | 用户验证通过 | +| Failure | 2 | 认证失败 | 验证不通过或发生错误 | -### AppType +> **最佳实践**: +> - 推荐使用头文件中提供的枚举,而不是直接使用数字 -**说明:** 发起认证的应用类型 +#### CustomLoginType +**说明:** 自定义登录方式类型 **类型:** 枚举 +**使用场景:** 指定插件提供的登录方式类型 -| 字段 | 说明 | 备注 | -| :---- | :----- | :-------------------------------------------- | -| None | 异常值 | 如果为 None 则说明出现异常 | -| Login | 登录 | 二进制文件为:/usr/bin/lightdm-deepin-greeter | -| Lock | 锁屏 | 二进制文件为:/usr/bin/dde-lock | +| 类型 | 值 | 说明 | 适用场景 | +|:-----|:---|:-----|:---------| +| CLT_Default | 0 | 标准登录 | 🟢 单一认证方式 | +| CLT_MFA | 1 | 多因子认证 | 🟡 需要多重验证 | +| CLT_ThirdParty | 2 | 第三方认证 | 🟡 外部认证服务 | -### LoadType -**说明:** 模块加载的类型 +### 回调函数相关 +#### LoginCallBack + +**说明:** 登录器提供的回调函数集合 +**类型:** 结构体 +**使用场景:** 插件与登录器之间的双向通信 + +| 字段 | 类型 | 说明 | 使用方式 | +|:-----|:-----|:-----|:---------| +| app_data | void* | 登录器上下文 | 仅作为回调参数传递,不要修改 | +| authCallbackFun | AuthCallbackFun | 认证结果回调 | 用于返回认证结果 | +| messageCallbackFunc | MessageCallbackFun | 消息通信回调 | 用于主动发送消息给登录器 | + +> **使用说明**: +> - 保存函数指针的变量必须初始化为空,并在使用指针前进行判空,避免使用空指针或野指针。 + +### 应用类型相关 + +#### AppType + +**说明:** 认证发起方的类型标识 **类型:** 枚举 +**使用场景:** 区分认证请求来源,采用不同的认证策略 -| 字段 | 说明 | 备注 | -| :------ | :--------- | :--- | -| Load | 加载插件 | | -| NotLoad | 不加载插件 | | +| 类型 | 值 | 说明 | 进程信息 | 特点 | +|:-----|:---|:-----|:---------|:-----| +| None | 0 | 异常状态 | - | 表示初始化失败或配置错误 | +| Login | 1 | 登录界面 | `/usr/bin/lightdm-deepin-greeter` | 和lightdm配合有标准化的登录流程 | +| Lock | 2 | 锁屏界面 | `/usr/bin/dde-lock` | 认证完成后隐藏界面,仅此而已 | -### AuthObjectType +> **处理建议**: +> - 如果在登录界面和锁屏界面有不同的业务逻辑,可以通过这个变量来判断。 -**说明:** 模块加载的类型 +#### LoadType +**说明:** 插件加载控制标识 **类型:** 枚举 +**使用场景:** 控制插件是否被登录器加载 -| 字段 | 说明 | 备注 | -| :----------------- | :--------- | :--- | -| LightDM | 显示管理器 | | -| DeepinAuthenticate | 不加载插件 | | +| 标识 | 值 | 说明 | 使用场景 | +|:-----|:---|:-----|:---------| +| Load | 0 | 允许加载 | 默认值 | +| Notload | 1 | 禁止加载 | 插件根据自身的业务逻辑不想进行认证,例如:
- 系统环境不支持
- 依赖服务未启动
- 配置文件缺失
- 业务需求等 | -### AuthType +> **最佳实践**: +> - 在插件构造时检查运行环境 +> - 插件应记录不加载的原因 -**说明:** 认证类型 +### 认证框架相关 +#### AuthObjectType + +**说明:** 认证服务提供方类型 **类型:** 枚举 +**使用场景:** 识别当前使用的认证框架,一般认证插件无需关注 + +| 框架类型 | 值 | 说明 | +|:---------|:---|:-----| +| LightDM | 0 | 显示管理器 | +| DeepinAuthenticate | 1 | 深度认证框架 | + + +### 认证方式相关 + +#### AuthType -| 字段 | 说明 | 备注 | -| :------ | :----- | :--- | -| 0 | 默认 | | -| 1 << 0 | 密码 | | -| 1 << 1 | 指纹 | | -| 1 << 2 | 人脸 | | -| 1 << 3 | AD 域 | | -| 1 << 4 | UKey | | -| 1 << 5 | 指静脉 | | -| 1 << 6 | 虹膜 | | -| 1 << 7 | PIN | | -| 1 << 29 | PAM | | -| 1 << 30 | 自定义 | | -| -1 | ALL | | - -### AuthState - -**说明:** 认证状态 +**说明:** 系统支持的认证方式 +**类型:** 枚举(位掩码) +**使用场景:** 指定认证方式或组合多种认证方式 +| 类型 | 值 | 说明 | +|:-----|:---|:-----| +| AT_None | 0 | 未指定 | +| AT_Password | 1<<0 | 密码认证 | +| AT_Fingerprint | 1<<1 | 指纹识别 | +| AT_Face | 1<<2 | 人脸识别 | +| AT_ActiveDirectory | 1<<3 | AD域认证 | +| AT_Ukey | 1<<4 | UKey认证 | +| AT_FingerVein | 1<<5 | 指静脉识别 | +| AT_Iris | 1<<6 | 虹膜识别 | +| AT_Passkey | 1<<7 | 安全密钥 | +| AT_Pattern | 1<<8 | 图案解锁 | +| AT_PAM | 1<<29 | PAM认证 | +| AT_Custom | 1<<30 | 自定义认证 | +| AT_All | -1 | 支持所有方式 | + +> **使用说明**: +> - 可以通过位运算组合多种认证方式,例如:`AT_Password | AT_Fingerprint` 表示同时支持密码和指纹 +> - 插件的认证方式是 `AT_Custom`。 + +#### AuthState + +**说明:** 认证过程的状态标识 **类型:** 枚举 +**使用场景:** 跟踪和管理认证流程的各个状态 + +| 字段 | 值 | 说明 | 备注 | +| :--- | :-- | :--------- | :---------------------------------------------------------------------------------- | +| AS_None | -1 | 默认 | | +| AS_Success | 0 | 成功 | 此次认证的最终结果 | +| AS_Failure | 1 | 失败 | 此次认证的最终结果 | +| AS_Cancel | 2 | 取消 | 当认证没有给出最终结果时,调用 End 会出发 Cancel 信号 | +| AS_Timeout | 3 | 超时 | 一些认证设备会有超时状态的设定 | +| AS_Error | 4 | 错误 | | +| AS_Verify | 5 | 验证中 | | +| AS_Exception | 6 | 设备异常 | 当前认证会被 End | +| AS_Prompt | 7 | 设备提示 | | +| AS_Started | 8 | 认证已启动 | 调用 Start 之后,每种成功开启都会发送此信号 | +| AS_Ended | 9 | 认证已结束 | 调用 End 之后,每种成功关闭的都会发送此信号,当某种认证类型被锁定时,也会触发此信号 | +| AS_Locked | 10 | 认证已锁定 | 认证类型已锁定,表示认证类型不可用(从安全角度考虑,失败次数过多时会导致这种情况) | +| AS_Recover | 11 | 设备恢复 | 需要调用 Start 重新开启认证,对应 AS_Exception | +| AS_Unlocked | 12 | 认证解锁 | 认证类型解除锁定,表示可以继续继续使用此认证类型开始认证了 | +| AS_Unknown | 13 | 未知状态 | | +| AS_VerifyCode | 14 | 需要验证码 | | + +### 认证级别相关 + +#### DefaultAuthLevel + +**说明:** 认证方式的优先级设置 +**类型:** 枚举 +**使用场景:** 配置默认的认证方式选择策略 + +| 级别 | 值 | 策略 | 使用建议 | +|:-----|:---|:-----|:---------| +| NoDefault | 0 | 智能选择 | 🟢 推荐使用
- 优先使用上次成功的认证方式
- 无历史记录时按系统默认顺序 | +| Default | 1 | 已废弃 | ⛔ 不要使用
- 保留向后兼容
- 功能与 NoDefault 类似 | +| StrongDefault | 2 | 强制插件 | 🟡 特殊场景
- 始终优先使用插件认证
| -| 字段 | 说明 | 备注 | -| :--- | :--------- | :---------------------------------------------------------------------------------- | -| -1 | 默认 | | -| 0 | 成功 | 此次认证的最终结果 | -| 1 | 失败 | 此次认证的最终结果 | -| 2 | 取消 | 当认证没有给出最终结果时,调用 End 会出发 Cancel 信号 | -| 3 | 超时 | 一些认证设备会有超时状态的设定 | -| 4 | 错误 | | -| 5 | 验证中 | | -| 6 | 设备异常 | 当前认证会被 End | -| 7 | 设备提示 | | -| 8 | 认证已启动 | 调用 Start 之后,每种成功开启都会发送此信号 | -| 9 | 认证已结束 | 调用 End 之后,每种成功关闭的都会发送此信号,当某种认证类型被锁定时,也会触发此信号 | -| 10 | 认证已锁定 | 认证类型已锁定,表示认证类型不可用(从安全角度考虑,失败次数过多时会导致这种情况) | -| 11 | 设备恢复 | 需要调用 Start 重新开启认证,对应 AS_Exception | -| 12 | 认证解锁 | 认证类型解除锁定,表示可以继续继续使用此认证类型开始认证了 | +> **选择建议**: +> - 一般情况使用 `NoDefault`,提供更好的用户体验 +> - 需要强制使用插件认证时,使用 `StrongDefault` +> - 避免使用已废弃的 `Default` 级别 ## 接口说明 @@ -193,7 +337,10 @@ virtual ModuleType type() const = 0 virtual void init() = 0 ``` -**说明:** 界面相关的初始化,插件在非主线程加载,故界面相关的初始化需要放在这个方法里,由主程序调用并初始化。 +**说明:** +1. 插件加载是在子线程进行的,界面相关的初始化操作放在这个函数中,不要放在构造函数里处理。 +2. 这个方法在主线程中调用,不要进行耗时操作,耗时操作请异步处理。 +3. 这个函数可能会重复调用,以重置界面内容。 **入参:** 无 @@ -217,7 +364,7 @@ virtual QString key() const = 0 | 类型 | 说明 | 备注 | | :----- | :------- | :----------------------- | -| string | 唯一编码 | 传入与项目相关的命名即可 | +| string | 唯一编码 | 传入与项目相关的命名即可,切记要带有独特性(比如公司、项目缩写),避免和其它插件重名 | **必须实现:** 是 @@ -230,15 +377,16 @@ virtual QString icon() const ``` **说明:** -当认证因子不止一种时,登录器会显示认证类型切换组件,它是由一个按钮组(Button Group)组成的,此函数返回的图标会展示在“认证插件切换按钮”上面。 +当认证因子不止一种时,登录器会显示认证类型切换组件,它是由一个按钮组(Button Group)组成的,此函数返回的图标会展示在"认证插件切换按钮"上面。 **入参:** 无 **返回值:** + | 类型 | 说明 | 备注 | | :----- | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | -| string | 认证插件的图标 | 可以返回图标的绝对路径(加载方式为 QIcon(“absolute path”)),也可以返回系统图标的名称(加载方式为 QIcon::fromTheme(“system icon name”))。 | +| string | 认证插件的图标 | - 使用图标的绝对路径(加载方式为 QIcon("absolute path"))
- 使用系统图标的名称(加载方式为 QIcon::fromTheme("system icon name"))。 | **必须实现:** 否 @@ -286,21 +434,20 @@ virtual LoadType loadPluginType() const #### setAppData -**函数定义:** 无 +**函数定义:** ```c++ -virtual LoadType setAppdata(void*) const +virtual void setAppData(void*) const ``` **说明:** -设置登录器的回调指针,插件需要保存指针,在使用回调函数的时候回传给登录器。如果要使用回调函数,则必须实现此函数。 -函数可能会被重复调用,插件只需要保证回传的时候是最后一次设置的即可。 +设置登录器的回调指针,插件需要保存指针,在使用回调函数的时候回传给登录器。如果要使用回调函数,则必须实现此函数。函数可能会被重复调用,插件回传最后一次设置的即可。 -**入参:** 无 +**入参:** -| 类型 | 说明 | 备注 | -| :----- | :--- | :--- | -| void\* | - | - | +| 类型 | 说明 | 备注 | +| :----- | :------------------- | :--- | +| void\* | 登录器的回调指针 | - | **返回值:** 无 @@ -341,112 +488,174 @@ QString (*)(const QString &, void *) | 类型 | 说明 | 备注 | | :----- | :-------------------------------- | :-------------------------------------- | -| string | 发送给登录器的 json 格式数据 | json 数据的具体内容详见下面数据协议部分 | +| string | 发送给登录器的 JSON 格式数据 | JSON 数据的具体内容详见 "JSON 数据协议" 部分 | | void\* | 即 LoginCallBack 的 app_data 字段 | | **返回值:** | 类型 | 说明 | 备注 | | :----- | :--------------------------- | :--- | -| string | 发送给登录器的 json 格式数据 | | +| string | 登录器返回的 JSON 格式数据 | | -**数据协议:** -默认返回的数据: -| 字段 | 类型 | 说明 | -| :------ | :----- | :--------------- | -| Code | int | 0 成功,其他失败 | -| Message | string | 提示消息 | +### LoginModuleInterfaceV2 -例: +#### message -```json -{ - "Code": 0, - "Message": "Success" -} +**函数定义:** + +```c++ +virtual QString message(const QString &) ``` -1. 获取属性 +**说明:** +登录器主动向认证插件发起通讯,用于获取插件信息或者给插件提供信息。**这是插件与登录器交互的核心方法**,插件通过处理不同的 JSON 消息来实现各种功能。具体的 JSON 协议格式请参考本文档的 "JSON 数据协议" 部分。 + +**入参:** + +| 类型 | 说明 | 备注 | +| :------ | :-------------- | :--- | +| QString | json 格式字符串 | 包含CmdType等字段的JSON消息 | + +**返回值:** -请求: +| 类型 | 说明 | 备注 | +| :------ | :-------------- | :--- | +| QString | json 格式字符串 | 插件的响应数据 | -| 字段 | 类型 | 值 | 说明 | -| :------ | :----- | :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------- | -| CmdType | string | "GetProperties" | | -| Data | array | ["AppType","CurrentUser"] | [值]列中展示的是目前支持的值,传入其它值无效。`
`根据 api 版本号来判断支持哪些字段:`
`since api-2.0.0 ("AppType", "CurrentUser") | +**必须实现:** 否 -例: +**实现示例:** -```json -{ - "CmdType": "GetProperties", - "Data": ["AppType", "CurrentUser"] +```c++ +QString YourPlugin::message(const QString &jsonStr) { + QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8()); + QJsonObject obj = doc.object(); + QString cmdType = obj["CmdType"].toString(); + + if (cmdType == "IsPluginEnabled") { + QJsonObject response; + response["Code"] = 0; + response["Message"] = "Success"; + QJsonObject data; + data["IsPluginEnabled"] = true; // 插件是否启用 + response["Data"] = data; + return QJsonDocument(response).toJson(); + } else if (cmdType == "GetConfigs") { + // 处理获取配置的请求 + QJsonObject response; + response["Code"] = 0; + response["Message"] = "Success"; + QJsonObject data; + data["ShowAvatar"] = true; + data["ShowUserName"] = true; + data["DefaultAuthLevel"] = 1; + response["Data"] = data; + return QJsonDocument(response).toJson(); + } + // 处理其他命令... + + return "{}"; // 默认返回空JSON对象 } ``` -返回值: -| 字段 | 类型 | 说明 | -| :-------------------- | :----- | :-------------------- | -| Code | int | 0 成功,其他失败 | -| Message | string | 提示消息 | -| Data | object | - | -| Data.AppType | int | 详见 AppType 枚举说明 | -| Data.CurrentUser | object | 当前用户信息 | -| Data.CurrentUser.Name | string | 当前用户的用户名 | +**必须实现:** 否 -例: +#### setAuthCallback -```json -{ - "Code": 0, - "Message": "Success", - "Data": { - "AppType": 2, - "CurrentUser": { - "Name": "uos" - } - } -} +**函数定义:** + +```c++ +virtual void setAuthCallback(AuthCallbackFunc *) = 0 ``` -#### message +**说明:** 设置认证结果回调函数,会在init函数之前调用。 + +**入参:** + +| 类型 | 说明 | 备注 | +| :----------------- | :-------------------------------- | :--- | +| AuthCallbackFunc\* | 详见 `AuthCallbackFunc`类型说明 | | + +**返回值:** 无 + +**必须实现:** 是 + +#### AuthCallbackFun **函数定义:** ```c++ -virtual QString message(const QString &) +void (*)(const AuthCallbackData *, void *) ``` -**说明:** -登录器主动向认证插件发起通讯,一般用于获取插件信息或者给插件提供信息,入参和返回值都是 json 格式的字符串。 +**说明:** 认证回调函数,用于插件返回认证的状态、结果等 **入参:** -| 类型 | 说明 | 备注 | -| :------ | :-------------- | :--- | -| QString | json 格式字符串 | | +| 类型 | 说明 | 备注 | +| :----------------- | :-------------------------------- | :--- | +| AuthCallbackData\* | 认证相关信息 | | +| void\* | 即 LoginCallBack 的 app_data 字段 | | -**返回值:** +**返回值:** 无 -| 类型 | 说明 | 备注 | -| :------ | :-------------- | :--- | -| QString | json 格式字符串 | | +#### reset -**必须实现:** 否 +**函数定义:** + +```c++ +virtual void reset() = 0 +``` + +**说明:** +插件需要在这个方法中重置UI和验证状态,通常验证开始之前登录器会调用这个方法,但是不保证每次验证前都会调用。插件必须实现这个函数,并在函数内重置之前的验证结果,避免将以前的结果应用在当前认证中。 + +**入参:** 无 + +**返回值:** 无 + +**必须实现:** 是 + +### 初始化接口调用 + +认证插件初始化时接口调用顺序如下: + +```plantuml +@startuml +start +if (loadPluginType() == Load) then (yes) +:setAuthCallback +setMessageCallback +setAppdata] +:init] +:初始化后其它方法都可以会被调用] +else (no) +endif +stop +@enduml + +``` -**数据协议:** +## JSON 数据协议 -默认返回的数据: +插件与登录器之间通过 JSON 格式进行消息交互。这些协议在 `message()` 方法和 `messageCallback()` 函数中使用。 + +### 指令方向说明 + +- **登录器→插件**:登录器主动发送给插件的协议(插件在 `message()` 方法中处理) +- **插件→登录器**:插件主动发送给登录器的协议(插件通过 `messageCallback()` 函数发送) + +### 默认响应格式 | 字段 | 类型 | 说明 | | :------ | :----- | :--------------- | | Code | int | 0 成功,其他失败 | | Message | string | 提示消息 | -例: +示例: ```json { @@ -455,64 +664,80 @@ virtual QString message(const QString &) } ``` -1. **用户发生变化(CurrentUserChanged)** - 说明:程序启动或者切换用户的时候登录器会主动发送此消息,告知认证插件当前正在认证的用户是谁。 +### 🔴 核心协议 -请求: +核心协议是认证插件必须关注的基础通信协议,确保插件能够正常集成到登录系统中。 -| 字段 | 类型 | 值 | 说明 | -| :-------- | :----- | :----------------- | :--------------- | -| CmdType | string | CurrentUserChanged | | -| Data | object | - | | -| Data.Name | string | - | 当前用户的用户名 | +#### IsPluginEnabled(登录器 ⟶ 插件) -示例: +登录器查询插件是否启用。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :---------------- | :--- | +| CmdType | string | "IsPluginEnabled" | 插件没有实现则默认为ture | + +**响应:** + +| 字段 | 类型 | 必填项 | 说明 | +| :------------------- | :----- | :----- | :--------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.IsPluginEnabled | bool | 是 | 默认为 true | + +**示例:** ```json +// 请求 { - "CmdType": "CurrentUserChanged", + "CmdType": "IsPluginEnabled" +} + +// 响应 +{ + "Code": 0, + "Message": "Success", "Data": { - "Name": "uos" + "IsPluginEnabled": true } } ``` -返回值:默认数据。 +#### GetConfigs(登录器 ⟶ 插件) -2. **获取配置(GetConfigs)** - 支持认证插件来控制登录器其它的 UI 控件。 +登录器获取插件的UI控制配置。 -请求: +**请求:** -| 字段 | 类型 | 值 | 说明 | -| :------ | :----- | :------------- | :--- | -| CmdType | string | “GetConfigs” | | +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :----------- | :--- | +| CmdType | string | "GetConfigs" | | -示例: +**响应:** + +| 字段 | 类型 | 必填项 | 说明 | +| :---------------------- | :----- | :----- | :----------------------------------------------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.ShowAvatar | bool | 否 | 是否显示用户头像 | +| Data.ShowUserName | bool | 否 | 是否显示用户名 | +| Data.ShowSwitchButton | bool | 否 | 是否显示认证类型切换按钮 | +| Data.ShowLockButton | bool | 否 | 是否显示解锁按钮 | +| Data.DefaultAuthLevel | int | 否 | 见`DefaultAuthLevel`枚举说明 | +| Data.SupportDefaultUser | bool | 否 | 是否支持默认用户登录 | + +**示例:** ```json +// 请求 { "CmdType": "GetConfigs" } -``` - -返回值: - -| 字段 | 类型 | 必填项 | 说明 | -| :---------------------- | :----- | :----- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Code | int | 否 | 0 成功,其他失败 | -| Message | string | 否 | 提示消息 | -| Data | object | 是 | | -| Data.ShowAvatar | bool | 否 | 是否显示用户头像 | -| Data.ShowUserName | bool | 否 | 是否显示用户名 | -| Data.ShowSwitchButton | bool | 否 | 是否显示认证类型切换按钮 | -| Data.ShowLockButton | bool | 否 | 是否显示解锁按钮 | -| Data.DefaultAuthLevel | int | 否 | 0:如果获取到上次认证成功的类型,则默认使用上次登录成功的类型,否则根据系统默认的顺序来选择。`
`1:如果获取到上次认证成功的类型,则默认使用上次登录成功的类型,否则使用插件认证。`
`2:无论是否可以获取到上次认证成功的类型,都默认选择插件验证。`
`默认为 1 | -| Data.SupportDefaultUser | bool | 否 | 插件是否支持默认用户的登录。默认用户:即当前没有指定用户,默认用户为"...",一般在域管和服务器环境下会出现。`
`支持的场景:用户在登录界面扫描二维码,认证成功后插件得知是扫码人员的身份,与系统中的人员进行比对,将人员身份和认证结果返回给登录器,登录器可以登录此用户。`
`不支持的场景:必须先知道当前验证人员的身份,才能进行验证。`
`插件不处理此接口,则默认为支持。 | - 示例: - -```json +// 响应 { "Code": 0, "Message": "Success", @@ -526,63 +751,120 @@ virtual QString message(const QString &) } ``` -3. **认证开始(StartAuth)** - 登陆界面会有两次开启验证,一次是 deepin-authenticate 服务,还有一次是 lightdm,插件需要等到这两个验证都开启后再发送验证结果。 - 锁屏界面只有 deepin-authenticate 会发起一次验证,插件需要等 deepin-authenticate 开启验证后再发送验证结果。 - 这两种验证类型都会在插件加载后的很短的一段时间后开启验证(1s 以内),主要是处理指纹一键登录的需求。如果插件发送验证结果的时间较晚(比如扫码、输入验证码等)则无需关心此消息。 +#### CurrentUserChanged(用户变化通知) -请求: +登录器通知插件当前用户发生变化。 -| 字段 | 类型 | 值 | 说明 | -| :------------------ | :----- | :-------- | :-------------------------- | -| CmdType | string | StartAuth | | -| Data | object | | | -| Data.AuthObjectType | int | | 见 `AuthObjectType`的说明 | +**请求:** -示例: +| 字段 | 类型 | 值 | 说明 | +| :-------- | :----- | :----------------- | :----------------- | +| CmdType | string | CurrentUserChanged | | +| Data | object | - | | +| Data.Name | string | - | 当前用户的用户名 | +| Data.Uid | int | - | 当前用户的ID | + +**响应:** 默认响应格式 + +**示例:** ```json +// 请求 { - "CmdType": "StartAuth", + "CmdType": "CurrentUserChanged", "Data": { - "AuthObjectType": 1 + "Name": "uos", + "Uid": 1001 } } ``` -返回值:默认数据。 +#### SetAuthTypeInfo(设置认证类型信息) -4. **认证状态(AuthState)** - 当前登录器的认证状态 +插件向登录器发送认证类型相关信息。 -请求: +**请求:** -| 字段 | 类型 | 值 | 说明 | -| :------------- | :----- | :---------- | :--------------------- | -| CmdType | string | "AuthState" | | -| Data | object | | | -| Data.AuthType | int | | 见 `AuthType`的说明 | -| Data.AuthState | int | | 见 `AuthState`的说明 | +| 字段 | 类型 | 值 | 说明 | +|:-----|:-----|:---|:-----| +| CmdType | string | "setAuthTypeInfo" | ⚠️ 这里首字母是小写(历史原因) | +| Data | object | - | | +| Data.AuthType | int | - | 见 `AuthType` 枚举说明 | -示例: +**示例:** ```json +// 请求 { - "CmdType": "AuthState", - "Data": { - "AuthType": 1, - "AuthState": 0 - - } + "CmdType": "SetAuthTypeInfo", + "Data": { + "AuthType": 1 + } } ``` -返回值:默认数据。 -5. **限制信息(LimitsInfo)** - 所有认证类型的限制信息 +### 🟡 扩展协议 + +扩展协议提供额外的功能特性,开发者可根据实际需求选择性实现,用于增强插件的功能表现。 + +#### GetProperties(登录器 ⟶ 插件) + +登录器获取系统属性信息。 + +**请求:** -请求: +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :------------------------ | :----------------------------------------------------------- | +| CmdType | string | "GetProperties" | | +| Data | array | ["AppType","CurrentUser"] | 支持的属性列表,API 2.0.0+ 支持 "AppType", "CurrentUser" | + +**响应:** + +| 字段 | 类型 | 说明 | +| :-------------------- | :----- | :-------------------- | +| Code | int | 0 成功,其他失败 | +| Message | string | 提示消息 | +| Data | object | - | +| Data.AppType | int | 详见 AppType 枚举说明 | +| Data.CurrentUser | object | 当前用户信息 | +| Data.CurrentUser.Name | string | 当前用户的用户名 | +| Data.CurrentUser.Uid | int | 当前用户的ID | + +#### StartAuth(登录器 ⟶ 插件) + +登录器通知插件开始认证。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------------------ | :----- | :-------- | :-------------------------- | +| CmdType | string | StartAuth | | +| Data | object | | | +| Data.AuthObjectType | int | | 见 `AuthObjectType`的说明 | + +**响应:** 默认响应格式 + +#### AuthState(登录器 ⟶ 插件) + +登录器发送当前认证状态。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------------- | :----- | :---------- | :----------------------- | +| CmdType | string | "AuthState" | | +| Data | object | | | +| Data.AuthType | int | | 见 `AuthType`的说明 | +| Data.AuthState | int | | 见 `AuthState`的说明 | + +**响应:** 默认响应格式 + +#### LimitsInfo(登录器 ⟶ 插件) + +登录器发送认证限制信息。 + +**请求:** | 字段 | 类型 | 值 | 说明 | | :----------------- | :----- | :----------- | :------------------------ | @@ -595,148 +877,166 @@ virtual QString message(const QString &) | Object.UnlockSecs | int | | 还剩多久解锁 | | Object.UnlockTime | string | | 解锁时间 | -示例: +**响应:** 默认响应格式 -```json -{ - "CmdType": "LimitsInfo", - "Data": [ - { - "Flag": 1, - "Locked": false, - "MaxTries": 5, - "NumFailures": 0, - "UnlockSecs": 0, - "UnlockTime": "0001-01-01T00:00:00Z" - }, - { - "Flag": 2, - "Locked": false, - "MaxTries": 3, - "NumFailures": 0, - "UnlockSecs": -1, - "UnlockTime": "0001-01-01T00:00:00Z" - } - ] -} -``` +#### AccountError(登录器 ⟶ 插件) -返回值:默认数据。 +登录器通知插件账户验证出现错误。 -6. **是否启用插件** - 登录器在开始验证的时候会向插件发起此请求,插件自行决定现在是否要启用插件,登录器默认插件是启用的。 +**请求:** -请求: +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :------------- | :------------------------------------------------- | +| CmdType | string | "AccountError" | 当账户信息错误的时候会发出这个信息,比如用户不存在,用户密码过期等 | -| 字段 | 类型 | 值 | 说明 | -| :------ | :----- | :---------------- | :--- | -| CmdType | string | "IsPluginEnabled" | | +**响应:** 默认响应格式 -示例: +#### AuthFactorsChanged(登录器 ⟶ 插件) -```json -{ - "CmdType": "IsPluginEnabled" -} -``` +登录器通知插件认证因子发生变化。 -返回值: +**请求:** -| 字段 | 类型 | 必填项 | 说明 | -| :------ | :----- | :----- | :--------------- | -| Code | int | 否 | 0 成功,其他失败 | -| Message | string | 否 | 提示消息 | -| Data | bool | 是 | 默认为 true | +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :------------------ | :------- | +| CmdType | string | "AuthFactorsChanged" | | +| Data | int | | 认证因子 | -示例: +**响应:** 默认响应格式 -```json -{ - "Code": 0, - "Message": "Success", - "Data": { - "IsPluginEnabled": true - } -} -``` +#### ReadyToAuthChanged(插件 ⟶ 登录器) -### LoginModuleInterfaceV2 +插件通知登录器认证准备状态发生变化。 -#### setAuthCallback +**发送方式:** 插件通过 `messageCallback()` 发送 -**函数定义:** +**请求:** -```c++ -virtual void setAuthCallback(AuthCallbackFunc *) = 0 -``` +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :------------------- | :----------- | +| CmdType | string | "ReadyToAuthChanged" | | +| Data | bool | | 是否准备就绪 | -**说明:** 设置回调函数,会在init函数之前调用。 +**响应:** 默认响应格式 -**入参:** +#### ReadyToAuth(登录器 ⟶ 插件) -| 类型 | 说明 | 备注 | -| :----------------- | :-------------------------------- | :--- | -| AuthCallbackFunc\* | 详见 `AuthCallbackFunc`类型说明 | | +登录器查询插件是否准备好进行认证。 -**返回值:** 无 +**请求:** -**必须实现:** 是 +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :----------- | :--- | +| CmdType | string | "ReadyToAuth" | | -#### AuthCallbackFun +**响应:** -**函数定义:** +| 字段 | 类型 | 必填项 | 说明 | +| :--------------- | :----- | :----- | :--------------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.ReadyToAuth | bool | 是 | 是否准备好认证,默认true | -```c++ -void (*)(const AuthCallbackData *, void *) -``` +### ⚪ 特殊协议 -**说明:** 认证回调函数,用于插件返回认证的状态、结果等 +特殊协议专门用于处理复杂业务场景和特殊需求,适用于特殊定制化开发,按需实现。 -**入参:** +#### GetLevel(登录器 ⟶ 插件) -| 类型 | 说明 | 备注 | -| :----------------- | :-------------------------------- | :--- | -| AuthCallbackData\* | 认证相关信息 | | -| void\* | 即 LoginCallBack 的 app_data 字段 | | +用于多层级认证场景。 -**返回值:** 无 +**请求:** -#### reset +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :-------- | :--- | +| CmdType | string | "GetLevel" | | -**函数定义:** +**响应:** -```c++ -virtual void reset() = 0 -``` +| 字段 | 类型 | 必填项 | 说明 | +| :--------- | :----- | :----- | :------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.Level | int | 是 | 插件层级,默认为1 | -**说明:** -插件需要在这个方法中重置UI和验证状态,通常验证开始之前登录器会调用这个方法,但是不保证每次验证前都会调用。插件必须实现这个函数,并在函数内重置之前的验证结果,避免将以前的结果应用在当前认证中。 +#### GetLoginType(登录器 ⟶ 插件) -**入参:** 无 +获取插件的登录类型。 -**返回值:** 无 +**请求:** -**必须实现:** 是 +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :------------ | :--- | +| CmdType | string | "GetLoginType" | | -### 初始化接口调用 +**响应:** -认证插件初始化时接口调用顺序如下: +| 字段 | 类型 | 必填项 | 说明 | +| :------------- | :----- | :----- | :----------------------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.LoginType | int | 是 | 登录类型,见`CustomLoginType` | -```plantuml -@startuml -start -if (loadPluginType() == Load) then (yes) -:setAuthCallback -setMessageCallback -setAppdata] -:init] -:初始化后其它方法都可以会被调用] -else (no) -endif -stop -@enduml +#### HasSecondLevel(登录器 ⟶ 插件) -``` +检查指定用户是否需要第二层认证。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :-------------- | :------- | +| CmdType | string | "HasSecondLevel" | | +| Data | string | | 用户名 | + +**响应:** + +| 字段 | 类型 | 必填项 | 说明 | +| :------------------ | :----- | :----- | :------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.HasSecondLevel | bool | 是 | 是否需要二层认证 | + +#### GetSessionTimeout(登录器 ⟶ 插件) + +获取插件自定义的会话超时时长。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :----------------- | :--- | +| CmdType | string | "GetSessionTimeout" | | + +**响应:** + +| 字段 | 类型 | 必填项 | 说明 | +| :------------------ | :----- | :----- | :------------------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.SessionTimeout | int | 是 | 会话超时时长(毫秒),默认15000 | + +#### UpdateLoginType(登录器 ⟶ 插件) + +通知插件更新登录类型,登录类型信息依赖远程配置。 + +**请求:** + +| 字段 | 类型 | 值 | 说明 | +| :------ | :----- | :--------------- | :--- | +| CmdType | string | "UpdateLoginType" | | + +**响应:** + +| 字段 | 类型 | 必填项 | 说明 | +| :------------- | :----- | :----- | :------------------- | +| Code | int | 否 | 0 成功,其他失败 | +| Message | string | 否 | 提示消息 | +| Data | object | 是 | | +| Data.LoginType | int | 是 | 更新后的登录类型 | ## 编程实例 @@ -778,8 +1078,8 @@ find_package(DdeSessionShell REQUIRED) # find_package 命令还可以用来加载 cmake 的功能模块 # 并不是所有的库都直接支持 cmake 查找的,但大部分都支持了 pkg-config 这个标准, # 因此 cmake 提供了间接加载库的模块:FindPkgConfig, 下面这行命令表示加载 FindPkgConfig 模块, -# 这个 cmake 模块提供了额外的基于 “pkg-config” 加载库的能力 -# 执行下面的命令后后会设置如下变量,不过一般用不到: +# 这个 cmake 模块提供了额外的基于 "pkg-config" 加载库的能力 +# 执行下面的命令后会设置如下变量,不过一般用不到: # PKG_CONFIG_FOUND pkg-config 可执行文件是否找到了 # PKG_CONFIG_EXECUTABLE pkg-config 可执行文件的路径 # PKG_CONFIG_VERSION_STRING pkg-config 的版本信息 @@ -855,6 +1155,7 @@ public: void reset() override; void setAppData(AppDataPtr) override; void setAuthCallback(AuthCallbackFun) override; + QString message(const QString &) override; private: void initUI(); @@ -951,6 +1252,11 @@ void LoginModule::initUI() ### 元数据文件 +插件需要提供一个 JSON 格式的元数据文件,用于描述插件的基本信息和兼容性要求。 + +**文件名:** `login.json` + +**基本格式:** ```json { "api": "2.0.0", @@ -958,27 +1264,27 @@ void LoginModule::initUI() } ``` -# 安装 +### 二进制路径 -登录器会在启动的时候从 /usr/lib/dde-session-shell/modules 目录下加载后缀为.so 的插件,在开发的时候需要在 CMakeLists.txt 或.pro 文件中设置插件的安装路径为 /usr/lib/dde-session-shell/modules。 +登录器会在启动时从 `/usr/lib/dde-session-shell/modules` 目录下加载后缀为 `.so` 的插件。在开发时需要在 `CMakeLists.txt` 或 `.pro` 文件中设置插件的安装路径为 `/usr/lib/dde-session-shell/modules`。 # 安全性 -在整个流程中,登录器不参与用户身份的校验,用户身份的真实性和访问权限完全由认证插件来保证,所以在开发认证插件的时候对于安全性的考虑要全面且慎重。这是除了功能需求之外最重要的特性,下面列出一些常见的需要考量的问题: +在整个流程中,登录器不参与用户身份的校验,用户身份的真实性和访问权限完全由认证插件来保证。因此,在开发认证插件时对于安全性的考虑要全面且慎重。这是除了功能需求之外最重要的特性,下面列出一些常见的安全考量: -- 输入密码等认证信息应该使用加密手段保护数据的不被窃取以及中间人攻击。 -- 图形界面程序不应该把自己提权到 root 权限(含 capabilities)运行。 -- 存储的用户数据(如密码与隐私数据)应该加密保护且加密强度足够高。 -- 不应该未经用户允许保存或者传输用户数据。 +- 输入密码等认证信息必须使用加密手段防止数据被窃取和中间人攻击。 +- 图形界面程序不应该将自己提权到 root 权限(含 capabilities)运行。 +- 存储的用户数据(如密码与隐私数据)必须进行高强度加密保护。 +- 未经用户明确授权,不得保存或传输用户数据。 # 可靠性 -登录器会把插件加载到内存中,如果认证插件的质量不过关,出现指针错误、内存溢出、在 UI 线程做耗时操作等,会导致登录器崩溃、卡死,进而导致用户无法正常进入系统。登录器是系统的门户,如果用户无法正常使用登录器,将无法进入系统,这是致命性问题,故而在开发时要将可靠性做为重点去考量。 +登录器会将插件加载到内存中。如果认证插件的质量不过关,出现指针错误、内存溢出、在 UI 线程做耗时操作等问题,可能导致登录器崩溃或卡死,进而导致用户无法正常进入系统。由于登录器是系统的门户,如果用户无法正常使用登录器就无法进入系统,这是一个致命性问题。因此,在开发时必须将可靠性作为重点考虑因素。 -# 兼容性 +### API 版本兼容性 -认证插件的工程需要增加一个 json 文件,用来描述当前插件适配的 api 版本,在代码中使用 Q_PLUGIN_METADATA 将 json 文件设置为 metadata 文件,例如:Q_PLUGIN_METADATA(IID "com.deepin.dde.shell.Login" FILE "login.json")。登录器在加载插件的时候会解析 json 文件中的内容,获取 api 版本号。 -登录器会做到小版本向下兼容(1.x.x版本都互相兼容),如果出现无法兼容的情况(比如增加了接口头文件的虚函数,导致二进制不兼容),此时会修改大版本号(比如从1.x.x变更到2.x.x),登录器会比对 api 版本号,如果低于可兼容的版本号,则不会加载插件,以保证登录器本身能够正常运行。json 文件示例: +认证插件的工程需要增加一个 json 文件,用来描述当前插件适配的 api 版本,在代码中使用 Q_PLUGIN_METADATA 将 json 文件设置为 metadata 文件,例如:Q_PLUGIN_METADATA(IID "com.deepin.dde.shell.Login" FILE "login.json")。登录器在加载插件时会解析 JSON 文件中的内容,获取 API 版本号。 +登录器会做到小版本向下兼容(1.x.x 版本都互相兼容),如果出现无法兼容的情况(比如增加了接口头文件的虚函数,导致二进制不兼容),此时会修改大版本号(比如从 1.x.x 变更到 2.x.x)。登录器会比对 API 版本号,如果低于可兼容的版本号,则不会加载插件,以保证登录器本身能够正常运行。JSON 文件示例: ```c++ { @@ -987,4 +1293,4 @@ void LoginModule::initUI() } ``` -login_module_interface.h 中 API_VERSION 宏定义的字符串即为当前 api 的版本号,json 文件中 api 字段记录的版本号务必与 API_VERSION 保持一致。 +login_module_interface.h 中 API_VERSION 宏定义的字符串即为当前 API 的版本号,JSON 文件中 api 字段记录的版本号务必与 API_VERSION 保持一致。 diff --git a/src/app/dde-lock.cpp b/src/app/dde-lock.cpp index 0236194e..1ccb677a 100644 --- a/src/app/dde-lock.cpp +++ b/src/app/dde-lock.cpp @@ -86,8 +86,9 @@ int main(int argc, char *argv[]) }); DLogManager::setLogFormat("%{time}{yyyy-MM-dd, HH:mm:ss.zzz} [%{type:-7}] [ %{function:-35} %{line}] %{message}\n"); +#ifdef QT_DEBUG DLogManager::registerConsoleAppender(); - DLogManager::registerFileAppender(); +#endif DLogManager::registerJournalAppender(); QCommandLineParser cmdParser; @@ -203,6 +204,7 @@ int main(int argc, char *argv[]) QObject::connect(WarningContent::instance(), &WarningContent::requestLockFrameHide, [model] { model->setVisible(false); }); + QObject::connect(LockContent::instance(), &LockContent::requestLockStateChange, worker, &LockWorker::setLocked); auto createFrame = [&](QPointer screen, int count) -> QWidget* { LockFrame *lockFrame = new LockFrame(model); diff --git a/src/app/greeter-display-setting.cpp b/src/app/greeter-display-setting.cpp index f54c8363..881d7eb5 100644 --- a/src/app/greeter-display-setting.cpp +++ b/src/app/greeter-display-setting.cpp @@ -108,6 +108,65 @@ static double calcMaxScaleFactor(unsigned int width, unsigned int height) { return maxScale; } +static QStringList getScaleList() +{ + qDebug() << "Get scale list"; + Display *display = XOpenDisplay(nullptr); + if (!display) { + qWarning() << "Display is null"; + return QStringList{}; + } + + XRRScreenResources *resources = XRRGetScreenResourcesCurrent(display, DefaultRootWindow(display)); + if (!resources) { + resources = XRRGetScreenResources(display, DefaultRootWindow(display)); + qCWarning(DDE_SHELL) << "Get current XRR screen resources failed, instead of getting XRR screen resources, resources: " << resources; + } + + static const float MinScreenWidth = 1024.0f; + static const float MinScreenHeight = 768.0f; + static const QStringList tvstring = {"1.0", "1.25", "1.5", "1.75", "2.0", "2.25", "2.5", "2.75", "3.0"}; + QStringList fscaleList; + + if (resources) { + for (int i = 0; i < resources->noutput; i++) { + XRROutputInfo* outputInfo = XRRGetOutputInfo(display, resources, resources->outputs[i]); + if (outputInfo->crtc == 0 || outputInfo->mm_width == 0) + continue; + + XRRCrtcInfo *crtInfo = XRRGetCrtcInfo(display, resources, outputInfo->crtc); + if (crtInfo == nullptr) + continue; + + auto maxWScale = crtInfo->width / MinScreenWidth; + auto maxHScale = crtInfo->height / MinScreenHeight; + auto maxScale = maxWScale < maxHScale ? maxWScale : maxHScale; + maxScale = maxScale < 3.0f ? maxScale : 3.0f; + if (fscaleList.isEmpty()) { + for (int idx = 0; idx * 0.25f + 1.0f <= maxScale; ++idx) { + fscaleList << tvstring[idx]; + } + qDebug() << "First screen scale list:" << fscaleList; + } else { + QStringList tmpList; + for (int idx = 0; idx * 0.25f + 1.0f <= maxScale; ++idx) { + tmpList << tvstring[idx]; + } + qDebug() << "Current screen scale list:" << tmpList; + // fscaleList、tmpList两者取交集 + for (const auto &scale : fscaleList) { + if (!tmpList.contains(scale)) + fscaleList.removeAll(scale); + } + } + } + } else { + qCWarning(DDE_SHELL) << "Get scale factor failed, please check X11 Extension"; + } + + return fscaleList; +} + static double getScaleFactor() { Display *display = XOpenDisplay(nullptr); double scaleFactor = 0.0; @@ -199,6 +258,14 @@ double getScaleFormConfig() qDebug() << "Scale factor from system display config: " << scaleFactor; if(scaleFactor == 0.0) { scaleFactor = defaultScaleFactor; + } else { + // 处理关机期间从高分屏换到低分屏的场景 + const auto &scales = getScaleList(); + qDebug() << "Scales:" << scales; + if (!scales.isEmpty() && !scales.contains(QString::number(scaleFactor, 'f', 2).replace("00", "0"))) { + qInfo() << "Scale factor is not in scales, use default scale factor"; + scaleFactor = defaultScaleFactor; + } } return scaleFactor; } else { diff --git a/src/dde-lock/lockworker.cpp b/src/dde-lock/lockworker.cpp index 62d28358..7ce5e463 100644 --- a/src/dde-lock/lockworker.cpp +++ b/src/dde-lock/lockworker.cpp @@ -190,27 +190,39 @@ void LockWorker::initConnections() const bool sleepLock = isSleepLock(); #endif qCInfo(DDE_SHELL) << "Lock screen when system wakes up: " << sleepLock << ", is visible:" << m_model->visible(); + + FullScreenBackground::setContent(LockContent::instance()); + m_model->setCurrentContentType(SessionBaseModel::LockContent); + if (isSleep) { - endAuthentication(m_account, AT_All); - destroyAuthentication(m_account); checkSystemWakeupTimer->start(); doSuspendTime = QDateTime::currentDateTime(); - } else { - // 非黑屏mode - m_model->setIsBlackMode(isSleep); - - // 如果待机唤醒后需要密码则创建验证 - if(m_login1SessionSelf->active() && sleepLock) - createAuthentication(m_model->currentUser()->name()); - } - if (!m_model->visible() && sleepLock) { - m_model->setIsBlackMode(isSleep); - m_model->setVisible(true); - } + m_model->setIsBlackMode(true); + // 休眠时按需拉起锁屏,注意此时是被动响应休眠 + if (sleepLock && m_login1SessionSelf->active()) { + // 仅sleepLock配置开启时执行 + m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); + setLocked(true); + m_model->setVisible(true); - if (!isSleep && !sleepLock) { - //待机唤醒后检查是否需要密码,若不需要密码直接隐藏锁定界面 - m_model->setVisible(false); + qCInfo(DDE_SHELL) << "locked for sleepLock config"; + } + } else { + SessionBaseModel::ModeStatus status = m_model->currentModeState(); + if (isLocked() || (m_model->isNoPasswordLogin() && (status == SessionBaseModel::ModeStatus::PowerMode || status == SessionBaseModel::ModeStatus::PasswordMode))) { + // 唤醒时如果处于lock状态,重置认证 + if (m_login1SessionSelf->active()) { + endAuthentication(m_account, AT_All); + destroyAuthentication(m_account); + createAuthentication(m_model->currentUser()->name()); + } + } else { + // 处理其他流程设置的可见状态 + m_model->setVisible(false); + } + // 避免在电源选项处理中设置密码模式,会导致唤醒后闪锁屏 + m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); + m_model->setIsBlackMode(false); } emit m_model->prepareForSleep(isSleep); }); @@ -286,40 +298,50 @@ void LockWorker::initConnections() void LockWorker::initData() { - /* com.deepin.daemon.Accounts */ - m_model->updateUserList(m_accountsInter->userList()); - m_model->updateLoginedUserList(m_loginedInter->userList()); - - m_model->setUserlistVisible(valueByQSettings("", "userlist", true)); - /* com.deepin.udcp.iam */ - QDBusInterface ifc(DSS_DBUS::udcpIamService, DSS_DBUS::udcpIamPath, DSS_DBUS::udcpIamService, QDBusConnection::systemBus(), this); - const bool allowShowCustomUser = (!m_model->userlistVisible()) || valueByQSettings("", "loginPromptInput", false) || - ifc.property("Enable").toBool() || checkIsADDomain(); - m_model->setAllowShowCustomUser(allowShowCustomUser); - - /* init server user or custom user */ - if (DSysInfo::deepinType() == DSysInfo::DeepinServer || m_model->allowShowCustomUser()) { - std::shared_ptr user(new User()); - m_model->setIsServerModel(DSysInfo::deepinType() == DSysInfo::DeepinServer); - m_model->addUser(user); - } + auto list = m_accountsInter->userList(); + if (!list.isEmpty()) { + /* com.deepin.daemon.Accounts */ + m_model->updateUserList(m_accountsInter->userList()); + m_model->updateLoginedUserList(m_loginedInter->userList()); + + m_model->setUserlistVisible(valueByQSettings("", "userlist", true)); + /* com.deepin.udcp.iam */ + QDBusInterface ifc(DSS_DBUS::udcpIamService, DSS_DBUS::udcpIamPath, DSS_DBUS::udcpIamService, QDBusConnection::systemBus(), this); + const bool allowShowCustomUser = (!m_model->userlistVisible()) || valueByQSettings("", "loginPromptInput", false) || + ifc.property("Enable").toBool() || checkIsADDomain(); + m_model->setAllowShowCustomUser(allowShowCustomUser); + + /* init server user or custom user */ + if (DSysInfo::deepinType() == DSysInfo::DeepinServer || m_model->allowShowCustomUser()) { + std::shared_ptr user(new User()); + m_model->setIsServerModel(DSysInfo::deepinType() == DSysInfo::DeepinServer); + m_model->addUser(user); + } - /* com.deepin.dde.LockService */ - std::shared_ptr user_ptr = m_model->findUserByUid(getuid()); - if (user_ptr.get()) { - m_model->updateCurrentUser(user_ptr); - const QString &userJson = m_lockInter->CurrentUser(); - QJsonParseError jsonParseError; - const QJsonDocument userDoc = QJsonDocument::fromJson(userJson.toUtf8(), &jsonParseError); - if (jsonParseError.error != QJsonParseError::NoError || userDoc.isEmpty()) { - qCWarning(DDE_SHELL) << "Failed to obtain current user information from lock service!"; + /* com.deepin.dde.LockService */ + std::shared_ptr user_ptr = m_model->findUserByUid(getuid()); + if (user_ptr.get()) { + m_model->updateCurrentUser(user_ptr); + const QString &userJson = m_lockInter->CurrentUser(); + QJsonParseError jsonParseError; + const QJsonDocument userDoc = QJsonDocument::fromJson(userJson.toUtf8(), &jsonParseError); + if (jsonParseError.error != QJsonParseError::NoError || userDoc.isEmpty()) { + qCWarning(DDE_SHELL) << "Failed to obtain current user information from lock service!"; + } else { + const QJsonObject userObj = userDoc.object(); + m_model->currentUser()->setLastAuthType(AUTH_TYPE_CAST(userObj["AuthType"].toInt())); + m_model->currentUser()->setLastCustomAuth(userObj["LastCustomAuth"].toString()); + } } else { - const QJsonObject userObj = userDoc.object(); - m_model->currentUser()->setLastAuthType(AUTH_TYPE_CAST(userObj["AuthType"].toInt())); - m_model->currentUser()->setLastCustomAuth(userObj["LastCustomAuth"].toString()); + m_model->updateCurrentUser(m_lockInter->CurrentUser()); } } else { - m_model->updateCurrentUser(m_lockInter->CurrentUser()); + qCWarning(DDE_SHELL) << "dbus com.deepin.daemon.Accounts userList is empty, use ..."; + + std::shared_ptr user(new User()); + m_model->addUser(user); + m_model->updateCurrentUser(user); + m_model->setAllowShowCustomUser(true); } /* com.deepin.daemon.Authenticate */ @@ -343,6 +365,8 @@ void LockWorker::initConfiguration() m_model->setAllowShowUserSwitchButton(getDconfigValue("switchUser", Ondemand).toInt() == AuthInterface::Ondemand); #endif + m_model->setGsCheckpwd(isCheckPwdBeforeRebootOrShut()); + checkPowerInfo(); } @@ -490,7 +514,6 @@ void LockWorker::doPowerAction(const SessionBaseModel::PowerAction action) case SessionBaseModel::PowerAction::RequireSuspend: { m_model->setIsBlackMode(true); - m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); int delayTime = 500; #ifndef ENABLE_DSS_SNIPE @@ -520,7 +543,6 @@ void LockWorker::doPowerAction(const SessionBaseModel::PowerAction action) case SessionBaseModel::PowerAction::RequireHibernate: { m_model->setIsBlackMode(true); - m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); int delayTime = 500; #ifndef ENABLE_DSS_SNIPE @@ -549,42 +571,44 @@ void LockWorker::doPowerAction(const SessionBaseModel::PowerAction action) } } break; - case SessionBaseModel::PowerAction::RequireRestart: + case SessionBaseModel::PowerAction::RequireRestart: { m_model->setShutdownMode(true); + auto gsCheckPwd = m_model->gsCheckpwd(); + if (!isLocked() || m_model->currentModeState() == SessionBaseModel::ModeStatus::ShutDownMode || !gsCheckPwd) { + m_sessionManagerInter->RequestReboot(); + } else { + createAuthentication(m_account); + m_model->setCurrentModeState(SessionBaseModel::ModeStatus::ConfirmPasswordMode); + } - QTimer::singleShot(350, this, [=] { - if (!isLocked() || m_model->currentModeState() == SessionBaseModel::ModeStatus::ShutDownMode || !isCheckPwdBeforeRebootOrShut()) { - m_sessionManagerInter->RequestReboot(); - } else { - createAuthentication(m_account); - m_model->setCurrentModeState(SessionBaseModel::ModeStatus::ConfirmPasswordMode); - } - - if (m_model->visibleShutdownWhenRebootOrShutdown()) { - return; - } + if (m_model->visibleShutdownWhenRebootOrShutdown()) { + return; + } + if (!gsCheckPwd) m_model->setVisible(false); - }); + return; - case SessionBaseModel::PowerAction::RequireShutdown: + } + case SessionBaseModel::PowerAction::RequireShutdown: { m_model->setShutdownMode(true); + auto gsCheckPwd = m_model->gsCheckpwd(); + if (!isLocked() || m_model->currentModeState() == SessionBaseModel::ModeStatus::ShutDownMode || !gsCheckPwd) { + m_sessionManagerInter->RequestShutdown(); + } else { + createAuthentication(m_account); + m_model->setCurrentModeState(SessionBaseModel::ModeStatus::ConfirmPasswordMode); + } - QTimer::singleShot(350, this, [=] { - if (!isLocked() || m_model->currentModeState() == SessionBaseModel::ModeStatus::ShutDownMode || !isCheckPwdBeforeRebootOrShut()) { - m_sessionManagerInter->RequestShutdown(); - } else { - createAuthentication(m_account); - m_model->setCurrentModeState(SessionBaseModel::ModeStatus::ConfirmPasswordMode); - } - - if (m_model->visibleShutdownWhenRebootOrShutdown()) { - return; - } + if (m_model->visibleShutdownWhenRebootOrShutdown()) { + return; + } + if (!gsCheckPwd) m_model->setVisible(false); - }); + return; + } case SessionBaseModel::PowerAction::RequireLock: m_model->setCurrentModeState(SessionBaseModel::ModeStatus::PasswordMode); createAuthentication(m_model->currentUser()->name()); diff --git a/src/dde-lock/lockworker.h b/src/dde-lock/lockworker.h index 385e57a9..273b3717 100644 --- a/src/dde-lock/lockworker.h +++ b/src/dde-lock/lockworker.h @@ -68,6 +68,7 @@ public slots: void onNoPasswordLoginChanged(const QString &account, bool noPassword); void sendExtraInfo(const QString &account, AuthCommon::AuthType authType, const QString &info); + void setLocked(const bool locked); private: void initConnections(); @@ -76,7 +77,6 @@ public slots: void doPowerAction(const SessionBaseModel::PowerAction action); void setCurrentUser(const std::shared_ptr user); - void setLocked(const bool locked); // lock void lockServiceEvent(quint32 eventType, quint32 pid, const QString &username, const QString &message); diff --git a/src/lightdm-deepin-greeter/greeterworker.cpp b/src/lightdm-deepin-greeter/greeterworker.cpp index 21cf5af3..f8b1daac 100644 --- a/src/lightdm-deepin-greeter/greeterworker.cpp +++ b/src/lightdm-deepin-greeter/greeterworker.cpp @@ -337,38 +337,47 @@ void GreeterWorker::initData() m_model->setSEType(true); } - /* com.deepin.daemon.Accounts */ - m_model->updateUserList(m_accountsInter->userList()); - m_model->updateLoginedUserList(m_loginedInter->userList()); - - m_model->setUserlistVisible(valueByQSettings("", "userlist", true)); - /* com.deepin.udcp.iam */ - QDBusInterface ifc(DSS_DBUS::udcpIamService, DSS_DBUS::udcpIamPath, DSS_DBUS::udcpIamService, QDBusConnection::systemBus(), this); - const bool allowShowCustomUser = (!m_model->userlistVisible()) || valueByQSettings("", "loginPromptInput", false) || - ifc.property("Enable").toBool() || checkIsADDomain(); - m_model->setAllowShowCustomUser(allowShowCustomUser); - - /* init current user */ - if (DSysInfo::deepinType() == DSysInfo::DeepinServer || m_model->allowShowCustomUser()) { - // 如果是服务器版本或者loginPromptInput配置为true,默认显示空用户 - std::shared_ptr user(new User()); - m_model->setIsServerModel(DSysInfo::deepinType() == DSysInfo::DeepinServer); - m_model->addUser(user); - if (DSysInfo::deepinType() == DSysInfo::DeepinServer || valueByQSettings("", "loginPromptInput", false) || !m_model->userlistVisible()) { - m_model->updateCurrentUser(user); + auto list = m_accountsInter->userList(); + if (!list.isEmpty()) { + /* com.deepin.daemon.Accounts */ + m_model->updateUserList(list); + m_model->updateLoginedUserList(m_loginedInter->userList()); + + m_model->setUserlistVisible(valueByQSettings("", "userlist", true)); + /* com.deepin.udcp.iam */ + QDBusInterface ifc(DSS_DBUS::udcpIamService, DSS_DBUS::udcpIamPath, DSS_DBUS::udcpIamService, QDBusConnection::systemBus(), this); + const bool allowShowCustomUser = (!m_model->userlistVisible()) || valueByQSettings("", "loginPromptInput", false) || + ifc.property("Enable").toBool() || checkIsADDomain(); + m_model->setAllowShowCustomUser(allowShowCustomUser); + + /* init current user */ + if (DSysInfo::deepinType() == DSysInfo::DeepinServer || m_model->allowShowCustomUser()) { + // 如果是服务器版本或者loginPromptInput配置为true,默认显示空用户 + std::shared_ptr user(new User()); + m_model->setIsServerModel(DSysInfo::deepinType() == DSysInfo::DeepinServer); + m_model->addUser(user); + if (DSysInfo::deepinType() == DSysInfo::DeepinServer || valueByQSettings("", "loginPromptInput", false) || !m_model->userlistVisible()) { + m_model->updateCurrentUser(user); + } else { + /* com.deepin.dde.LockService */ + m_model->updateCurrentUser(m_lockInter->CurrentUser()); + } } else { /* com.deepin.dde.LockService */ m_model->updateCurrentUser(m_lockInter->CurrentUser()); } - } else { - /* com.deepin.dde.LockService */ - m_model->updateCurrentUser(m_lockInter->CurrentUser()); - } #ifndef ENABLE_DSS_SNIPE m_soundPlayerInter->PrepareShutdownSound(static_cast(m_model->currentUser()->uid())); #else prepareShutdownSound(); #endif + } else { + qCWarning(DDE_SHELL) << "dbus com.deepin.daemon.Accounts userList is empty, use ..."; + m_model->setAllowShowCustomUser(true); + std::shared_ptr user(new User()); + m_model->addUser(user); + m_model->updateCurrentUser(user); + } /* com.deepin.daemon.Authenticate */ if (m_authFramework->isDAStartupCompleted() ) { @@ -1236,4 +1245,4 @@ void GreeterWorker::sendExtraInfo(const QString &account, AuthCommon::AuthType a default: break; } -} \ No newline at end of file +} diff --git a/src/session-widgets/auth_password.cpp b/src/session-widgets/auth_password.cpp index f719042d..e8c3d956 100644 --- a/src/session-widgets/auth_password.cpp +++ b/src/session-widgets/auth_password.cpp @@ -56,6 +56,7 @@ AuthPassword::AuthPassword(QWidget *parent) , m_isPasswdAuthWidgetReplaced(false) , m_assistLoginWidget(nullptr) , m_authenticationDconfig(DConfig::create("org.deepin.dde.authentication", "org.deepin.dde.authentication.errorecho", QString(), this)) + , m_canShowPasswordErrorTips(false) { qRegisterMetaType("LoginPlugin::PluginConfig"); @@ -72,6 +73,13 @@ AuthPassword::AuthPassword(QWidget *parent) setFocusProxy(m_lineEdit); } +AuthPassword::~AuthPassword() +{ + if (m_resetPasswordMessageVisible) { + closeResetPasswordMessage(); + } +} + /** * @brief 初始化界面 */ @@ -264,6 +272,16 @@ void AuthPassword::initConnections() }); connect(m_assistLoginWidget, &AssistLoginWidget::readyToAuthChanged, this, &AuthPassword::onReadyToAuthChanged); } + + if (m_authenticationDconfig) { + auto updateCanShow = [this] { + m_canShowPasswordErrorTips = m_authenticationDconfig->value("PasswordErrorEcho", false).toBool(); + }; + + connect(m_authenticationDconfig, &DConfig::valueChanged, this, updateCanShow); + + updateCanShow(); + } } /** @@ -963,15 +981,6 @@ bool AuthPassword::isShowPasswrodErrorTip() return m_passwordTipsWidget->isVisible(); } -bool AuthPassword::canShowPasswrodErrorTip() -{ - // 从da的dconfig配置获取 - if (!m_authenticationDconfig) { - return false; - } - return m_authenticationDconfig->value("PasswordErrorEcho", false).toBool(); -} - void AuthPassword::showErrorTip(const QString &text) { if (canShowPasswrodErrorTip()) { diff --git a/src/session-widgets/auth_password.h b/src/session-widgets/auth_password.h index cdfe01ef..f970b3f3 100644 --- a/src/session-widgets/auth_password.h +++ b/src/session-widgets/auth_password.h @@ -27,6 +27,7 @@ class AuthPassword : public AuthModule Q_OBJECT public: explicit AuthPassword(QWidget *parent = nullptr); + ~AuthPassword() override; void reset(); QString lineEditText() const; @@ -61,7 +62,7 @@ class AuthPassword : public AuthModule m_isPasswdAuthWidgetReplaced = isPasswdAuthWidgetReplaced; } bool isShowPasswrodErrorTip(); - bool canShowPasswrodErrorTip(); + inline bool canShowPasswrodErrorTip() { return m_canShowPasswordErrorTips; } void showErrorTip(const QString &text); void clearPasswrodErrorTip(bool isClear); void updatePasswrodErrorTipUi(); @@ -124,6 +125,7 @@ public slots: bool m_isPasswdAuthWidgetReplaced; AssistLoginWidget *m_assistLoginWidget; DConfig *m_authenticationDconfig; + bool m_canShowPasswordErrorTips; }; #endif // AUTHPASSWORD_H diff --git a/src/session-widgets/lockcontent.cpp b/src/session-widgets/lockcontent.cpp index 9d5ceb24..074398de 100644 --- a/src/session-widgets/lockcontent.cpp +++ b/src/session-widgets/lockcontent.cpp @@ -420,6 +420,10 @@ void LockContent::pushUserFrame() // showDefaultFrame() -> hideStackedWidgets() -> 会将焦点置为空 // 导致默认用户无选中状态,多账户区域无键盘事件 setFocus(); + + // 用户列表界面显示时,设置锁屏状态为锁定 + // 主要是为了处理需求:切换用户界面属于进入锁屏,待机唤醒时仍进入锁屏界面,需输入密码进入系统。 + Q_EMIT requestLockStateChange(true); } void LockContent::pushConfirmFrame() diff --git a/src/session-widgets/lockcontent.h b/src/session-widgets/lockcontent.h index 65f68c46..404780eb 100644 --- a/src/session-widgets/lockcontent.h +++ b/src/session-widgets/lockcontent.h @@ -61,6 +61,7 @@ class LockContent : public SessionBaseWindow void requestLockFrameHide(); void parentChanged(); void noPasswordLoginChanged(const QString &account, bool noPassword); + void requestLockStateChange(const bool locked); public slots: void pushPasswordFrame(); diff --git a/src/session-widgets/sessionbasemodel.cpp b/src/session-widgets/sessionbasemodel.cpp index bf35fc82..b7f09194 100644 --- a/src/session-widgets/sessionbasemodel.cpp +++ b/src/session-widgets/sessionbasemodel.cpp @@ -42,6 +42,7 @@ SessionBaseModel::SessionBaseModel(QObject *parent) , m_enableShellBlackMode(DConfigHelper::instance()->getConfig("enableShellBlack", true).toBool()) , m_enableShutdownBlackWidget(DConfigHelper::instance()->getConfig("enableShutdownBlackWidget", true).toBool()) , m_visibleShutdownWhenRebootOrShutdown(DConfigHelper::instance()->getConfig("visibleShutdownWhenRebootOrShutdown", true).toBool()) + , m_gsCheckpwd(false) { #ifndef ENABLE_DSS_SNIPE if (QGSettings::isSchemaInstalled("com.deepin.dde.power")) { @@ -122,7 +123,7 @@ void SessionBaseModel::setPowerAction(const PowerAction &powerAction) m_powerAction = powerAction; - if (m_enableShutdownBlackWidget && (powerAction == SessionBaseModel::PowerAction::RequireRestart || powerAction == SessionBaseModel::PowerAction::RequireShutdown)) + if (m_enableShutdownBlackWidget && !gsCheckpwd() && (powerAction == SessionBaseModel::PowerAction::RequireRestart || powerAction == SessionBaseModel::PowerAction::RequireShutdown)) Q_EMIT shutdownkModeChanged(true); emit onPowerActionChanged(powerAction); @@ -247,7 +248,7 @@ void SessionBaseModel::setIsBlackMode(bool is_black) void SessionBaseModel::setShutdownMode(bool is_black) { - if (!m_enableShutdownBlackWidget) { + if (!m_enableShutdownBlackWidget || gsCheckpwd()) { return; } Q_EMIT shutdownkModeChanged(is_black); @@ -726,3 +727,22 @@ void SessionBaseModel::setQuickLoginProcess(bool val) { m_isQuickLoginProcess = val; } + +void SessionBaseModel::setGsCheckpwd(bool value) +{ + if (m_gsCheckpwd != value) { + m_gsCheckpwd = value; + } +} + +bool SessionBaseModel::isNoPasswordLogin() const +{ + // 检查当前用户是否存在 + if (!m_currentUser) { + qCWarning(DDE_SHELL) << "Current user is null"; + return false; + } + + // 直接调用当前用户的isNoPasswordLogin方法 + return m_currentUser->isNoPasswordLogin(); +} \ No newline at end of file diff --git a/src/session-widgets/sessionbasemodel.h b/src/session-widgets/sessionbasemodel.h index 9032e6ba..784c7c61 100644 --- a/src/session-widgets/sessionbasemodel.h +++ b/src/session-widgets/sessionbasemodel.h @@ -189,6 +189,12 @@ class SessionBaseModel : public QObject inline bool isQuickLoginProcess() const { return m_isQuickLoginProcess; } void setQuickLoginProcess(bool ); + inline bool gsCheckpwd() const { return m_gsCheckpwd; } + void setGsCheckpwd(bool value); + + // 检查当前用户是否设置了免密登录 + bool isNoPasswordLogin() const; + signals: /* com.deepin.daemon.Accounts */ void currentUserChanged(const std::shared_ptr); @@ -317,6 +323,7 @@ public slots: bool m_enableShutdownBlackWidget; bool m_visibleShutdownWhenRebootOrShutdown; bool m_isQuickLoginProcess=false;//标志当前界面展示是否为快速登录流程 + bool m_gsCheckpwd; }; #endif // SESSIONBASEMODEL_H diff --git a/src/session-widgets/sfa_widget.cpp b/src/session-widgets/sfa_widget.cpp index 8af47b08..067d1391 100644 --- a/src/session-widgets/sfa_widget.cpp +++ b/src/session-widgets/sfa_widget.cpp @@ -171,7 +171,7 @@ void SFAWidget::setAuthType(const AuthFlags type) chooseAuthType(authType); // 如果是无密码认证,焦点默认在解锁按钮上面 - if (m_model->currentUser()->isNoPasswordLogin()) { + if (m_model->currentUser()->isNoPasswordLogin() && !m_model->terminalLocked()) { m_lockButton->setEnabled(true); setFocusProxy(m_lockButton); setFocus(); diff --git a/src/widgets/fullscreenbackground.cpp b/src/widgets/fullscreenbackground.cpp index 6fc81674..4b60cd5f 100644 --- a/src/widgets/fullscreenbackground.cpp +++ b/src/widgets/fullscreenbackground.cpp @@ -167,7 +167,8 @@ void FullScreenBackground::setScreen(QPointer screen, bool isVisible) qCInfo(DDE_SHELL) << "Set screen:" << screen << ", screen geometry:" << screen->geometry() - << ", full screen background object:" << this; + << ", full screen background object:" << this + << ", visible:" << isVisible; if (isVisible) { updateCurrentFrame(this); } else { @@ -287,6 +288,7 @@ void FullScreenBackground::enterEvent(QEnterEvent *event) void FullScreenBackground::enterEvent(QEvent *event) #endif { + qCInfo(DDE_SS) << "Enter event, enable enter event:" << m_enableEnterEvent << ", visible:" << m_model->visible(); if (m_enableEnterEvent && m_model->visible()) { updateCurrentFrame(this); // 多屏情况下,此Frame晚于其它Frame显示出来时,可能处于未激活状态(特别是在wayland环境下比较明显) @@ -649,6 +651,8 @@ void FullScreenBackground::updateCurrentFrame(FullScreenBackground *frame) if (frame->m_screen) qCInfo(DDE_SHELL) << "Update current frame:" << frame << ", screen:" << frame->m_screen->name(); + else + qWarning() << "Frame's screen is null, frame:" << frame; currentFrame = frame; setContent(currentContent); diff --git a/src/widgets/passworderrortipswidget.cpp b/src/widgets/passworderrortipswidget.cpp index ce1e3515..4469bd1d 100644 --- a/src/widgets/passworderrortipswidget.cpp +++ b/src/widgets/passworderrortipswidget.cpp @@ -39,10 +39,12 @@ PasswordErrorTipsWidget::PasswordErrorTipsWidget(QWidget *parent) void PasswordErrorTipsWidget::initUi() { QVBoxLayout* mainLay = new QVBoxLayout(this); + mainLay->setContentsMargins(10, 5, 10, 5); mainLay->setSpacing(0); QHBoxLayout* hlay = new QHBoxLayout(m_tipsWidget); - hlay->setSpacing(0); + hlay->setContentsMargins(0, 0, 0, 0); + hlay->setSpacing(5); hlay->addWidget(m_tipLabel, 0, Qt::AlignmentFlag::AlignLeft | Qt::AlignmentFlag::AlignTop); DFontSizeManager::instance()->bind(m_tipLabel, DFontSizeManager::T6); m_tipLabel->setForegroundRole(DPalette::TextWarning); @@ -50,9 +52,8 @@ void PasswordErrorTipsWidget::initUi() QPixmap pixmap; pixmap.load(":img/warning.svg"); m_showMore->setPixmap(pixmap); - m_showMore->hide(); + m_showMore->setVisible(false); - hlay->addSpacing(5); hlay->addWidget(m_showMore, 0, Qt::AlignmentFlag::AlignCenter); mainLay->addWidget(m_tipsWidget, 0, Qt::AlignmentFlag::AlignTop | Qt::AlignmentFlag::AlignLeft); @@ -152,4 +153,4 @@ void PasswordErrorTipsWidget::reset() bool PasswordErrorTipsWidget::hasErrorTips() { return !m_tipLabel->text().isEmpty(); -} \ No newline at end of file +}