diff --git a/idp/github.go b/idp/github.go index 21b328cf..4c65dd92 100644 --- a/idp/github.go +++ b/idp/github.go @@ -20,6 +20,7 @@ import ( "io/ioutil" "net/http" "sync" + "time" "golang.org/x/oauth2" ) @@ -100,12 +101,102 @@ func (idp *GithubIdProvider) getEmail(token *oauth2.Token) string { return res } +//{ +// "login": "jimgreen", +// "id": 3781234, +// "node_id": "MDQ6VXNlcjM3O123456=", +// "avatar_url": "https://avatars.githubusercontent.com/u/3781234?v=4", +// "gravatar_id": "", +// "url": "https://api.github.com/users/jimgreen", +// "html_url": "https://github.com/jimgreen", +// "followers_url": "https://api.github.com/users/jimgreen/followers", +// "following_url": "https://api.github.com/users/jimgreen/following{/other_user}", +// "gists_url": "https://api.github.com/users/jimgreen/gists{/gist_id}", +// "starred_url": "https://api.github.com/users/jimgreen/starred{/owner}{/repo}", +// "subscriptions_url": "https://api.github.com/users/jimgreen/subscriptions", +// "organizations_url": "https://api.github.com/users/jimgreen/orgs", +// "repos_url": "https://api.github.com/users/jimgreen/repos", +// "events_url": "https://api.github.com/users/jimgreen/events{/privacy}", +// "received_events_url": "https://api.github.com/users/jimgreen/received_events", +// "type": "User", +// "site_admin": false, +// "name": "Jim Green", +// "company": "Casbin", +// "blog": "https://casbin.org", +// "location": "Bay Area", +// "email": "jimgreen@gmail.com", +// "hireable": true, +// "bio": "My bio", +// "twitter_username": null, +// "public_repos": 45, +// "public_gists": 3, +// "followers": 123, +// "following": 31, +// "created_at": "2016-03-06T13:16:13Z", +// "updated_at": "2020-05-30T12:15:29Z", +// "private_gists": 0, +// "total_private_repos": 12, +// "owned_private_repos": 12, +// "disk_usage": 46331, +// "collaborators": 5, +// "two_factor_authentication": true, +// "plan": { +// "name": "free", +// "space": 976562499, +// "collaborators": 0, +// "private_repos": 10000 +// } +//} + +type GitHubUserInfo struct { + Login string `json:"login"` + Id int `json:"id"` + NodeId string `json:"node_id"` + AvatarUrl string `json:"avatar_url"` + GravatarId string `json:"gravatar_id"` + Url string `json:"url"` + HtmlUrl string `json:"html_url"` + FollowersUrl string `json:"followers_url"` + FollowingUrl string `json:"following_url"` + GistsUrl string `json:"gists_url"` + StarredUrl string `json:"starred_url"` + SubscriptionsUrl string `json:"subscriptions_url"` + OrganizationsUrl string `json:"organizations_url"` + ReposUrl string `json:"repos_url"` + EventsUrl string `json:"events_url"` + ReceivedEventsUrl string `json:"received_events_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + Name string `json:"name"` + Company string `json:"company"` + Blog string `json:"blog"` + Location string `json:"location"` + Email string `json:"email"` + Hireable bool `json:"hireable"` + Bio string `json:"bio"` + TwitterUsername interface{} `json:"twitter_username"` + PublicRepos int `json:"public_repos"` + PublicGists int `json:"public_gists"` + Followers int `json:"followers"` + Following int `json:"following"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PrivateGists int `json:"private_gists"` + TotalPrivateRepos int `json:"total_private_repos"` + OwnedPrivateRepos int `json:"owned_private_repos"` + DiskUsage int `json:"disk_usage"` + Collaborators int `json:"collaborators"` + TwoFactorAuthentication bool `json:"two_factor_authentication"` + Plan struct { + Name string `json:"name"` + Space int `json:"space"` + Collaborators int `json:"collaborators"` + PrivateRepos int `json:"private_repos"` + } `json:"plan"` +} + func (idp *GithubIdProvider) getLoginAndAvatar(token *oauth2.Token) (string, string) { - type GithubUser struct { - Login string `json:"login"` - AvatarUrl string `json:"avatar_url"` - } - var githubUser GithubUser + var githubUserInfo GitHubUserInfo req, err := http.NewRequest("GET", "https://api.github.com/user", nil) if err != nil { @@ -116,14 +207,20 @@ func (idp *GithubIdProvider) getLoginAndAvatar(token *oauth2.Token) (string, str if err != nil { panic(err) } + defer resp.Body.Close() - contents2, err := ioutil.ReadAll(resp.Body) - err = json.Unmarshal(contents2, &githubUser) + + body, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } - return githubUser.Login, githubUser.AvatarUrl + err = json.Unmarshal(body, &githubUserInfo) + if err != nil { + panic(err) + } + + return githubUserInfo.Login, githubUserInfo.AvatarUrl } func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { diff --git a/idp/google.go b/idp/google.go index 001106f9..ebf1fd27 100644 --- a/idp/google.go +++ b/idp/google.go @@ -18,6 +18,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io/ioutil" "net/http" @@ -64,28 +65,56 @@ func (idp *GoogleIdProvider) GetToken(code string) (*oauth2.Token, error) { return idp.Config.Exchange(ctx, code) } +//{ +// "id": "110613473084924141234", +// "email": "jimgreen@gmail.com", +// "verified_email": true, +// "name": "Jim Green", +// "given_name": "Jim", +// "family_name": "Green", +// "picture": "https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg", +// "locale": "en" +//} + +type GoogleUserInfo struct { + Id string `json:"id"` + Email string `json:"email"` + VerifiedEmail bool `json:"verified_email"` + Name string `json:"name"` + GivenName string `json:"given_name"` + FamilyName string `json:"family_name"` + Picture string `json:"picture"` + Locale string `json:"locale"` +} + func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { - userInfo := &UserInfo{} - - type response struct { - Picture string `json:"picture"` - Email string `json:"email"` - } - - resp, err := idp.Client.Get("https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token=" + token.AccessToken) - defer resp.Body.Close() - contents, err := ioutil.ReadAll(resp.Body) - var userResponse response - err = json.Unmarshal(contents, &userResponse) + url := fmt.Sprintf("https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token=%s", token.AccessToken) + resp, err := idp.Client.Get(url) if err != nil { return nil, err } - if userResponse.Email == "" { - return userInfo, errors.New("google email is empty") + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err } - userInfo.Username = userResponse.Email - userInfo.Email = userResponse.Email - userInfo.AvatarUrl = userResponse.Picture - return userInfo, nil + var googleUserInfo GoogleUserInfo + err = json.Unmarshal(body, &googleUserInfo) + if err != nil { + return nil, err + } + + if googleUserInfo.Email == "" { + return nil, errors.New("google email is empty") + } + + userInfo := UserInfo{ + Username: googleUserInfo.Id, + DisplayName: googleUserInfo.Name, + Email: googleUserInfo.Email, + AvatarUrl: googleUserInfo.Picture, + } + return &userInfo, nil } diff --git a/idp/provider.go b/idp/provider.go index 90fa5e19..7ab6020a 100644 --- a/idp/provider.go +++ b/idp/provider.go @@ -21,9 +21,11 @@ import ( ) type UserInfo struct { - Username string - Email string - AvatarUrl string + Id string + Username string + DisplayName string + Email string + AvatarUrl string } type IdProvider interface { diff --git a/idp/qq.go b/idp/qq.go index 8844fa1a..c43ff43d 100644 --- a/idp/qq.go +++ b/idp/qq.go @@ -69,45 +69,43 @@ func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) { params.Add("redirect_uri", idp.Config.RedirectURL) getAccessTokenUrl := fmt.Sprintf("https://graph.qq.com/oauth2.0/token?%s", params.Encode()) - tokenResponse, err := idp.Client.Get(getAccessTokenUrl) + resp, err := idp.Client.Get(getAccessTokenUrl) if err != nil { return nil, err } - defer tokenResponse.Body.Close() - tokenContent, err := ioutil.ReadAll(tokenResponse.Body) + defer resp.Body.Close() + tokenContent, err := ioutil.ReadAll(resp.Body) - tokenReg := regexp.MustCompile("token=(.*?)&") - tokenRegRes := tokenReg.FindAllStringSubmatch(string(tokenContent), -1) - tokenStr := tokenRegRes[0][1] + re := regexp.MustCompile("token=(.*?)&") + matched := re.FindAllStringSubmatch(string(tokenContent), -1) + accessToken := matched[0][1] token := &oauth2.Token{ - AccessToken: tokenStr, + AccessToken: accessToken, TokenType: "Bearer", } return token, nil } func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { - userInfo := &UserInfo{} - getOpenIdUrl := fmt.Sprintf("https://graph.qq.com/oauth2.0/me?access_token=%s", token.AccessToken) - openIdResponse, err := idp.Client.Get(getOpenIdUrl) + resp, err := idp.Client.Get(getOpenIdUrl) if err != nil { return nil, err } - defer openIdResponse.Body.Close() - openIdContent, err := ioutil.ReadAll(openIdResponse.Body) + defer resp.Body.Close() + openIdBody, err := ioutil.ReadAll(resp.Body) - openIdReg := regexp.MustCompile("\"openid\":\"(.*?)\"}") - openIdRegRes := openIdReg.FindAllStringSubmatch(string(openIdContent), -1) - openId := openIdRegRes[0][1] + re := regexp.MustCompile("\"openid\":\"(.*?)\"}") + matched := re.FindAllStringSubmatch(string(openIdBody), -1) + openId := matched[0][1] if openId == "" { return nil, errors.New("openId is empty") } getUserInfoUrl := fmt.Sprintf("https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s", token.AccessToken, idp.Config.ClientID, openId) - getUserInfoResponse, err := idp.Client.Get(getUserInfoUrl) + resp, err = idp.Client.Get(getUserInfoUrl) if err != nil { return nil, err } @@ -118,8 +116,8 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { AvatarUrl string `json:"figureurl_qq_1"` } - defer getUserInfoResponse.Body.Close() - userInfoContent, err := ioutil.ReadAll(getUserInfoResponse.Body) + defer resp.Body.Close() + userInfoContent, err := ioutil.ReadAll(resp.Body) var userResponse response err = json.Unmarshal(userInfoContent, &userResponse) if err != nil { @@ -129,7 +127,10 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { return nil, errors.New(fmt.Sprintf("ret expected 0, got %d", userResponse.Ret)) } - userInfo.Username = userResponse.Nickname - userInfo.AvatarUrl = userResponse.AvatarUrl - return userInfo, nil + userInfo := UserInfo{ + Username: openId, + DisplayName: userResponse.Nickname, + AvatarUrl: userResponse.AvatarUrl, + } + return &userInfo, nil } diff --git a/idp/weChat.go b/idp/wechat.go similarity index 86% rename from idp/weChat.go rename to idp/wechat.go index 87b4d1e1..13d5ce75 100644 --- a/idp/weChat.go +++ b/idp/wechat.go @@ -18,11 +18,12 @@ import ( "bytes" "encoding/json" "fmt" - "golang.org/x/oauth2" "io" "net/http" "net/url" "time" + + "golang.org/x/oauth2" ) type WeChatIdProvider struct { @@ -30,7 +31,7 @@ type WeChatIdProvider struct { Config *oauth2.Config } -type TencentAccessToken struct { +type WechatAccessToken struct { 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 @@ -39,7 +40,7 @@ type TencentAccessToken struct { Unionid string `json:"unionid"` //This field will appear if and only if the website application has been authorized by the user's UserInfo. } -type TencentUserInfo struct { +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 @@ -109,34 +110,34 @@ func (idp *WeChatIdProvider) GetToken(code string) (*oauth2.Token, error) { return nil, err } - var tencentAccessToken TencentAccessToken - if err = json.Unmarshal([]byte(buf.String()), &tencentAccessToken); err != nil { + var wechatAccessToken WechatAccessToken + if err = json.Unmarshal([]byte(buf.String()), &wechatAccessToken); err != nil { return nil, err } token := oauth2.Token{ - AccessToken: tencentAccessToken.AccessToken, + AccessToken: wechatAccessToken.AccessToken, TokenType: "WeChatAccessToken", - RefreshToken: tencentAccessToken.RefreshToken, + RefreshToken: wechatAccessToken.RefreshToken, Expiry: time.Time{}, } raw := make(map[string]string) - raw["Openid"] = tencentAccessToken.Openid + raw["Openid"] = wechatAccessToken.Openid token.WithExtra(raw) return &token, nil } -// GetUserInfo use TencentAccessToken gotten before return TencentUserInfo +// GetUserInfo use WechatAccessToken gotten before return WechatUserInfo // 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) { - var tencentUserInfo TencentUserInfo + var wechatUserInfo WechatUserInfo accessToken := token.AccessToken openid := token.Extra("Openid") getUserInfoUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s", accessToken, openid) - getUserInfoResponse, err := idp.Client.Get(getUserInfoUrl) + resp, err := idp.Client.Get(getUserInfoUrl) if err != nil { return nil, err } @@ -146,22 +147,26 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) if err != nil { return } - }(getUserInfoResponse.Body) + }(resp.Body) buf := new(bytes.Buffer) - _, err = buf.ReadFrom(getUserInfoResponse.Body) + _, err = buf.ReadFrom(resp.Body) if err != nil { return nil, err } - if err = json.Unmarshal([]byte(buf.String()), &tencentUserInfo); err != nil { + if err = json.Unmarshal([]byte(buf.String()), &wechatUserInfo); err != nil { return nil, err } - userInfo := UserInfo{ - Username: tencentUserInfo.Nickname, - Email: "", - AvatarUrl: tencentUserInfo.Headimgurl, + username := wechatUserInfo.Unionid + if username == "" { + username = wechatUserInfo.Openid } + userInfo := UserInfo{ + Username: username, + DisplayName: wechatUserInfo.Nickname, + AvatarUrl: wechatUserInfo.Headimgurl, + } return &userInfo, nil }