重写Auth2.0登录逻辑 (#851)

* go mod update

* feat: change to new wxwork sso login

* fix: can't log in by workwx browser

* fix: workwx auto regist

* fix: change app.conf.example

* fix: workwx account can't be disabled

* fix: workwx account delete

* fix: workwx bind error

* feat: optimize wecom login

* feat: rewrite dingtalk login

* feat: rewrite dingtalk login

* feat: optimize auth2 login
This commit is contained in:
LawyZheng
2023-04-20 13:24:28 +08:00
committed by GitHub
parent 725b6ac24e
commit 08d0e1613d
17 changed files with 1936 additions and 791 deletions

89
utils/auth2/auth2.go Normal file
View File

@@ -0,0 +1,89 @@
package auth2
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type UserInfo struct {
UserId string `json:"userid"` // 企业成员userid
Name string `json:"name"` // 姓名
Avatar string `json:"avatar"` // 头像
Mobile string `json:"mobile"` // 手机号
Mail string `json:"mail"` // 邮箱
}
func NewAccessToken(token IAccessToken) AccessTokenCache {
return AccessTokenCache{
AccessToken: token.GetToken(),
ExpireIn: token.GetExpireIn(),
ExpireTime: token.GetExpireTime(),
}
}
type AccessTokenCache struct {
ExpireIn time.Duration
ExpireTime time.Time
AccessToken string
}
func (a AccessTokenCache) GetToken() string {
return a.AccessToken
}
func (a AccessTokenCache) GetExpireIn() time.Duration {
return a.ExpireIn
}
func (a AccessTokenCache) GetExpireTime() time.Time {
return a.ExpireTime
}
func (a AccessTokenCache) IsExpired() bool {
return time.Now().After(a.ExpireTime)
}
type IAccessToken interface {
GetToken() string
GetExpireIn() time.Duration
GetExpireTime() time.Time
}
type Client interface {
GetAccessToken(ctx context.Context) (IAccessToken, error)
SetAccessToken(token IAccessToken)
BuildURL(callback string, isAppBrowser bool) string
ValidateCallback(state string) error
GetUserInfo(ctx context.Context, code string) (UserInfo, error)
}
type IResponse interface {
AsError() error
}
func Request(req *http.Request, v IResponse) error {
response, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer response.Body.Close()
b, err := io.ReadAll(response.Body)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return fmt.Errorf("status = %d, msg = %s", response.StatusCode, string(b))
}
if err := json.Unmarshal(b, v); err != nil {
return err
}
return v.AsError()
}

View File

