2022-02-13 23:39:27 +08:00
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
2021-05-17 14:17:46 +08:00
//
// 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 (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
2022-02-11 20:42:48 +08:00
"strings"
2021-05-17 14:17:46 +08:00
"time"
2021-05-31 00:13:38 +08:00
"golang.org/x/oauth2"
2021-05-17 14:17:46 +08:00
)
type WeChatIdProvider struct {
Client * http . Client
Config * oauth2 . Config
}
func NewWeChatIdProvider ( clientId string , clientSecret string , redirectUrl string ) * WeChatIdProvider {
idp := & WeChatIdProvider { }
config := idp . getConfig ( clientId , clientSecret , redirectUrl )
idp . Config = config
return idp
}
func ( idp * WeChatIdProvider ) SetHttpClient ( client * http . Client ) {
idp . Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func ( idp * WeChatIdProvider ) getConfig ( clientId string , clientSecret string , redirectUrl string ) * oauth2 . Config {
2022-08-07 12:26:14 +08:00
endpoint := oauth2 . Endpoint {
2021-05-17 14:17:46 +08:00
TokenURL : "https://graph.qq.com/oauth2.0/token" ,
}
2022-08-07 12:26:14 +08:00
config := & oauth2 . Config {
2021-05-17 14:17:46 +08:00
Scopes : [ ] string { "snsapi_login" } ,
Endpoint : endpoint ,
ClientID : clientId ,
ClientSecret : clientSecret ,
RedirectURL : redirectUrl ,
}
return config
}
2021-05-31 00:55:29 +08:00
type WechatAccessToken struct {
2022-08-07 12:26:14 +08:00
AccessToken string ` json:"access_token" ` // Interface call credentials
ExpiresIn int64 ` json:"expires_in" ` // access_token interface call credential timeout time, unit (seconds)
RefreshToken string ` json:"refresh_token" ` // User refresh access_token
Openid string ` json:"openid" ` // Unique ID of authorized user
Scope string ` json:"scope" ` // The scope of user authorization, separated by commas. (,)
Unionid string ` json:"unionid" ` // This field will appear if and only if the website application has been authorized by the user's UserInfo.
2021-05-31 00:55:29 +08:00
}
2021-05-17 14:17:46 +08:00
// 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 * WeChatIdProvider ) GetToken ( code string ) ( * oauth2 . Token , error ) {
params := url . Values { }
params . Add ( "grant_type" , "authorization_code" )
params . Add ( "appid" , idp . Config . ClientID )
params . Add ( "secret" , idp . Config . ClientSecret )
params . Add ( "code" , code )
2021-05-31 00:55:29 +08:00
accessTokenUrl := fmt . Sprintf ( "https://api.weixin.qq.com/sns/oauth2/access_token?%s" , params . Encode ( ) )
tokenResponse , err := idp . Client . Get ( accessTokenUrl )
2021-05-17 14:17:46 +08:00
if err != nil {
return nil , err
}
defer func ( Body io . ReadCloser ) {
err := Body . Close ( )
if err != nil {
return
}
} ( tokenResponse . Body )
buf := new ( bytes . Buffer )
_ , err = buf . ReadFrom ( tokenResponse . Body )
if err != nil {
return nil , err
}
2022-02-11 20:42:48 +08:00
// {"errcode":40163,"errmsg":"code been used, rid: 6206378a-793424c0-2e4091cc"}
if strings . Contains ( buf . String ( ) , "errcode" ) {
return nil , fmt . Errorf ( buf . String ( ) )
}
2021-05-31 00:13:38 +08:00
var wechatAccessToken WechatAccessToken
2021-08-07 22:02:56 +08:00
if err = json . Unmarshal ( buf . Bytes ( ) , & wechatAccessToken ) ; err != nil {
2021-05-17 14:17:46 +08:00
return nil , err
}
token := oauth2 . Token {
2021-05-31 00:13:38 +08:00
AccessToken : wechatAccessToken . AccessToken ,
2021-05-17 14:17:46 +08:00
TokenType : "WeChatAccessToken" ,
2021-05-31 00:13:38 +08:00
RefreshToken : wechatAccessToken . RefreshToken ,
2021-05-17 14:17:46 +08:00
Expiry : time . Time { } ,
}
raw := make ( map [ string ] string )
2021-05-31 00:13:38 +08:00
raw [ "Openid" ] = wechatAccessToken . Openid
2021-05-17 14:17:46 +08:00
token . WithExtra ( raw )
return & token , nil
}
2021-05-31 00:55:29 +08:00
//{
// "openid": "of_Hl5zVpyj0vwzIlAyIlnXe1234",
// "nickname": "飞翔的企鹅",
// "sex": 1,
// "language": "zh_CN",
// "city": "Shanghai",
// "province": "Shanghai",
// "country": "CN",
// "headimgurl": "https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/Q0j4TwGTfTK6xc7vGca4KtibJib5dslRianc9VHt9k2N7fewYOl8fak7grRM7nS5V6HcvkkIkGThWUXPjDbXkQFYA\/132",
// "privilege": [],
// "unionid": "oxW9O1VAL8x-zfWP2hrqW9c81234"
//}
type WechatUserInfo struct {
Openid string ` json:"openid" ` // The ID of an ordinary user, which is unique to the current developer account
Nickname string ` json:"nickname" ` // Ordinary user nickname
Sex int ` json:"sex" ` // Ordinary user gender, 1 is male, 2 is female
Language string ` json:"language" `
City string ` json:"city" ` // City filled in by general user's personal data
Province string ` json:"province" ` // Province filled in by ordinary user's personal information
Country string ` json:"country" ` // Country, such as China is CN
Headimgurl string ` json:"headimgurl" ` // User avatar, the last value represents the size of the square avatar (there are optional values of 0, 46, 64, 96, 132, 0 represents a 640*640 square avatar), this item is empty when the user does not have a avatar
Privilege [ ] string ` json:"privilege" ` // User Privilege information, json array, such as Wechat Woka user (chinaunicom)
Unionid string ` json:"unionid" ` // Unified user identification. For an application under a WeChat open platform account, the unionid of the same user is unique.
}
2021-05-31 00:13:38 +08:00
// GetUserInfo use WechatAccessToken gotten before return WechatUserInfo
2021-05-17 14:17:46 +08:00
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
func ( idp * WeChatIdProvider ) GetUserInfo ( token * oauth2 . Token ) ( * UserInfo , error ) {
2021-05-31 00:13:38 +08:00
var wechatUserInfo WechatUserInfo
2021-05-17 14:17:46 +08:00
accessToken := token . AccessToken
openid := token . Extra ( "Openid" )
2021-05-31 00:55:29 +08:00
userInfoUrl := fmt . Sprintf ( "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s" , accessToken , openid )
resp , err := idp . Client . Get ( userInfoUrl )
2021-05-17 14:17:46 +08:00
if err != nil {
return nil , err
}
defer func ( Body io . ReadCloser ) {
err := Body . Close ( )
if err != nil {
return
}
2021-05-31 00:13:38 +08:00
} ( resp . Body )
2021-05-17 14:17:46 +08:00
buf := new ( bytes . Buffer )
2021-05-31 00:13:38 +08:00
_ , err = buf . ReadFrom ( resp . Body )
2021-05-17 14:17:46 +08:00
if err != nil {
return nil , err
}
2021-08-07 22:02:56 +08:00
if err = json . Unmarshal ( buf . Bytes ( ) , & wechatUserInfo ) ; err != nil {
2021-05-17 14:17:46 +08:00
return nil , err
}
2021-05-31 00:55:29 +08:00
id := wechatUserInfo . Unionid
if id == "" {
id = wechatUserInfo . Openid
2021-05-17 14:17:46 +08:00
}
2021-05-31 00:13:38 +08:00
userInfo := UserInfo {
2021-05-31 00:55:29 +08:00
Id : id ,
2022-05-31 21:49:56 +08:00
Username : wechatUserInfo . Nickname ,
2021-05-31 00:13:38 +08:00
DisplayName : wechatUserInfo . Nickname ,
AvatarUrl : wechatUserInfo . Headimgurl ,
}
2021-05-17 14:17:46 +08:00
return & userInfo , nil
}