mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-24 08:20:31 +08:00
feat: add wecom provider (#200)
Signed-off-by: sh1luo <690898835@qq.com>
This commit is contained in:
parent
c9ae582802
commit
512a451800
2
go.mod
2
go.mod
@ -28,7 +28,7 @@ require (
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
xorm.io/core v0.7.2
|
||||
xorm.io/xorm v1.0.3
|
||||
)
|
||||
|
@ -53,6 +53,8 @@ func GetIdProvider(providerType string, clientId string, clientSecret string, re
|
||||
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "LinkedIn" {
|
||||
return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "WeCom" {
|
||||
return NewWeComIdProvider(clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
197
idp/wecom.go
Normal file
197
idp/wecom.go
Normal file
@ -0,0 +1,197 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type WeComIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewWeComIdProvider(clientId string, clientSecret string, redirectUrl string) *WeComIdProvider {
|
||||
idp := &WeComIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *WeComIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *WeComIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://graph.qq.com/oauth2.0/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"snsapi_login"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type WeComProviderToken struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
ProviderAccessToken string `json:"provider_access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
|
||||
func (idp *WeComIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
pTokenParams := &struct {
|
||||
CorpId string `json:"corpid"`
|
||||
ProviderSecret string `json:"provider_secret"`
|
||||
}{idp.Config.ClientID, idp.Config.ClientSecret}
|
||||
data, err := idp.postWithBody(pTokenParams, "https://qyapi.weixin.qq.com/cgi-bin/service/get_provider_token")
|
||||
pToken := &WeComProviderToken{}
|
||||
if err = json.Unmarshal(data, pToken); err != nil || pToken.Errcode != 0 || pToken.Errmsg != "ok" {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: pToken.ProviderAccessToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
|
||||
}
|
||||
|
||||
raw := make(map[string]string)
|
||||
raw["code"] = code
|
||||
token = token.WithExtra(raw)
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"errcode":0,
|
||||
"errmsg":"ok",
|
||||
"usertype": 1,
|
||||
"user_info":{
|
||||
"userid":"xxxx",
|
||||
"open_userid":"xxx",
|
||||
"name":"xxxx",
|
||||
"avatar":"xxxx"
|
||||
},
|
||||
"corp_info":{
|
||||
"corpid":"wxCorpId",
|
||||
},
|
||||
"agent":[
|
||||
{"agentid":0,"auth_type":1},
|
||||
{"agentid":1,"auth_type":1},
|
||||
{"agentid":2,"auth_type":1}
|
||||
],
|
||||
"auth_info":{
|
||||
"department":[
|
||||
{
|
||||
"id":2,
|
||||
"writable":true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
type WeComUserInfo struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
Usertype int `json:"usertype"`
|
||||
UserInfo struct {
|
||||
Userid string `json:"userid"`
|
||||
OpenUserid string `json:"open_userid"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"avatar"`
|
||||
} `json:"user_info"`
|
||||
CorpInfo struct {
|
||||
Corpid string `json:"corpid"`
|
||||
} `json:"corp_info"`
|
||||
Agent []struct {
|
||||
Agentid int `json:"agentid"`
|
||||
AuthType int `json:"auth_type"`
|
||||
} `json:"agent"`
|
||||
AuthInfo struct {
|
||||
Department []struct {
|
||||
Id int `json:"id"`
|
||||
Writable bool `json:"writable"`
|
||||
} `json:"department"`
|
||||
} `json:"auth_info"`
|
||||
}
|
||||
|
||||
// GetUserInfo use WeComProviderToken gotten before return WeComUserInfo
|
||||
// get more detail via: https://work.weixin.qq.com/api/doc/90001/90143/91125
|
||||
func (idp *WeComIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
accessToken := token.AccessToken
|
||||
code := token.Extra("code").(string)
|
||||
|
||||
requestBody := &struct {
|
||||
AuthCode string `json:"auth_code"`
|
||||
}{code}
|
||||
data, err := idp.postWithBody(requestBody, fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/service/get_login_info?access_token=%s", accessToken))
|
||||
wecomUserInfo := WeComUserInfo{}
|
||||
if err = json.Unmarshal(data, &wecomUserInfo); err != nil || wecomUserInfo.Errcode != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: wecomUserInfo.UserInfo.OpenUserid,
|
||||
Username: wecomUserInfo.UserInfo.Name,
|
||||
DisplayName: wecomUserInfo.UserInfo.Name,
|
||||
AvatarUrl: wecomUserInfo.UserInfo.Avatar,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *WeComIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
|
||||
bs, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := strings.NewReader(string(bs))
|
||||
resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
return data, nil
|
||||
}
|
@ -55,6 +55,7 @@ type User struct {
|
||||
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
||||
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
||||
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
||||
WeCom string `xorm:"wecom varchar(100)" json:"we_com"`
|
||||
|
||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||
Properties map[string]string `json:"properties"`
|
||||
|
@ -75,6 +75,7 @@ class ProviderEditPage extends React.Component {
|
||||
{id: 'Weibo', name: 'Weibo'},
|
||||
{id: 'Gitee', name: 'Gitee'},
|
||||
{id: 'LinkedIn', name: 'LinkedIn'},
|
||||
{id: 'WeCom', name: 'WeCom'},
|
||||
]
|
||||
);
|
||||
} else if (provider.category === "Email") {
|
||||
|
@ -51,6 +51,10 @@ const LinkedInAuthScope = "r_liteprofile%20r_emailaddress";
|
||||
const LinkedInAuthUri = "https://www.linkedin.com/oauth/v2/authorization";
|
||||
const LinkedInAuthLogo = `${StaticBaseUrl}/img/social_linkedin.png`;
|
||||
|
||||
// const WeComAuthScope = "";
|
||||
const WeComAuthUri = "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect";
|
||||
const WeComAuthLogo = `${StaticBaseUrl}/img/social_wecom.png`;
|
||||
|
||||
export function getAuthLogo(provider) {
|
||||
if (provider.type === "Google") {
|
||||
return GoogleAuthLogo;
|
||||
@ -70,6 +74,8 @@ export function getAuthLogo(provider) {
|
||||
return GiteeAuthLogo;
|
||||
} else if (provider.type === "LinkedIn") {
|
||||
return LinkedInAuthLogo;
|
||||
} else if (provider.type === "WeCom") {
|
||||
return WeComAuthLogo;
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,5 +104,7 @@ export function getAuthUrl(application, provider, method) {
|
||||
return `${GiteeAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${GiteeAuthScope}&response_type=code&state=${state}`;
|
||||
} else if (provider.type === "LinkedIn") {
|
||||
return `${LinkedInAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${LinkedInAuthScope}&response_type=code&state=${state}`
|
||||
} else if (provider.type === "WeCom") {
|
||||
return `${WeComAuthUri}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&usertype=member`
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user