@@ -0,0 +1,234 @@
package dingtalk
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/mindoc-org/mindoc/utils/auth2"
"net/http"
"net/url"
"time"
)
const (
AppName = "dingtalk"
callbackState = "mindoc"
)
type BasicResponse struct {
Message string `json:"errmsg"`
Code int `json:"errcode"`
}
func (r *BasicResponse) Error() string {
return fmt.Sprintf("errcode=%d, errmsg=%s", r.Code, r.Message)
}
func (r *BasicResponse) AsError() error {
if r == nil {
return nil
}
if r.Code != 0 || r.Message != "ok" {
return r
}
return nil
}
type AccessToken struct {
// 文档: https://open.dingtalk.com/document/orgapp/obtain-orgapp-token
*BasicResponse
AccessToken string `json:"access_token"`
ExpireIn int `json:"expires_in"`
createTime time.Time
}
func (a AccessToken) GetToken() string {
return a.AccessToken
}
func (a AccessToken) GetExpireIn() time.Duration {
return time.Duration(a.ExpireIn) * time.Second
}
func (a AccessToken) GetExpireTime() time.Time {
return a.createTime.Add(a.GetExpireIn())
}
type UserAccessToken struct {
// 文档: https://open.dingtalk.com/document/orgapp/obtain-user-token
*BasicResponse // 此接口未返回错误代码信息仅仅能检查HTTP状态码
ExpireIn int `json:"expireIn"`
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
CorpId string `json:"corpId"`
}
type UserInfo struct {
// 文档: https://open.dingtalk.com/document/orgapp/dingtalk-retrieve-user-information
*BasicResponse
NickName string `json:"nick"`
Avatar string `json:"avatarUrl"`
Mobile string `json:"mobile"`
OpenId string `json:"openId"`
UnionId string `json:"unionId"`
Email string `json:"email"`
StateCode string `json:"stateCode"`
}
type UserIdByUnion struct {
// 文档: https://open.dingtalk.com/document/isvapp/query-a-user-by-the-union-id
*BasicResponse
RequestId string `json:"request_id"`
Result struct {
ContactType int `json:"contact_type"`
UserId string `json:"userid"`
} `json:"result"`
}
func NewClient(appSecret string, appKey string) auth2.Client {
return NewDingtalkClient(appSecret, appKey)
}
func NewDingtalkClient(appSecret string, appKey string) *DingtalkClient {
return &DingtalkClient{AppSecret: appSecret, AppKey: appKey}
}
type DingtalkClient struct {
AppSecret string
AppKey string
token auth2.IAccessToken
}
func (d *DingtalkClient) GetAccessToken(ctx context.Context) (auth2.IAccessToken, error) {
if d.token != nil {
return d.token, nil
}
endpoint := fmt.Sprintf("https://oapi.dingtalk.com/gettoken?appkey=%s&appsecret=%s", d.AppKey, d.AppSecret)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
var token AccessToken
if err := auth2.Request(req, &token); err != nil {
return nil, err
}
token.createTime = time.Now()
return token, nil
}
func (d *DingtalkClient) SetAccessToken(token auth2.IAccessToken) {
d.token = token
}
func (d *DingtalkClient) BuildURL(callback string, _ bool) string {
v := url.Values{}
v.Set("redirect_uri", callback)
v.Set("response_type", "code")
v.Set("client_id", d.AppKey)
v.Set("scope", "openid")
v.Set("state", callbackState)
v.Set("prompt", "consent")
return "https://login.dingtalk.com/oauth2/auth?" + v.Encode()
}
func (d *DingtalkClient) ValidateCallback(state string) error {
if state != callbackState {
return errors.New("auth2.state.wrong")
}
return nil
}
func (d *DingtalkClient) getUserAccessToken(ctx context.Context, code string) (UserAccessToken, error) {
val := map[string]string{
"clientId": d.AppKey,
"clientSecret": d.AppSecret,
"code": code,
"grantType": "authorization_code",
}
jv, _ := json.Marshal(val)
endpoint := "https://api.dingtalk.com/v1.0/oauth2/userAccessToken"
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(jv))
req.Header.Set("Content-Type", "application/json")
var token UserAccessToken
if err := auth2.Request(req, &token); err != nil {
return token, err
}
return token, nil
}
func (d *DingtalkClient) getUserInfo(ctx context.Context, userToken UserAccessToken, unionId string) (UserInfo, error) {
var user UserInfo
endpoint := fmt.Sprintf("https://api.dingtalk.com/v1.0/contact/users/%s", unionId)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
req.Header.Set("x-acs-dingtalk-access-token", userToken.AccessToken)
req.Header.Set("Content-Type", "application/json")
if err := auth2.Request(req, &user); err != nil {
return user, err
}
return user, nil
}
func (d *DingtalkClient) getUserIdByUnion(ctx context.Context, union string) (UserIdByUnion, error) {
var userId UserIdByUnion
token, err := d.GetAccessToken(ctx)
if err != nil {
return userId, err
}
endpoint := fmt.Sprintf("https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=%s", token.GetToken())
b, _ := json.Marshal(map[string]string{
"unionid": union,
})
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(b))
req.Header.Set("Content-Type", "application/json")
if err := auth2.Request(req, &userId); err != nil {
return userId, err
}
return userId, nil
}
func (d *DingtalkClient) GetUserInfo(ctx context.Context, code string) (auth2.UserInfo, error) {
var info auth2.UserInfo
userToken, err := d.getUserAccessToken(ctx, code)
if err != nil {
return info, err
}
userInfo, err := d.getUserInfo(ctx, userToken, "me")
if err != nil {
return info, err
}
userId, err := d.getUserIdByUnion(ctx, userInfo.UnionId)
if err != nil {
return info, err
}
if userId.Result.ContactType > 0 {
return info, errors.New("auth2.user.outer")
}
info.UserId = userId.Result.UserId
info.Mail = userInfo.Email
info.Mobile = userInfo.Mobile
info.Name = userInfo.NickName
info.Avatar = userInfo.Avatar
return info, nil
}

