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  }