| @@ -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) | |||