diff --git a/conf/app.conf.example b/conf/app.conf.example
index 3bc5d116..b7ce3b31 100644
--- a/conf/app.conf.example
+++ b/conf/app.conf.example
@@ -224,6 +224,12 @@ dingtalk_app_secret="${MINDOC_DINGTALK_APPSECRET}"
# 钉钉登录默认只读账号
dingtalk_tmp_reader="${MINDOC_DINGTALK_READER}"
+# 钉钉扫码登录Key
+dingtalk_qr_key="${MINDOC_DINGTALK_QRKEY}"
+
+# 钉钉扫码登录Secret
+dingtalk_qr_secret="${MINDOC_DINGTALK_QRSECRET}"
+
diff --git a/controllers/AccountController.go b/controllers/AccountController.go
index a40b7487..ff126c81 100644
--- a/controllers/AccountController.go
+++ b/controllers/AccountController.go
@@ -34,8 +34,14 @@ func (c *AccountController) referer() string {
func (c *AccountController) Prepare() {
c.BaseController.Prepare()
c.EnableXSRF = web.AppConfig.DefaultBool("enablexsrf", true)
+
c.Data["xsrfdata"] = template.HTML(c.XSRFFormHTML())
- c.Data["corpID"],_ = web.AppConfig.String("dingtalk_corpid")
+ c.Data["corpID"], _ = web.AppConfig.String("dingtalk_corpid")
+ if dtcorpid, _ := web.AppConfig.String("dingtalk_corpid"); dtcorpid != "" {
+ c.Data["ENABLE_QR_DINGTALK"] = true
+ }
+ c.Data["dingtalk_qr_key"], _ = web.AppConfig.String("dingtalk_qr_key")
+
if !c.EnableXSRF {
return
}
@@ -166,14 +172,14 @@ func (c *AccountController) DingTalkLogin() {
userid, err := dingtalkAgent.GetUserIDByCode(code)
if err != nil {
- logs.Warn("钉钉自动登录失败 ->", err)
+ logs.Warn("获取钉钉用户ID失败 ->", err)
c.JsonResult(500, "自动登录失败", nil)
c.StopRun()
}
username, avatar, err := dingtalkAgent.GetUserNameAndAvatarByUserID(userid)
if err != nil {
- logs.Warn("钉钉自动登录失败 ->", err)
+ logs.Warn("获取钉钉用户信息失败 ->", err)
c.JsonResult(500, "自动登录失败", nil)
c.StopRun()
}
@@ -192,6 +198,79 @@ func (c *AccountController) DingTalkLogin() {
c.JsonResult(0, "ok", username)
}
+// QR二维码登录
+func (c *AccountController) QRLogin() {
+ c.Prepare()
+
+ appName := c.Ctx.Input.Param(":app")
+
+ switch appName {
+ // 钉钉扫码登录
+ case "dingtalk":
+ code := c.GetString("code")
+ state := c.GetString("state")
+ if state != "1" || code == "" {
+ c.Redirect(conf.URLFor("AccountController.Login"), 302)
+ c.StopRun()
+ }
+ appKey, _ := web.AppConfig.String("dingtalk_qr_key")
+ appSecret, _ := web.AppConfig.String("dingtalk_qr_secret")
+
+ qrDingtalk := dingtalk.NewDingtalkQRLogin(appSecret, appKey)
+ unionID, err := qrDingtalk.GetUnionIDByCode(code)
+ if err != nil {
+ logs.Warn("获取钉钉临时UnionID失败 ->", err)
+ c.Redirect(conf.URLFor("AccountController.Login"), 302)
+ c.StopRun()
+ }
+
+ appKey, _ = web.AppConfig.String("dingtalk_app_key")
+ appSecret, _ = web.AppConfig.String("dingtalk_app_secret")
+ tmpReader, _ := web.AppConfig.String("dingtalk_tmp_reader")
+
+ dingtalkAgent := dingtalk.NewDingTalkAgent(appSecret, appKey)
+ err = dingtalkAgent.GetAccesstoken()
+ if err != nil {
+ logs.Warn("获取钉钉临时Token失败 ->", err)
+ c.Redirect(conf.URLFor("AccountController.Login"), 302)
+ c.StopRun()
+ }
+
+ userid, err := dingtalkAgent.GetUserIDByUnionID(unionID)
+ if err != nil {
+ logs.Warn("获取钉钉用户ID失败 ->", err)
+ c.Redirect(conf.URLFor("AccountController.Login"), 302)
+ c.StopRun()
+ }
+
+ username, avatar, err := dingtalkAgent.GetUserNameAndAvatarByUserID(userid)
+ if err != nil {
+ logs.Warn("获取钉钉用户信息失败 ->", err)
+ c.Redirect(conf.URLFor("AccountController.Login"), 302)
+ c.StopRun()
+ }
+
+ member, err := models.NewMember().TmpLogin(tmpReader)
+ if err == nil {
+ member.LastLoginTime = time.Now()
+ _ = member.Update("last_login_time")
+ member.Account = username
+ if avatar != "" {
+ member.Avatar = avatar
+ }
+
+ c.SetMember(*member)
+ c.LoggedIn(false)
+ c.StopRun()
+ }
+ c.Redirect(conf.URLFor("AccountController.Login"), 302)
+
+ default:
+ c.Redirect(conf.URLFor("AccountController.Login"), 302)
+ c.StopRun()
+ }
+}
+
// 登录成功后的操作,如重定向到原始请求页面
func (c *AccountController) LoggedIn(isPost bool) interface{} {
diff --git a/models/DocumentModel.go b/models/DocumentModel.go
index 4de63889..4ed46c7a 100644
--- a/models/DocumentModel.go
+++ b/models/DocumentModel.go
@@ -304,17 +304,8 @@ func (item *Document) Processor() *Document {
//处理文档结尾信息
docCreator, err := NewMember().Find(item.MemberId, "real_name", "account")
release := "
"
- if item.ModifyAt > 0 {
- docModify, err := NewMember().Find(item.ModifyAt, "real_name", "account")
- if err == nil {
- if docModify.RealName != "" {
- release += "最后编辑: " + docModify.RealName + " "
- } else {
- release += "最后编辑: " + docModify.Account + " "
- }
- }
- }
- release += "文档更新时间: " + item.ModifyTime.Local().Format("2006-01-02 15:04") + " 作者:"
+
+ release += "作者:"
if err == nil && docCreator != nil {
if docCreator.RealName != "" {
release += docCreator.RealName
@@ -322,6 +313,19 @@ func (item *Document) Processor() *Document {
release += docCreator.Account
}
}
+ release += " 创建时间:" + item.CreateTime.Local().Format("2006-01-02 15:04") + "
"
+
+ if item.ModifyAt > 0 {
+ docModify, err := NewMember().Find(item.ModifyAt, "real_name", "account")
+ if err == nil {
+ if docModify.RealName != "" {
+ release += "最后编辑:" + docModify.RealName
+ } else {
+ release += "最后编辑:" + docModify.Account
+ }
+ }
+ }
+ release += " 更新时间:" + item.ModifyTime.Local().Format("2006-01-02 15:04") + "
"
release += "
"
if selector := docQuery.Find("div.markdown-article").First(); selector.Size() > 0 {
diff --git a/routers/router.go b/routers/router.go
index fc18d297..0335e62e 100644
--- a/routers/router.go
+++ b/routers/router.go
@@ -10,6 +10,7 @@ func init() {
web.Router("/login", &controllers.AccountController{}, "*:Login")
web.Router("/dingtalk_login", &controllers.AccountController{}, "*:DingTalkLogin")
+ web.Router("/qrlogin/:app", &controllers.AccountController{}, "*:QRLogin")
web.Router("/logout", &controllers.AccountController{}, "*:Logout")
web.Router("/register", &controllers.AccountController{}, "*:Register")
web.Router("/find_password", &controllers.AccountController{}, "*:FindPassword")
diff --git a/static/js/dingtalk-ddlogin.js b/static/js/dingtalk-ddlogin.js
new file mode 100644
index 00000000..f62433be
--- /dev/null
+++ b/static/js/dingtalk-ddlogin.js
@@ -0,0 +1,18 @@
+!function (window, document) {
+ function d(a) {
+ var e, c = document.createElement("iframe"),
+ d = "https://login.dingtalk.com/login/qrcode.htm?goto=" + a.goto ;
+ d += a.style ? "&style=" + encodeURIComponent(a.style) : "",
+ d += a.href ? "&href=" + a.href : "",
+ c.src = d,
+ c.frameBorder = "0",
+ c.allowTransparency = "true",
+ c.scrolling = "no",
+ c.width = a.width ? a.width + 'px' : "365px",
+ c.height = a.height ? a.height + 'px' : "400px",
+ e = document.getElementById(a.id),
+ e.innerHTML = "",
+ e.appendChild(c)
+ }
+ window.DDLogin = d
+}(window, document);
\ No newline at end of file
diff --git a/utils/dingtalk/dingtalk.go b/utils/dingtalk/dingtalk.go
index 53954abe..fa8e6883 100644
--- a/utils/dingtalk/dingtalk.go
+++ b/utils/dingtalk/dingtalk.go
@@ -10,6 +10,9 @@ import (
"io/ioutil"
"net/http"
"net/url"
+ "strconv"
+ "strings"
+ "time"
)
// DingTalkAgent 用于钉钉交互
@@ -106,6 +109,46 @@ func (d *DingTalkAgent) GetUserNameAndAvatarByUserID(userid string) (string, str
return username, avatar, nil
}
+// GetUserIDByUnionID 根据UnionID获取用户Userid
+func (d *DingTalkAgent) GetUserIDByUnionID(unionid string) (string, error) {
+ urlEndpoint, err := url.Parse("https://oapi.dingtalk.com/topapi/user/getbyunionid")
+ if err != nil {
+ return "", err
+ }
+
+ query := url.Values{}
+ query.Set("access_token", d.AccessToken)
+ urlEndpoint.RawQuery = query.Encode()
+ urlPath := urlEndpoint.String()
+
+ resp, err := http.PostForm(urlPath, url.Values{"unionid": {unionid}})
+ if err != nil {
+ return "", err
+ }
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", err
+ }
+ // 解析钉钉返回数据
+ var rdata map[string]interface{}
+ err = json.Unmarshal(body, &rdata)
+ if err != nil {
+ return "", err
+ }
+
+ errcode := rdata["errcode"].(float64)
+ if errcode != 0 {
+ return "", errors.New(fmt.Sprintf("登录错误: %.0f, %s", errcode, rdata["errmsg"].(string)))
+ }
+
+ result := rdata["result"].(map[string]interface{})
+ if result["contact_type"].(float64) != 0 {
+ return "", errors.New("该用户不属于企业内部员工,无法登录。")
+ }
+ userid := result["userid"].(string)
+ return userid, nil
+}
+
// GetAccesstoken 获取钉钉请求Token
func (d *DingTalkAgent) GetAccesstoken() (err error) {
@@ -132,16 +175,71 @@ func (d *DingTalkAgent) GetAccesstoken() (err error) {
return errors.New("accesstoken获取错误:" + i["errmsg"].(string))
}
-func (d *DingTalkAgent) encodeSHA256(message string) string {
+// DingtalkQRLogin 用于钉钉扫码登录
+type DingtalkQRLogin struct {
+ AppSecret string
+ AppKey string
+}
+
+// NewDingtalkQRLogin 构造钉钉扫码登录实例
+func NewDingtalkQRLogin(appSecret, appKey string) DingtalkQRLogin {
+ return DingtalkQRLogin{
+ AppSecret: appSecret,
+ AppKey: appKey,
+ }
+}
+
+// GetUnionIDByCode 获取扫码用户UnionID
+func (d *DingtalkQRLogin) GetUnionIDByCode(code string) (userid string, err error) {
+ var resp *http.Response
+ //服务端通过临时授权码获取授权用户的个人信息
+ timestamp := strconv.FormatInt(time.Now().UnixNano()/1000000, 10) // 毫秒时间戳
+ signature := d.encodeSHA256(timestamp) // 加密签名
+ urlPath := fmt.Sprintf(
+ "https://oapi.dingtalk.com/sns/getuserinfo_bycode?accessKey=%s×tamp=%s&signature=%s",
+ d.AppKey, timestamp, signature)
+
+ // 构造请求数据
+ param := struct {
+ Tmp_auth_code string `json:"tmp_auth_code"`
+ }{code}
+ paraByte, _ := json.Marshal(param)
+ paraString := string(paraByte)
+
+ resp, err = http.Post(urlPath, "application/json;charset=UTF-8", strings.NewReader(paraString))
+ if err != nil {
+ return "", err
+ }
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", err
+ }
+
+ // 解析钉钉返回数据
+ var rdata map[string]interface{}
+ err = json.Unmarshal(body, &rdata)
+ if err != nil {
+ return "", err
+ }
+ errcode := rdata["errcode"].(float64)
+ if errcode != 0 {
+ return "", errors.New(fmt.Sprintf("登录错误: %.0f, %s", errcode, rdata["errmsg"].(string)))
+ }
+ unionid := rdata["user_info"].(map[string]interface{})["unionid"].(string)
+ return unionid, nil
+}
+
+func (d *DingtalkQRLogin) encodeSHA256(timestamp string) string {
// 钉钉签名算法实现
h := hmac.New(sha256.New, []byte(d.AppSecret))
- h.Write([]byte(message))
+ h.Write([]byte(timestamp))
sum := h.Sum(nil) // 二进制流
tmpMsg := base64.StdEncoding.EncodeToString(sum)
uv := url.Values{}
uv.Add("0", tmpMsg)
- message = uv.Encode()[2:]
+ message := uv.Encode()[2:]
return message
}
diff --git a/views/account/login.tpl b/views/account/login.tpl
index 04efc2a8..0a34c379 100644
--- a/views/account/login.tpl
+++ b/views/account/login.tpl
@@ -70,6 +70,11 @@
+ {{if .ENABLE_QR_DINGTALK}}
+
+ {{end}}
{{if .ENABLED_REGISTER}}
{{if ne .ENABLED_REGISTER "false"}}
@@ -87,46 +96,74 @@
+
+
+
+
+