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  }