285
utils/auth2/wecom/wecom.go Normal file
View File

@@ -0,0 +1,285 @@
package wecom
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/mindoc-org/mindoc/utils/auth2"
"net/http"
"net/url"
"time"
)
// doc
// - 全局错误码: https://work.weixin.qq.com/api/doc/90000/90139/90313
const (
AppName = "workwx"
auth2Url = "https://open.weixin.qq.com/connect/oauth2/authorize"
ssoUrl = "https://login.work.weixin.qq.com/wwlogin/sso/login"
callbackState = "mindoc"
)
type BasicResponse struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
func (r *BasicResponse) Error() string {
return fmt.Sprintf("errcode=%d,errmsg=%s", r.ErrCode, r.ErrMsg)
}
func (r *BasicResponse) AsError() error {
if r == nil {
return nil
}
if r.ErrCode != 0 {
return r
}
return nil
}
// 获取用户Id-请求响应结构
type UserIdResponse struct {
// 接口文档: https://developer.work.weixin.qq.com/document/path/91023
*BasicResponse
UserId string `json:"userid"` // 企业成员UserID
UserTicket string `json:"user_ticket"` // 用于获取敏感信息
OpenId string `json:"openid"` // 非企业成员的标识,对当前企业唯一
ExternalUserId string `json:"external_userid"` // 外部联系人ID
}
// 获取用户信息-请求响应结构
type UserInfoResponse struct {
// 接口文档: https://developer.work.weixin.qq.com/document/path/90196
*BasicResponse
UserId string `json:"userid"` // 企业成员UserID
Name string `json:"name"` // 成员名称
Department []int `json:"department"` // 成员所属部门id列表
IsLeaderInDept []int `json:"is_leader_in_dept"` // 表示在所在的部门内是否为上级
IsLeader int `json:"isleader"` // 是否是部门上级(领导)
Alias string `json:"alias"` // 别名
Status int `json:"status"` // 激活状态: 1=已激活2=已禁用4=未激活5=退出企业
MainDepartment int `json:"main_department"` // 主部门
}
type UserPrivateInfoResponse struct {
// 文档地址: https://developer.work.weixin.qq.com/document/path/95833
*BasicResponse
UserId string `json:"userid"` // 企业成员userid
Gender string `json:"gender"` // 成员性别
Avatar string `json:"avatar"` // 头像
QrCode string `json:"qr_code"` // 二维码
Mobile string `json:"mobile"` // 手机号
Mail string `json:"mail"` // 邮箱
BizMail string `json:"biz_mail"` // 企业邮箱
Address string `json:"address"` // 地址
}
// 访问凭据缓存-结构
type AccessToken struct {
*BasicResponse
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
createTime time.Time `json:"create_time"`
}
func (a AccessToken) GetToken() string {
return a.AccessToken
}
func (a AccessToken) GetExpireIn() time.Duration {
return time.Duration(a.ExpiresIn) * time.Second
}
func (a AccessToken) GetExpireTime() time.Time {
return a.createTime.Add(a.GetExpireIn())
}
// 企业微信用户敏感信息-结构
type WorkWeixinUserPrivateInfo struct {
UserId string `json:"userid"` // 企业成员userid
Name string `json:"name"` // 姓名
Gender string `json:"gender"` // 成员性别
Avatar string `json:"avatar"` // 头像
QrCode string `json:"qr_code"` // 二维码
Mobile string `json:"mobile"` // 手机号
Mail string `json:"mail"` // 邮箱
BizMail string `json:"biz_mail"` // 企业邮箱
Address string `json:"address"` // 地址
}
// 企业微信用户信息-结构
type WorkWeixinUserInfo struct {
UserId string `json:"UserId"` // 企业成员UserID
Name string `json:"name"` // 成员名称
HideMobile int `json:"hide_mobile"` // 是否隐藏了手机号码
Mobile string `json:"mobile"` // 手机号码
Department []int `json:"department"` // 成员所属部门id列表
Email string `json:"email"` // 邮箱
IsLeaderInDept []int `json:"is_leader_in_dept"` // 表示在所在的部门内是否为上级
IsLeader int `json:"isleader"` // 是否是部门上级(领导)
Avatar string `json:"avatar"` // 头像url
Alias string `json:"alias"` // 别名
Status int `json:"status"` // 激活状态: 1=已激活2=已禁用4=未激活5=退出企业
MainDepartment int `json:"main_department"` // 主部门
}
func NewClient(corpId, appId, appSecrete string) auth2.Client {
return NewWorkWechatClient(corpId, appId, appSecrete)
}
func NewWorkWechatClient(corpId, appId, appSecrete string) *WorkWechatClient {
return &WorkWechatClient{
CorpId: corpId,
AppId: appId,
AppSecret: appSecrete,
}
}
type WorkWechatClient struct {
CorpId string
AppId string
AppSecret string
token auth2.IAccessToken
}
func (c *WorkWechatClient) GetAccessToken(ctx context.Context) (auth2.IAccessToken, error) {
if c.token != nil {
return c.token, nil
}
endpoint := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", c.CorpId, c.AppSecret)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
var token AccessToken
if err := auth2.Request(req, &token); err != nil {
return token, err
}
token.createTime = time.Now()
return token, nil
}
func (c *WorkWechatClient) SetAccessToken(token auth2.IAccessToken) {
c.token = token
return
}
func (c *WorkWechatClient) BuildURL(callback string, isAppBrowser bool) string {
var endpoint string
if isAppBrowser {
// 企业微信内-网页授权登录
urlFmt := "%s?appid=%s&agentid=%s&redirect_uri=%s&response_type=code&scope=snsapi_privateinfo&state=%s#wechat_redirect"
endpoint = fmt.Sprintf(urlFmt, auth2Url, c.CorpId, c.AppId, url.PathEscape(callback), callbackState)
} else {
// 浏览器内-扫码授权登录
urlFmt := "%s?login_type=CorpApp&appid=%s&agentid=%s&redirect_uri=%s&state=%s"
endpoint = fmt.Sprintf(urlFmt, ssoUrl, c.CorpId, c.AppId, url.PathEscape(callback), callbackState)
}
return endpoint
}
func (c *WorkWechatClient) ValidateCallback(state string) error {
if state != callbackState {
return errors.New("auth2.state.wrong")
}
return nil
}
func (c *WorkWechatClient) getUserId(ctx context.Context, code string) (UserIdResponse, error) {
var userId UserIdResponse
token, err := c.GetAccessToken(ctx)
if err != nil {
return userId, err
}
endpoint := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=%s&code=%s", token.GetToken(), code)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err := auth2.Request(req, &userId); err != nil {
return userId, err
}
if userId.UserId == "" {
return userId, errors.New("auth2.userid.empty")
}
return userId, nil
}
func (c *WorkWechatClient) getUserInfo(ctx context.Context, userid string) (UserInfoResponse, error) {
var userInfo UserInfoResponse
token, err := c.GetAccessToken(ctx)
if err != nil {
return userInfo, err
}
endpoint := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s", token.GetToken(), userid)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err := auth2.Request(req, &userInfo); err != nil {
return userInfo, err
}
return userInfo, nil
}
func (c *WorkWechatClient) getUserPrivateInfo(ctx context.Context, ticket string) (UserPrivateInfoResponse, error) {
var userInfo UserPrivateInfoResponse
token, err := c.GetAccessToken(ctx)
if err != nil {
return userInfo, err
}
endpoint := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail?access_token=%s", token.GetToken())
b, _ := json.Marshal(map[string]string{
"user_ticket": ticket,
})
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(b))
if err := auth2.Request(req, &userInfo); err != nil {
return userInfo, err
}
return userInfo, nil
}
func (c *WorkWechatClient) GetUserInfo(ctx context.Context, code string) (auth2.UserInfo, error) {
var info auth2.UserInfo
userid, err := c.getUserId(ctx, code)
if err != nil {
return info, err
}
userInfo, err := c.getUserInfo(ctx, userid.UserId)
if err != nil {
return info, err
}
info.UserId = userInfo.UserId
info.Name = userInfo.Name
if userid.UserTicket == "" {
return info, nil
}
private, err := c.getUserPrivateInfo(ctx, userid.UserTicket)
if err != nil {
return info, err
}
info.Mail = private.BizMail
info.Avatar = private.Avatar
info.Mobile = private.Mobile
return info, nil
}

