social.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package user
  5. import (
  6. "encoding/json"
  7. "fmt"
  8. "net/http"
  9. "net/url"
  10. "strconv"
  11. "strings"
  12. "code.google.com/p/goauth2/oauth"
  13. "github.com/go-martini/martini"
  14. "github.com/gogits/gogs/models"
  15. "github.com/gogits/gogs/modules/base"
  16. "github.com/gogits/gogs/modules/log"
  17. "github.com/gogits/gogs/modules/middleware"
  18. )
  19. type BasicUserInfo struct {
  20. Identity string
  21. Name string
  22. Email string
  23. }
  24. type SocialConnector interface {
  25. Type() int
  26. SetRedirectUrl(string)
  27. UserInfo(*oauth.Token, *url.URL) (*BasicUserInfo, error)
  28. AuthCodeURL(string) string
  29. Exchange(string) (*oauth.Token, error)
  30. }
  31. func extractPath(next string) string {
  32. n, err := url.Parse(next)
  33. if err != nil {
  34. return "/"
  35. }
  36. return n.Path
  37. }
  38. var (
  39. SocialBaseUrl = "/user/login"
  40. SocialMap = make(map[string]SocialConnector)
  41. )
  42. // github && google && ...
  43. func SocialSignIn(params martini.Params, ctx *middleware.Context) {
  44. if base.OauthService == nil || !base.OauthService.GitHub.Enabled {
  45. ctx.Handle(404, "social login not enabled", nil)
  46. return
  47. }
  48. next := extractPath(ctx.Query("next"))
  49. name := params["name"]
  50. connect, ok := SocialMap[name]
  51. if !ok {
  52. ctx.Handle(404, "social login", nil)
  53. return
  54. }
  55. code := ctx.Query("code")
  56. if code == "" {
  57. // redirect to social login page
  58. connect.SetRedirectUrl(strings.TrimSuffix(base.AppUrl, "/") + ctx.Req.URL.Host + ctx.Req.URL.Path)
  59. ctx.Redirect(connect.AuthCodeURL(next))
  60. return
  61. }
  62. // handle call back
  63. tk, err := connect.Exchange(code) // exchange for token
  64. if err != nil {
  65. log.Error("oauth2 handle callback error: %v", err)
  66. ctx.Handle(500, "exchange code error", nil)
  67. return
  68. }
  69. next = extractPath(ctx.Query("state"))
  70. log.Trace("success get token")
  71. ui, err := connect.UserInfo(tk, ctx.Req.URL)
  72. if err != nil {
  73. ctx.Handle(500, fmt.Sprintf("get infomation from %s error: %v", name, err), nil)
  74. log.Error("social connect error: %s", err)
  75. return
  76. }
  77. log.Info("social login: %s", ui)
  78. oa, err := models.GetOauth2(ui.Identity)
  79. switch err {
  80. case nil:
  81. ctx.Session.Set("userId", oa.User.Id)
  82. ctx.Session.Set("userName", oa.User.Name)
  83. case models.ErrOauth2RecordNotExists:
  84. oa = &models.Oauth2{}
  85. raw, _ := json.Marshal(tk) // json encode
  86. oa.Token = string(raw)
  87. oa.Uid = -1
  88. oa.Type = connect.Type()
  89. oa.Identity = ui.Identity
  90. log.Trace("oa: %v", oa)
  91. if err = models.AddOauth2(oa); err != nil {
  92. log.Error("add oauth2 %v", err) // 501
  93. return
  94. }
  95. case models.ErrOauth2NotAssociatedWithUser:
  96. next = "/user/sign_up"
  97. default:
  98. log.Error("other error: %v", err)
  99. ctx.Handle(500, err.Error(), nil)
  100. return
  101. }
  102. ctx.Session.Set("socialId", oa.Id)
  103. ctx.Session.Set("socialName", ui.Name)
  104. ctx.Session.Set("socialEmail", ui.Email)
  105. log.Trace("socialId: %v", oa.Id)
  106. ctx.Redirect(next)
  107. }
  108. // ________.__ __ ___ ___ ___.
  109. // / _____/|__|/ |_ / | \ __ _\_ |__
  110. // / \ ___| \ __\/ ~ \ | \ __ \
  111. // \ \_\ \ || | \ Y / | / \_\ \
  112. // \______ /__||__| \___|_ /|____/|___ /
  113. // \/ \/ \/
  114. type SocialGithub struct {
  115. Token *oauth.Token
  116. *oauth.Transport
  117. }
  118. func (s *SocialGithub) Type() int {
  119. return models.OT_GITHUB
  120. }
  121. func init() {
  122. github := &SocialGithub{}
  123. name := "github"
  124. config := &oauth.Config{
  125. ClientId: "09383403ff2dc16daaa1", //base.OauthService.GitHub.ClientId, // FIXME: panic when set
  126. ClientSecret: "0e4aa0c3630df396cdcea01a9d45cacf79925fea", //base.OauthService.GitHub.ClientSecret,
  127. RedirectURL: strings.TrimSuffix(base.AppUrl, "/") + "/user/login/" + name, //ctx.Req.URL.RequestURI(),
  128. Scope: "https://api.github.com/user",
  129. AuthURL: "https://github.com/login/oauth/authorize",
  130. TokenURL: "https://github.com/login/oauth/access_token",
  131. }
  132. github.Transport = &oauth.Transport{
  133. Config: config,
  134. Transport: http.DefaultTransport,
  135. }
  136. SocialMap[name] = github
  137. }
  138. func (s *SocialGithub) SetRedirectUrl(url string) {
  139. s.Transport.Config.RedirectURL = url
  140. }
  141. func (s *SocialGithub) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) {
  142. transport := &oauth.Transport{
  143. Token: token,
  144. }
  145. var data struct {
  146. Id int `json:"id"`
  147. Name string `json:"login"`
  148. Email string `json:"email"`
  149. }
  150. var err error
  151. r, err := transport.Client().Get(s.Transport.Scope)
  152. if err != nil {
  153. return nil, err
  154. }
  155. defer r.Body.Close()
  156. if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
  157. return nil, err
  158. }
  159. return &BasicUserInfo{
  160. Identity: strconv.Itoa(data.Id),
  161. Name: data.Name,
  162. Email: data.Email,
  163. }, nil
  164. }
  165. // ________ .__
  166. // / _____/ ____ ____ ____ | | ____
  167. // / \ ___ / _ \ / _ \ / ___\| | _/ __ \
  168. // \ \_\ ( <_> | <_> ) /_/ > |_\ ___/
  169. // \______ /\____/ \____/\___ /|____/\___ >
  170. // \/ /_____/ \/
  171. type SocialGoogle struct {
  172. Token *oauth.Token
  173. *oauth.Transport
  174. }
  175. func (s *SocialGoogle) Type() int {
  176. return models.OT_GOOGLE
  177. }
  178. func init() {
  179. google := &SocialGoogle{}
  180. name := "google"
  181. // get client id and secret from
  182. // https://console.developers.google.com/project
  183. config := &oauth.Config{
  184. ClientId: "849753812404-mpd7ilvlb8c7213qn6bre6p6djjskti9.apps.googleusercontent.com", //base.OauthService.GitHub.ClientId, // FIXME: panic when set
  185. ClientSecret: "VukKc4MwaJUSmiyv3D7ANVCa", //base.OauthService.GitHub.ClientSecret,
  186. Scope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
  187. AuthURL: "https://accounts.google.com/o/oauth2/auth",
  188. TokenURL: "https://accounts.google.com/o/oauth2/token",
  189. }
  190. google.Transport = &oauth.Transport{
  191. Config: config,
  192. Transport: http.DefaultTransport,
  193. }
  194. SocialMap[name] = google
  195. }
  196. func (s *SocialGoogle) SetRedirectUrl(url string) {
  197. s.Transport.Config.RedirectURL = url
  198. }
  199. func (s *SocialGoogle) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) {
  200. transport := &oauth.Transport{Token: token}
  201. var data struct {
  202. Id string `json:"id"`
  203. Name string `json:"name"`
  204. Email string `json:"email"`
  205. }
  206. var err error
  207. reqUrl := "https://www.googleapis.com/oauth2/v1/userinfo"
  208. r, err := transport.Client().Get(reqUrl)
  209. if err != nil {
  210. return nil, err
  211. }
  212. defer r.Body.Close()
  213. if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
  214. return nil, err
  215. }
  216. return &BasicUserInfo{
  217. Identity: data.Id,
  218. Name: data.Name,
  219. Email: data.Email,
  220. }, nil
  221. }
  222. // ________ ________
  223. // \_____ \ \_____ \
  224. // / / \ \ / / \ \
  225. // / \_/. \/ \_/. \
  226. // \_____\ \_/\_____\ \_/
  227. // \__> \__>
  228. type SocialQQ struct {
  229. Token *oauth.Token
  230. *oauth.Transport
  231. reqUrl string
  232. }
  233. func (s *SocialQQ) Type() int {
  234. return models.OT_QQ
  235. }
  236. func init() {
  237. qq := &SocialQQ{}
  238. name := "qq"
  239. config := &oauth.Config{
  240. ClientId: "801497180", //base.OauthService.GitHub.ClientId, // FIXME: panic when set
  241. ClientSecret: "16cd53b8ad2e16a36fc2c8f87d9388f2", //base.OauthService.GitHub.ClientSecret,
  242. Scope: "all",
  243. AuthURL: "https://open.t.qq.com/cgi-bin/oauth2/authorize",
  244. TokenURL: "https://open.t.qq.com/cgi-bin/oauth2/access_token",
  245. }
  246. qq.reqUrl = "https://open.t.qq.com/api/user/info"
  247. qq.Transport = &oauth.Transport{
  248. Config: config,
  249. Transport: http.DefaultTransport,
  250. }
  251. SocialMap[name] = qq
  252. }
  253. func (s *SocialQQ) SetRedirectUrl(url string) {
  254. s.Transport.Config.RedirectURL = url
  255. }
  256. func (s *SocialQQ) UserInfo(token *oauth.Token, URL *url.URL) (*BasicUserInfo, error) {
  257. var data struct {
  258. Data struct {
  259. Id string `json:"openid"`
  260. Name string `json:"name"`
  261. Email string `json:"email"`
  262. } `json:"data"`
  263. }
  264. var err error
  265. // https://open.t.qq.com/api/user/info?
  266. //oauth_consumer_key=APP_KEY&
  267. //access_token=ACCESSTOKEN&openid=openid
  268. //clientip=CLIENTIP&oauth_version=2.a
  269. //scope=all
  270. var urls = url.Values{
  271. "oauth_consumer_key": {s.Transport.Config.ClientId},
  272. "access_token": {token.AccessToken},
  273. "openid": URL.Query()["openid"],
  274. "oauth_version": {"2.a"},
  275. "scope": {"all"},
  276. }
  277. r, err := http.Get(s.reqUrl + "?" + urls.Encode())
  278. if err != nil {
  279. return nil, err
  280. }
  281. defer r.Body.Close()
  282. if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
  283. return nil, err
  284. }
  285. return &BasicUserInfo{
  286. Identity: data.Data.Id,
  287. Name: data.Data.Name,
  288. Email: data.Data.Email,
  289. }, nil
  290. }