коммит произвёл
GitHub
родитель
2de65cfb11
Коммит
869da7a78b
@@ -363,6 +363,8 @@ func InitLocal(configservice configservice.ConfigService, globalOptionsFunc app.
|
|||||||
|
|
||||||
api.BaseRoutes.Jobs = api.BaseRoutes.ApiRoot.PathPrefix("/jobs").Subrouter()
|
api.BaseRoutes.Jobs = api.BaseRoutes.ApiRoot.PathPrefix("/jobs").Subrouter()
|
||||||
|
|
||||||
|
api.BaseRoutes.SAML = api.BaseRoutes.ApiRoot.PathPrefix("/saml").Subrouter()
|
||||||
|
|
||||||
api.InitUserLocal()
|
api.InitUserLocal()
|
||||||
api.InitTeamLocal()
|
api.InitTeamLocal()
|
||||||
api.InitChannelLocal()
|
api.InitChannelLocal()
|
||||||
@@ -381,6 +383,7 @@ func InitLocal(configservice configservice.ConfigService, globalOptionsFunc app.
|
|||||||
api.InitImportLocal()
|
api.InitImportLocal()
|
||||||
api.InitExportLocal()
|
api.InitExportLocal()
|
||||||
api.InitJobLocal()
|
api.InitJobLocal()
|
||||||
|
api.InitSamlLocal()
|
||||||
|
|
||||||
root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))
|
root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))
|
||||||
|
|
||||||
|
|||||||
32
api4/saml.go
32
api4/saml.go
@@ -4,6 +4,7 @@
|
|||||||
package api4
|
package api4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime"
|
"mime"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@@ -27,6 +28,12 @@ func (api *API) InitSaml() {
|
|||||||
api.BaseRoutes.SAML.Handle("/certificate/status", api.ApiSessionRequired(getSamlCertificateStatus)).Methods("GET")
|
api.BaseRoutes.SAML.Handle("/certificate/status", api.ApiSessionRequired(getSamlCertificateStatus)).Methods("GET")
|
||||||
|
|
||||||
api.BaseRoutes.SAML.Handle("/metadatafromidp", api.ApiHandler(getSamlMetadataFromIdp)).Methods("POST")
|
api.BaseRoutes.SAML.Handle("/metadatafromidp", api.ApiHandler(getSamlMetadataFromIdp)).Methods("POST")
|
||||||
|
|
||||||
|
api.BaseRoutes.SAML.Handle("/reset_auth_data", api.ApiSessionRequired(resetAuthDataToEmail)).Methods("POST")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) InitSamlLocal() {
|
||||||
|
api.BaseRoutes.SAML.Handle("/reset_auth_data", api.ApiLocal(resetAuthDataToEmail)).Methods("POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSamlMetadata(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getSamlMetadata(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -247,3 +254,28 @@ func getSamlMetadataFromIdp(c *Context, w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
w.Write([]byte(metadata.ToJson()))
|
w.Write([]byte(metadata.ToJson()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resetAuthDataToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !c.App.SessionHasPermissionTo(*c.App.Session(), model.PERMISSION_MANAGE_SYSTEM) {
|
||||||
|
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type ResetAuthDataParams struct {
|
||||||
|
IncludeDeleted bool `json:"include_deleted"`
|
||||||
|
DryRun bool `json:"dry_run"`
|
||||||
|
SpecifiedUserIDs []string `json:"user_ids"`
|
||||||
|
}
|
||||||
|
var params *ResetAuthDataParams
|
||||||
|
jsonErr := json.NewDecoder(r.Body).Decode(¶ms)
|
||||||
|
if jsonErr != nil {
|
||||||
|
c.Err = model.NewAppError("resetAuthDataToEmail", "model.utils.decode_json.app_error", nil, jsonErr.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
numAffected, appErr := c.App.ResetSamlAuthDataToEmail(params.IncludeDeleted, params.DryRun, params.SpecifiedUserIDs)
|
||||||
|
if appErr != nil {
|
||||||
|
c.Err = appErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(map[string]interface{}{"num_affected": numAffected})
|
||||||
|
w.Write(b)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/einterfaces/mocks"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -50,3 +51,23 @@ func TestSamlCompleteCSRFPass(t *testing.T) {
|
|||||||
require.NotEqual(t, http.StatusUnauthorized, resp.StatusCode)
|
require.NotEqual(t, http.StatusUnauthorized, resp.StatusCode)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSamlResetId(t *testing.T) {
|
||||||
|
th := SetupEnterprise(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
th.App.Srv().Saml = &mocks.SamlInterface{}
|
||||||
|
|
||||||
|
user := th.BasicUser
|
||||||
|
_, appErr := th.App.UpdateUserAuth(user.Id, &model.UserAuth{
|
||||||
|
AuthData: model.NewString(model.NewId()),
|
||||||
|
AuthService: model.USER_AUTH_SERVICE_SAML,
|
||||||
|
})
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
_, resp := th.Client.ResetSamlAuthDataToEmail(false, false, nil)
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
|
||||||
|
numAffected, resp := th.SystemAdminClient.ResetSamlAuthDataToEmail(false, false, nil)
|
||||||
|
CheckOKStatus(t, resp)
|
||||||
|
require.Equal(t, int64(1), numAffected)
|
||||||
|
}
|
||||||
|
|||||||
@@ -893,6 +893,7 @@ type AppIface interface {
|
|||||||
RequestLicenseAndAckWarnMetric(warnMetricId string, isBot bool) *model.AppError
|
RequestLicenseAndAckWarnMetric(warnMetricId string, isBot bool) *model.AppError
|
||||||
ResetPasswordFromToken(userSuppliedTokenString, newPassword string) *model.AppError
|
ResetPasswordFromToken(userSuppliedTokenString, newPassword string) *model.AppError
|
||||||
ResetPermissionsSystem() *model.AppError
|
ResetPermissionsSystem() *model.AppError
|
||||||
|
ResetSamlAuthDataToEmail(includeDeleted bool, dryRun bool, userIDs []string) (numAffected int, appErr *model.AppError)
|
||||||
RestoreChannel(channel *model.Channel, userID string) (*model.Channel, *model.AppError)
|
RestoreChannel(channel *model.Channel, userID string) (*model.Channel, *model.AppError)
|
||||||
RestoreTeam(teamID string) *model.AppError
|
RestoreTeam(teamID string) *model.AppError
|
||||||
RestrictUsersGetByPermissions(userID string, options *model.UserGetOptions) (*model.UserGetOptions, *model.AppError)
|
RestrictUsersGetByPermissions(userID string, options *model.UserGetOptions) (*model.UserGetOptions, *model.AppError)
|
||||||
|
|||||||
@@ -12785,6 +12785,28 @@ func (a *OpenTracingAppLayer) ResetPermissionsSystem() *model.AppError {
|
|||||||
return resultVar0
|
return resultVar0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *OpenTracingAppLayer) ResetSamlAuthDataToEmail(includeDeleted bool, dryRun bool, userIDs []string) (numAffected int, appErr *model.AppError) {
|
||||||
|
origCtx := a.ctx
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.ResetSamlAuthDataToEmail")
|
||||||
|
|
||||||
|
a.ctx = newCtx
|
||||||
|
a.app.Srv().Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
a.app.Srv().Store.SetContext(origCtx)
|
||||||
|
a.ctx = origCtx
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
resultVar0, resultVar1 := a.app.ResetSamlAuthDataToEmail(includeDeleted, dryRun, userIDs)
|
||||||
|
|
||||||
|
if resultVar1 != nil {
|
||||||
|
span.LogFields(spanlog.Error(resultVar1))
|
||||||
|
ext.Error.Set(span, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultVar0, resultVar1
|
||||||
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) RestoreChannel(channel *model.Channel, userID string) (*model.Channel, *model.AppError) {
|
func (a *OpenTracingAppLayer) RestoreChannel(channel *model.Channel, userID string) (*model.Channel, *model.AppError) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RestoreChannel")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.RestoreChannel")
|
||||||
|
|||||||
13
app/saml.go
13
app/saml.go
@@ -282,3 +282,16 @@ func (a *App) SetSamlIdpCertificateFromMetadata(data []byte) *model.AppError {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) ResetSamlAuthDataToEmail(includeDeleted bool, dryRun bool, userIDs []string) (numAffected int, appErr *model.AppError) {
|
||||||
|
if a.Saml() == nil {
|
||||||
|
appErr = model.NewAppError("ResetAuthDataToEmail", "api.admin.saml.not_available.app_error", nil, "", http.StatusNotImplemented)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
numAffected, err := a.srv.Store.User().ResetAuthDataToEmailForUsers(model.USER_AUTH_SERVICE_SAML, userIDs, includeDeleted, dryRun)
|
||||||
|
if err != nil {
|
||||||
|
appErr = model.NewAppError("ResetAuthDataToEmail", "api.admin.saml.failure_reset_authdata_to_email.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ To use this keycloak image, we suggest you to use this configuration settings:
|
|||||||
- Override SAML bind data with AD/LDAP information: `false`
|
- Override SAML bind data with AD/LDAP information: `false`
|
||||||
- Identity Provider Metadata URL: empty string
|
- Identity Provider Metadata URL: empty string
|
||||||
- SAML SSO URL: `http://localhost:8484/auth/realms/mattermost/protocol/saml`
|
- SAML SSO URL: `http://localhost:8484/auth/realms/mattermost/protocol/saml`
|
||||||
- Identity Provider Issuer URL: h`ttp://localhost:8065/login/sso/SAML`
|
- Identity Provider Issuer URL: `http://localhost:8484/auth/realms/mattermost`
|
||||||
- Identity Provider Public Certificate: The file `keycloak_cert.pem` in this same directory
|
- Identity Provider Public Certificate: The file `keycloak.crt` in this same directory
|
||||||
- Verify Signature: `true`
|
- Verify Signature: `true`
|
||||||
- Service Provider Login URL: `http://localhost:8065/login/sso/saml`
|
- Service Provider Login URL: `http://localhost:8065/login/sso/saml`
|
||||||
|
- Service Provider Identifier: `http://localhost:8065/login/sso/saml`
|
||||||
- Enable Encryption: `false`
|
- Enable Encryption: `false`
|
||||||
- Sign Request: `false`
|
- Sign Request: `false`
|
||||||
- Email Attribute: `email`
|
- Email Attribute: `email`
|
||||||
@@ -25,12 +26,14 @@ database configuration) and restart the server:
|
|||||||
"Enable": true,
|
"Enable": true,
|
||||||
"EnableSyncWithLdap": true,
|
"EnableSyncWithLdap": true,
|
||||||
"EnableSyncWithLdapIncludeAuth": false,
|
"EnableSyncWithLdapIncludeAuth": false,
|
||||||
|
"IgnoreGuestsLdapSync": false,
|
||||||
"Verify": true,
|
"Verify": true,
|
||||||
"Encrypt": false,
|
"Encrypt": false,
|
||||||
"SignRequest": false,
|
"SignRequest": false,
|
||||||
"IdpUrl": "http://localhost:8484/auth/realms/mattermost/protocol/saml",
|
"IdpUrl": "http://localhost:8484/auth/realms/mattermost/protocol/saml",
|
||||||
"IdpDescriptorUrl": "http://localhost:8065/login/sso/saml",
|
"IdpDescriptorUrl": "http://localhost:8484/auth/realms/mattermost",
|
||||||
"IdpMetadataUrl": "",
|
"IdpMetadataUrl": "",
|
||||||
|
"ServiceProviderIdentifier": "http://localhost:8065/login/sso/saml",
|
||||||
"AssertionConsumerServiceURL": "http://localhost:8065/login/sso/saml",
|
"AssertionConsumerServiceURL": "http://localhost:8065/login/sso/saml",
|
||||||
"SignatureAlgorithm": "RSAwithSHA1",
|
"SignatureAlgorithm": "RSAwithSHA1",
|
||||||
"CanonicalAlgorithm": "Canonical1.0",
|
"CanonicalAlgorithm": "Canonical1.0",
|
||||||
|
|||||||
@@ -95,6 +95,10 @@
|
|||||||
"id": "api.admin.saml.failure_parse_idp_certificate.app_error",
|
"id": "api.admin.saml.failure_parse_idp_certificate.app_error",
|
||||||
"translation": "Failure encountered while parsing the metadata information received from the Identity Provider to a certificate."
|
"translation": "Failure encountered while parsing the metadata information received from the Identity Provider to a certificate."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.admin.saml.failure_reset_authdata_to_email.app_error",
|
||||||
|
"translation": "Failed to reset AuthData field to Email."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.admin.saml.failure_save_idp_certificate_file.app_error",
|
"id": "api.admin.saml.failure_save_idp_certificate_file.app_error",
|
||||||
"translation": "Could not save certificate file."
|
"translation": "Could not save certificate file."
|
||||||
|
|||||||
@@ -3899,6 +3899,28 @@ func (c *Client4) GetSamlMetadataFromIdp(samlMetadataURL string) (*SamlMetadataR
|
|||||||
return SamlMetadataResponseFromJson(r.Body), BuildResponse(r)
|
return SamlMetadataResponseFromJson(r.Body), BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetSamlAuthDataToEmail resets the AuthData field of SAML users to their Email.
|
||||||
|
func (c *Client4) ResetSamlAuthDataToEmail(includeDeleted bool, dryRun bool, userIDs []string) (int64, *Response) {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"include_deleted": includeDeleted,
|
||||||
|
"dry_run": dryRun,
|
||||||
|
"user_ids": userIDs,
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(params)
|
||||||
|
r, err := c.doApiPostBytes(c.GetSamlRoute()+"/reset_auth_data", b)
|
||||||
|
if err != nil {
|
||||||
|
return 0, BuildErrorResponse(r, err)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
respBody := map[string]int64{}
|
||||||
|
jsonErr := json.NewDecoder(r.Body).Decode(&respBody)
|
||||||
|
if jsonErr != nil {
|
||||||
|
appErr := NewAppError("Api4.ResetSamlAuthDataToEmail", "api.marshal_error", nil, err.Error(), http.StatusInternalServerError)
|
||||||
|
return 0, BuildErrorResponse(r, appErr)
|
||||||
|
}
|
||||||
|
return respBody["num_affected"], BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
// Compliance Section
|
// Compliance Section
|
||||||
|
|
||||||
// CreateComplianceReport creates an incoming webhook for a channel.
|
// CreateComplianceReport creates an incoming webhook for a channel.
|
||||||
|
|||||||
@@ -9788,6 +9788,24 @@ func (s *OpenTracingLayerUserStore) PromoteGuestToUser(userID string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OpenTracingLayerUserStore) ResetAuthDataToEmailForUsers(service string, userIDs []string, includeDeleted bool, dryRun bool) (int, error) {
|
||||||
|
origCtx := s.Root.Store.Context()
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.ResetAuthDataToEmailForUsers")
|
||||||
|
s.Root.Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
s.Root.Store.SetContext(origCtx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
result, err := s.UserStore.ResetAuthDataToEmailForUsers(service, userIDs, includeDeleted, dryRun)
|
||||||
|
if err != nil {
|
||||||
|
span.LogFields(spanlog.Error(err))
|
||||||
|
ext.Error.Set(span, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *OpenTracingLayerUserStore) ResetLastPictureUpdate(userID string) error {
|
func (s *OpenTracingLayerUserStore) ResetLastPictureUpdate(userID string) error {
|
||||||
origCtx := s.Root.Store.Context()
|
origCtx := s.Root.Store.Context()
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.ResetLastPictureUpdate")
|
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.ResetLastPictureUpdate")
|
||||||
|
|||||||
@@ -10606,6 +10606,26 @@ func (s *RetryLayerUserStore) PromoteGuestToUser(userID string) error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *RetryLayerUserStore) ResetAuthDataToEmailForUsers(service string, userIDs []string, includeDeleted bool, dryRun bool) (int, error) {
|
||||||
|
|
||||||
|
tries := 0
|
||||||
|
for {
|
||||||
|
result, err := s.UserStore.ResetAuthDataToEmailForUsers(service, userIDs, includeDeleted, dryRun)
|
||||||
|
if err == nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
if !isRepeatableError(err) {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
tries++
|
||||||
|
if tries >= 3 {
|
||||||
|
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (s *RetryLayerUserStore) ResetLastPictureUpdate(userID string) error {
|
func (s *RetryLayerUserStore) ResetLastPictureUpdate(userID string) error {
|
||||||
|
|
||||||
tries := 0
|
tries := 0
|
||||||
|
|||||||
@@ -308,6 +308,48 @@ func (us SqlUserStore) UpdateAuthData(userId string, service string, authData *s
|
|||||||
return userId, nil
|
return userId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetAuthDataToEmailForUsers resets the AuthData of users whose AuthService
|
||||||
|
// is |service| to their Email. If userIDs is non-empty, only the users whose
|
||||||
|
// IDs are in userIDs will be affected. If dryRun is true, only the number
|
||||||
|
// of users who *would* be affected is returned; otherwise, the number of
|
||||||
|
// users who actually were affected is returned.
|
||||||
|
func (us SqlUserStore) ResetAuthDataToEmailForUsers(service string, userIDs []string, includeDeleted bool, dryRun bool) (int, error) {
|
||||||
|
whereEquals := sq.Eq{"AuthService": service}
|
||||||
|
if len(userIDs) > 0 {
|
||||||
|
whereEquals["Id"] = userIDs
|
||||||
|
}
|
||||||
|
if !includeDeleted {
|
||||||
|
whereEquals["DeleteAt"] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if dryRun {
|
||||||
|
builder := us.getQueryBuilder().
|
||||||
|
Select("COUNT(*)").
|
||||||
|
From("Users").
|
||||||
|
Where(whereEquals)
|
||||||
|
query, args, err := builder.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "select_count_users_tosql")
|
||||||
|
}
|
||||||
|
numAffected, err := us.GetReplica().SelectInt(query, args...)
|
||||||
|
return int(numAffected), err
|
||||||
|
}
|
||||||
|
builder := us.getQueryBuilder().
|
||||||
|
Update("Users").
|
||||||
|
Set("AuthData", sq.Expr("Email")).
|
||||||
|
Where(whereEquals)
|
||||||
|
query, args, err := builder.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "update_users_tosql")
|
||||||
|
}
|
||||||
|
result, err := us.GetMaster().Exec(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "failed to update users' AuthData")
|
||||||
|
}
|
||||||
|
numAffected, err := result.RowsAffected()
|
||||||
|
return int(numAffected), err
|
||||||
|
}
|
||||||
|
|
||||||
func (us SqlUserStore) UpdateMfaSecret(userId, secret string) error {
|
func (us SqlUserStore) UpdateMfaSecret(userId, secret string) error {
|
||||||
updateAt := model.GetMillis()
|
updateAt := model.GetMillis()
|
||||||
|
|
||||||
|
|||||||
@@ -328,6 +328,7 @@ type UserStore interface {
|
|||||||
UpdatePassword(userID, newPassword string) error
|
UpdatePassword(userID, newPassword string) error
|
||||||
UpdateUpdateAt(userID string) (int64, error)
|
UpdateUpdateAt(userID string) (int64, error)
|
||||||
UpdateAuthData(userID string, service string, authData *string, email string, resetMfa bool) (string, error)
|
UpdateAuthData(userID string, service string, authData *string, email string, resetMfa bool) (string, error)
|
||||||
|
ResetAuthDataToEmailForUsers(service string, userIDs []string, includeDeleted bool, dryRun bool) (int, error)
|
||||||
UpdateMfaSecret(userID, secret string) error
|
UpdateMfaSecret(userID, secret string) error
|
||||||
UpdateMfaActive(userID string, active bool) error
|
UpdateMfaActive(userID string, active bool) error
|
||||||
Get(ctx context.Context, id string) (*model.User, error)
|
Get(ctx context.Context, id string) (*model.User, error)
|
||||||
|
|||||||
@@ -1066,6 +1066,27 @@ func (_m *UserStore) PromoteGuestToUser(userID string) error {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetAuthDataToEmailForUsers provides a mock function with given fields: service, userIDs, includeDeleted, dryRun
|
||||||
|
func (_m *UserStore) ResetAuthDataToEmailForUsers(service string, userIDs []string, includeDeleted bool, dryRun bool) (int, error) {
|
||||||
|
ret := _m.Called(service, userIDs, includeDeleted, dryRun)
|
||||||
|
|
||||||
|
var r0 int
|
||||||
|
if rf, ok := ret.Get(0).(func(string, []string, bool, bool) int); ok {
|
||||||
|
r0 = rf(service, userIDs, includeDeleted, dryRun)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(string, []string, bool, bool) error); ok {
|
||||||
|
r1 = rf(service, userIDs, includeDeleted, dryRun)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// ResetLastPictureUpdate provides a mock function with given fields: userID
|
// ResetLastPictureUpdate provides a mock function with given fields: userID
|
||||||
func (_m *UserStore) ResetLastPictureUpdate(userID string) error {
|
func (_m *UserStore) ResetLastPictureUpdate(userID string) error {
|
||||||
ret := _m.Called(userID)
|
ret := _m.Called(userID)
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ func TestUserStore(t *testing.T, ss store.Store, s SqlStore) {
|
|||||||
t.Run("UpdatePassword", func(t *testing.T) { testUserStoreUpdatePassword(t, ss) })
|
t.Run("UpdatePassword", func(t *testing.T) { testUserStoreUpdatePassword(t, ss) })
|
||||||
t.Run("Delete", func(t *testing.T) { testUserStoreDelete(t, ss) })
|
t.Run("Delete", func(t *testing.T) { testUserStoreDelete(t, ss) })
|
||||||
t.Run("UpdateAuthData", func(t *testing.T) { testUserStoreUpdateAuthData(t, ss) })
|
t.Run("UpdateAuthData", func(t *testing.T) { testUserStoreUpdateAuthData(t, ss) })
|
||||||
|
t.Run("ResetAuthDataToEmailForUsers", func(t *testing.T) { testUserStoreResetAuthDataToEmailForUsers(t, ss) })
|
||||||
t.Run("UserUnreadCount", func(t *testing.T) { testUserUnreadCount(t, ss) })
|
t.Run("UserUnreadCount", func(t *testing.T) { testUserUnreadCount(t, ss) })
|
||||||
t.Run("UpdateMfaSecret", func(t *testing.T) { testUserStoreUpdateMfaSecret(t, ss) })
|
t.Run("UpdateMfaSecret", func(t *testing.T) { testUserStoreUpdateMfaSecret(t, ss) })
|
||||||
t.Run("UpdateMfaActive", func(t *testing.T) { testUserStoreUpdateMfaActive(t, ss) })
|
t.Run("UpdateMfaActive", func(t *testing.T) { testUserStoreUpdateMfaActive(t, ss) })
|
||||||
@@ -2183,6 +2184,55 @@ func testUserStoreUpdateAuthData(t *testing.T, ss store.Store) {
|
|||||||
require.Equal(t, "", user.Password, "Password was not cleared properly")
|
require.Equal(t, "", user.Password, "Password was not cleared properly")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testUserStoreResetAuthDataToEmailForUsers(t *testing.T, ss store.Store) {
|
||||||
|
user := &model.User{}
|
||||||
|
user.Username = "user1" + model.NewId()
|
||||||
|
user.Email = MakeEmail()
|
||||||
|
_, err := ss.User().Save(user)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() { require.NoError(t, ss.User().PermanentDelete(user.Id)) }()
|
||||||
|
|
||||||
|
resetAuthDataToID := func() {
|
||||||
|
_, err = ss.User().UpdateAuthData(
|
||||||
|
user.Id, model.USER_AUTH_SERVICE_SAML, model.NewString("some-id"), "", false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
resetAuthDataToID()
|
||||||
|
|
||||||
|
// dry run
|
||||||
|
numAffected, err := ss.User().ResetAuthDataToEmailForUsers(model.USER_AUTH_SERVICE_SAML, nil, false, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, numAffected)
|
||||||
|
// real run
|
||||||
|
numAffected, err = ss.User().ResetAuthDataToEmailForUsers(model.USER_AUTH_SERVICE_SAML, nil, false, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, numAffected)
|
||||||
|
user, appErr := ss.User().Get(context.Background(), user.Id)
|
||||||
|
require.NoError(t, appErr)
|
||||||
|
require.Equal(t, *user.AuthData, user.Email)
|
||||||
|
|
||||||
|
resetAuthDataToID()
|
||||||
|
// with specific user IDs
|
||||||
|
numAffected, err = ss.User().ResetAuthDataToEmailForUsers(model.USER_AUTH_SERVICE_SAML, []string{model.NewId()}, false, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, numAffected)
|
||||||
|
numAffected, err = ss.User().ResetAuthDataToEmailForUsers(model.USER_AUTH_SERVICE_SAML, []string{user.Id}, false, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, numAffected)
|
||||||
|
|
||||||
|
// delete user
|
||||||
|
user.DeleteAt = model.GetMillisForTime(time.Now())
|
||||||
|
ss.User().Update(user, true)
|
||||||
|
// without deleted user
|
||||||
|
numAffected, err = ss.User().ResetAuthDataToEmailForUsers(model.USER_AUTH_SERVICE_SAML, nil, false, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, numAffected)
|
||||||
|
// with deleted user
|
||||||
|
numAffected, err = ss.User().ResetAuthDataToEmailForUsers(model.USER_AUTH_SERVICE_SAML, nil, true, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, numAffected)
|
||||||
|
}
|
||||||
|
|
||||||
func testUserUnreadCount(t *testing.T, ss store.Store) {
|
func testUserUnreadCount(t *testing.T, ss store.Store) {
|
||||||
teamId := model.NewId()
|
teamId := model.NewId()
|
||||||
|
|
||||||
|
|||||||
@@ -8839,6 +8839,22 @@ func (s *TimerLayerUserStore) PromoteGuestToUser(userID string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *TimerLayerUserStore) ResetAuthDataToEmailForUsers(service string, userIDs []string, includeDeleted bool, dryRun bool) (int, error) {
|
||||||
|
start := timemodule.Now()
|
||||||
|
|
||||||
|
result, err := s.UserStore.ResetAuthDataToEmailForUsers(service, userIDs, includeDeleted, dryRun)
|
||||||
|
|
||||||
|
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||||
|
if s.Root.Metrics != nil {
|
||||||
|
success := "false"
|
||||||
|
if err == nil {
|
||||||
|
success = "true"
|
||||||
|
}
|
||||||
|
s.Root.Metrics.ObserveStoreMethodDuration("UserStore.ResetAuthDataToEmailForUsers", success, elapsed)
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *TimerLayerUserStore) ResetLastPictureUpdate(userID string) error {
|
func (s *TimerLayerUserStore) ResetLastPictureUpdate(userID string) error {
|
||||||
start := timemodule.Now()
|
start := timemodule.Now()
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user