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