@@ -0,0 +1,44 @@ | |||
package wechat | |||
import ( | |||
"code.gitea.io/gitea/modules/redis/redis_client" | |||
"code.gitea.io/gitea/modules/redis/redis_key" | |||
"time" | |||
) | |||
const EMPTY_REDIS_VAL = "Nil" | |||
func GetWechatAccessToken() string { | |||
token, _ := redis_client.Get(redis_key.WechatAccessTokenKey()) | |||
if token != "" { | |||
if token == EMPTY_REDIS_VAL { | |||
return "" | |||
} | |||
live, _ := redis_client.TTL(token) | |||
//refresh wechat access token when expire time less than 5 minutes | |||
if live > 0 && live < 300 { | |||
refreshAccessTokenCache() | |||
} | |||
return token | |||
} | |||
return refreshAccessTokenCache() | |||
} | |||
func refreshAccessTokenCache() string { | |||
r := callAccessToken() | |||
var token string | |||
if r != nil { | |||
token = r.Access_token | |||
} | |||
if token == "" { | |||
redis_client.Setex(redis_key.WechatAccessTokenKey(), EMPTY_REDIS_VAL, 10*time.Second) | |||
return "" | |||
} | |||
redis_client.Setex(redis_key.WechatAccessTokenKey(), token, time.Duration(r.Expires_in)*time.Second) | |||
return token | |||
} |
@@ -0,0 +1,95 @@ | |||
package wechat | |||
import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"encoding/json" | |||
"github.com/go-resty/resty/v2" | |||
"time" | |||
) | |||
var ( | |||
client *resty.Client | |||
) | |||
const ( | |||
GRANT_TYPE = "client_credential" | |||
ACCESS_TOKEN_PATH = "/cgi-bin/token" | |||
QR_CODE_Path = "/cgi-bin/qrcode/create" | |||
ACTION_QR_STR_SCENE = "QR_STR_SCENE" | |||
) | |||
type AccessTokenResponse struct { | |||
Access_token string | |||
Expires_in int | |||
} | |||
type QRCodeResponse struct { | |||
Ticket string `json:"ticket"` | |||
Expire_Seconds int `json:"expire_seconds"` | |||
Url string `json:"url"` | |||
} | |||
type QRCodeRequest struct { | |||
Action_name string `json:"action_name"` | |||
Action_info ActionInfo `json:"action_info"` | |||
Expire_seconds int `json:"expire_seconds"` | |||
} | |||
type ActionInfo struct { | |||
Scene Scene | |||
} | |||
type Scene struct { | |||
Scene_str string | |||
} | |||
func getWechatRestyClient() *resty.Client { | |||
if client == nil { | |||
client = resty.New() | |||
client.SetTimeout(time.Duration(setting.WechatApiTimeoutSeconds) * time.Second) | |||
} | |||
return client | |||
} | |||
func callAccessToken() *AccessTokenResponse { | |||
client := getWechatRestyClient() | |||
var result AccessTokenResponse | |||
_, err := client.R(). | |||
SetQueryParam("grant_type", GRANT_TYPE). | |||
SetQueryParam("appid", setting.WechatAppId). | |||
SetQueryParam("secret", setting.WechatAppSecret). | |||
SetResult(&result). | |||
Get(setting.WechatApiHost + ACCESS_TOKEN_PATH) | |||
if err != nil { | |||
log.Error("get wechat access token failed,e=%v", err) | |||
return nil | |||
} | |||
return &result | |||
} | |||
func callQRCodeCreate(sceneStr string) *QRCodeResponse { | |||
client := getWechatRestyClient() | |||
body := &QRCodeRequest{ | |||
Action_name: ACTION_QR_STR_SCENE, | |||
Action_info: ActionInfo{Scene: Scene{Scene_str: sceneStr}}, | |||
Expire_seconds: setting.WechatQRCodeExpireSeconds, | |||
} | |||
bodyJson, _ := json.Marshal(body) | |||
var result QRCodeResponse | |||
r, err := client.R(). | |||
SetHeader("Content-Type", "application/json"). | |||
SetQueryParam("access_token", GetWechatAccessToken()). | |||
SetBody(bodyJson). | |||
SetResult(&result). | |||
Post(setting.WechatApiHost + QR_CODE_Path) | |||
if err != nil || result.Url == "" { | |||
log.Error("create QR code failed,e=%v", err) | |||
return nil | |||
} | |||
//todo 识别token失效的错误码,重试机制 | |||
log.Info("%v", r) | |||
return &result | |||
} |
@@ -0,0 +1 @@ | |||
package wechat |
@@ -0,0 +1,6 @@ | |||
package wechat | |||
func GetWechatQRCode4Bind(sceneStr string) *QRCodeResponse { | |||
r := callQRCodeCreate(sceneStr) | |||
return r | |||
} |
@@ -0,0 +1,54 @@ | |||
package redis_client | |||
import ( | |||
"code.gitea.io/gitea/modules/labelmsg" | |||
"fmt" | |||
"math" | |||
"strconv" | |||
"time" | |||
) | |||
//todo redis连接池 | |||
func Setex(key, value string, timeout time.Duration) (bool, error) { | |||
redisClient := labelmsg.Get() | |||
defer redisClient.Close() | |||
seconds := int(math.Floor(timeout.Seconds())) | |||
reply, err := redisClient.Do("SETEX", key, seconds, value) | |||
if err != nil { | |||
return false, err | |||
} | |||
if reply != "OK" { | |||
return false, nil | |||
} | |||
return true, nil | |||
} | |||
func Get(key string) (string, error) { | |||
redisClient := labelmsg.Get() | |||
defer redisClient.Close() | |||
reply, err := redisClient.Do("GET", key) | |||
if err != nil { | |||
return "", err | |||
} | |||
if reply == nil { | |||
return "", err | |||
} | |||
return fmt.Sprint(reply), nil | |||
} | |||
func TTL(key string) (int, error) { | |||
redisClient := labelmsg.Get() | |||
defer redisClient.Close() | |||
reply, err := redisClient.Do("TTL", key) | |||
if err != nil { | |||
return 0, err | |||
} | |||
n, _ := strconv.Atoi(fmt.Sprint(reply)) | |||
return n, nil | |||
} |
@@ -0,0 +1,16 @@ | |||
package redis_key | |||
import "strings" | |||
const KEY_SEPARATE = ":" | |||
func KeyJoin(keys ...string) string { | |||
var build strings.Builder | |||
for _, v := range keys { | |||
build.WriteString(v) | |||
build.WriteString(KEY_SEPARATE) | |||
} | |||
s := build.String() | |||
s = strings.TrimSuffix(s, KEY_SEPARATE) | |||
return s | |||
} |
@@ -0,0 +1,11 @@ | |||
package redis_key | |||
const PREFIX = "wechat" | |||
func WechatBindingUserIdKey(sceneStr string) string { | |||
return KeyJoin(PREFIX, sceneStr, "scene_userId") | |||
} | |||
func WechatAccessTokenKey() string { | |||
return KeyJoin(PREFIX, "access_token") | |||
} |
@@ -470,7 +470,7 @@ var ( | |||
BenchmarkTypes string | |||
BenchmarkGpuTypes string | |||
BenchmarkResourceSpecs string | |||
BenchmarkMaxDuration int64 | |||
BenchmarkMaxDuration int64 | |||
//snn4imagenet config | |||
IsSnn4imagenetEnabled bool | |||
@@ -512,7 +512,7 @@ var ( | |||
ProfileID string | |||
PoolInfos string | |||
Flavor string | |||
DebugHost string | |||
DebugHost string | |||
//train-job | |||
ResourcePools string | |||
Engines string | |||
@@ -529,6 +529,13 @@ var ( | |||
ElkTimeFormat string | |||
PROJECT_LIMIT_PAGES []string | |||
//wechat config | |||
WechatApiHost string | |||
WechatApiTimeoutSeconds int | |||
WechatAppId string | |||
WechatAppSecret string | |||
WechatQRCodeExpireSeconds int | |||
//nginx proxy | |||
PROXYURL string | |||
RadarMap = struct { | |||
@@ -1342,6 +1349,13 @@ func NewContext() { | |||
ElkTimeFormat = sec.Key("ELKTIMEFORMAT").MustString("date_time") | |||
PROJECT_LIMIT_PAGES = strings.Split(sec.Key("project_limit_pages").MustString(""), ",") | |||
sec = Cfg.Section("wechat") | |||
WechatApiHost = sec.Key("HOST").MustString("https://api.weixin.qq.com") | |||
WechatApiTimeoutSeconds = sec.Key("TIMEOUT_SECONDS").MustInt(3) | |||
WechatAppId = sec.Key("APP_ID").MustString("wxba77b915a305a57d") | |||
WechatAppSecret = sec.Key("APP_SECRET").MustString("e48e13f315adc32749ddc7057585f198") | |||
WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120) | |||
SetRadarMapConfig() | |||
sec = Cfg.Section("warn_mail") | |||
@@ -0,0 +1,67 @@ | |||
package authentication | |||
import ( | |||
"code.gitea.io/gitea/modules/auth/wechat" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/redis/redis_client" | |||
"code.gitea.io/gitea/modules/redis/redis_key" | |||
"code.gitea.io/gitea/modules/setting" | |||
"errors" | |||
"fmt" | |||
gouuid "github.com/satori/go.uuid" | |||
"time" | |||
) | |||
type QRCodeResponse struct { | |||
Url string | |||
Ticket string | |||
SceneStr string | |||
ExpireSeconds int | |||
} | |||
// GetQRCode4Bind get QR code for wechat binding | |||
func GetQRCode4Bind(ctx *context.Context) { | |||
userId := ctx.User.ID | |||
r, err := createQRCode4Bind(userId) | |||
if err != nil { | |||
ctx.JSON(200, map[string]interface{}{ | |||
"code": 9, | |||
"msg": "Get QR code failed", | |||
}) | |||
return | |||
} | |||
ctx.JSON(200, map[string]interface{}{ | |||
"code": 0, | |||
"msg": "success", | |||
"data": r, | |||
}) | |||
} | |||
func createQRCode4Bind(userId int64) (*QRCodeResponse, error) { | |||
sceneStr := gouuid.NewV4().String() | |||
r := wechat.GetWechatQRCode4Bind(sceneStr) | |||
if r == nil { | |||
return nil, errors.New("createQRCode4Bind failed") | |||
} | |||
isOk, err := redis_client.Setex(redis_key.WechatBindingUserIdKey(sceneStr), fmt.Sprint(userId), time.Duration(setting.WechatQRCodeExpireSeconds)*time.Second) | |||
if err != nil { | |||
log.Error("createQRCode4Bind failed.e=%v", err) | |||
return nil, err | |||
} | |||
if !isOk { | |||
log.Error("createQRCode4Bind failed.redis reply is not ok") | |||
return nil, errors.New("reply is not ok when set WechatBindingUserIdKey") | |||
} | |||
result := &QRCodeResponse{ | |||
Url: r.Url, | |||
Ticket: r.Ticket, | |||
SceneStr: sceneStr, | |||
ExpireSeconds: setting.WechatQRCodeExpireSeconds, | |||
} | |||
return result, nil | |||
} |
@@ -6,6 +6,7 @@ package routes | |||
import ( | |||
"bytes" | |||
"code.gitea.io/gitea/routers/authentication" | |||
"encoding/gob" | |||
"net/http" | |||
"path" | |||
@@ -391,6 +392,13 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
}, ignSignInAndCsrf, reqSignIn) | |||
m.Post("/login/oauth/access_token", bindIgnErr(auth.AccessTokenForm{}), ignSignInAndCsrf, user.AccessTokenOAuth) | |||
m.Group("/authentication/wechat", func() { | |||
m.Get("/qrCode4Bind", authentication.GetQRCode4Bind) | |||
m.Post("/grant", bindIgnErr(auth.GrantApplicationForm{}), user.GrantApplicationOAuth) | |||
// TODO manage redirection | |||
m.Post("/authorize", bindIgnErr(auth.AuthorizationForm{}), user.AuthorizeOAuth) | |||
}, reqSignIn) | |||
m.Group("/user/settings", func() { | |||
m.Get("", userSetting.Profile) | |||
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), userSetting.ProfilePost) | |||