VYPR
High severityNVD Advisory· Published Oct 16, 2025· Updated Feb 26, 2026

Arbitrary Mattermost Team can be joined by manipulating the SAML RelayState

CVE-2025-58075

Description

Mattermost versions 10.11.x <= 10.11.1, 10.10.x <= 10.10.2, 10.5.x <= 10.5.10 fail to verify a user has permission to join a Mattermost team using the original invite token which allows any attacked to join any team on a Mattermost server regardless of restrictions via manipulating the RelayState

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/mattermost/mattermost/server/v8Go
< 8.0.0-20250815100400-2d5cdc6e217e8.0.0-20250815100400-2d5cdc6e217e
github.com/mattermost/mattermost-serverGo
>= 10.11.0, < 10.11.210.11.2
github.com/mattermost/mattermost-serverGo
>= 10.10.0, < 10.10.310.10.3
github.com/mattermost/mattermost-serverGo
>= 10.5.0, < 10.5.1110.5.11

Affected products

1

Patches

3
7a2ac23e2074

[MM-64896][MM-64898] Pass inviteid/tokenid to relay state/props for external auth when auto-joining a team (#33545) (#33739)

https://github.com/mattermost/mattermostDevin BinnieAug 18, 2025via ghsa
9 files changed · +172 124
  • server/channels/app/app_iface.go+7 6 modified
    @@ -460,6 +460,7 @@ type AppIface interface {
     	AddTeamsToRetentionPolicy(policyID string, teamIDs []string) *model.AppError
     	AddUserToTeam(c request.CTX, teamID string, userID string, userRequestorId string) (*model.Team, *model.TeamMember, *model.AppError)
     	AddUserToTeamByInviteId(c request.CTX, inviteId string, userID string) (*model.Team, *model.TeamMember, *model.AppError)
    +	AddUserToTeamByInviteIfNeeded(c request.CTX, user *model.User, inviteToken string, inviteId string) *model.AppError
     	AddUserToTeamByTeamId(c request.CTX, teamID string, user *model.User) *model.AppError
     	AddUserToTeamByToken(c request.CTX, userID string, tokenID string) (*model.Team, *model.TeamMember, *model.AppError)
     	AdjustImage(rctx request.CTX, file io.ReadSeeker) (*bytes.Buffer, *model.AppError)
    @@ -472,7 +473,7 @@ type AppIface interface {
     	AttachDeviceId(sessionID string, deviceID string, expiresAt int64) *model.AppError
     	AttachSessionCookies(c request.CTX, w http.ResponseWriter, r *http.Request)
     	AuthenticateUserForLogin(c request.CTX, id, loginId, password, mfaToken, cwsToken string, ldapOnly bool) (user *model.User, err *model.AppError)
    -	AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.Request, service, code, state, redirectURI string) (io.ReadCloser, string, map[string]string, *model.User, *model.AppError)
    +	AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.Request, service, code, state, redirectURI string) (io.ReadCloser, map[string]string, *model.User, *model.AppError)
     	AutocompleteChannels(c request.CTX, userID, term string) (model.ChannelListWithTeamData, *model.AppError)
     	AutocompleteChannelsForSearch(c request.CTX, teamID string, userID string, term string) (model.ChannelList, *model.AppError)
     	AutocompleteChannelsForTeam(c request.CTX, teamID, userID, term string) (model.ChannelList, *model.AppError)
    @@ -515,7 +516,7 @@ type AppIface interface {
     	CompareAndDeletePluginKey(rctx request.CTX, pluginID string, key string, oldValue []byte) (bool, *model.AppError)
     	CompareAndSetPluginKey(pluginID string, key string, oldValue, newValue []byte) (bool, *model.AppError)
     	CompileReportChunks(format string, prefix string, numberOfChunks int, headers []string) *model.AppError
    -	CompleteOAuth(c request.CTX, service string, body io.ReadCloser, teamID string, props map[string]string, tokenUser *model.User) (*model.User, *model.AppError)
    +	CompleteOAuth(c request.CTX, service string, body io.ReadCloser, props map[string]string, tokenUser *model.User) (*model.User, *model.AppError)
     	CompleteOnboarding(c request.CTX, request *model.CompleteOnboardingRequest) *model.AppError
     	CompleteSwitchWithOAuth(c request.CTX, service string, userData io.Reader, email string, tokenUser *model.User) (*model.User, *model.AppError)
     	Compliance() einterfaces.ComplianceInterface
    @@ -540,7 +541,7 @@ type AppIface interface {
     	CreateJob(c request.CTX, job *model.Job) (*model.Job, *model.AppError)
     	CreateOAuthApp(app *model.OAuthApp) (*model.OAuthApp, *model.AppError)
     	CreateOAuthStateToken(extra string) (*model.Token, *model.AppError)
    -	CreateOAuthUser(c request.CTX, service string, userData io.Reader, teamID string, tokenUser *model.User) (*model.User, *model.AppError)
    +	CreateOAuthUser(c request.CTX, service string, userData io.Reader, inviteToken string, inviteId string, tokenUser *model.User) (*model.User, *model.AppError)
     	CreateOutgoingWebhook(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError)
     	CreatePasswordRecoveryToken(rctx request.CTX, userID, email string) (*model.Token, *model.AppError)
     	CreatePost(c request.CTX, post *model.Post, channel *model.Channel, flags model.CreatePostFlags) (savedPost *model.Post, err *model.AppError)
    @@ -763,8 +764,8 @@ type AppIface interface {
     	GetOAuthAppsByCreator(userID string, page, perPage int) ([]*model.OAuthApp, *model.AppError)
     	GetOAuthCodeRedirect(userID string, authRequest *model.AuthorizeRequest) (string, *model.AppError)
     	GetOAuthImplicitRedirect(c request.CTX, userID string, authRequest *model.AuthorizeRequest) (string, *model.AppError)
    -	GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, teamID, action, redirectTo, loginHint string, isMobile bool, desktopToken string) (string, *model.AppError)
    -	GetOAuthSignupEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, teamID string, desktopToken string) (string, *model.AppError)
    +	GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, action, redirectTo, loginHint string, isMobile bool, desktopToken string, inviteToken string, inviteId string) (string, *model.AppError)
    +	GetOAuthSignupEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, desktopToken string, inviteToken string, inviteId string) (string, *model.AppError)
     	GetOAuthStateToken(token string) (*model.Token, *model.AppError)
     	GetOnboarding() (*model.System, *model.AppError)
     	GetOpenGraphMetadata(requestURL string) ([]byte, error)
    @@ -977,7 +978,7 @@ type AppIface interface {
     	ListPluginKeys(pluginID string, page, perPage int) ([]string, *model.AppError)
     	ListTeamCommands(teamID string) ([]*model.Command, *model.AppError)
     	Log() *mlog.Logger
    -	LoginByOAuth(c request.CTX, service string, userData io.Reader, teamID string, tokenUser *model.User) (*model.User, *model.AppError)
    +	LoginByOAuth(c request.CTX, service string, userData io.Reader, inviteToken string, inviteId string, tokenUser *model.User) (*model.User, *model.AppError)
     	MFARequired(rctx request.CTX) *model.AppError
     	MarkChannelsAsViewed(c request.CTX, channelIDs []string, userID string, currentSessionId string, collapsedThreadsSupported, isCRTEnabled bool) (map[string]int64, *model.AppError)
     	MaxPostSize() int
    
  • server/channels/app/oauth.go+46 37 modified
    @@ -436,11 +436,14 @@ func (a *App) newSessionUpdateToken(c request.CTX, app *model.OAuthApp, accessDa
     	return accessRsp, nil
     }
     
    -func (a *App) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, teamID, action, redirectTo, loginHint string, isMobile bool, desktopToken string) (string, *model.AppError) {
    +func (a *App) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, action, redirectTo, loginHint string, isMobile bool, desktopToken string, inviteToken string, inviteId string) (string, *model.AppError) {
     	stateProps := map[string]string{}
     	stateProps["action"] = action
    -	if teamID != "" {
    -		stateProps["team_id"] = teamID
    +
    +	if inviteToken != "" {
    +		stateProps["invite_token"] = inviteToken
    +	} else if inviteId != "" {
    +		stateProps["invite_id"] = inviteId
     	}
     
     	if redirectTo != "" {
    @@ -461,11 +464,14 @@ func (a *App) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *htt
     	return authURL, nil
     }
     
    -func (a *App) GetOAuthSignupEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, teamID string, desktopToken string) (string, *model.AppError) {
    +func (a *App) GetOAuthSignupEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, desktopToken string, inviteToken string, inviteId string) (string, *model.AppError) {
     	stateProps := map[string]string{}
     	stateProps["action"] = model.OAuthActionSignup
    -	if teamID != "" {
    -		stateProps["team_id"] = teamID
    +
    +	if inviteToken != "" {
    +		stateProps["invite_token"] = inviteToken
    +	} else if inviteId != "" {
    +		stateProps["invite_id"] = inviteId
     	}
     
     	if desktopToken != "" {
    @@ -568,22 +574,26 @@ func (a *App) RevokeAccessToken(c request.CTX, token string) *model.AppError {
     	return nil
     }
     
    -func (a *App) CompleteOAuth(c request.CTX, service string, body io.ReadCloser, teamID string, props map[string]string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *App) CompleteOAuth(c request.CTX, service string, body io.ReadCloser, props map[string]string, tokenUser *model.User) (*model.User, *model.AppError) {
     	defer body.Close()
     
     	action := props["action"]
     
    +	// Extract invite token or ID from props so we can add the user to the team if needed
    +	inviteToken := props["invite_token"]
    +	inviteId := props["invite_id"]
    +
     	switch action {
     	case model.OAuthActionSignup:
    -		return a.CreateOAuthUser(c, service, body, teamID, tokenUser)
    +		return a.CreateOAuthUser(c, service, body, inviteToken, inviteId, tokenUser)
     	case model.OAuthActionLogin:
    -		return a.LoginByOAuth(c, service, body, teamID, tokenUser)
    +		return a.LoginByOAuth(c, service, body, inviteToken, inviteId, tokenUser)
     	case model.OAuthActionEmailToSSO:
     		return a.CompleteSwitchWithOAuth(c, service, body, props["email"], tokenUser)
     	case model.OAuthActionSSOToEmail:
    -		return a.LoginByOAuth(c, service, body, teamID, tokenUser)
    +		return a.LoginByOAuth(c, service, body, inviteToken, inviteId, tokenUser)
     	default:
    -		return a.LoginByOAuth(c, service, body, teamID, tokenUser)
    +		return a.LoginByOAuth(c, service, body, inviteToken, inviteId, tokenUser)
     	}
     }
     
    @@ -604,7 +614,7 @@ func (a *App) getSSOProvider(service string) (einterfaces.OAuthProvider, *model.
     	return provider, nil
     }
     
    -func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, teamID string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, inviteToken string, inviteId string, tokenUser *model.User) (*model.User, *model.AppError) {
     	provider, e := a.getSSOProvider(service)
     	if e != nil {
     		return nil, e
    @@ -630,7 +640,7 @@ func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, te
     	user, err := a.GetUserByAuth(model.NewPointer(*authUser.AuthData), service)
     	if err != nil {
     		if err.Id == MissingAuthAccountError {
    -			user, err = a.CreateOAuthUser(c, service, bytes.NewReader(buf.Bytes()), teamID, tokenUser)
    +			user, err = a.CreateOAuthUser(c, service, bytes.NewReader(buf.Bytes()), inviteToken, inviteId, tokenUser)
     		} else {
     			return nil, err
     		}
    @@ -645,8 +655,9 @@ func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, te
     		if err = a.UpdateOAuthUserAttrs(c, bytes.NewReader(buf.Bytes()), user, provider, service, tokenUser); err != nil {
     			return nil, err
     		}
    -		if teamID != "" {
    -			err = a.AddUserToTeamByTeamId(c, teamID, user)
    +
    +		if err = a.AddUserToTeamByInviteIfNeeded(c, user, inviteToken, inviteId); err != nil {
    +			c.Logger().Warn("Failed to add user to team", mlog.Err(err))
     		}
     	}
     
    @@ -800,46 +811,46 @@ func (a *App) GetAuthorizationCode(c request.CTX, w http.ResponseWriter, r *http
     	return authURL, nil
     }
     
    -func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.Request, service, code, state, redirectURI string) (io.ReadCloser, string, map[string]string, *model.User, *model.AppError) {
    +func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.Request, service, code, state, redirectURI string) (io.ReadCloser, map[string]string, *model.User, *model.AppError) {
     	provider, e := a.getSSOProvider(service)
     	if e != nil {
    -		return nil, "", nil, nil, e
    +		return nil, nil, nil, e
     	}
     
     	sso, e2 := provider.GetSSOSettings(c, a.Config(), service)
     	if e2 != nil {
    -		return nil, "", nil, nil, model.NewAppError("AuthorizeOAuthUser.GetSSOSettings", "api.user.get_authorization_code.endpoint.app_error", nil, "", http.StatusNotImplemented).Wrap(e2)
    +		return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser.GetSSOSettings", "api.user.get_authorization_code.endpoint.app_error", nil, "", http.StatusNotImplemented).Wrap(e2)
     	}
     
     	b, strErr := b64.StdEncoding.DecodeString(state)
     	if strErr != nil {
    -		return nil, "", nil, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(strErr)
    +		return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(strErr)
     	}
     
     	stateStr := string(b)
     	stateProps := model.MapFromJSON(strings.NewReader(stateStr))
     
     	expectedToken, appErr := a.GetOAuthStateToken(stateProps["token"])
     	if appErr != nil {
    -		return nil, "", stateProps, nil, appErr
    +		return nil, stateProps, nil, appErr
     	}
     
     	stateEmail := stateProps["email"]
     	stateAction := stateProps["action"]
     	if stateAction == model.OAuthActionEmailToSSO && stateEmail == "" {
     		err := errors.New("No email provided in state when trying to switch from email to SSO")
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
     	}
     
     	cookie, cookieErr := r.Cookie(CookieOAuth)
     	if cookieErr != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(cookieErr)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(cookieErr)
     	}
     
     	expectedTokenExtra := generateOAuthStateTokenExtra(stateEmail, stateAction, cookie.Value)
     	if expectedTokenExtra != expectedToken.Extra {
     		err := errors.New("Extra token value does not match token generated from state")
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
     	}
     
     	appErr = a.DeleteToken(expectedToken)
    @@ -859,8 +870,6 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     
     	http.SetCookie(w, httpCookie)
     
    -	teamID := stateProps["team_id"]
    -
     	p := url.Values{}
     	p.Set("client_id", *sso.Id)
     	p.Set("client_secret", *sso.Secret)
    @@ -870,15 +879,15 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     
     	req, requestErr := http.NewRequest("POST", *sso.TokenEndpoint, strings.NewReader(p.Encode()))
     	if requestErr != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(requestErr)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(requestErr)
     	}
     
     	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
     	req.Header.Set("Accept", "application/json")
     
     	resp, err := a.HTTPService().MakeClient(true).Do(req)
     	if err != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
     	}
     	defer resp.Body.Close()
     
    @@ -887,15 +896,15 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     	var ar *model.AccessResponse
     	err = json.NewDecoder(tee).Decode(&ar)
     	if err != nil || resp.StatusCode != http.StatusOK {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, fmt.Sprintf("response_body=%s, status_code=%d, error=%v", buf.String(), resp.StatusCode, err), http.StatusInternalServerError).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, fmt.Sprintf("response_body=%s, status_code=%d, error=%v", buf.String(), resp.StatusCode, err), http.StatusInternalServerError).Wrap(err)
     	}
     
     	if strings.ToLower(ar.TokenType) != model.AccessTokenType {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+buf.String(), http.StatusInternalServerError)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+buf.String(), http.StatusInternalServerError)
     	}
     
     	if ar.AccessToken == "" {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "response_body="+buf.String(), http.StatusInternalServerError)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "response_body="+buf.String(), http.StatusInternalServerError)
     	}
     
     	p = url.Values{}
    @@ -905,13 +914,13 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     	if ar.IdToken != "" {
     		userFromToken, err = provider.GetUserFromIdToken(c, ar.IdToken)
     		if err != nil {
    -			return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
    +			return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
     		}
     	}
     
     	req, requestErr = http.NewRequest("GET", *sso.UserAPIEndpoint, strings.NewReader(""))
     	if requestErr != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(requestErr)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(requestErr)
     	}
     
     	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    @@ -920,7 +929,7 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     
     	resp, err = a.HTTPService().MakeClient(true).Do(req)
     	if err != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(err)
     	} else if resp.StatusCode != http.StatusOK {
     		defer resp.Body.Close()
     
    @@ -933,17 +942,17 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     		if service == model.ServiceGitlab && resp.StatusCode == http.StatusForbidden && strings.Contains(bodyString, "Terms of Service") {
     			url, err := url.Parse(*sso.UserAPIEndpoint)
     			if err != nil {
    -				return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(errors.Wrapf(err, "error parsing %s", *sso.UserAPIEndpoint))
    +				return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(errors.Wrapf(err, "error parsing %s", *sso.UserAPIEndpoint))
     			}
     			// Return a nicer error when the user hasn't accepted GitLab's terms of service
    -			return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "oauth.gitlab.tos.error", map[string]any{"URL": url.Hostname()}, "", http.StatusBadRequest)
    +			return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "oauth.gitlab.tos.error", map[string]any{"URL": url.Hostname()}, "", http.StatusBadRequest)
     		}
     
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.response.app_error", nil, "response_body="+bodyString, http.StatusInternalServerError)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.response.app_error", nil, "response_body="+bodyString, http.StatusInternalServerError)
     	}
     
     	// Note that resp.Body is not closed here, so it must be closed by the caller
    -	return resp.Body, teamID, stateProps, userFromToken, nil
    +	return resp.Body, stateProps, userFromToken, nil
     }
     
     func (a *App) SwitchEmailToOAuth(c request.CTX, w http.ResponseWriter, r *http.Request, email, password, code, service string) (string, *model.AppError) {
    
  • server/channels/app/oauth_test.go+18 18 modified
    @@ -187,7 +187,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		th := setup(t, false, true, true, "")
     		defer th.TearDown()
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", "", "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", "", "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.unsupported.app_error", err.Id)
     	})
    @@ -198,7 +198,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     
     		state := "!"
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -211,7 +211,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     			"token": model.NewId(),
     		})))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.oauth.invalid_state_token.app_error", err.Id)
     		assert.Error(t, err.Unwrap())
    @@ -226,7 +226,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     
     		state := makeState(token)
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.oauth.invalid_state_token.app_error", err.Id)
     		assert.Equal(t, "", err.DetailedError)
    @@ -249,7 +249,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     			"token":  token.Token,
     		})))
     
    -		_, _, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -262,7 +262,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest("")
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -279,7 +279,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(token)
     
    -		_, _, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -292,7 +292,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.token_failed.app_error", err.Id)
     	})
    @@ -310,7 +310,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.bad_response.app_error", err.Id)
     		assert.Contains(t, err.DetailedError, "status_code=418")
    @@ -330,7 +330,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.bad_response.app_error", err.Id)
     		assert.Contains(t, err.DetailedError, "response_body=invalid")
    @@ -353,7 +353,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.bad_token.app_error", err.Id)
     	})
    @@ -375,7 +375,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.missing.app_error", err.Id)
     	})
    @@ -397,7 +397,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.service.app_error", err.Id)
     	})
    @@ -426,7 +426,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.response.app_error", err.Id)
     	})
    @@ -457,7 +457,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "oauth.gitlab.tos.error", err.Id)
     	})
    @@ -474,7 +474,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		providerMock.On("GetSSOSettings", mock.AnythingOfType("*request.Context"), mock.Anything, model.ServiceOpenid).Return(nil, errors.New("error"))
     		einterfaces.RegisterOAuthProvider(model.ServiceOpenid, providerMock)
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceOpenid, "", "", "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceOpenid, "", "", "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.get_authorization_code.endpoint.app_error", err.Id)
     	})
    @@ -526,14 +526,14 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     				state := base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
     
     				recorder := httptest.ResponseRecorder{}
    -				body, receivedTeamID, receivedStateProps, _, err := th.App.AuthorizeOAuthUser(th.Context, &recorder, request, model.ServiceGitlab, "", state, "")
    +				body, receivedStateProps, _, err := th.App.AuthorizeOAuthUser(th.Context, &recorder, request, model.ServiceGitlab, "", state, "")
     
     				require.NotNil(t, body)
     				bodyBytes, bodyErr := io.ReadAll(body)
     				require.NoError(t, bodyErr)
     				assert.Equal(t, userData, string(bodyBytes))
     
    -				assert.Equal(t, stateProps["team_id"], receivedTeamID)
    +				// team_id is no longer returned as it was removed for security reasons
     				assert.Equal(t, stateProps, receivedStateProps)
     				assert.Nil(t, err)
     
    
  • server/channels/app/opentracing/opentracing_layer.go+37 15 modified
    @@ -565,6 +565,28 @@ func (a *OpenTracingAppLayer) AddUserToTeamByInviteId(c request.CTX, inviteId st
     	return resultVar0, resultVar1, resultVar2
     }
     
    +func (a *OpenTracingAppLayer) AddUserToTeamByInviteIfNeeded(c request.CTX, user *model.User, inviteToken string, inviteId string) *model.AppError {
    +	origCtx := a.ctx
    +	span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddUserToTeamByInviteIfNeeded")
    +
    +	a.ctx = newCtx
    +	a.app.Srv().Store().SetContext(newCtx)
    +	defer func() {
    +		a.app.Srv().Store().SetContext(origCtx)
    +		a.ctx = origCtx
    +	}()
    +
    +	defer span.Finish()
    +	resultVar0 := a.app.AddUserToTeamByInviteIfNeeded(c, user, inviteToken, inviteId)
    +
    +	if resultVar0 != nil {
    +		span.LogFields(spanlog.Error(resultVar0))
    +		ext.Error.Set(span, true)
    +	}
    +
    +	return resultVar0
    +}
    +
     func (a *OpenTracingAppLayer) AddUserToTeamByTeamId(c request.CTX, teamID string, user *model.User) *model.AppError {
     	origCtx := a.ctx
     	span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AddUserToTeamByTeamId")
    @@ -810,7 +832,7 @@ func (a *OpenTracingAppLayer) AuthenticateUserForLogin(c request.CTX, id string,
     	return resultVar0, resultVar1
     }
     
    -func (a *OpenTracingAppLayer) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.Request, service string, code string, state string, redirectURI string) (io.ReadCloser, string, map[string]string, *model.User, *model.AppError) {
    +func (a *OpenTracingAppLayer) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.Request, service string, code string, state string, redirectURI string) (io.ReadCloser, map[string]string, *model.User, *model.AppError) {
     	origCtx := a.ctx
     	span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AuthorizeOAuthUser")
     
    @@ -822,14 +844,14 @@ func (a *OpenTracingAppLayer) AuthorizeOAuthUser(c request.CTX, w http.ResponseW
     	}()
     
     	defer span.Finish()
    -	resultVar0, resultVar1, resultVar2, resultVar3, resultVar4 := a.app.AuthorizeOAuthUser(c, w, r, service, code, state, redirectURI)
    +	resultVar0, resultVar1, resultVar2, resultVar3 := a.app.AuthorizeOAuthUser(c, w, r, service, code, state, redirectURI)
     
    -	if resultVar4 != nil {
    -		span.LogFields(spanlog.Error(resultVar4))
    +	if resultVar3 != nil {
    +		span.LogFields(spanlog.Error(resultVar3))
     		ext.Error.Set(span, true)
     	}
     
    -	return resultVar0, resultVar1, resultVar2, resultVar3, resultVar4
    +	return resultVar0, resultVar1, resultVar2, resultVar3
     }
     
     func (a *OpenTracingAppLayer) AutocompleteChannels(c request.CTX, userID string, term string) (model.ChannelListWithTeamData, *model.AppError) {
    @@ -1737,7 +1759,7 @@ func (a *OpenTracingAppLayer) CompileReportChunks(format string, prefix string,
     	return resultVar0
     }
     
    -func (a *OpenTracingAppLayer) CompleteOAuth(c request.CTX, service string, body io.ReadCloser, teamID string, props map[string]string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *OpenTracingAppLayer) CompleteOAuth(c request.CTX, service string, body io.ReadCloser, props map[string]string, tokenUser *model.User) (*model.User, *model.AppError) {
     	origCtx := a.ctx
     	span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CompleteOAuth")
     
    @@ -1749,7 +1771,7 @@ func (a *OpenTracingAppLayer) CompleteOAuth(c request.CTX, service string, body
     	}()
     
     	defer span.Finish()
    -	resultVar0, resultVar1 := a.app.CompleteOAuth(c, service, body, teamID, props, tokenUser)
    +	resultVar0, resultVar1 := a.app.CompleteOAuth(c, service, body, props, tokenUser)
     
     	if resultVar1 != nil {
     		span.LogFields(spanlog.Error(resultVar1))
    @@ -2441,7 +2463,7 @@ func (a *OpenTracingAppLayer) CreateOAuthStateToken(extra string) (*model.Token,
     	return resultVar0, resultVar1
     }
     
    -func (a *OpenTracingAppLayer) CreateOAuthUser(c request.CTX, service string, userData io.Reader, teamID string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *OpenTracingAppLayer) CreateOAuthUser(c request.CTX, service string, userData io.Reader, inviteToken string, inviteId string, tokenUser *model.User) (*model.User, *model.AppError) {
     	origCtx := a.ctx
     	span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateOAuthUser")
     
    @@ -2453,7 +2475,7 @@ func (a *OpenTracingAppLayer) CreateOAuthUser(c request.CTX, service string, use
     	}()
     
     	defer span.Finish()
    -	resultVar0, resultVar1 := a.app.CreateOAuthUser(c, service, userData, teamID, tokenUser)
    +	resultVar0, resultVar1 := a.app.CreateOAuthUser(c, service, userData, inviteToken, inviteId, tokenUser)
     
     	if resultVar1 != nil {
     		span.LogFields(spanlog.Error(resultVar1))
    @@ -8150,7 +8172,7 @@ func (a *OpenTracingAppLayer) GetOAuthImplicitRedirect(c request.CTX, userID str
     	return resultVar0, resultVar1
     }
     
    -func (a *OpenTracingAppLayer) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service string, teamID string, action string, redirectTo string, loginHint string, isMobile bool, desktopToken string) (string, *model.AppError) {
    +func (a *OpenTracingAppLayer) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service string, action string, redirectTo string, loginHint string, isMobile bool, desktopToken string, inviteToken string, inviteId string) (string, *model.AppError) {
     	origCtx := a.ctx
     	span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetOAuthLoginEndpoint")
     
    @@ -8162,7 +8184,7 @@ func (a *OpenTracingAppLayer) GetOAuthLoginEndpoint(c request.CTX, w http.Respon
     	}()
     
     	defer span.Finish()
    -	resultVar0, resultVar1 := a.app.GetOAuthLoginEndpoint(c, w, r, service, teamID, action, redirectTo, loginHint, isMobile, desktopToken)
    +	resultVar0, resultVar1 := a.app.GetOAuthLoginEndpoint(c, w, r, service, action, redirectTo, loginHint, isMobile, desktopToken, inviteToken, inviteId)
     
     	if resultVar1 != nil {
     		span.LogFields(spanlog.Error(resultVar1))
    @@ -8172,7 +8194,7 @@ func (a *OpenTracingAppLayer) GetOAuthLoginEndpoint(c request.CTX, w http.Respon
     	return resultVar0, resultVar1
     }
     
    -func (a *OpenTracingAppLayer) GetOAuthSignupEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service string, teamID string, desktopToken string) (string, *model.AppError) {
    +func (a *OpenTracingAppLayer) GetOAuthSignupEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service string, desktopToken string, inviteToken string, inviteId string) (string, *model.AppError) {
     	origCtx := a.ctx
     	span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetOAuthSignupEndpoint")
     
    @@ -8184,7 +8206,7 @@ func (a *OpenTracingAppLayer) GetOAuthSignupEndpoint(c request.CTX, w http.Respo
     	}()
     
     	defer span.Finish()
    -	resultVar0, resultVar1 := a.app.GetOAuthSignupEndpoint(c, w, r, service, teamID, desktopToken)
    +	resultVar0, resultVar1 := a.app.GetOAuthSignupEndpoint(c, w, r, service, desktopToken, inviteToken, inviteId)
     
     	if resultVar1 != nil {
     		span.LogFields(spanlog.Error(resultVar1))
    @@ -13054,7 +13076,7 @@ func (a *OpenTracingAppLayer) LogAuditRecWithLevel(rctx request.CTX, rec *audit.
     	a.app.LogAuditRecWithLevel(rctx, rec, level, err)
     }
     
    -func (a *OpenTracingAppLayer) LoginByOAuth(c request.CTX, service string, userData io.Reader, teamID string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *OpenTracingAppLayer) LoginByOAuth(c request.CTX, service string, userData io.Reader, inviteToken string, inviteId string, tokenUser *model.User) (*model.User, *model.AppError) {
     	origCtx := a.ctx
     	span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.LoginByOAuth")
     
    @@ -13066,7 +13088,7 @@ func (a *OpenTracingAppLayer) LoginByOAuth(c request.CTX, service string, userDa
     	}()
     
     	defer span.Finish()
    -	resultVar0, resultVar1 := a.app.LoginByOAuth(c, service, userData, teamID, tokenUser)
    +	resultVar0, resultVar1 := a.app.LoginByOAuth(c, service, userData, inviteToken, inviteId, tokenUser)
     
     	if resultVar1 != nil {
     		span.LogFields(spanlog.Error(resultVar1))
    
  • server/channels/app/team.go+5 5 modified
    @@ -718,6 +718,10 @@ func (a *App) AddUserToTeamByInviteId(c request.CTX, inviteId string, userID str
     	}
     	team := teamChanResult.Data
     
    +	if team.IsGroupConstrained() {
    +		return nil, nil, model.NewAppError("AddUserToTeamByInviteId", "app.team.invite_id.group_constrained.error", nil, "", http.StatusForbidden)
    +	}
    +
     	userChanResult := <-uchan
     	if userChanResult.NErr != nil {
     		var nfErr *store.ErrNotFound
    @@ -1086,15 +1090,11 @@ func (a *App) AddTeamMemberByToken(c request.CTX, userID, tokenID string) (*mode
     }
     
     func (a *App) AddTeamMemberByInviteId(c request.CTX, inviteId, userID string) (*model.TeamMember, *model.AppError) {
    -	team, teamMember, err := a.AddUserToTeamByInviteId(c, inviteId, userID)
    +	_, teamMember, err := a.AddUserToTeamByInviteId(c, inviteId, userID)
     	if err != nil {
     		return nil, err
     	}
     
    -	if team.IsGroupConstrained() {
    -		return nil, model.NewAppError("AddTeamMemberByInviteId", "app.team.invite_id.group_constrained.error", nil, "", http.StatusForbidden)
    -	}
    -
     	return teamMember, nil
     }
     
    
  • server/channels/app/user.go+28 7 modified
    @@ -339,7 +339,7 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m
     	return ruser, nil
     }
     
    -func (a *App) CreateOAuthUser(c request.CTX, service string, userData io.Reader, teamID string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *App) CreateOAuthUser(c request.CTX, service string, userData io.Reader, inviteToken string, inviteId string, tokenUser *model.User) (*model.User, *model.AppError) {
     	if !*a.Config().TeamSettings.EnableUserCreation {
     		return nil, model.NewAppError("CreateOAuthUser", "api.user.create_user.disabled.app_error", nil, "", http.StatusNotImplemented)
     	}
    @@ -392,19 +392,40 @@ func (a *App) CreateOAuthUser(c request.CTX, service string, userData io.Reader,
     		return nil, err
     	}
     
    -	if teamID != "" {
    -		err = a.AddUserToTeamByTeamId(c, teamID, user)
    +	if err = a.AddUserToTeamByInviteIfNeeded(c, ruser, inviteToken, inviteId); err != nil {
    +		c.Logger().Warn("Failed to add user to team", mlog.Err(err))
    +	}
    +
    +	return ruser, nil
    +}
    +
    +func (a *App) AddUserToTeamByInviteIfNeeded(c request.CTX, user *model.User, inviteToken string, inviteId string) *model.AppError {
    +	var team *model.Team
    +	var err *model.AppError
    +
    +	if inviteToken != "" {
    +		team, _, err = a.AddUserToTeamByToken(c, user.Id, inviteToken)
     		if err != nil {
    -			return nil, err
    +			c.Logger().Warn("Failed to add user to team using invite token", mlog.Err(err))
    +			return err
     		}
    -
    -		err = a.AddDirectChannels(c, teamID, user)
    +	} else if inviteId != "" {
    +		team, _, err = a.AddUserToTeamByInviteId(c, inviteId, user.Id)
     		if err != nil {
    +			c.Logger().Warn("Failed to add user to team using invite ID", mlog.Err(err))
    +			return err
    +		}
    +	} else {
    +		return nil
    +	}
    +
    +	if team != nil {
    +		if err = a.AddDirectChannels(c, team.Id, user); err != nil {
     			c.Logger().Warn("Failed to add direct channels", mlog.Err(err))
     		}
     	}
     
    -	return ruser, nil
    +	return nil
     }
     
     func (a *App) GetUser(userID string) (*model.User, *model.AppError) {
    
  • server/channels/app/user_test.go+4 4 modified
    @@ -42,7 +42,7 @@ func TestCreateOAuthUser(t *testing.T) {
     		js, jsonErr := json.Marshal(glUser)
     		require.NoError(t, jsonErr)
     
    -		user, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, bytes.NewReader(js), th.BasicTeam.Id, nil)
    +		user, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, bytes.NewReader(js), "", "", nil)
     		require.Nil(t, err)
     
     		require.Equal(t, glUser.Username, user.Username, "usernames didn't match")
    @@ -71,7 +71,7 @@ func TestCreateOAuthUser(t *testing.T) {
     		assert.Equal(t, dbUser.Id, s)
     
     		// data passed doesn't matter as return is mocked
    -		_, err := th.App.CreateOAuthUser(th.Context, model.ServiceOffice365, strings.NewReader("{}"), th.BasicTeam.Id, nil)
    +		_, err := th.App.CreateOAuthUser(th.Context, model.ServiceOffice365, strings.NewReader("{}"), "", "", nil)
     		assert.Nil(t, err)
     		u, er := th.App.Srv().Store().User().GetByEmail(dbUser.Email)
     		assert.NoError(t, er)
    @@ -81,7 +81,7 @@ func TestCreateOAuthUser(t *testing.T) {
     
     	t.Run("user creation disabled", func(t *testing.T) {
     		*th.App.Config().TeamSettings.EnableUserCreation = false
    -		_, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, strings.NewReader("{}"), th.BasicTeam.Id, nil)
    +		_, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, strings.NewReader("{}"), "", "", nil)
     		require.NotNil(t, err, "should have failed - user creation disabled")
     	})
     }
    @@ -732,7 +732,7 @@ func createGitlabUser(t *testing.T, a *App, c request.CTX, id int64, username st
     	var user *model.User
     	var err *model.AppError
     
    -	user, err = a.CreateOAuthUser(c, "gitlab", bytes.NewReader(gitlabUser), "", nil)
    +	user, err = a.CreateOAuthUser(c, "gitlab", bytes.NewReader(gitlabUser), "", "", nil)
     	require.Nil(t, err, "unable to create the user", err)
     
     	return user, gitlabUserObj
    
  • server/channels/web/oauth.go+14 20 modified
    @@ -307,7 +307,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     
     	uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete"
     
    -	body, teamId, props, tokenUser, err := c.App.AuthorizeOAuthUser(c.AppContext, w, r, service, code, state, uri)
    +	body, props, tokenUser, err := c.App.AuthorizeOAuthUser(c.AppContext, w, r, service, code, state, uri)
     
     	action := ""
     	hasRedirectURL := false
    @@ -338,7 +338,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    -	user, err := c.App.CompleteOAuth(c.AppContext, service, body, teamId, props, tokenUser)
    +	user, err := c.App.CompleteOAuth(c.AppContext, service, body, props, tokenUser)
     	if err != nil {
     		err.Translate(c.AppContext.T)
     		c.LogErrorByCode(err)
    @@ -450,13 +450,11 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     	auditRec.AddMeta("service", c.Params.Service)
     	defer c.LogAuditRec(auditRec)
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	// Get invite token or ID instead of team_id
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
     
    -	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, teamId, model.OAuthActionLogin, redirectURL, loginHint, false, desktopToken)
    +	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, model.OAuthActionLogin, redirectURL, loginHint, false, desktopToken, tokenID, inviteId)
     	if err != nil {
     		c.Err = err
     		return
    @@ -486,13 +484,11 @@ func mobileLoginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     	auditRec.AddMeta("service", c.Params.Service)
     	defer c.LogAuditRec(auditRec)
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	// Get invite token or ID instead of team_id
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
     
    -	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, teamId, model.OAuthActionMobile, redirectURL, "", true, "")
    +	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, model.OAuthActionMobile, redirectURL, "", true, "", tokenID, inviteId)
     	if err != nil {
     		c.Err = err
     		return
    @@ -521,15 +517,13 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     	auditRec.AddMeta("service", c.Params.Service)
     	defer c.LogAuditRec(auditRec)
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	// Get invite token or ID instead of team_id
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
     
     	desktopToken := r.URL.Query().Get("desktop_token")
     
    -	authURL, err := c.App.GetOAuthSignupEndpoint(c.AppContext, w, r, c.Params.Service, teamId, desktopToken)
    +	authURL, err := c.App.GetOAuthSignupEndpoint(c.AppContext, w, r, c.Params.Service, desktopToken, tokenID, inviteId)
     	if err != nil {
     		c.Err = err
     		return
    
  • server/channels/web/saml.go+13 12 modified
    @@ -31,19 +31,21 @@ func loginWithSaml(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
    +
     	action := r.URL.Query().Get("action")
     	isMobile := action == model.OAuthActionMobile
     	redirectURL := html.EscapeString(r.URL.Query().Get("redirect_to"))
     	relayProps := map[string]string{}
     	relayState := ""
     
     	if action != "" {
    -		relayProps["team_id"] = teamId
    +		if tokenID != "" {
    +			relayProps["invite_token"] = tokenID
    +		} else if inviteId != "" {
    +			relayProps["invite_id"] = inviteId
    +		}
     		relayProps["action"] = action
     		if action == model.OAuthActionEmailToSSO {
     			relayProps["email_token"] = r.URL.Query().Get("email_token")
    @@ -149,12 +151,11 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
     
     	switch action {
     	case model.OAuthActionSignup:
    -		if teamId := relayProps["team_id"]; teamId != "" {
    -			if err = c.App.AddUserToTeamByTeamId(c.AppContext, teamId, user); err != nil {
    -				c.LogErrorByCode(err)
    -				break
    -			}
    -			c.App.AddDirectChannels(c.AppContext, teamId, user)
    +		inviteToken := relayProps["invite_token"]
    +		inviteId := relayProps["invite_id"]
    +		if err = c.App.AddUserToTeamByInviteIfNeeded(c.AppContext, user, inviteToken, inviteId); err != nil {
    +			c.LogErrorByCode(err)
    +			break
     		}
     	case model.OAuthActionEmailToSSO:
     		if err = c.App.RevokeAllSessions(c.AppContext, user.Id); err != nil {
    
0e478b2d895e

[MM-64896][MM-64898] Pass inviteid/tokenid to relay state/props for external auth when auto-joining a team (#33545) (#33727)

https://github.com/mattermost/mattermostMattermost BuildAug 15, 2025via ghsa
7 files changed · +128 105
  • server/channels/app/oauth.go+46 37 modified
    @@ -438,11 +438,14 @@ func (a *App) newSessionUpdateToken(c request.CTX, app *model.OAuthApp, accessDa
     	return accessRsp, nil
     }
     
    -func (a *App) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, teamID, action, redirectTo, loginHint string, isMobile bool, desktopToken string) (string, *model.AppError) {
    +func (a *App) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, action, redirectTo, loginHint string, isMobile bool, desktopToken string, inviteToken string, inviteId string) (string, *model.AppError) {
     	stateProps := map[string]string{}
     	stateProps["action"] = action
    -	if teamID != "" {
    -		stateProps["team_id"] = teamID
    +
    +	if inviteToken != "" {
    +		stateProps["invite_token"] = inviteToken
    +	} else if inviteId != "" {
    +		stateProps["invite_id"] = inviteId
     	}
     
     	if redirectTo != "" {
    @@ -463,11 +466,14 @@ func (a *App) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *htt
     	return authURL, nil
     }
     
    -func (a *App) GetOAuthSignupEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, teamID string, desktopToken string) (string, *model.AppError) {
    +func (a *App) GetOAuthSignupEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, desktopToken string, inviteToken string, inviteId string) (string, *model.AppError) {
     	stateProps := map[string]string{}
     	stateProps["action"] = model.OAuthActionSignup
    -	if teamID != "" {
    -		stateProps["team_id"] = teamID
    +
    +	if inviteToken != "" {
    +		stateProps["invite_token"] = inviteToken
    +	} else if inviteId != "" {
    +		stateProps["invite_id"] = inviteId
     	}
     
     	if desktopToken != "" {
    @@ -570,22 +576,26 @@ func (a *App) RevokeAccessToken(c request.CTX, token string) *model.AppError {
     	return nil
     }
     
    -func (a *App) CompleteOAuth(c request.CTX, service string, body io.ReadCloser, teamID string, props map[string]string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *App) CompleteOAuth(c request.CTX, service string, body io.ReadCloser, props map[string]string, tokenUser *model.User) (*model.User, *model.AppError) {
     	defer body.Close()
     
     	action := props["action"]
     
    +	// Extract invite token or ID from props so we can add the user to the team if needed
    +	inviteToken := props["invite_token"]
    +	inviteId := props["invite_id"]
    +
     	switch action {
     	case model.OAuthActionSignup:
    -		return a.CreateOAuthUser(c, service, body, teamID, tokenUser)
    +		return a.CreateOAuthUser(c, service, body, inviteToken, inviteId, tokenUser)
     	case model.OAuthActionLogin:
    -		return a.LoginByOAuth(c, service, body, teamID, tokenUser)
    +		return a.LoginByOAuth(c, service, body, inviteToken, inviteId, tokenUser)
     	case model.OAuthActionEmailToSSO:
     		return a.CompleteSwitchWithOAuth(c, service, body, props["email"], tokenUser)
     	case model.OAuthActionSSOToEmail:
    -		return a.LoginByOAuth(c, service, body, teamID, tokenUser)
    +		return a.LoginByOAuth(c, service, body, inviteToken, inviteId, tokenUser)
     	default:
    -		return a.LoginByOAuth(c, service, body, teamID, tokenUser)
    +		return a.LoginByOAuth(c, service, body, inviteToken, inviteId, tokenUser)
     	}
     }
     
    @@ -606,7 +616,7 @@ func (a *App) getSSOProvider(service string) (einterfaces.OAuthProvider, *model.
     	return provider, nil
     }
     
    -func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, teamID string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, inviteToken string, inviteId string, tokenUser *model.User) (*model.User, *model.AppError) {
     	provider, e := a.getSSOProvider(service)
     	if e != nil {
     		return nil, e
    @@ -632,7 +642,7 @@ func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, te
     	user, err := a.GetUserByAuth(model.NewPointer(*authUser.AuthData), service)
     	if err != nil {
     		if err.Id == MissingAuthAccountError {
    -			user, err = a.CreateOAuthUser(c, service, bytes.NewReader(buf.Bytes()), teamID, tokenUser)
    +			user, err = a.CreateOAuthUser(c, service, bytes.NewReader(buf.Bytes()), inviteToken, inviteId, tokenUser)
     		} else {
     			return nil, err
     		}
    @@ -647,8 +657,9 @@ func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, te
     		if err = a.UpdateOAuthUserAttrs(c, bytes.NewReader(buf.Bytes()), user, provider, service, tokenUser); err != nil {
     			return nil, err
     		}
    -		if teamID != "" {
    -			err = a.AddUserToTeamByTeamId(c, teamID, user)
    +
    +		if err = a.AddUserToTeamByInviteIfNeeded(c, user, inviteToken, inviteId); err != nil {
    +			c.Logger().Warn("Failed to add user to team", mlog.Err(err))
     		}
     	}
     
    @@ -802,46 +813,46 @@ func (a *App) GetAuthorizationCode(c request.CTX, w http.ResponseWriter, r *http
     	return authURL, nil
     }
     
    -func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.Request, service, code, state, redirectURI string) (io.ReadCloser, string, map[string]string, *model.User, *model.AppError) {
    +func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.Request, service, code, state, redirectURI string) (io.ReadCloser, map[string]string, *model.User, *model.AppError) {
     	provider, e := a.getSSOProvider(service)
     	if e != nil {
    -		return nil, "", nil, nil, e
    +		return nil, nil, nil, e
     	}
     
     	sso, e2 := provider.GetSSOSettings(c, a.Config(), service)
     	if e2 != nil {
    -		return nil, "", nil, nil, model.NewAppError("AuthorizeOAuthUser.GetSSOSettings", "api.user.get_authorization_code.endpoint.app_error", nil, "", http.StatusNotImplemented).Wrap(e2)
    +		return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser.GetSSOSettings", "api.user.get_authorization_code.endpoint.app_error", nil, "", http.StatusNotImplemented).Wrap(e2)
     	}
     
     	b, strErr := b64.StdEncoding.DecodeString(state)
     	if strErr != nil {
    -		return nil, "", nil, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(strErr)
    +		return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(strErr)
     	}
     
     	stateStr := string(b)
     	stateProps := model.MapFromJSON(strings.NewReader(stateStr))
     
     	expectedToken, appErr := a.GetOAuthStateToken(stateProps["token"])
     	if appErr != nil {
    -		return nil, "", stateProps, nil, appErr
    +		return nil, stateProps, nil, appErr
     	}
     
     	stateEmail := stateProps["email"]
     	stateAction := stateProps["action"]
     	if stateAction == model.OAuthActionEmailToSSO && stateEmail == "" {
     		err := errors.New("No email provided in state when trying to switch from email to SSO")
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
     	}
     
     	cookie, cookieErr := r.Cookie(CookieOAuth)
     	if cookieErr != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(cookieErr)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(cookieErr)
     	}
     
     	expectedTokenExtra := generateOAuthStateTokenExtra(stateEmail, stateAction, cookie.Value)
     	if expectedTokenExtra != expectedToken.Extra {
     		err := errors.New("Extra token value does not match token generated from state")
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
     	}
     
     	appErr = a.DeleteToken(expectedToken)
    @@ -861,8 +872,6 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     
     	http.SetCookie(w, httpCookie)
     
    -	teamID := stateProps["team_id"]
    -
     	p := url.Values{}
     	p.Set("client_id", *sso.Id)
     	p.Set("client_secret", *sso.Secret)
    @@ -872,15 +881,15 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     
     	req, requestErr := http.NewRequest("POST", *sso.TokenEndpoint, strings.NewReader(p.Encode()))
     	if requestErr != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(requestErr)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(requestErr)
     	}
     
     	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
     	req.Header.Set("Accept", "application/json")
     
     	resp, err := a.HTTPService().MakeClient(true).Do(req)
     	if err != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
     	}
     	defer resp.Body.Close()
     
    @@ -889,15 +898,15 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     	var ar *model.AccessResponse
     	err = json.NewDecoder(tee).Decode(&ar)
     	if err != nil || resp.StatusCode != http.StatusOK {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, fmt.Sprintf("response_body=%s, status_code=%d, error=%v", buf.String(), resp.StatusCode, err), http.StatusInternalServerError).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, fmt.Sprintf("response_body=%s, status_code=%d, error=%v", buf.String(), resp.StatusCode, err), http.StatusInternalServerError).Wrap(err)
     	}
     
     	if strings.ToLower(ar.TokenType) != model.AccessTokenType {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+buf.String(), http.StatusInternalServerError)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+buf.String(), http.StatusInternalServerError)
     	}
     
     	if ar.AccessToken == "" {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "response_body="+buf.String(), http.StatusInternalServerError)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "response_body="+buf.String(), http.StatusInternalServerError)
     	}
     
     	p = url.Values{}
    @@ -907,13 +916,13 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     	if ar.IdToken != "" {
     		userFromToken, err = provider.GetUserFromIdToken(c, ar.IdToken)
     		if err != nil {
    -			return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
    +			return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
     		}
     	}
     
     	req, requestErr = http.NewRequest("GET", *sso.UserAPIEndpoint, strings.NewReader(""))
     	if requestErr != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(requestErr)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(requestErr)
     	}
     
     	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    @@ -922,7 +931,7 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     
     	resp, err = a.HTTPService().MakeClient(true).Do(req)
     	if err != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(err)
     	} else if resp.StatusCode != http.StatusOK {
     		defer resp.Body.Close()
     
    @@ -935,17 +944,17 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     		if service == model.ServiceGitlab && resp.StatusCode == http.StatusForbidden && strings.Contains(bodyString, "Terms of Service") {
     			url, err := url.Parse(*sso.UserAPIEndpoint)
     			if err != nil {
    -				return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(errors.Wrapf(err, "error parsing %s", *sso.UserAPIEndpoint))
    +				return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(errors.Wrapf(err, "error parsing %s", *sso.UserAPIEndpoint))
     			}
     			// Return a nicer error when the user hasn't accepted GitLab's terms of service
    -			return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "oauth.gitlab.tos.error", map[string]any{"URL": url.Hostname()}, "", http.StatusBadRequest)
    +			return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "oauth.gitlab.tos.error", map[string]any{"URL": url.Hostname()}, "", http.StatusBadRequest)
     		}
     
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.response.app_error", nil, "response_body="+bodyString, http.StatusInternalServerError)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.response.app_error", nil, "response_body="+bodyString, http.StatusInternalServerError)
     	}
     
     	// Note that resp.Body is not closed here, so it must be closed by the caller
    -	return resp.Body, teamID, stateProps, userFromToken, nil
    +	return resp.Body, stateProps, userFromToken, nil
     }
     
     func (a *App) SwitchEmailToOAuth(c request.CTX, w http.ResponseWriter, r *http.Request, email, password, code, service string) (string, *model.AppError) {
    
  • server/channels/app/oauth_test.go+18 18 modified
    @@ -193,7 +193,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		th := setup(t, false, true, true, "")
     		defer th.TearDown()
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", "", "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", "", "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.unsupported.app_error", err.Id)
     	})
    @@ -204,7 +204,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     
     		state := "!"
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -217,7 +217,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     			"token": model.NewId(),
     		})))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.oauth.invalid_state_token.app_error", err.Id)
     		assert.Error(t, err.Unwrap())
    @@ -232,7 +232,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     
     		state := makeState(token)
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.oauth.invalid_state_token.app_error", err.Id)
     		assert.Equal(t, "", err.DetailedError)
    @@ -255,7 +255,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     			"token":  token.Token,
     		})))
     
    -		_, _, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -268,7 +268,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest("")
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -285,7 +285,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(token)
     
    -		_, _, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -298,7 +298,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.token_failed.app_error", err.Id)
     	})
    @@ -316,7 +316,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.bad_response.app_error", err.Id)
     		assert.Contains(t, err.DetailedError, "status_code=418")
    @@ -336,7 +336,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.bad_response.app_error", err.Id)
     		assert.Contains(t, err.DetailedError, "response_body=invalid")
    @@ -359,7 +359,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.bad_token.app_error", err.Id)
     	})
    @@ -381,7 +381,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.missing.app_error", err.Id)
     	})
    @@ -403,7 +403,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.service.app_error", err.Id)
     	})
    @@ -432,7 +432,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.response.app_error", err.Id)
     	})
    @@ -463,7 +463,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "oauth.gitlab.tos.error", err.Id)
     	})
    @@ -480,7 +480,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		providerMock.On("GetSSOSettings", mock.AnythingOfType("*request.Context"), mock.Anything, model.ServiceOpenid).Return(nil, errors.New("error"))
     		einterfaces.RegisterOAuthProvider(model.ServiceOpenid, providerMock)
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceOpenid, "", "", "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceOpenid, "", "", "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.get_authorization_code.endpoint.app_error", err.Id)
     	})
    @@ -532,14 +532,14 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     				state := base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
     
     				recorder := httptest.ResponseRecorder{}
    -				body, receivedTeamID, receivedStateProps, _, err := th.App.AuthorizeOAuthUser(th.Context, &recorder, request, model.ServiceGitlab, "", state, "")
    +				body, receivedStateProps, _, err := th.App.AuthorizeOAuthUser(th.Context, &recorder, request, model.ServiceGitlab, "", state, "")
     
     				require.NotNil(t, body)
     				bodyBytes, bodyErr := io.ReadAll(body)
     				require.NoError(t, bodyErr)
     				assert.Equal(t, userData, string(bodyBytes))
     
    -				assert.Equal(t, stateProps["team_id"], receivedTeamID)
    +				// team_id is no longer returned as it was removed for security reasons
     				assert.Equal(t, stateProps, receivedStateProps)
     				assert.Nil(t, err)
     
    
  • server/channels/app/team.go+5 5 modified
    @@ -720,6 +720,10 @@ func (a *App) AddUserToTeamByInviteId(c request.CTX, inviteId string, userID str
     	}
     	team := teamChanResult.Data
     
    +	if team.IsGroupConstrained() {
    +		return nil, nil, model.NewAppError("AddUserToTeamByInviteId", "app.team.invite_id.group_constrained.error", nil, "", http.StatusForbidden)
    +	}
    +
     	userChanResult := <-uchan
     	if userChanResult.NErr != nil {
     		var nfErr *store.ErrNotFound
    @@ -1088,15 +1092,11 @@ func (a *App) AddTeamMemberByToken(c request.CTX, userID, tokenID string) (*mode
     }
     
     func (a *App) AddTeamMemberByInviteId(c request.CTX, inviteId, userID string) (*model.TeamMember, *model.AppError) {
    -	team, teamMember, err := a.AddUserToTeamByInviteId(c, inviteId, userID)
    +	_, teamMember, err := a.AddUserToTeamByInviteId(c, inviteId, userID)
     	if err != nil {
     		return nil, err
     	}
     
    -	if team.IsGroupConstrained() {
    -		return nil, model.NewAppError("AddTeamMemberByInviteId", "app.team.invite_id.group_constrained.error", nil, "", http.StatusForbidden)
    -	}
    -
     	return teamMember, nil
     }
     
    
  • server/channels/app/user.go+28 7 modified
    @@ -348,7 +348,7 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m
     	return ruser, nil
     }
     
    -func (a *App) CreateOAuthUser(c request.CTX, service string, userData io.Reader, teamID string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *App) CreateOAuthUser(c request.CTX, service string, userData io.Reader, inviteToken string, inviteId string, tokenUser *model.User) (*model.User, *model.AppError) {
     	if !*a.Config().TeamSettings.EnableUserCreation {
     		return nil, model.NewAppError("CreateOAuthUser", "api.user.create_user.disabled.app_error", nil, "", http.StatusNotImplemented)
     	}
    @@ -401,19 +401,40 @@ func (a *App) CreateOAuthUser(c request.CTX, service string, userData io.Reader,
     		return nil, err
     	}
     
    -	if teamID != "" {
    -		err = a.AddUserToTeamByTeamId(c, teamID, user)
    +	if err = a.AddUserToTeamByInviteIfNeeded(c, ruser, inviteToken, inviteId); err != nil {
    +		c.Logger().Warn("Failed to add user to team", mlog.Err(err))
    +	}
    +
    +	return ruser, nil
    +}
    +
    +func (a *App) AddUserToTeamByInviteIfNeeded(c request.CTX, user *model.User, inviteToken string, inviteId string) *model.AppError {
    +	var team *model.Team
    +	var err *model.AppError
    +
    +	if inviteToken != "" {
    +		team, _, err = a.AddUserToTeamByToken(c, user.Id, inviteToken)
     		if err != nil {
    -			return nil, err
    +			c.Logger().Warn("Failed to add user to team using invite token", mlog.Err(err))
    +			return err
     		}
    -
    -		err = a.AddDirectChannels(c, teamID, user)
    +	} else if inviteId != "" {
    +		team, _, err = a.AddUserToTeamByInviteId(c, inviteId, user.Id)
     		if err != nil {
    +			c.Logger().Warn("Failed to add user to team using invite ID", mlog.Err(err))
    +			return err
    +		}
    +	} else {
    +		return nil
    +	}
    +
    +	if team != nil {
    +		if err = a.AddDirectChannels(c, team.Id, user); err != nil {
     			c.Logger().Warn("Failed to add direct channels", mlog.Err(err))
     		}
     	}
     
    -	return ruser, nil
    +	return nil
     }
     
     func (a *App) GetUser(userID string) (*model.User, *model.AppError) {
    
  • server/channels/app/user_test.go+4 4 modified
    @@ -42,7 +42,7 @@ func TestCreateOAuthUser(t *testing.T) {
     		js, jsonErr := json.Marshal(glUser)
     		require.NoError(t, jsonErr)
     
    -		user, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, bytes.NewReader(js), th.BasicTeam.Id, nil)
    +		user, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, bytes.NewReader(js), "", "", nil)
     		require.Nil(t, err)
     
     		require.Equal(t, glUser.Username, user.Username, "usernames didn't match")
    @@ -71,7 +71,7 @@ func TestCreateOAuthUser(t *testing.T) {
     		assert.Equal(t, dbUser.Id, s)
     
     		// data passed doesn't matter as return is mocked
    -		_, err := th.App.CreateOAuthUser(th.Context, model.ServiceOffice365, strings.NewReader("{}"), th.BasicTeam.Id, nil)
    +		_, err := th.App.CreateOAuthUser(th.Context, model.ServiceOffice365, strings.NewReader("{}"), "", "", nil)
     		assert.Nil(t, err)
     		u, er := th.App.Srv().Store().User().GetByEmail(dbUser.Email)
     		assert.NoError(t, er)
    @@ -81,7 +81,7 @@ func TestCreateOAuthUser(t *testing.T) {
     
     	t.Run("user creation disabled", func(t *testing.T) {
     		*th.App.Config().TeamSettings.EnableUserCreation = false
    -		_, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, strings.NewReader("{}"), th.BasicTeam.Id, nil)
    +		_, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, strings.NewReader("{}"), "", "", nil)
     		require.NotNil(t, err, "should have failed - user creation disabled")
     	})
     }
    @@ -743,7 +743,7 @@ func createGitlabUser(t *testing.T, a *App, c request.CTX, id int64, username st
     	var user *model.User
     	var err *model.AppError
     
    -	user, err = a.CreateOAuthUser(c, "gitlab", bytes.NewReader(gitlabUser), "", nil)
    +	user, err = a.CreateOAuthUser(c, "gitlab", bytes.NewReader(gitlabUser), "", "", nil)
     	require.Nil(t, err, "unable to create the user", err)
     
     	return user, gitlabUserObj
    
  • server/channels/web/oauth.go+14 20 modified
    @@ -307,7 +307,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     
     	uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete"
     
    -	body, teamId, props, tokenUser, err := c.App.AuthorizeOAuthUser(c.AppContext, w, r, service, code, state, uri)
    +	body, props, tokenUser, err := c.App.AuthorizeOAuthUser(c.AppContext, w, r, service, code, state, uri)
     
     	action := ""
     	hasRedirectURL := false
    @@ -338,7 +338,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    -	user, err := c.App.CompleteOAuth(c.AppContext, service, body, teamId, props, tokenUser)
    +	user, err := c.App.CompleteOAuth(c.AppContext, service, body, props, tokenUser)
     	if err != nil {
     		err.Translate(c.AppContext.T)
     		c.LogErrorByCode(err)
    @@ -450,13 +450,11 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     	auditRec.AddMeta("service", c.Params.Service)
     	defer c.LogAuditRec(auditRec)
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	// Get invite token or ID instead of team_id
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
     
    -	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, teamId, model.OAuthActionLogin, redirectURL, loginHint, false, desktopToken)
    +	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, model.OAuthActionLogin, redirectURL, loginHint, false, desktopToken, tokenID, inviteId)
     	if err != nil {
     		c.Err = err
     		return
    @@ -486,13 +484,11 @@ func mobileLoginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     	auditRec.AddMeta("service", c.Params.Service)
     	defer c.LogAuditRec(auditRec)
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	// Get invite token or ID instead of team_id
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
     
    -	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, teamId, model.OAuthActionMobile, redirectURL, "", true, "")
    +	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, model.OAuthActionMobile, redirectURL, "", true, "", tokenID, inviteId)
     	if err != nil {
     		c.Err = err
     		return
    @@ -521,15 +517,13 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     	auditRec.AddMeta("service", c.Params.Service)
     	defer c.LogAuditRec(auditRec)
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	// Get invite token or ID instead of team_id
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
     
     	desktopToken := r.URL.Query().Get("desktop_token")
     
    -	authURL, err := c.App.GetOAuthSignupEndpoint(c.AppContext, w, r, c.Params.Service, teamId, desktopToken)
    +	authURL, err := c.App.GetOAuthSignupEndpoint(c.AppContext, w, r, c.Params.Service, desktopToken, tokenID, inviteId)
     	if err != nil {
     		c.Err = err
     		return
    
  • server/channels/web/saml.go+13 14 modified
    @@ -32,19 +32,21 @@ func loginWithSaml(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
    +
     	action := r.URL.Query().Get("action")
     	isMobile := action == model.OAuthActionMobile
     	redirectURL := html.EscapeString(r.URL.Query().Get("redirect_to"))
     	relayProps := map[string]string{}
     	relayState := ""
     
     	if action != "" {
    -		relayProps["team_id"] = teamId
    +		if tokenID != "" {
    +			relayProps["invite_token"] = tokenID
    +		} else if inviteId != "" {
    +			relayProps["invite_id"] = inviteId
    +		}
     		relayProps["action"] = action
     		if action == model.OAuthActionEmailToSSO {
     			relayProps["email_token"] = r.URL.Query().Get("email_token")
    @@ -150,14 +152,11 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
     
     	switch action {
     	case model.OAuthActionSignup:
    -		if teamId := relayProps["team_id"]; teamId != "" {
    -			if err = c.App.AddUserToTeamByTeamId(c.AppContext, teamId, user); err != nil {
    -				c.LogErrorByCode(err)
    -				break
    -			}
    -			if err = c.App.AddDirectChannels(c.AppContext, teamId, user); err != nil {
    -				c.LogErrorByCode(err)
    -			}
    +		inviteToken := relayProps["invite_token"]
    +		inviteId := relayProps["invite_id"]
    +		if err = c.App.AddUserToTeamByInviteIfNeeded(c.AppContext, user, inviteToken, inviteId); err != nil {
    +			c.LogErrorByCode(err)
    +			break
     		}
     	case model.OAuthActionEmailToSSO:
     		if err = c.App.RevokeAllSessions(c.AppContext, user.Id); err != nil {
    
2d5cdc6e217e

[MM-64896][MM-64898] Pass inviteid/tokenid to relay state/props for external auth when auto-joining a team (#33545) (#33666)

https://github.com/mattermost/mattermostMattermost BuildAug 13, 2025via ghsa
7 files changed · +128 105
  • server/channels/app/oauth.go+46 37 modified
    @@ -438,11 +438,14 @@ func (a *App) newSessionUpdateToken(c request.CTX, app *model.OAuthApp, accessDa
     	return accessRsp, nil
     }
     
    -func (a *App) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, teamID, action, redirectTo, loginHint string, isMobile bool, desktopToken string) (string, *model.AppError) {
    +func (a *App) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, action, redirectTo, loginHint string, isMobile bool, desktopToken string, inviteToken string, inviteId string) (string, *model.AppError) {
     	stateProps := map[string]string{}
     	stateProps["action"] = action
    -	if teamID != "" {
    -		stateProps["team_id"] = teamID
    +
    +	if inviteToken != "" {
    +		stateProps["invite_token"] = inviteToken
    +	} else if inviteId != "" {
    +		stateProps["invite_id"] = inviteId
     	}
     
     	if redirectTo != "" {
    @@ -463,11 +466,14 @@ func (a *App) GetOAuthLoginEndpoint(c request.CTX, w http.ResponseWriter, r *htt
     	return authURL, nil
     }
     
    -func (a *App) GetOAuthSignupEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, teamID string, desktopToken string) (string, *model.AppError) {
    +func (a *App) GetOAuthSignupEndpoint(c request.CTX, w http.ResponseWriter, r *http.Request, service, desktopToken string, inviteToken string, inviteId string) (string, *model.AppError) {
     	stateProps := map[string]string{}
     	stateProps["action"] = model.OAuthActionSignup
    -	if teamID != "" {
    -		stateProps["team_id"] = teamID
    +
    +	if inviteToken != "" {
    +		stateProps["invite_token"] = inviteToken
    +	} else if inviteId != "" {
    +		stateProps["invite_id"] = inviteId
     	}
     
     	if desktopToken != "" {
    @@ -570,22 +576,26 @@ func (a *App) RevokeAccessToken(c request.CTX, token string) *model.AppError {
     	return nil
     }
     
    -func (a *App) CompleteOAuth(c request.CTX, service string, body io.ReadCloser, teamID string, props map[string]string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *App) CompleteOAuth(c request.CTX, service string, body io.ReadCloser, props map[string]string, tokenUser *model.User) (*model.User, *model.AppError) {
     	defer body.Close()
     
     	action := props["action"]
     
    +	// Extract invite token or ID from props so we can add the user to the team if needed
    +	inviteToken := props["invite_token"]
    +	inviteId := props["invite_id"]
    +
     	switch action {
     	case model.OAuthActionSignup:
    -		return a.CreateOAuthUser(c, service, body, teamID, tokenUser)
    +		return a.CreateOAuthUser(c, service, body, inviteToken, inviteId, tokenUser)
     	case model.OAuthActionLogin:
    -		return a.LoginByOAuth(c, service, body, teamID, tokenUser)
    +		return a.LoginByOAuth(c, service, body, inviteToken, inviteId, tokenUser)
     	case model.OAuthActionEmailToSSO:
     		return a.CompleteSwitchWithOAuth(c, service, body, props["email"], tokenUser)
     	case model.OAuthActionSSOToEmail:
    -		return a.LoginByOAuth(c, service, body, teamID, tokenUser)
    +		return a.LoginByOAuth(c, service, body, inviteToken, inviteId, tokenUser)
     	default:
    -		return a.LoginByOAuth(c, service, body, teamID, tokenUser)
    +		return a.LoginByOAuth(c, service, body, inviteToken, inviteId, tokenUser)
     	}
     }
     
    @@ -606,7 +616,7 @@ func (a *App) getSSOProvider(service string) (einterfaces.OAuthProvider, *model.
     	return provider, nil
     }
     
    -func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, teamID string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, inviteToken string, inviteId string, tokenUser *model.User) (*model.User, *model.AppError) {
     	provider, e := a.getSSOProvider(service)
     	if e != nil {
     		return nil, e
    @@ -632,7 +642,7 @@ func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, te
     	user, err := a.GetUserByAuth(model.NewPointer(*authUser.AuthData), service)
     	if err != nil {
     		if err.Id == MissingAuthAccountError {
    -			user, err = a.CreateOAuthUser(c, service, bytes.NewReader(buf.Bytes()), teamID, tokenUser)
    +			user, err = a.CreateOAuthUser(c, service, bytes.NewReader(buf.Bytes()), inviteToken, inviteId, tokenUser)
     		} else {
     			return nil, err
     		}
    @@ -647,8 +657,9 @@ func (a *App) LoginByOAuth(c request.CTX, service string, userData io.Reader, te
     		if err = a.UpdateOAuthUserAttrs(c, bytes.NewReader(buf.Bytes()), user, provider, service, tokenUser); err != nil {
     			return nil, err
     		}
    -		if teamID != "" {
    -			err = a.AddUserToTeamByTeamId(c, teamID, user)
    +
    +		if err = a.AddUserToTeamByInviteIfNeeded(c, user, inviteToken, inviteId); err != nil {
    +			c.Logger().Warn("Failed to add user to team", mlog.Err(err))
     		}
     	}
     
    @@ -802,46 +813,46 @@ func (a *App) GetAuthorizationCode(c request.CTX, w http.ResponseWriter, r *http
     	return authURL, nil
     }
     
    -func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.Request, service, code, state, redirectURI string) (io.ReadCloser, string, map[string]string, *model.User, *model.AppError) {
    +func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.Request, service, code, state, redirectURI string) (io.ReadCloser, map[string]string, *model.User, *model.AppError) {
     	provider, e := a.getSSOProvider(service)
     	if e != nil {
    -		return nil, "", nil, nil, e
    +		return nil, nil, nil, e
     	}
     
     	sso, e2 := provider.GetSSOSettings(c, a.Config(), service)
     	if e2 != nil {
    -		return nil, "", nil, nil, model.NewAppError("AuthorizeOAuthUser.GetSSOSettings", "api.user.get_authorization_code.endpoint.app_error", nil, "", http.StatusNotImplemented).Wrap(e2)
    +		return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser.GetSSOSettings", "api.user.get_authorization_code.endpoint.app_error", nil, "", http.StatusNotImplemented).Wrap(e2)
     	}
     
     	b, strErr := b64.StdEncoding.DecodeString(state)
     	if strErr != nil {
    -		return nil, "", nil, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(strErr)
    +		return nil, nil, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(strErr)
     	}
     
     	stateStr := string(b)
     	stateProps := model.MapFromJSON(strings.NewReader(stateStr))
     
     	expectedToken, appErr := a.GetOAuthStateToken(stateProps["token"])
     	if appErr != nil {
    -		return nil, "", stateProps, nil, appErr
    +		return nil, stateProps, nil, appErr
     	}
     
     	stateEmail := stateProps["email"]
     	stateAction := stateProps["action"]
     	if stateAction == model.OAuthActionEmailToSSO && stateEmail == "" {
     		err := errors.New("No email provided in state when trying to switch from email to SSO")
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
     	}
     
     	cookie, cookieErr := r.Cookie(CookieOAuth)
     	if cookieErr != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(cookieErr)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(cookieErr)
     	}
     
     	expectedTokenExtra := generateOAuthStateTokenExtra(stateEmail, stateAction, cookie.Value)
     	if expectedTokenExtra != expectedToken.Extra {
     		err := errors.New("Extra token value does not match token generated from state")
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest).Wrap(err)
     	}
     
     	appErr = a.DeleteToken(expectedToken)
    @@ -861,8 +872,6 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     
     	http.SetCookie(w, httpCookie)
     
    -	teamID := stateProps["team_id"]
    -
     	p := url.Values{}
     	p.Set("client_id", *sso.Id)
     	p.Set("client_secret", *sso.Secret)
    @@ -872,15 +881,15 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     
     	req, requestErr := http.NewRequest("POST", *sso.TokenEndpoint, strings.NewReader(p.Encode()))
     	if requestErr != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(requestErr)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(requestErr)
     	}
     
     	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
     	req.Header.Set("Accept", "application/json")
     
     	resp, err := a.HTTPService().MakeClient(true).Do(req)
     	if err != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
     	}
     	defer resp.Body.Close()
     
    @@ -889,15 +898,15 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     	var ar *model.AccessResponse
     	err = json.NewDecoder(tee).Decode(&ar)
     	if err != nil || resp.StatusCode != http.StatusOK {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, fmt.Sprintf("response_body=%s, status_code=%d, error=%v", buf.String(), resp.StatusCode, err), http.StatusInternalServerError).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, fmt.Sprintf("response_body=%s, status_code=%d, error=%v", buf.String(), resp.StatusCode, err), http.StatusInternalServerError).Wrap(err)
     	}
     
     	if strings.ToLower(ar.TokenType) != model.AccessTokenType {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+buf.String(), http.StatusInternalServerError)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+buf.String(), http.StatusInternalServerError)
     	}
     
     	if ar.AccessToken == "" {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "response_body="+buf.String(), http.StatusInternalServerError)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "response_body="+buf.String(), http.StatusInternalServerError)
     	}
     
     	p = url.Values{}
    @@ -907,13 +916,13 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     	if ar.IdToken != "" {
     		userFromToken, err = provider.GetUserFromIdToken(c, ar.IdToken)
     		if err != nil {
    -			return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
    +			return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
     		}
     	}
     
     	req, requestErr = http.NewRequest("GET", *sso.UserAPIEndpoint, strings.NewReader(""))
     	if requestErr != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(requestErr)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(requestErr)
     	}
     
     	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    @@ -922,7 +931,7 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     
     	resp, err = a.HTTPService().MakeClient(true).Do(req)
     	if err != nil {
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(err)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(err)
     	} else if resp.StatusCode != http.StatusOK {
     		defer resp.Body.Close()
     
    @@ -935,17 +944,17 @@ func (a *App) AuthorizeOAuthUser(c request.CTX, w http.ResponseWriter, r *http.R
     		if service == model.ServiceGitlab && resp.StatusCode == http.StatusForbidden && strings.Contains(bodyString, "Terms of Service") {
     			url, err := url.Parse(*sso.UserAPIEndpoint)
     			if err != nil {
    -				return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(errors.Wrapf(err, "error parsing %s", *sso.UserAPIEndpoint))
    +				return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", model.NoTranslation, nil, "", http.StatusInternalServerError).Wrap(errors.Wrapf(err, "error parsing %s", *sso.UserAPIEndpoint))
     			}
     			// Return a nicer error when the user hasn't accepted GitLab's terms of service
    -			return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "oauth.gitlab.tos.error", map[string]any{"URL": url.Hostname()}, "", http.StatusBadRequest)
    +			return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "oauth.gitlab.tos.error", map[string]any{"URL": url.Hostname()}, "", http.StatusBadRequest)
     		}
     
    -		return nil, "", stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.response.app_error", nil, "response_body="+bodyString, http.StatusInternalServerError)
    +		return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.response.app_error", nil, "response_body="+bodyString, http.StatusInternalServerError)
     	}
     
     	// Note that resp.Body is not closed here, so it must be closed by the caller
    -	return resp.Body, teamID, stateProps, userFromToken, nil
    +	return resp.Body, stateProps, userFromToken, nil
     }
     
     func (a *App) SwitchEmailToOAuth(c request.CTX, w http.ResponseWriter, r *http.Request, email, password, code, service string) (string, *model.AppError) {
    
  • server/channels/app/oauth_test.go+18 18 modified
    @@ -193,7 +193,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		th := setup(t, false, true, true, "")
     		defer th.TearDown()
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", "", "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", "", "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.unsupported.app_error", err.Id)
     	})
    @@ -204,7 +204,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     
     		state := "!"
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -217,7 +217,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     			"token": model.NewId(),
     		})))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.oauth.invalid_state_token.app_error", err.Id)
     		assert.Error(t, err.Unwrap())
    @@ -232,7 +232,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     
     		state := makeState(token)
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.oauth.invalid_state_token.app_error", err.Id)
     		assert.Equal(t, "", err.DetailedError)
    @@ -255,7 +255,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     			"token":  token.Token,
     		})))
     
    -		_, _, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
    +		_, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -268,7 +268,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest("")
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -285,7 +285,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(token)
     
    -		_, _, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err = th.App.AuthorizeOAuthUser(th.Context, nil, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id)
     	})
    @@ -298,7 +298,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.token_failed.app_error", err.Id)
     	})
    @@ -316,7 +316,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.bad_response.app_error", err.Id)
     		assert.Contains(t, err.DetailedError, "status_code=418")
    @@ -336,7 +336,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.bad_response.app_error", err.Id)
     		assert.Contains(t, err.DetailedError, "response_body=invalid")
    @@ -359,7 +359,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.bad_token.app_error", err.Id)
     	})
    @@ -381,7 +381,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.missing.app_error", err.Id)
     	})
    @@ -403,7 +403,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.service.app_error", err.Id)
     	})
    @@ -432,7 +432,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.authorize_oauth_user.response.app_error", err.Id)
     	})
    @@ -463,7 +463,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		request := makeRequest(cookie)
     		state := makeState(makeToken(th, cookie))
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "")
     		require.NotNil(t, err)
     		assert.Equal(t, "oauth.gitlab.tos.error", err.Id)
     	})
    @@ -480,7 +480,7 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     		providerMock.On("GetSSOSettings", mock.AnythingOfType("*request.Context"), mock.Anything, model.ServiceOpenid).Return(nil, errors.New("error"))
     		einterfaces.RegisterOAuthProvider(model.ServiceOpenid, providerMock)
     
    -		_, _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceOpenid, "", "", "")
    +		_, _, _, err := th.App.AuthorizeOAuthUser(th.Context, nil, nil, model.ServiceOpenid, "", "", "")
     		require.NotNil(t, err)
     		assert.Equal(t, "api.user.get_authorization_code.endpoint.app_error", err.Id)
     	})
    @@ -532,14 +532,14 @@ func TestAuthorizeOAuthUser(t *testing.T) {
     				state := base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
     
     				recorder := httptest.ResponseRecorder{}
    -				body, receivedTeamID, receivedStateProps, _, err := th.App.AuthorizeOAuthUser(th.Context, &recorder, request, model.ServiceGitlab, "", state, "")
    +				body, receivedStateProps, _, err := th.App.AuthorizeOAuthUser(th.Context, &recorder, request, model.ServiceGitlab, "", state, "")
     
     				require.NotNil(t, body)
     				bodyBytes, bodyErr := io.ReadAll(body)
     				require.NoError(t, bodyErr)
     				assert.Equal(t, userData, string(bodyBytes))
     
    -				assert.Equal(t, stateProps["team_id"], receivedTeamID)
    +				// team_id is no longer returned as it was removed for security reasons
     				assert.Equal(t, stateProps, receivedStateProps)
     				assert.Nil(t, err)
     
    
  • server/channels/app/team.go+5 5 modified
    @@ -720,6 +720,10 @@ func (a *App) AddUserToTeamByInviteId(c request.CTX, inviteId string, userID str
     	}
     	team := teamChanResult.Data
     
    +	if team.IsGroupConstrained() {
    +		return nil, nil, model.NewAppError("AddUserToTeamByInviteId", "app.team.invite_id.group_constrained.error", nil, "", http.StatusForbidden)
    +	}
    +
     	userChanResult := <-uchan
     	if userChanResult.NErr != nil {
     		var nfErr *store.ErrNotFound
    @@ -1084,15 +1088,11 @@ func (a *App) AddTeamMemberByToken(c request.CTX, userID, tokenID string) (*mode
     }
     
     func (a *App) AddTeamMemberByInviteId(c request.CTX, inviteId, userID string) (*model.TeamMember, *model.AppError) {
    -	team, teamMember, err := a.AddUserToTeamByInviteId(c, inviteId, userID)
    +	_, teamMember, err := a.AddUserToTeamByInviteId(c, inviteId, userID)
     	if err != nil {
     		return nil, err
     	}
     
    -	if team.IsGroupConstrained() {
    -		return nil, model.NewAppError("AddTeamMemberByInviteId", "app.team.invite_id.group_constrained.error", nil, "", http.StatusForbidden)
    -	}
    -
     	return teamMember, nil
     }
     
    
  • server/channels/app/user.go+28 7 modified
    @@ -348,7 +348,7 @@ func (a *App) createUserOrGuest(c request.CTX, user *model.User, guest bool) (*m
     	return ruser, nil
     }
     
    -func (a *App) CreateOAuthUser(c request.CTX, service string, userData io.Reader, teamID string, tokenUser *model.User) (*model.User, *model.AppError) {
    +func (a *App) CreateOAuthUser(c request.CTX, service string, userData io.Reader, inviteToken string, inviteId string, tokenUser *model.User) (*model.User, *model.AppError) {
     	if !*a.Config().TeamSettings.EnableUserCreation {
     		return nil, model.NewAppError("CreateOAuthUser", "api.user.create_user.disabled.app_error", nil, "", http.StatusNotImplemented)
     	}
    @@ -401,19 +401,40 @@ func (a *App) CreateOAuthUser(c request.CTX, service string, userData io.Reader,
     		return nil, err
     	}
     
    -	if teamID != "" {
    -		err = a.AddUserToTeamByTeamId(c, teamID, user)
    +	if err = a.AddUserToTeamByInviteIfNeeded(c, ruser, inviteToken, inviteId); err != nil {
    +		c.Logger().Warn("Failed to add user to team", mlog.Err(err))
    +	}
    +
    +	return ruser, nil
    +}
    +
    +func (a *App) AddUserToTeamByInviteIfNeeded(c request.CTX, user *model.User, inviteToken string, inviteId string) *model.AppError {
    +	var team *model.Team
    +	var err *model.AppError
    +
    +	if inviteToken != "" {
    +		team, _, err = a.AddUserToTeamByToken(c, user.Id, inviteToken)
     		if err != nil {
    -			return nil, err
    +			c.Logger().Warn("Failed to add user to team using invite token", mlog.Err(err))
    +			return err
     		}
    -
    -		err = a.AddDirectChannels(c, teamID, user)
    +	} else if inviteId != "" {
    +		team, _, err = a.AddUserToTeamByInviteId(c, inviteId, user.Id)
     		if err != nil {
    +			c.Logger().Warn("Failed to add user to team using invite ID", mlog.Err(err))
    +			return err
    +		}
    +	} else {
    +		return nil
    +	}
    +
    +	if team != nil {
    +		if err = a.AddDirectChannels(c, team.Id, user); err != nil {
     			c.Logger().Warn("Failed to add direct channels", mlog.Err(err))
     		}
     	}
     
    -	return ruser, nil
    +	return nil
     }
     
     func (a *App) GetUser(userID string) (*model.User, *model.AppError) {
    
  • server/channels/app/user_test.go+4 4 modified
    @@ -44,7 +44,7 @@ func TestCreateOAuthUser(t *testing.T) {
     		js, jsonErr := json.Marshal(glUser)
     		require.NoError(t, jsonErr)
     
    -		user, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, bytes.NewReader(js), th.BasicTeam.Id, nil)
    +		user, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, bytes.NewReader(js), "", "", nil)
     		require.Nil(t, err)
     
     		require.Equal(t, glUser.Username, user.Username, "usernames didn't match")
    @@ -73,7 +73,7 @@ func TestCreateOAuthUser(t *testing.T) {
     		assert.Equal(t, dbUser.Id, s)
     
     		// data passed doesn't matter as return is mocked
    -		_, err := th.App.CreateOAuthUser(th.Context, model.ServiceOffice365, strings.NewReader("{}"), th.BasicTeam.Id, nil)
    +		_, err := th.App.CreateOAuthUser(th.Context, model.ServiceOffice365, strings.NewReader("{}"), "", "", nil)
     		assert.Nil(t, err)
     		u, er := th.App.Srv().Store().User().GetByEmail(dbUser.Email)
     		assert.NoError(t, er)
    @@ -83,7 +83,7 @@ func TestCreateOAuthUser(t *testing.T) {
     
     	t.Run("user creation disabled", func(t *testing.T) {
     		*th.App.Config().TeamSettings.EnableUserCreation = false
    -		_, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, strings.NewReader("{}"), th.BasicTeam.Id, nil)
    +		_, err := th.App.CreateOAuthUser(th.Context, model.UserAuthServiceGitlab, strings.NewReader("{}"), "", "", nil)
     		require.NotNil(t, err, "should have failed - user creation disabled")
     	})
     }
    @@ -745,7 +745,7 @@ func createGitlabUser(t *testing.T, a *App, c request.CTX, id int64, username st
     	var user *model.User
     	var err *model.AppError
     
    -	user, err = a.CreateOAuthUser(c, "gitlab", bytes.NewReader(gitlabUser), "", nil)
    +	user, err = a.CreateOAuthUser(c, "gitlab", bytes.NewReader(gitlabUser), "", "", nil)
     	require.Nil(t, err, "unable to create the user", err)
     
     	return user, gitlabUserObj
    
  • server/channels/web/oauth.go+14 20 modified
    @@ -306,7 +306,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     
     	uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete"
     
    -	body, teamId, props, tokenUser, err := c.App.AuthorizeOAuthUser(c.AppContext, w, r, service, code, state, uri)
    +	body, props, tokenUser, err := c.App.AuthorizeOAuthUser(c.AppContext, w, r, service, code, state, uri)
     
     	action := ""
     	hasRedirectURL := false
    @@ -337,7 +337,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    -	user, err := c.App.CompleteOAuth(c.AppContext, service, body, teamId, props, tokenUser)
    +	user, err := c.App.CompleteOAuth(c.AppContext, service, body, props, tokenUser)
     	if err != nil {
     		err.Translate(c.AppContext.T)
     		c.LogErrorByCode(err)
    @@ -449,13 +449,11 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     	auditRec.AddMeta("service", c.Params.Service)
     	defer c.LogAuditRec(auditRec)
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	// Get invite token or ID instead of team_id
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
     
    -	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, teamId, model.OAuthActionLogin, redirectURL, loginHint, false, desktopToken)
    +	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, model.OAuthActionLogin, redirectURL, loginHint, false, desktopToken, tokenID, inviteId)
     	if err != nil {
     		c.Err = err
     		return
    @@ -485,13 +483,11 @@ func mobileLoginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     	auditRec.AddMeta("service", c.Params.Service)
     	defer c.LogAuditRec(auditRec)
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	// Get invite token or ID instead of team_id
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
     
    -	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, teamId, model.OAuthActionMobile, redirectURL, "", true, "")
    +	authURL, err := c.App.GetOAuthLoginEndpoint(c.AppContext, w, r, c.Params.Service, model.OAuthActionMobile, redirectURL, "", true, "", tokenID, inviteId)
     	if err != nil {
     		c.Err = err
     		return
    @@ -520,15 +516,13 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
     	auditRec.AddMeta("service", c.Params.Service)
     	defer c.LogAuditRec(auditRec)
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	// Get invite token or ID instead of team_id
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
     
     	desktopToken := r.URL.Query().Get("desktop_token")
     
    -	authURL, err := c.App.GetOAuthSignupEndpoint(c.AppContext, w, r, c.Params.Service, teamId, desktopToken)
    +	authURL, err := c.App.GetOAuthSignupEndpoint(c.AppContext, w, r, c.Params.Service, desktopToken, tokenID, inviteId)
     	if err != nil {
     		c.Err = err
     		return
    
  • server/channels/web/saml.go+13 14 modified
    @@ -31,19 +31,21 @@ func loginWithSaml(c *Context, w http.ResponseWriter, r *http.Request) {
     		return
     	}
     
    -	teamId, err := c.App.GetTeamIdFromQuery(c.AppContext, r.URL.Query())
    -	if err != nil {
    -		c.Err = err
    -		return
    -	}
    +	tokenID := r.URL.Query().Get("t")
    +	inviteId := r.URL.Query().Get("id")
    +
     	action := r.URL.Query().Get("action")
     	isMobile := action == model.OAuthActionMobile
     	redirectURL := html.EscapeString(r.URL.Query().Get("redirect_to"))
     	relayProps := map[string]string{}
     	relayState := ""
     
     	if action != "" {
    -		relayProps["team_id"] = teamId
    +		if tokenID != "" {
    +			relayProps["invite_token"] = tokenID
    +		} else if inviteId != "" {
    +			relayProps["invite_id"] = inviteId
    +		}
     		relayProps["action"] = action
     		if action == model.OAuthActionEmailToSSO {
     			relayProps["email_token"] = r.URL.Query().Get("email_token")
    @@ -149,14 +151,11 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
     
     	switch action {
     	case model.OAuthActionSignup:
    -		if teamId := relayProps["team_id"]; teamId != "" {
    -			if err = c.App.AddUserToTeamByTeamId(c.AppContext, teamId, user); err != nil {
    -				c.LogErrorByCode(err)
    -				break
    -			}
    -			if err = c.App.AddDirectChannels(c.AppContext, teamId, user); err != nil {
    -				c.LogErrorByCode(err)
    -			}
    +		inviteToken := relayProps["invite_token"]
    +		inviteId := relayProps["invite_id"]
    +		if err = c.App.AddUserToTeamByInviteIfNeeded(c.AppContext, user, inviteToken, inviteId); err != nil {
    +			c.LogErrorByCode(err)
    +			break
     		}
     	case model.OAuthActionEmailToSSO:
     		if err = c.App.RevokeAllSessions(c.AppContext, user.Id); err != nil {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.