Ver código fonte

feat: 新增普通群outgoing机器人限制逻辑,提升安全性;同时修改allow_groups配置为群ID,群ID获取方法请见配置文件说明 (#191)

Finly 2 anos atrás
pai
commit
0c9cfd4639
6 arquivos alterados com 75 adições e 16 exclusões
  1. 9 5
      README.md
  2. 7 1
      config.example.yml
  3. 9 3
      config/config.go
  4. 5 1
      docker-compose.yml
  5. 30 4
      main.go
  6. 15 2
      public/tools.go

+ 9 - 5
README.md

@@ -216,7 +216,7 @@ $ docker run -itd --name chatgpt -p 8090:8090 \
   -e HTTP_PROXY="http://host.docker.internal:15732" \
   -e DEFAULT_MODE="单聊" -e MAX_REQUEST=0 -e PORT=8090 \
   -e SERVICE_URL="你当前服务外网可访问的URL" -e CHAT_TYPE="0" \
-  -e ALLOW_GROUPS=a,b -e ALLOW_USERS=a,b -e DENY_USERS=a,b -e VIP_USERS=a,b -e ADMIN_USERS=a,b -e APP_SECRETS="xxx,yyy" \
+  -e ALLOW_GROUPS=a,b -e ALLOW_OUTGOING_GROUPS=a,b -e ALLOW_USERS=a,b -e DENY_USERS=a,b -e VIP_USERS=a,b -e ADMIN_USERS=a,b -e APP_SECRETS="xxx,yyy" \
   -e AZURE_ON="false" -e AZURE_API_VERSION="" -e AZURE_RESOURCE_NAME="" \
   -e AZURE_DEPLOYMENT_NAME="" -e AZURE_OPENAI_TOKEN="" \
   -e HELP="欢迎使用本工具\n\n你可以查看:[用户指南](https://github.com/eryajf/chatgpt-dingtalk/blob/main/docs/userGuide.md)\n\n这是一个[开源项目](https://github.com/eryajf/chatgpt-dingtalk/)
@@ -459,10 +459,14 @@ port: "8090"
 service_url: "http://chat.eryajf.net"
 # 限定对话类型 0:不限 1:只能单聊 2:只能群聊
 chat_type: "0"
-# 哪些群组可以进行对话,如果留空,则表示允许所有群组,如果要限制,则写群组的名称,比如 ["aa","bb"]
-# 对话聊天时,如下三个满足其一即可通过校验
-allow_groups:
-  - "学无止境"
+# 哪些群组可以进行对话(仅在chat_type为0、2时有效),如果留空,则表示允许所有群组,如果要限制,则列表中写群ID(ConversationID)
+# 群ID,可在群组中 @机器人 群ID 来查看日志获取,例如日志会输出:[🙋 企业内部机器人 在『测试』群的ConversationID为: "cidrabcdefgh1234567890AAAAA"],获取后可填写该参数并重启程序
+allow_groups: []
+# 哪些普通群(使用outgoing机器人)可以进行对话,如果留空,则表示允许所有群组,如果要限制,则列表中写群ID(ConversationID)
+# 群ID,可在群组中 @机器人 群ID 来查看日志获取,例如日志会输出:[🙋 outgoing机器人 在『测试』群的ConversationID为: "cidrabcdefgh1234567890AAAAA"],获取后可填写该参数并重启程序
+# 如果不想支持outgoing机器人功能,这里可以随意设置一个内部群组,例如:cidrabcdefgh1234567890AAAAA;或随意一个字符串,例如:disabled
+# 建议该功能默认关闭:除非你必须要用到outgoing机器人
+allow_outgoing_groups: ["disabled"]
 # 以下 allow_users、deny_users、vip_users、admin_users 配置中填写的是用户的userid,outgoing机器人模式下不适用这些配置
 # 比如 ["1301691029702722","1301691029702733"],这个信息需要在钉钉管理后台的通讯录当中获取:https://oa.dingtalk.com/contacts.htm#/contacts
 # 哪些用户可以进行对话,如果留空,则表示允许所有用户,如果要限制,则列表中写用户的userid

+ 7 - 1
config.example.yml

@@ -20,8 +20,14 @@ port: "8090"
 service_url: "http://xxxxxx"
 # 限定对话类型 0:不限 1:只能单聊 2:只能群聊
 chat_type: "0"
-# 哪些群组可以进行对话,如果留空,则表示允许所有群组,如果要限制,则列表中写群组的名称,比如 ["aa","bb"]
+# 哪些群组可以进行对话(仅在chat_type为0、2时有效),如果留空,则表示允许所有群组,如果要限制,则列表中写群ID(ConversationID)
+# 群ID,可在群组中 @机器人 群ID 来查看日志获取,例如日志会输出:[🙋 企业内部机器人 在『测试』群的ConversationID为: "cidrabcdefgh1234567890AAAAA"],获取后可填写该参数并重启程序
 allow_groups: []
+# 哪些普通群(使用outgoing机器人)可以进行对话,如果留空,则表示允许所有群组,如果要限制,则列表中写群ID(ConversationID)
+# 群ID,可在群组中 @机器人 群ID 来查看日志获取,例如日志会输出:[🙋 outgoing机器人 在『测试』群的ConversationID为: "cidrabcdefgh1234567890AAAAA"],获取后可填写该参数并重启程序
+# 如果不想支持outgoing机器人功能,这里可以随意设置一个内部群组,例如:cidrabcdefgh1234567890AAAAA;或随意一个字符串,例如:disabled
+# 建议该功能默认关闭:除非你必须要用到outgoing机器人
+allow_outgoing_groups: ["disabled"]
 # 以下 allow_users、deny_users、vip_users、admin_users 配置中填写的是用户的userid,outgoing机器人模式下不适用这些配置
 # 比如 ["1301691029702722","1301691029702733"],这个信息需要在钉钉管理后台的通讯录当中获取:https://oa.dingtalk.com/contacts.htm#/contacts
 # 哪些用户可以进行对话,如果留空,则表示允许所有用户,如果要限制,则列表中写用户的userid

+ 9 - 3
config/config.go

@@ -40,6 +40,8 @@ type Configuration struct {
 	ChatType string `yaml:"chat_type"`
 	// 哪些群组可以进行对话
 	AllowGroups []string `yaml:"allow_groups"`
+	// 哪些outgoing群组可以进行对话
+	AllowOutgoingGroups []string `yaml:"allow_outgoing_groups"`
 	// 哪些用户可以进行对话
 	AllowUsers []string `yaml:"allow_users"`
 	// 哪些用户不可以进行对话
@@ -130,9 +132,13 @@ func LoadConfig() *Configuration {
 		if chatType != "" {
 			config.ChatType = chatType
 		}
-		allowGroup := os.Getenv("ALLOW_GROUPS")
-		if allowGroup != "" {
-			config.AllowGroups = strings.Split(allowGroup, ",")
+		allowGroups := os.Getenv("ALLOW_GROUPS")
+		if allowGroups != "" {
+			config.AllowGroups = strings.Split(allowGroups, ",")
+		}
+		allowOutgoingGroups := os.Getenv("ALLOW_OUTGOING_GROUPS")
+		if allowOutgoingGroups != "" {
+			config.AllowOutgoingGroups = strings.Split(allowOutgoingGroups, ",")
 		}
 		allowUsers := os.Getenv("ALLOW_USERS")
 		if allowUsers != "" {

+ 5 - 1
docker-compose.yml

@@ -17,7 +17,11 @@ services:
       PORT: 8090 # 指定服务启动端口,默认为 8090,容器化部署时,不需要调整,一般在二进制宿主机部署时,遇到端口冲突时使用
       SERVICE_URL: ""  # 指定服务的地址,就是当前服务可供外网访问的地址(或者直接理解为你配置在钉钉回调那里的地址),用于生成图片时给钉钉做渲染
       CHAT_TYPE: "0" # 限定对话类型 0:不限 1:只能单聊 2:只能群聊
-      ALLOW_GROUPS: "" # 哪些群组可以进行对话,如果留空,则表示允许所有群组,如果要限制,则填写群组的名字,比如 "aa,bb"
+      ALLOW_GROUPS: "" # 哪些群组可以进行对话(仅在CHAT_TYPE为0、2时有效),如果留空,则表示允许所有群组,如果要限制,则列表中写群ID(ConversationID)
+      # 群ID,可在群组中 @机器人 群ID 来查看日志获取,例如日志会输出:[🙋 企业内部机器人 在『测试』群的ConversationID为: "cidrabcdefgh1234567890AAAAA"],获取后可填写该参数并重启程序
+      # 如果不想支持outgoing机器人功能,这里可以随意设置一个内部群组,例如:cidrabcdefgh1234567890AAAAA;或随意一个字符串,例如:disabled
+      # 建议该功能默认关闭:除非你必须要用到outgoing机器人
+      ALLOW_OUTGOING_GROUPS: "disabled"   # 哪些普通群(使用outgoing机器人)可以进行对话,如果留空,则表示允许所有群组,如果要限制,则列表中写群ID(ConversationID)
       # 以下 ALLOW_USERS、DENY_USERS、VIP_USERS、ADMIN_USERS 配置中填写的是用户的userid
       # 比如 ["1301691029702722","1301691029702733"],这个信息需要在钉钉管理后台的通讯录当中获取:https://oa.dingtalk.com/contacts.htm#/contacts
       # 哪些用户可以进行对话,如果留空,则表示允许所有用户,如果要限制,则列表中写用户的userid

+ 30 - 4
main.go

@@ -34,9 +34,12 @@ func Start() {
 			return ship.ErrBadRequest.New(fmt.Errorf("bind to receivemsg failed : %v", err))
 		}
 		// 先校验回调是否合法
-		if !public.CheckRequest(c.GetReqHeader("timestamp"), c.GetReqHeader("sign")) {
+		if !public.CheckRequest(c.GetReqHeader("timestamp"), c.GetReqHeader("sign")) && msgObj.SenderStaffId != "" {
 			logger.Warning("该请求不合法,可能是其他企业或者未经允许的应用调用所致,请知悉!")
 			return nil
+		} else if !public.JudgeOutgoingGroup(msgObj.ConversationID) && msgObj.SenderStaffId == "" {
+			logger.Warning("该请求不合法,可能是未经允许的普通群outgoing机器人调用所致,请知悉!")
+			return nil
 		}
 		// 再校验回调参数是否有价值
 		if msgObj.Text.Content == "" || msgObj.ChatbotUserID == "" {
@@ -57,9 +60,33 @@ func Start() {
 			}
 			return nil
 		}
+
+		// 查询群ID,发送指令后,可通过查看日志来获取
+		if msgObj.ConversationType == "2" && msgObj.Text.Content == "群ID" {
+			if msgObj.RobotCode == "normal" {
+				logger.Info(fmt.Sprintf("🙋 outgoing机器人 在『%s』群的ConversationID为: %#v", msgObj.ConversationTitle, msgObj.ConversationID))
+			} else {
+				logger.Info(fmt.Sprintf("🙋 企业内部机器人 在『%s』群的ConversationID为: %#v", msgObj.ConversationTitle, msgObj.ConversationID))
+			}
+			//_, err = msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), msgObj.ConversationID)
+			if err != nil {
+				logger.Warning(fmt.Errorf("send message error: %v", err))
+				return err
+			}
+			return nil
+		}
+
 		// 不在允许群组,不在允许用户(包括在黑名单),满足任一条件,拒绝会话;管理员不受限制
-		if (!public.JudgeGroup(msgObj.GetChatTitle()) || !public.JudgeUsers(msgObj.SenderStaffId)) && !public.JudgeAdminUsers(msgObj.SenderStaffId) {
-			logger.Info(fmt.Sprintf("🙋 %s身份信息未被验证通过", msgObj.SenderNick))
+		if !public.JudgeGroup(msgObj.ConversationID) && !public.JudgeAdminUsers(msgObj.SenderStaffId) && msgObj.SenderStaffId != "" {
+			logger.Info(fmt.Sprintf("🙋『%s』群组未被验证通过,群ID: %#v,userid:%#v, 昵称: %#v,消息: %#v", msgObj.ConversationTitle, msgObj.ConversationID, msgObj.SenderStaffId, msgObj.SenderNick, msgObj.Text.Content))
+			_, err = msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,该群组未被认证通过,无法使用机器人对话功能。**\n>如需继续使用,请联系管理员申请访问权限。")
+			if err != nil {
+				logger.Warning(fmt.Errorf("send message error: %v", err))
+				return err
+			}
+			return nil
+		} else if !public.JudgeUsers(msgObj.SenderStaffId) && !public.JudgeAdminUsers(msgObj.SenderStaffId) && msgObj.SenderStaffId != "" {
+			logger.Info(fmt.Sprintf("🙋 %s身份信息未被验证通过,userid:%#v,消息: %#v", msgObj.SenderNick, msgObj.SenderStaffId, msgObj.Text.Content))
 			_, err = msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,您的身份信息未被认证通过,无法使用机器人对话功能。**\n>如需继续使用,请联系管理员申请访问权限。")
 			if err != nil {
 				logger.Warning(fmt.Errorf("send message error: %v", err))
@@ -127,7 +154,6 @@ func Start() {
 			"status": "ok",
 			"msg":    "欢迎使用钉钉机器人",
 		})
-
 	})
 	port := ":" + public.Config.Port
 	srv := &http.Server{

+ 15 - 2
public/tools.go

@@ -29,7 +29,7 @@ func WriteToFile(path string, data []byte) error {
 	return nil
 }
 
-// JudgeGroup 判断群聊名称是否在白名单
+// JudgeGroup 判断群ID是否在白名单
 func JudgeGroup(s string) bool {
 	if len(Config.AllowGroups) == 0 {
 		return true
@@ -42,7 +42,20 @@ func JudgeGroup(s string) bool {
 	return false
 }
 
-// JudgeUsers 判断用户名称是否在白名单
+// JudgeOutgoingGroup 判断群ID是否在为outgoing白名单
+func JudgeOutgoingGroup(s string) bool {
+	if len(Config.AllowOutgoingGroups) == 0 {
+		return true
+	}
+	for _, v := range Config.AllowOutgoingGroups {
+		if v == s {
+			return true
+		}
+	}
+	return false
+}
+
+// JudgeUsers 判断用户是否在白名单
 func JudgeUsers(s string) bool {
 	// 优先判断黑名单,黑名单用户返回:不在白名单
 	if len(Config.DenyUsers) != 0 {