You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

oauth.go 36KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. // Copyright 2019 The Gitea 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 auth
  5. import (
  6. "encoding/base64"
  7. "errors"
  8. "fmt"
  9. "html"
  10. "io"
  11. "net/http"
  12. "net/url"
  13. "strings"
  14. "code.gitea.io/gitea/models"
  15. "code.gitea.io/gitea/models/auth"
  16. "code.gitea.io/gitea/models/db"
  17. user_model "code.gitea.io/gitea/models/user"
  18. "code.gitea.io/gitea/modules/base"
  19. "code.gitea.io/gitea/modules/context"
  20. "code.gitea.io/gitea/modules/json"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/session"
  23. "code.gitea.io/gitea/modules/setting"
  24. "code.gitea.io/gitea/modules/timeutil"
  25. "code.gitea.io/gitea/modules/web"
  26. "code.gitea.io/gitea/modules/web/middleware"
  27. auth_service "code.gitea.io/gitea/services/auth"
  28. "code.gitea.io/gitea/services/auth/source/oauth2"
  29. "code.gitea.io/gitea/services/externalaccount"
  30. "code.gitea.io/gitea/services/forms"
  31. user_service "code.gitea.io/gitea/services/user"
  32. "gitea.com/go-chi/binding"
  33. "github.com/golang-jwt/jwt/v4"
  34. "github.com/markbates/goth"
  35. )
  36. const (
  37. tplGrantAccess base.TplName = "user/auth/grant"
  38. tplGrantError base.TplName = "user/auth/grant_error"
  39. )
  40. // TODO move error and responses to SDK or models
  41. // AuthorizeErrorCode represents an error code specified in RFC 6749
  42. type AuthorizeErrorCode string
  43. const (
  44. // ErrorCodeInvalidRequest represents the according error in RFC 6749
  45. ErrorCodeInvalidRequest AuthorizeErrorCode = "invalid_request"
  46. // ErrorCodeUnauthorizedClient represents the according error in RFC 6749
  47. ErrorCodeUnauthorizedClient AuthorizeErrorCode = "unauthorized_client"
  48. // ErrorCodeAccessDenied represents the according error in RFC 6749
  49. ErrorCodeAccessDenied AuthorizeErrorCode = "access_denied"
  50. // ErrorCodeUnsupportedResponseType represents the according error in RFC 6749
  51. ErrorCodeUnsupportedResponseType AuthorizeErrorCode = "unsupported_response_type"
  52. // ErrorCodeInvalidScope represents the according error in RFC 6749
  53. ErrorCodeInvalidScope AuthorizeErrorCode = "invalid_scope"
  54. // ErrorCodeServerError represents the according error in RFC 6749
  55. ErrorCodeServerError AuthorizeErrorCode = "server_error"
  56. // ErrorCodeTemporaryUnavailable represents the according error in RFC 6749
  57. ErrorCodeTemporaryUnavailable AuthorizeErrorCode = "temporarily_unavailable"
  58. )
  59. // AuthorizeError represents an error type specified in RFC 6749
  60. type AuthorizeError struct {
  61. ErrorCode AuthorizeErrorCode `json:"error" form:"error"`
  62. ErrorDescription string
  63. State string
  64. }
  65. // Error returns the error message
  66. func (err AuthorizeError) Error() string {
  67. return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
  68. }
  69. // AccessTokenErrorCode represents an error code specified in RFC 6749
  70. type AccessTokenErrorCode string
  71. const (
  72. // AccessTokenErrorCodeInvalidRequest represents an error code specified in RFC 6749
  73. AccessTokenErrorCodeInvalidRequest AccessTokenErrorCode = "invalid_request"
  74. // AccessTokenErrorCodeInvalidClient represents an error code specified in RFC 6749
  75. AccessTokenErrorCodeInvalidClient = "invalid_client"
  76. // AccessTokenErrorCodeInvalidGrant represents an error code specified in RFC 6749
  77. AccessTokenErrorCodeInvalidGrant = "invalid_grant"
  78. // AccessTokenErrorCodeUnauthorizedClient represents an error code specified in RFC 6749
  79. AccessTokenErrorCodeUnauthorizedClient = "unauthorized_client"
  80. // AccessTokenErrorCodeUnsupportedGrantType represents an error code specified in RFC 6749
  81. AccessTokenErrorCodeUnsupportedGrantType = "unsupported_grant_type"
  82. // AccessTokenErrorCodeInvalidScope represents an error code specified in RFC 6749
  83. AccessTokenErrorCodeInvalidScope = "invalid_scope"
  84. )
  85. // AccessTokenError represents an error response specified in RFC 6749
  86. type AccessTokenError struct {
  87. ErrorCode AccessTokenErrorCode `json:"error" form:"error"`
  88. ErrorDescription string `json:"error_description"`
  89. }
  90. // Error returns the error message
  91. func (err AccessTokenError) Error() string {
  92. return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
  93. }
  94. // errCallback represents a oauth2 callback error
  95. type errCallback struct {
  96. Code string
  97. Description string
  98. }
  99. func (err errCallback) Error() string {
  100. return err.Description
  101. }
  102. // TokenType specifies the kind of token
  103. type TokenType string
  104. const (
  105. // TokenTypeBearer represents a token type specified in RFC 6749
  106. TokenTypeBearer TokenType = "bearer"
  107. // TokenTypeMAC represents a token type specified in RFC 6749
  108. TokenTypeMAC = "mac"
  109. )
  110. // AccessTokenResponse represents a successful access token response
  111. type AccessTokenResponse struct {
  112. AccessToken string `json:"access_token"`
  113. TokenType TokenType `json:"token_type"`
  114. ExpiresIn int64 `json:"expires_in"`
  115. RefreshToken string `json:"refresh_token"`
  116. IDToken string `json:"id_token,omitempty"`
  117. }
  118. func newAccessTokenResponse(grant *auth.OAuth2Grant, serverKey, clientKey oauth2.JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) {
  119. if setting.OAuth2.InvalidateRefreshTokens {
  120. if err := grant.IncreaseCounter(); err != nil {
  121. return nil, &AccessTokenError{
  122. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  123. ErrorDescription: "cannot increase the grant counter",
  124. }
  125. }
  126. }
  127. // generate access token to access the API
  128. expirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.AccessTokenExpirationTime)
  129. accessToken := &oauth2.Token{
  130. GrantID: grant.ID,
  131. Type: oauth2.TypeAccessToken,
  132. // FIXME: Migrate to RegisteredClaims
  133. StandardClaims: jwt.StandardClaims{ //nolint
  134. ExpiresAt: expirationDate.AsTime().Unix(),
  135. },
  136. }
  137. signedAccessToken, err := accessToken.SignToken(serverKey)
  138. if err != nil {
  139. return nil, &AccessTokenError{
  140. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  141. ErrorDescription: "cannot sign token",
  142. }
  143. }
  144. // generate refresh token to request an access token after it expired later
  145. refreshExpirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.RefreshTokenExpirationTime * 60 * 60).AsTime().Unix()
  146. refreshToken := &oauth2.Token{
  147. GrantID: grant.ID,
  148. Counter: grant.Counter,
  149. Type: oauth2.TypeRefreshToken,
  150. // FIXME: Migrate to RegisteredClaims
  151. StandardClaims: jwt.StandardClaims{ // nolint
  152. ExpiresAt: refreshExpirationDate,
  153. },
  154. }
  155. signedRefreshToken, err := refreshToken.SignToken(serverKey)
  156. if err != nil {
  157. return nil, &AccessTokenError{
  158. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  159. ErrorDescription: "cannot sign token",
  160. }
  161. }
  162. // generate OpenID Connect id_token
  163. signedIDToken := ""
  164. if grant.ScopeContains("openid") {
  165. app, err := auth.GetOAuth2ApplicationByID(grant.ApplicationID)
  166. if err != nil {
  167. return nil, &AccessTokenError{
  168. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  169. ErrorDescription: "cannot find application",
  170. }
  171. }
  172. user, err := user_model.GetUserByID(grant.UserID)
  173. if err != nil {
  174. if user_model.IsErrUserNotExist(err) {
  175. return nil, &AccessTokenError{
  176. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  177. ErrorDescription: "cannot find user",
  178. }
  179. }
  180. log.Error("Error loading user: %v", err)
  181. return nil, &AccessTokenError{
  182. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  183. ErrorDescription: "server error",
  184. }
  185. }
  186. idToken := &oauth2.OIDCToken{
  187. // FIXME: migrate to RegisteredClaims
  188. StandardClaims: jwt.StandardClaims{ //nolint
  189. ExpiresAt: expirationDate.AsTime().Unix(),
  190. Issuer: setting.AppURL,
  191. Audience: app.ClientID,
  192. Subject: fmt.Sprint(grant.UserID),
  193. },
  194. Nonce: grant.Nonce,
  195. }
  196. if grant.ScopeContains("profile") {
  197. idToken.Name = user.FullName
  198. idToken.PreferredUsername = user.Name
  199. idToken.Profile = user.HTMLURL()
  200. idToken.Picture = user.AvatarLink()
  201. idToken.Website = user.Website
  202. idToken.Locale = user.Language
  203. idToken.UpdatedAt = user.UpdatedUnix
  204. }
  205. if grant.ScopeContains("email") {
  206. idToken.Email = user.Email
  207. idToken.EmailVerified = user.IsActive
  208. }
  209. if grant.ScopeContains("groups") {
  210. groups, err := getOAuthGroupsForUser(user)
  211. if err != nil {
  212. log.Error("Error getting groups: %v", err)
  213. return nil, &AccessTokenError{
  214. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  215. ErrorDescription: "server error",
  216. }
  217. }
  218. idToken.Groups = groups
  219. }
  220. signedIDToken, err = idToken.SignToken(clientKey)
  221. if err != nil {
  222. return nil, &AccessTokenError{
  223. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  224. ErrorDescription: "cannot sign token",
  225. }
  226. }
  227. }
  228. return &AccessTokenResponse{
  229. AccessToken: signedAccessToken,
  230. TokenType: TokenTypeBearer,
  231. ExpiresIn: setting.OAuth2.AccessTokenExpirationTime,
  232. RefreshToken: signedRefreshToken,
  233. IDToken: signedIDToken,
  234. }, nil
  235. }
  236. type userInfoResponse struct {
  237. Sub string `json:"sub"`
  238. Name string `json:"name"`
  239. Username string `json:"preferred_username"`
  240. Email string `json:"email"`
  241. Picture string `json:"picture"`
  242. Groups []string `json:"groups"`
  243. }
  244. // InfoOAuth manages request for userinfo endpoint
  245. func InfoOAuth(ctx *context.Context) {
  246. if ctx.User == nil || ctx.Data["AuthedMethod"] != (&auth_service.OAuth2{}).Name() {
  247. ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
  248. ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
  249. return
  250. }
  251. response := &userInfoResponse{
  252. Sub: fmt.Sprint(ctx.User.ID),
  253. Name: ctx.User.FullName,
  254. Username: ctx.User.Name,
  255. Email: ctx.User.Email,
  256. Picture: ctx.User.AvatarLink(),
  257. }
  258. groups, err := getOAuthGroupsForUser(ctx.User)
  259. if err != nil {
  260. ctx.ServerError("Oauth groups for user", err)
  261. return
  262. }
  263. response.Groups = groups
  264. ctx.JSON(http.StatusOK, response)
  265. }
  266. // returns a list of "org" and "org:team" strings,
  267. // that the given user is a part of.
  268. func getOAuthGroupsForUser(user *user_model.User) ([]string, error) {
  269. orgs, err := models.GetUserOrgsList(user)
  270. if err != nil {
  271. return nil, fmt.Errorf("GetUserOrgList: %v", err)
  272. }
  273. var groups []string
  274. for _, org := range orgs {
  275. groups = append(groups, org.Name)
  276. teams, err := org.LoadTeams()
  277. if err != nil {
  278. return nil, fmt.Errorf("LoadTeams: %v", err)
  279. }
  280. for _, team := range teams {
  281. if team.IsMember(user.ID) {
  282. groups = append(groups, org.Name+":"+team.LowerName)
  283. }
  284. }
  285. }
  286. return groups, nil
  287. }
  288. // IntrospectOAuth introspects an oauth token
  289. func IntrospectOAuth(ctx *context.Context) {
  290. if ctx.User == nil {
  291. ctx.Resp.Header().Set("WWW-Authenticate", `Bearer realm=""`)
  292. ctx.PlainText(http.StatusUnauthorized, "no valid authorization")
  293. return
  294. }
  295. var response struct {
  296. Active bool `json:"active"`
  297. Scope string `json:"scope,omitempty"`
  298. // FIXME: Migrate to RegisteredClaims
  299. jwt.StandardClaims //nolint
  300. }
  301. form := web.GetForm(ctx).(*forms.IntrospectTokenForm)
  302. token, err := oauth2.ParseToken(form.Token, oauth2.DefaultSigningKey)
  303. if err == nil {
  304. if token.Valid() == nil {
  305. grant, err := auth.GetOAuth2GrantByID(token.GrantID)
  306. if err == nil && grant != nil {
  307. app, err := auth.GetOAuth2ApplicationByID(grant.ApplicationID)
  308. if err == nil && app != nil {
  309. response.Active = true
  310. response.Scope = grant.Scope
  311. response.Issuer = setting.AppURL
  312. response.Audience = app.ClientID
  313. response.Subject = fmt.Sprint(grant.UserID)
  314. }
  315. }
  316. }
  317. }
  318. ctx.JSON(http.StatusOK, response)
  319. }
  320. // AuthorizeOAuth manages authorize requests
  321. func AuthorizeOAuth(ctx *context.Context) {
  322. form := web.GetForm(ctx).(*forms.AuthorizationForm)
  323. errs := binding.Errors{}
  324. errs = form.Validate(ctx.Req, errs)
  325. if len(errs) > 0 {
  326. errstring := ""
  327. for _, e := range errs {
  328. errstring += e.Error() + "\n"
  329. }
  330. ctx.ServerError("AuthorizeOAuth: Validate: ", fmt.Errorf("errors occurred during validation: %s", errstring))
  331. return
  332. }
  333. app, err := auth.GetOAuth2ApplicationByClientID(form.ClientID)
  334. if err != nil {
  335. if auth.IsErrOauthClientIDInvalid(err) {
  336. handleAuthorizeError(ctx, AuthorizeError{
  337. ErrorCode: ErrorCodeUnauthorizedClient,
  338. ErrorDescription: "Client ID not registered",
  339. State: form.State,
  340. }, "")
  341. return
  342. }
  343. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  344. return
  345. }
  346. user, err := user_model.GetUserByID(app.UID)
  347. if err != nil {
  348. ctx.ServerError("GetUserByID", err)
  349. return
  350. }
  351. if !app.ContainsRedirectURI(form.RedirectURI) {
  352. handleAuthorizeError(ctx, AuthorizeError{
  353. ErrorCode: ErrorCodeInvalidRequest,
  354. ErrorDescription: "Unregistered Redirect URI",
  355. State: form.State,
  356. }, "")
  357. return
  358. }
  359. if form.ResponseType != "code" {
  360. handleAuthorizeError(ctx, AuthorizeError{
  361. ErrorCode: ErrorCodeUnsupportedResponseType,
  362. ErrorDescription: "Only code response type is supported.",
  363. State: form.State,
  364. }, form.RedirectURI)
  365. return
  366. }
  367. // pkce support
  368. switch form.CodeChallengeMethod {
  369. case "S256":
  370. case "plain":
  371. if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallengeMethod); err != nil {
  372. handleAuthorizeError(ctx, AuthorizeError{
  373. ErrorCode: ErrorCodeServerError,
  374. ErrorDescription: "cannot set code challenge method",
  375. State: form.State,
  376. }, form.RedirectURI)
  377. return
  378. }
  379. if err := ctx.Session.Set("CodeChallengeMethod", form.CodeChallenge); err != nil {
  380. handleAuthorizeError(ctx, AuthorizeError{
  381. ErrorCode: ErrorCodeServerError,
  382. ErrorDescription: "cannot set code challenge",
  383. State: form.State,
  384. }, form.RedirectURI)
  385. return
  386. }
  387. // Here we're just going to try to release the session early
  388. if err := ctx.Session.Release(); err != nil {
  389. // we'll tolerate errors here as they *should* get saved elsewhere
  390. log.Error("Unable to save changes to the session: %v", err)
  391. }
  392. case "":
  393. break
  394. default:
  395. handleAuthorizeError(ctx, AuthorizeError{
  396. ErrorCode: ErrorCodeInvalidRequest,
  397. ErrorDescription: "unsupported code challenge method",
  398. State: form.State,
  399. }, form.RedirectURI)
  400. return
  401. }
  402. grant, err := app.GetGrantByUserID(ctx.User.ID)
  403. if err != nil {
  404. handleServerError(ctx, form.State, form.RedirectURI)
  405. return
  406. }
  407. // Redirect if user already granted access
  408. if grant != nil {
  409. code, err := grant.GenerateNewAuthorizationCode(form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
  410. if err != nil {
  411. handleServerError(ctx, form.State, form.RedirectURI)
  412. return
  413. }
  414. redirect, err := code.GenerateRedirectURI(form.State)
  415. if err != nil {
  416. handleServerError(ctx, form.State, form.RedirectURI)
  417. return
  418. }
  419. // Update nonce to reflect the new session
  420. if len(form.Nonce) > 0 {
  421. err := grant.SetNonce(form.Nonce)
  422. if err != nil {
  423. log.Error("Unable to update nonce: %v", err)
  424. }
  425. }
  426. ctx.Redirect(redirect.String(), 302)
  427. return
  428. }
  429. // show authorize page to grant access
  430. ctx.Data["Application"] = app
  431. ctx.Data["RedirectURI"] = form.RedirectURI
  432. ctx.Data["State"] = form.State
  433. ctx.Data["Scope"] = form.Scope
  434. ctx.Data["Nonce"] = form.Nonce
  435. ctx.Data["ApplicationUserLink"] = "<a href=\"" + html.EscapeString(user.HTMLURL()) + "\">@" + html.EscapeString(user.Name) + "</a>"
  436. ctx.Data["ApplicationRedirectDomainHTML"] = "<strong>" + html.EscapeString(form.RedirectURI) + "</strong>"
  437. // TODO document SESSION <=> FORM
  438. err = ctx.Session.Set("client_id", app.ClientID)
  439. if err != nil {
  440. handleServerError(ctx, form.State, form.RedirectURI)
  441. log.Error(err.Error())
  442. return
  443. }
  444. err = ctx.Session.Set("redirect_uri", form.RedirectURI)
  445. if err != nil {
  446. handleServerError(ctx, form.State, form.RedirectURI)
  447. log.Error(err.Error())
  448. return
  449. }
  450. err = ctx.Session.Set("state", form.State)
  451. if err != nil {
  452. handleServerError(ctx, form.State, form.RedirectURI)
  453. log.Error(err.Error())
  454. return
  455. }
  456. // Here we're just going to try to release the session early
  457. if err := ctx.Session.Release(); err != nil {
  458. // we'll tolerate errors here as they *should* get saved elsewhere
  459. log.Error("Unable to save changes to the session: %v", err)
  460. }
  461. ctx.HTML(http.StatusOK, tplGrantAccess)
  462. }
  463. // GrantApplicationOAuth manages the post request submitted when a user grants access to an application
  464. func GrantApplicationOAuth(ctx *context.Context) {
  465. form := web.GetForm(ctx).(*forms.GrantApplicationForm)
  466. if ctx.Session.Get("client_id") != form.ClientID || ctx.Session.Get("state") != form.State ||
  467. ctx.Session.Get("redirect_uri") != form.RedirectURI {
  468. ctx.Error(http.StatusBadRequest)
  469. return
  470. }
  471. app, err := auth.GetOAuth2ApplicationByClientID(form.ClientID)
  472. if err != nil {
  473. ctx.ServerError("GetOAuth2ApplicationByClientID", err)
  474. return
  475. }
  476. grant, err := app.CreateGrant(ctx.User.ID, form.Scope)
  477. if err != nil {
  478. handleAuthorizeError(ctx, AuthorizeError{
  479. State: form.State,
  480. ErrorDescription: "cannot create grant for user",
  481. ErrorCode: ErrorCodeServerError,
  482. }, form.RedirectURI)
  483. return
  484. }
  485. if len(form.Nonce) > 0 {
  486. err := grant.SetNonce(form.Nonce)
  487. if err != nil {
  488. log.Error("Unable to update nonce: %v", err)
  489. }
  490. }
  491. var codeChallenge, codeChallengeMethod string
  492. codeChallenge, _ = ctx.Session.Get("CodeChallenge").(string)
  493. codeChallengeMethod, _ = ctx.Session.Get("CodeChallengeMethod").(string)
  494. code, err := grant.GenerateNewAuthorizationCode(form.RedirectURI, codeChallenge, codeChallengeMethod)
  495. if err != nil {
  496. handleServerError(ctx, form.State, form.RedirectURI)
  497. return
  498. }
  499. redirect, err := code.GenerateRedirectURI(form.State)
  500. if err != nil {
  501. handleServerError(ctx, form.State, form.RedirectURI)
  502. return
  503. }
  504. ctx.Redirect(redirect.String(), 302)
  505. }
  506. // OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities
  507. func OIDCWellKnown(ctx *context.Context) {
  508. t := ctx.Render.TemplateLookup("user/auth/oidc_wellknown")
  509. ctx.Resp.Header().Set("Content-Type", "application/json")
  510. ctx.Data["SigningKey"] = oauth2.DefaultSigningKey
  511. if err := t.Execute(ctx.Resp, ctx.Data); err != nil {
  512. log.Error("%v", err)
  513. ctx.Error(http.StatusInternalServerError)
  514. }
  515. }
  516. // OIDCKeys generates the JSON Web Key Set
  517. func OIDCKeys(ctx *context.Context) {
  518. jwk, err := oauth2.DefaultSigningKey.ToJWK()
  519. if err != nil {
  520. log.Error("Error converting signing key to JWK: %v", err)
  521. ctx.Error(http.StatusInternalServerError)
  522. return
  523. }
  524. jwk["use"] = "sig"
  525. jwks := map[string][]map[string]string{
  526. "keys": {
  527. jwk,
  528. },
  529. }
  530. ctx.Resp.Header().Set("Content-Type", "application/json")
  531. enc := json.NewEncoder(ctx.Resp)
  532. if err := enc.Encode(jwks); err != nil {
  533. log.Error("Failed to encode representation as json. Error: %v", err)
  534. }
  535. }
  536. // AccessTokenOAuth manages all access token requests by the client
  537. func AccessTokenOAuth(ctx *context.Context) {
  538. form := *web.GetForm(ctx).(*forms.AccessTokenForm)
  539. if form.ClientID == "" {
  540. authHeader := ctx.Req.Header.Get("Authorization")
  541. authContent := strings.SplitN(authHeader, " ", 2)
  542. if len(authContent) == 2 && authContent[0] == "Basic" {
  543. payload, err := base64.StdEncoding.DecodeString(authContent[1])
  544. if err != nil {
  545. handleAccessTokenError(ctx, AccessTokenError{
  546. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  547. ErrorDescription: "cannot parse basic auth header",
  548. })
  549. return
  550. }
  551. pair := strings.SplitN(string(payload), ":", 2)
  552. if len(pair) != 2 {
  553. handleAccessTokenError(ctx, AccessTokenError{
  554. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  555. ErrorDescription: "cannot parse basic auth header",
  556. })
  557. return
  558. }
  559. form.ClientID = pair[0]
  560. form.ClientSecret = pair[1]
  561. }
  562. }
  563. serverKey := oauth2.DefaultSigningKey
  564. clientKey := serverKey
  565. if serverKey.IsSymmetric() {
  566. var err error
  567. clientKey, err = oauth2.CreateJWTSigningKey(serverKey.SigningMethod().Alg(), []byte(form.ClientSecret))
  568. if err != nil {
  569. handleAccessTokenError(ctx, AccessTokenError{
  570. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  571. ErrorDescription: "Error creating signing key",
  572. })
  573. return
  574. }
  575. }
  576. switch form.GrantType {
  577. case "refresh_token":
  578. handleRefreshToken(ctx, form, serverKey, clientKey)
  579. case "authorization_code":
  580. handleAuthorizationCode(ctx, form, serverKey, clientKey)
  581. default:
  582. handleAccessTokenError(ctx, AccessTokenError{
  583. ErrorCode: AccessTokenErrorCodeUnsupportedGrantType,
  584. ErrorDescription: "Only refresh_token or authorization_code grant type is supported",
  585. })
  586. }
  587. }
  588. func handleRefreshToken(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) {
  589. token, err := oauth2.ParseToken(form.RefreshToken, serverKey)
  590. if err != nil {
  591. handleAccessTokenError(ctx, AccessTokenError{
  592. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  593. ErrorDescription: "client is not authorized",
  594. })
  595. return
  596. }
  597. // get grant before increasing counter
  598. grant, err := auth.GetOAuth2GrantByID(token.GrantID)
  599. if err != nil || grant == nil {
  600. handleAccessTokenError(ctx, AccessTokenError{
  601. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  602. ErrorDescription: "grant does not exist",
  603. })
  604. return
  605. }
  606. // check if token got already used
  607. if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) {
  608. handleAccessTokenError(ctx, AccessTokenError{
  609. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  610. ErrorDescription: "token was already used",
  611. })
  612. log.Warn("A client tried to use a refresh token for grant_id = %d was used twice!", grant.ID)
  613. return
  614. }
  615. accessToken, tokenErr := newAccessTokenResponse(grant, serverKey, clientKey)
  616. if tokenErr != nil {
  617. handleAccessTokenError(ctx, *tokenErr)
  618. return
  619. }
  620. ctx.JSON(http.StatusOK, accessToken)
  621. }
  622. func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, serverKey, clientKey oauth2.JWTSigningKey) {
  623. app, err := auth.GetOAuth2ApplicationByClientID(form.ClientID)
  624. if err != nil {
  625. handleAccessTokenError(ctx, AccessTokenError{
  626. ErrorCode: AccessTokenErrorCodeInvalidClient,
  627. ErrorDescription: fmt.Sprintf("cannot load client with client id: '%s'", form.ClientID),
  628. })
  629. return
  630. }
  631. if !app.ValidateClientSecret([]byte(form.ClientSecret)) {
  632. handleAccessTokenError(ctx, AccessTokenError{
  633. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  634. ErrorDescription: "client is not authorized",
  635. })
  636. return
  637. }
  638. if form.RedirectURI != "" && !app.ContainsRedirectURI(form.RedirectURI) {
  639. handleAccessTokenError(ctx, AccessTokenError{
  640. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  641. ErrorDescription: "client is not authorized",
  642. })
  643. return
  644. }
  645. authorizationCode, err := auth.GetOAuth2AuthorizationByCode(form.Code)
  646. if err != nil || authorizationCode == nil {
  647. handleAccessTokenError(ctx, AccessTokenError{
  648. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  649. ErrorDescription: "client is not authorized",
  650. })
  651. return
  652. }
  653. // check if code verifier authorizes the client, PKCE support
  654. if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) {
  655. handleAccessTokenError(ctx, AccessTokenError{
  656. ErrorCode: AccessTokenErrorCodeUnauthorizedClient,
  657. ErrorDescription: "client is not authorized",
  658. })
  659. return
  660. }
  661. // check if granted for this application
  662. if authorizationCode.Grant.ApplicationID != app.ID {
  663. handleAccessTokenError(ctx, AccessTokenError{
  664. ErrorCode: AccessTokenErrorCodeInvalidGrant,
  665. ErrorDescription: "invalid grant",
  666. })
  667. return
  668. }
  669. // remove token from database to deny duplicate usage
  670. if err := authorizationCode.Invalidate(); err != nil {
  671. handleAccessTokenError(ctx, AccessTokenError{
  672. ErrorCode: AccessTokenErrorCodeInvalidRequest,
  673. ErrorDescription: "cannot proceed your request",
  674. })
  675. }
  676. resp, tokenErr := newAccessTokenResponse(authorizationCode.Grant, serverKey, clientKey)
  677. if tokenErr != nil {
  678. handleAccessTokenError(ctx, *tokenErr)
  679. return
  680. }
  681. // send successful response
  682. ctx.JSON(http.StatusOK, resp)
  683. }
  684. func handleAccessTokenError(ctx *context.Context, acErr AccessTokenError) {
  685. ctx.JSON(http.StatusBadRequest, acErr)
  686. }
  687. func handleServerError(ctx *context.Context, state, redirectURI string) {
  688. handleAuthorizeError(ctx, AuthorizeError{
  689. ErrorCode: ErrorCodeServerError,
  690. ErrorDescription: "A server error occurred",
  691. State: state,
  692. }, redirectURI)
  693. }
  694. func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirectURI string) {
  695. if redirectURI == "" {
  696. log.Warn("Authorization failed: %v", authErr.ErrorDescription)
  697. ctx.Data["Error"] = authErr
  698. ctx.HTML(400, tplGrantError)
  699. return
  700. }
  701. redirect, err := url.Parse(redirectURI)
  702. if err != nil {
  703. ctx.ServerError("url.Parse", err)
  704. return
  705. }
  706. q := redirect.Query()
  707. q.Set("error", string(authErr.ErrorCode))
  708. q.Set("error_description", authErr.ErrorDescription)
  709. q.Set("state", authErr.State)
  710. redirect.RawQuery = q.Encode()
  711. ctx.Redirect(redirect.String(), 302)
  712. }
  713. // SignInOAuth handles the OAuth2 login buttons
  714. func SignInOAuth(ctx *context.Context) {
  715. provider := ctx.Params(":provider")
  716. authSource, err := auth.GetActiveOAuth2SourceByName(provider)
  717. if err != nil {
  718. ctx.ServerError("SignIn", err)
  719. return
  720. }
  721. // try to do a direct callback flow, so we don't authenticate the user again but use the valid accesstoken to get the user
  722. user, gothUser, err := oAuth2UserLoginCallback(authSource, ctx.Req, ctx.Resp)
  723. if err == nil && user != nil {
  724. // we got the user without going through the whole OAuth2 authentication flow again
  725. handleOAuth2SignIn(ctx, authSource, user, gothUser)
  726. return
  727. }
  728. if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
  729. if strings.Contains(err.Error(), "no provider for ") {
  730. if err = oauth2.ResetOAuth2(); err != nil {
  731. ctx.ServerError("SignIn", err)
  732. return
  733. }
  734. if err = authSource.Cfg.(*oauth2.Source).Callout(ctx.Req, ctx.Resp); err != nil {
  735. ctx.ServerError("SignIn", err)
  736. }
  737. return
  738. }
  739. ctx.ServerError("SignIn", err)
  740. }
  741. // redirect is done in oauth2.Auth
  742. }
  743. // SignInOAuthCallback handles the callback from the given provider
  744. func SignInOAuthCallback(ctx *context.Context) {
  745. provider := ctx.Params(":provider")
  746. // first look if the provider is still active
  747. authSource, err := auth.GetActiveOAuth2SourceByName(provider)
  748. if err != nil {
  749. ctx.ServerError("SignIn", err)
  750. return
  751. }
  752. if authSource == nil {
  753. ctx.ServerError("SignIn", errors.New("No valid provider found, check configured callback url in provider"))
  754. return
  755. }
  756. u, gothUser, err := oAuth2UserLoginCallback(authSource, ctx.Req, ctx.Resp)
  757. if err != nil {
  758. if user_model.IsErrUserProhibitLogin(err) {
  759. uplerr := err.(*user_model.ErrUserProhibitLogin)
  760. log.Info("Failed authentication attempt for %s from %s: %v", uplerr.Name, ctx.RemoteAddr(), err)
  761. ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
  762. ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
  763. return
  764. }
  765. if callbackErr, ok := err.(errCallback); ok {
  766. log.Info("Failed OAuth callback: (%v) %v", callbackErr.Code, callbackErr.Description)
  767. switch callbackErr.Code {
  768. case "access_denied":
  769. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error.access_denied"))
  770. case "temporarily_unavailable":
  771. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error.temporarily_unavailable"))
  772. default:
  773. ctx.Flash.Error(ctx.Tr("auth.oauth.signin.error"))
  774. }
  775. ctx.Redirect(setting.AppSubURL + "/user/login")
  776. return
  777. }
  778. ctx.ServerError("UserSignIn", err)
  779. return
  780. }
  781. if u == nil {
  782. if !setting.Service.AllowOnlyInternalRegistration && setting.OAuth2Client.EnableAutoRegistration {
  783. // create new user with details from oauth2 provider
  784. var missingFields []string
  785. if gothUser.UserID == "" {
  786. missingFields = append(missingFields, "sub")
  787. }
  788. if gothUser.Email == "" {
  789. missingFields = append(missingFields, "email")
  790. }
  791. if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname && gothUser.NickName == "" {
  792. missingFields = append(missingFields, "nickname")
  793. }
  794. if len(missingFields) > 0 {
  795. log.Error("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields)
  796. if authSource.IsOAuth2() && authSource.Cfg.(*oauth2.Source).Provider == "openidConnect" {
  797. log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields")
  798. }
  799. err = fmt.Errorf("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields)
  800. ctx.ServerError("CreateUser", err)
  801. return
  802. }
  803. u = &user_model.User{
  804. Name: getUserName(&gothUser),
  805. FullName: gothUser.Name,
  806. Email: gothUser.Email,
  807. IsActive: !setting.OAuth2Client.RegisterEmailConfirm,
  808. LoginType: auth.OAuth2,
  809. LoginSource: authSource.ID,
  810. LoginName: gothUser.UserID,
  811. IsRestricted: setting.Service.DefaultUserIsRestricted,
  812. }
  813. setUserGroupClaims(authSource, u, &gothUser)
  814. if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
  815. // error already handled
  816. return
  817. }
  818. } else {
  819. // no existing user is found, request attach or new account
  820. showLinkingLogin(ctx, gothUser)
  821. return
  822. }
  823. }
  824. handleOAuth2SignIn(ctx, authSource, u, gothUser)
  825. }
  826. func claimValueToStringSlice(claimValue interface{}) []string {
  827. var groups []string
  828. switch rawGroup := claimValue.(type) {
  829. case []string:
  830. groups = rawGroup
  831. default:
  832. str := fmt.Sprintf("%s", rawGroup)
  833. groups = strings.Split(str, ",")
  834. }
  835. return groups
  836. }
  837. func setUserGroupClaims(loginSource *auth.Source, u *user_model.User, gothUser *goth.User) bool {
  838. source := loginSource.Cfg.(*oauth2.Source)
  839. if source.GroupClaimName == "" || (source.AdminGroup == "" && source.RestrictedGroup == "") {
  840. return false
  841. }
  842. groupClaims, has := gothUser.RawData[source.GroupClaimName]
  843. if !has {
  844. return false
  845. }
  846. groups := claimValueToStringSlice(groupClaims)
  847. wasAdmin, wasRestricted := u.IsAdmin, u.IsRestricted
  848. if source.AdminGroup != "" {
  849. u.IsAdmin = false
  850. }
  851. if source.RestrictedGroup != "" {
  852. u.IsRestricted = false
  853. }
  854. for _, g := range groups {
  855. if source.AdminGroup != "" && g == source.AdminGroup {
  856. u.IsAdmin = true
  857. } else if source.RestrictedGroup != "" && g == source.RestrictedGroup {
  858. u.IsRestricted = true
  859. }
  860. }
  861. return wasAdmin != u.IsAdmin || wasRestricted != u.IsRestricted
  862. }
  863. func showLinkingLogin(ctx *context.Context, gothUser goth.User) {
  864. if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil {
  865. ctx.ServerError("RegenerateSession", err)
  866. return
  867. }
  868. if err := ctx.Session.Set("linkAccountGothUser", gothUser); err != nil {
  869. log.Error("Error setting linkAccountGothUser in session: %v", err)
  870. }
  871. if err := ctx.Session.Release(); err != nil {
  872. log.Error("Error storing session: %v", err)
  873. }
  874. ctx.Redirect(setting.AppSubURL + "/user/link_account")
  875. }
  876. func updateAvatarIfNeed(url string, u *user_model.User) {
  877. if setting.OAuth2Client.UpdateAvatar && len(url) > 0 {
  878. resp, err := http.Get(url)
  879. if err == nil {
  880. defer func() {
  881. _ = resp.Body.Close()
  882. }()
  883. }
  884. // ignore any error
  885. if err == nil && resp.StatusCode == http.StatusOK {
  886. data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1))
  887. if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize {
  888. _ = user_service.UploadAvatar(u, data)
  889. }
  890. }
  891. }
  892. }
  893. func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) {
  894. updateAvatarIfNeed(gothUser.AvatarURL, u)
  895. needs2FA := false
  896. if !source.Cfg.(*oauth2.Source).SkipLocalTwoFA {
  897. _, err := auth.GetTwoFactorByUID(u.ID)
  898. if err != nil && !auth.IsErrTwoFactorNotEnrolled(err) {
  899. ctx.ServerError("UserSignIn", err)
  900. return
  901. }
  902. needs2FA = err == nil
  903. }
  904. // If this user is enrolled in 2FA and this source doesn't override it,
  905. // we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page.
  906. if !needs2FA {
  907. if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil {
  908. ctx.ServerError("RegenerateSession", err)
  909. return
  910. }
  911. if err := ctx.Session.Set("uid", u.ID); err != nil {
  912. log.Error("Error setting uid in session: %v", err)
  913. }
  914. if err := ctx.Session.Set("uname", u.Name); err != nil {
  915. log.Error("Error setting uname in session: %v", err)
  916. }
  917. if err := ctx.Session.Release(); err != nil {
  918. log.Error("Error storing session: %v", err)
  919. }
  920. // Clear whatever CSRF has right now, force to generate a new one
  921. middleware.DeleteCSRFCookie(ctx.Resp)
  922. // Register last login
  923. u.SetLastLogin()
  924. // Update GroupClaims
  925. changed := setUserGroupClaims(source, u, &gothUser)
  926. cols := []string{"last_login_unix"}
  927. if changed {
  928. cols = append(cols, "is_admin", "is_restricted")
  929. }
  930. if err := user_model.UpdateUserCols(db.DefaultContext, u, cols...); err != nil {
  931. ctx.ServerError("UpdateUserCols", err)
  932. return
  933. }
  934. // update external user information
  935. if err := externalaccount.UpdateExternalUser(u, gothUser); err != nil {
  936. log.Error("UpdateExternalUser failed: %v", err)
  937. }
  938. if err := resetLocale(ctx, u); err != nil {
  939. ctx.ServerError("resetLocale", err)
  940. return
  941. }
  942. if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 {
  943. middleware.DeleteRedirectToCookie(ctx.Resp)
  944. ctx.RedirectToFirst(redirectTo)
  945. return
  946. }
  947. ctx.Redirect(setting.AppSubURL + "/")
  948. return
  949. }
  950. changed := setUserGroupClaims(source, u, &gothUser)
  951. if changed {
  952. if err := user_model.UpdateUserCols(db.DefaultContext, u, "is_admin", "is_restricted"); err != nil {
  953. ctx.ServerError("UpdateUserCols", err)
  954. return
  955. }
  956. }
  957. if _, err := session.RegenerateSession(ctx.Resp, ctx.Req); err != nil {
  958. ctx.ServerError("RegenerateSession", err)
  959. return
  960. }
  961. // User needs to use 2FA, save data and redirect to 2FA page.
  962. if err := ctx.Session.Set("twofaUid", u.ID); err != nil {
  963. log.Error("Error setting twofaUid in session: %v", err)
  964. }
  965. if err := ctx.Session.Set("twofaRemember", false); err != nil {
  966. log.Error("Error setting twofaRemember in session: %v", err)
  967. }
  968. if err := ctx.Session.Release(); err != nil {
  969. log.Error("Error storing session: %v", err)
  970. }
  971. // If WebAuthn is enrolled -> Redirect to WebAuthn instead
  972. regs, err := auth.GetWebAuthnCredentialsByUID(u.ID)
  973. if err == nil && len(regs) > 0 {
  974. ctx.Redirect(setting.AppSubURL + "/user/webauthn")
  975. return
  976. }
  977. ctx.Redirect(setting.AppSubURL + "/user/two_factor")
  978. }
  979. // OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
  980. // login the user
  981. func oAuth2UserLoginCallback(authSource *auth.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) {
  982. oauth2Source := authSource.Cfg.(*oauth2.Source)
  983. gothUser, err := oauth2Source.Callback(request, response)
  984. if err != nil {
  985. if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") {
  986. log.Error("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", authSource.Name, setting.OAuth2.MaxTokenLength)
  987. err = fmt.Errorf("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", authSource.Name, setting.OAuth2.MaxTokenLength)
  988. }
  989. // goth does not provide the original error message
  990. // https://github.com/markbates/goth/issues/348
  991. if strings.Contains(err.Error(), "server response missing access_token") || strings.Contains(err.Error(), "could not find a matching session for this request") {
  992. errorCode := request.FormValue("error")
  993. errorDescription := request.FormValue("error_description")
  994. if errorCode != "" || errorDescription != "" {
  995. return nil, goth.User{}, errCallback{
  996. Code: errorCode,
  997. Description: errorDescription,
  998. }
  999. }
  1000. }
  1001. return nil, goth.User{}, err
  1002. }
  1003. if oauth2Source.RequiredClaimName != "" {
  1004. claimInterface, has := gothUser.RawData[oauth2Source.RequiredClaimName]
  1005. if !has {
  1006. return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
  1007. }
  1008. if oauth2Source.RequiredClaimValue != "" {
  1009. groups := claimValueToStringSlice(claimInterface)
  1010. found := false
  1011. for _, group := range groups {
  1012. if group == oauth2Source.RequiredClaimValue {
  1013. found = true
  1014. break
  1015. }
  1016. }
  1017. if !found {
  1018. return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
  1019. }
  1020. }
  1021. }
  1022. user := &user_model.User{
  1023. LoginName: gothUser.UserID,
  1024. LoginType: auth.OAuth2,
  1025. LoginSource: authSource.ID,
  1026. }
  1027. hasUser, err := user_model.GetUser(user)
  1028. if err != nil {
  1029. return nil, goth.User{}, err
  1030. }
  1031. if hasUser {
  1032. return user, gothUser, nil
  1033. }
  1034. // search in external linked users
  1035. externalLoginUser := &user_model.ExternalLoginUser{
  1036. ExternalID: gothUser.UserID,
  1037. LoginSourceID: authSource.ID,
  1038. }
  1039. hasUser, err = user_model.GetExternalLogin(externalLoginUser)
  1040. if err != nil {
  1041. return nil, goth.User{}, err
  1042. }
  1043. if hasUser {
  1044. user, err = user_model.GetUserByID(externalLoginUser.UserID)
  1045. return user, gothUser, err
  1046. }
  1047. // no user found to login
  1048. return nil, gothUser, nil
  1049. }