258 lines
7.4 KiB
Go
258 lines
7.4 KiB
Go
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
|
|
}
|