Arbitrary Mattermost Team can be joined by manipulating the SAML RelayState
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.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/mattermost/mattermost/server/v8Go | < 8.0.0-20250815100400-2d5cdc6e217e | 8.0.0-20250815100400-2d5cdc6e217e |
github.com/mattermost/mattermost-serverGo | >= 10.11.0, < 10.11.2 | 10.11.2 |
github.com/mattermost/mattermost-serverGo | >= 10.10.0, < 10.10.3 | 10.10.3 |
github.com/mattermost/mattermost-serverGo | >= 10.5.0, < 10.5.11 | 10.5.11 |
Affected products
1- Range: 10.11.0
Patches
37a2ac23e2074[MM-64896][MM-64898] Pass inviteid/tokenid to relay state/props for external auth when auto-joining a team (#33545) (#33739)
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)
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)
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- github.com/advisories/GHSA-r6qj-894f-5hr2ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-58075ghsaADVISORY
- github.com/mattermost/mattermost/commit/0e478b2d895e89143c7732c7b33e16d98ace663dghsaWEB
- github.com/mattermost/mattermost/commit/2d5cdc6e217eb244e7f2122cb89f85140b6d982aghsaWEB
- github.com/mattermost/mattermost/commit/7a2ac23e207429d7db8acc3770908c15c0b5c33eghsaWEB
- mattermost.com/security-updatesghsaWEB
News mentions
0No linked articles in our index yet.