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