package controllers import ( "encoding/json" "fmt" "io" "strconv" "strings" "server/models" "server/pkg/jwtutil" beego "github.com/beego/beego/v2/server/web" ) // PlatformAuthIdpController 统一认证 —— 第三方登录配置(平台端管理) // // 配置说明: // - tid = 0 :平台全局配置,所有租户共用 // - tid > 0 :租户自带身份源,仅该租户可见(第10条需求:企业自带钉钉/飞书等) // // 配置对象只有「租户用户登录」,平台端自身不使用第三方登录。 type PlatformAuthIdpController struct { beego.Controller } func (c *PlatformAuthIdpController) serveJSON(data map[string]interface{}) { c.Data["json"] = data _ = c.ServeJSON() } // Prepare 统一鉴权:仅平台管理员可维护第三方登录配置 func (c *PlatformAuthIdpController) Prepare() { authHeader := c.Ctx.Request.Header.Get("Authorization") if authHeader == "" { c.Ctx.Output.SetStatus(401) c.serveJSON(map[string]interface{}{"code": 401, "msg": "未登录"}) c.StopRun() return } parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || parts[0] != "Bearer" { c.Ctx.Output.SetStatus(401) c.serveJSON(map[string]interface{}{"code": 401, "msg": "认证信息格式错误"}) c.StopRun() return } claims, err := jwtutil.ParseToken(parts[1]) if err != nil { c.Ctx.Output.SetStatus(401) c.serveJSON(map[string]interface{}{"code": 401, "msg": "登录已失效,请重新登录"}) c.StopRun() return } if claims.UserType != "platform" { c.Ctx.Output.SetStatus(403) c.serveJSON(map[string]interface{}{"code": 403, "msg": "无权访问"}) c.StopRun() return } } type authIdpPayload struct { Tid uint64 `json:"tid"` Provider string `json:"provider"` Name string `json:"name"` AppID string `json:"app_id"` AppSecret string `json:"app_secret"` ProxyURL string `json:"proxy_url"` Scopes string `json:"scopes"` AuthURL string `json:"auth_url"` TokenURL string `json:"token_url"` UserinfoURL string `json:"userinfo_url"` Status *int8 `json:"status"` } func (c *PlatformAuthIdpController) parsePayload() (authIdpPayload, bool) { var p authIdpPayload raw, _ := io.ReadAll(c.Ctx.Request.Body) if len(raw) > 0 { if err := json.Unmarshal(raw, &p); err != nil { c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) return p, false } } return p, true } // List 配置列表 // GET /platform/authIdp/list?tid=0 func (c *PlatformAuthIdpController) List() { tid, _ := c.GetInt64("tid", 0) var rows []models.AuthTenantIdp if _, err := models.Orm.QueryTable(new(models.AuthTenantIdp)). Filter("tid", tid).OrderBy("id").All(&rows); err != nil { c.serveJSON(map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()}) return } // 脱敏:不下发 AppSecret list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { list = append(list, map[string]interface{}{ "id": r.ID, "tid": r.Tid, "provider": r.Provider, "name": derefAuthIdp(r.Name), "app_id": derefAuthIdp(r.AppID), "proxy_url": derefAuthIdp(r.ProxyURL), "scopes": derefAuthIdp(r.Scopes), "auth_url": derefAuthIdp(r.AuthURL), "token_url": derefAuthIdp(r.TokenURL), "userinfo_url": derefAuthIdp(r.UserinfoURL), "status": r.Status, "has_secret": r.AppSecret != nil && *r.AppSecret != "", }) } c.serveJSON(map[string]interface{}{ "code": 200, "msg": "success", "data": map[string]interface{}{"list": list, "total": len(list)}, }) } // Create 新增配置 // POST /platform/authIdp/create func (c *PlatformAuthIdpController) Create() { p, ok := c.parsePayload() if !ok { return } p.Provider = strings.TrimSpace(p.Provider) if p.Provider == "" { c.serveJSON(map[string]interface{}{"code": 400, "msg": "provider 不能为空"}) return } if exist := models.Orm.QueryTable(new(models.AuthTenantIdp)). Filter("tid", p.Tid).Filter("provider", p.Provider).Exist(); exist { c.serveJSON(map[string]interface{}{"code": 400, "msg": "该登录方式已配置,请直接编辑"}) return } item := &models.AuthTenantIdp{ Tid: p.Tid, Provider: p.Provider, Status: 1, } fillAuthIdp(item, &p) if p.Status != nil { item.Status = *p.Status } id, err := models.Orm.Insert(item) if err != nil { c.serveJSON(map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()}) return } c.serveJSON(map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}}) } // Edit 编辑配置(app_secret 留空表示不修改) // POST /platform/authIdp/edit/:id func (c *PlatformAuthIdpController) Edit() { id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.serveJSON(map[string]interface{}{"code": 400, "msg": "无效ID"}) return } p, ok := c.parsePayload() if !ok { return } var row models.AuthTenantIdp if err := models.Orm.QueryTable(new(models.AuthTenantIdp)).Filter("id", id).One(&row); err != nil { c.serveJSON(map[string]interface{}{"code": 404, "msg": "记录不存在"}) return } fillAuthIdp(&row, &p) if p.Status != nil { row.Status = *p.Status } if _, err := models.Orm.Update(&row); err != nil { c.serveJSON(map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()}) return } c.serveJSON(map[string]interface{}{"code": 200, "msg": "success"}) } // Delete 删除配置 // DELETE /platform/authIdp/delete/:id func (c *PlatformAuthIdpController) Delete() { id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.serveJSON(map[string]interface{}{"code": 400, "msg": "无效ID"}) return } if _, err := models.Orm.QueryTable(new(models.AuthTenantIdp)).Filter("id", id).Delete(); err != nil { c.serveJSON(map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()}) return } c.serveJSON(map[string]interface{}{"code": 200, "msg": "success"}) } // CallbackHint 返回各平台需要填写的回调地址(方便复制) // GET /platform/authIdp/callbackHint?provider=wechat func (c *PlatformAuthIdpController) CallbackHint() { provider := strings.TrimSpace(c.GetString("provider")) base := jwtutil.Issuer() if base == "" { scheme := "https" if c.Ctx.Request.TLS == nil { scheme = "http" } base = fmt.Sprintf("%s://%s/auth", scheme, c.Ctx.Request.Host) } c.serveJSON(map[string]interface{}{ "code": 200, "data": map[string]interface{}{ "callback": fmt.Sprintf("%s/third/%s/callback", base, provider), }, }) } // ---------------------------------------------------------------- 工具 // fillAuthIdp 把请求参数写入模型;留空字段表示不修改 func fillAuthIdp(item *models.AuthTenantIdp, p *authIdpPayload) { if v := strings.TrimSpace(p.Name); v != "" { item.Name = &v } if v := strings.TrimSpace(p.AppID); v != "" { item.AppID = &v } if v := strings.TrimSpace(p.AppSecret); v != "" { item.AppSecret = &v } if v := strings.TrimSpace(p.ProxyURL); v != "" { item.ProxyURL = &v } if v := strings.TrimSpace(p.Scopes); v != "" { item.Scopes = &v } if v := strings.TrimSpace(p.AuthURL); v != "" { item.AuthURL = &v } if v := strings.TrimSpace(p.TokenURL); v != "" { item.TokenURL = &v } if v := strings.TrimSpace(p.UserinfoURL); v != "" { item.UserinfoURL = &v } } func derefAuthIdp(p *string) string { if p == nil { return "" } return *p }