View File

@@ -3,7 +3,9 @@ package workweixin
import (
"context"
"crypto/tls"
// "encoding/json"
"encoding/json"
"errors"
"net/http"
"time"
@@ -17,8 +19,8 @@ import (
// - 全局错误码: https://work.weixin.qq.com/api/doc/90000/90139/90313
const (
AccessTokenCacheKey = "access-token-cache-key"
ContactAccessTokenCacheKey = "contact-access-token-cache-key"
AccessTokenCacheKey = "access-token-cache-key"
// ContactAccessTokenCacheKey = "contact-access-token-cache-key"
)
// 获取访问凭据-请求响应结构
@@ -31,11 +33,13 @@ type AccessTokenResponse struct {
// 获取用户Id-请求响应结构
type UserIdResponse struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
UserId string `json:"UserId"` // 企业成员UserID
OpenId string `json:"OpenId"` // 企业成员的标识,对当前企业唯一
DeviceId string `json:"DeviceId"` // 设备号
// 接口文档: https://developer.work.weixin.qq.com/document/path/91023
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
UserId string `json:"userid"` // 企业成员UserID
UserTicket string `json:"user_ticket"` // 用于获取敏感信息
OpenId string `json:"openid"` // 非企业成员的标识,对当前企业唯一
ExternalUserId string `json:"external_userid"` // 外部联系人ID
}
// 获取成员ID列表-请求响应结构
@@ -65,6 +69,20 @@ type UserInfoResponse struct {
MainDepartment int `json:"main_department"` // 主部门
}
type UserPrivateInfoResponse struct {
// 文档地址: https://developer.work.weixin.qq.com/document/path/95833
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
UserId string `json:"userid"` // 企业成员userid
Gender string `json:"gender"` // 成员性别
Avatar string `json:"avatar"` // 头像
QrCode string `json:"qr_code"` // 二维码
Mobile string `json:"mobile"` // 手机号
Mail string `json:"mail"` // 邮箱
BizMail string `json:"biz_mail"` // 企业邮箱
Address string `json:"address"` // 地址
}
// 访问凭据缓存-结构
type AccessTokenCache struct {
AccessToken string `json:"access_token"`
@@ -72,6 +90,19 @@ type AccessTokenCache struct {
UpdateTime time.Time `json:"update_time"`
}
// 企业微信用户敏感信息-结构
type WorkWeixinUserPrivateInfo struct {
UserId string `json:"userid"` // 企业成员userid
Name string `json:"name"` // 姓名
Gender string `json:"gender"` // 成员性别
Avatar string `json:"avatar"` // 头像
QrCode string `json:"qr_code"` // 二维码
Mobile string `json:"mobile"` // 手机号
Mail string `json:"mail"` // 邮箱
BizMail string `json:"biz_mail"` // 企业邮箱
Address string `json:"address"` // 地址
}
// 企业微信用户信息-结构
type WorkWeixinDeptUserInfo struct {
UserId string `json:"UserId"` // 企业成员UserID
@@ -133,12 +164,9 @@ func RequestAccessToken(corpid string, secret string) (cache_token AccessTokenCa
}
// 获取访问凭据
func GetAccessToken(is_contact bool) (access_token string, ok bool) {
func GetAccessToken() (access_token string, ok bool) {
var cache_token AccessTokenCache
cache_key := AccessTokenCacheKey
if is_contact {
cache_key = ContactAccessTokenCacheKey
}
err := cache.Get(cache_key, &cache_token)
if err == nil {
logs.Info("AccessToken从缓存读取成功")
@@ -150,11 +178,7 @@ func GetAccessToken(is_contact bool) (access_token string, ok bool) {
logs.Debug("corp_id: ", workweixinConfig.CorpId)
logs.Debug("agent_id: ", workweixinConfig.AgentId)
logs.Debug("secret: ", workweixinConfig.Secret)
logs.Debug("contact_secret: ", workweixinConfig.ContactSecret)
secret := workweixinConfig.Secret
if is_contact {
secret = workweixinConfig.ContactSecret
}
new_token, ok := RequestAccessToken(workweixinConfig.CorpId, secret)
if ok {
logs.Debug(new_token)
@@ -171,8 +195,8 @@ func GetAccessToken(is_contact bool) (access_token string, ok bool) {
}
// 获取用户id-请求
func RequestUserId(access_token string, code string) (user_id string, ok bool) {
url := "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo"
func RequestUserId(access_token string, code string) (user_id string, ticket string, ok bool) {
url := "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo"
req := httplib.Get(url)
req.Param("access_token", access_token) // 应用调用接口凭证
req.Param("code", code) // 通过成员授权获取到的code
@@ -182,15 +206,63 @@ func RequestUserId(access_token string, code string) (user_id string, ok bool) {
_ = resp
if err != nil {
logs.Error(err)
return "", false
return "", "", false
}
var uir UserIdResponse
err = req.ToJSON(&uir)
if err != nil {
logs.Error(err)
return "", false
return "", "", false
}
return uir.UserId, true
return uir.UserId, uir.UserTicket, uir.UserId != ""
}
func RequestUserPrivateInfo(access_token, userid, ticket string) (WorkWeixinUserPrivateInfo, error) {
url := "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail?access_token=" + access_token
req := httplib.Post(url)
body := map[string]string{
"user_ticket": ticket,
}
b, _ := json.Marshal(body)
req.Body(b)
req.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: false})
req.AddFilters(httpFilter)
resp, err := req.Response()
_ = resp
var uir UserPrivateInfoResponse
var info WorkWeixinUserPrivateInfo
if err != nil {
logs.Error(err)
return info, err
}
err = req.ToJSON(&uir)
if err != nil {
logs.Error(err)
return info, err
}
if uir.ErrCode != 0 {
return info, errors.New(uir.ErrMsg)
}
user_info, err, _ := RequestUserInfo(access_token, userid)
if err != nil {
return info, err
}
info = WorkWeixinUserPrivateInfo{
UserId: userid,
Name: user_info.Name,
Gender: uir.Gender,
Avatar: uir.Avatar,
QrCode: uir.QrCode,
Mobile: uir.Mobile,
Mail: uir.Mail,
BizMail: uir.BizMail,
Address: uir.Address,
}
return info, nil
}
/*
@@ -198,7 +270,7 @@ func RequestUserId(access_token string, code string) (user_id string, ok bool) {
从2022年8月15日10点开始“企业管理后台 - 管理工具 - 通讯录同步”的新增IP将不能再调用此接口
url:https://developer.work.weixin.qq.com/document/path/96079
*/
func RequestUserInfo(contact_access_token string, userid string) (user_info WorkWeixinUserInfo, error_msg string, ok bool) {
func RequestUserInfo(contact_access_token string, userid string) (user_info WorkWeixinUserInfo, error_msg error, ok bool) {
url := "https://qyapi.weixin.qq.com/cgi-bin/user/get"
req := httplib.Get(url)
req.Param("access_token", contact_access_token) // 通讯录应用调用接口凭证
@@ -210,7 +282,7 @@ func RequestUserInfo(contact_access_token string, userid string) (user_info Work
var info WorkWeixinUserInfo
if err != nil {
logs.Error(err)
return info, "请求失败", false
return info, err, false
} else {
logs.Debug(resp_str)
}
@@ -218,10 +290,10 @@ func RequestUserInfo(contact_access_token string, userid string) (user_info Work
err = req.ToJSON(&uir)
if err != nil {
logs.Error(err)
return info, "请求数据结果错误", false
return info, err, false
}
if uir.ErrCode != 0 {
return info, uir.ErrMsg, false
return info, errors.New(uir.ErrMsg), false
}
info = WorkWeixinUserInfo{
UserId: uir.UserId,
@@ -237,7 +309,7 @@ func RequestUserInfo(contact_access_token string, userid string) (user_info Work
Status: uir.Status,
MainDepartment: uir.MainDepartment,
}
return info, "", true
return info, nil, true
}
/*