| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | package commands | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	"context" | 
					
						
							|  |  |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	"fmt" | 
					
						
							|  |  |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	"path/filepath" | 
					
						
							|  |  |  |  | 	"regexp" | 
					
						
							|  |  |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 	"unicode" | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	"github.com/fatih/color" | 
					
						
							| 
									
										
										
										
											2022-10-19 21:02:15 +08:00
										 |  |  |  | 	"github.com/urfave/cli/v2" | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/api/routing" | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/bus" | 
					
						
							|  |  |  |  | 	"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" | 
					
						
							|  |  |  |  | 	"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" | 
					
						
							| 
									
										
										
										
											2022-10-19 21:02:15 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/infra/db" | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/infra/tracing" | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/services/accesscontrol" | 
					
						
							|  |  |  |  | 	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" | 
					
						
							| 
									
										
										
										
											2024-08-02 15:32:06 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/services/accesscontrol/permreg" | 
					
						
							| 
									
										
										
										
											2024-07-02 20:45:25 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/services/authz/zanzana" | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/services/featuremgmt" | 
					
						
							|  |  |  |  | 	"github.com/grafana/grafana/pkg/services/quota/quotaimpl" | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/services/sqlstore" | 
					
						
							|  |  |  |  | 	"github.com/grafana/grafana/pkg/services/sqlstore/migrations" | 
					
						
							| 
									
										
										
										
											2022-10-24 17:59:26 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/services/sqlstore/migrator" | 
					
						
							| 
									
										
										
										
											2023-02-07 00:50:03 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest" | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	"github.com/grafana/grafana/pkg/services/user" | 
					
						
							|  |  |  |  | 	"github.com/grafana/grafana/pkg/services/user/userimpl" | 
					
						
							|  |  |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | ) | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-30 19:15:56 +08:00
										 |  |  |  | func initConflictCfg(cmd *utils.ContextCommandLine) (*setting.Cfg, tracing.Tracer, featuremgmt.FeatureToggles, error) { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	configOptions := strings.Split(cmd.String("configOverrides"), " ") | 
					
						
							|  |  |  |  | 	configOptions = append(configOptions, cmd.Args().Slice()...) | 
					
						
							|  |  |  |  | 	cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{ | 
					
						
							|  |  |  |  | 		Config:   cmd.ConfigFile(), | 
					
						
							|  |  |  |  | 		HomePath: cmd.HomePath(), | 
					
						
							|  |  |  |  | 		Args:     append(configOptions, "cfg:log.level=error"), // tailing arguments have precedence over the options string
 | 
					
						
							|  |  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-04-30 19:15:56 +08:00
										 |  |  |  | 		return nil, nil, nil, err | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-01-29 07:22:45 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	features, err := featuremgmt.ProvideManagerService(cfg) | 
					
						
							| 
									
										
										
										
											2024-04-30 19:15:56 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, nil, nil, err | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	tracingCfg, err := tracing.ProvideTracingConfig(cfg) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, nil, nil, fmt.Errorf("%v: %w", "failed to initialize tracer config", err) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	tracer, err := tracing.ProvideService(tracingCfg) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, nil, nil, fmt.Errorf("%v: %w", "failed to initialize tracer service", err) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	return cfg, tracer, features, err | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx *cli.Context) (*ConflictResolver, error) { | 
					
						
							| 
									
										
										
										
											2024-04-30 19:15:56 +08:00
										 |  |  |  | 	cfg, tracer, features, err := initConflictCfg(cmd) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, fmt.Errorf("%v: %w", "failed to load configuration", err) | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-07-08 22:00:13 +08:00
										 |  |  |  | 	s, replstore, err := getSqlStore(cfg, tracer, features) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, fmt.Errorf("%v: %w", "failed to get to sql", err) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	conflicts, err := GetUsersWithConflictingEmailsOrLogins(ctx, s) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, fmt.Errorf("%v: %w", "failed to get users with conflicting logins", err) | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-08-09 01:41:33 +08:00
										 |  |  |  | 	quotaService := quotaimpl.ProvideService(replstore, cfg) | 
					
						
							| 
									
										
										
										
											2024-04-30 19:15:56 +08:00
										 |  |  |  | 	userService, err := userimpl.ProvideService(s, nil, cfg, nil, nil, tracer, quotaService, supportbundlestest.NewFakeBundleService()) | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, fmt.Errorf("%v: %w", "failed to get user service", err) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	routing := routing.ProvideRegister() | 
					
						
							| 
									
										
										
										
											2024-05-07 21:23:11 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, fmt.Errorf("%v: %w", "failed to initialize tracer config", err) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, fmt.Errorf("%v: %w", "failed to initialize tracer service", err) | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-08-02 15:32:06 +08:00
										 |  |  |  | 	acService, err := acimpl.ProvideService(cfg, replstore, routing, nil, nil, nil, features, tracer, zanzana.NewNoopClient(), permreg.ProvidePermissionRegistry()) | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, fmt.Errorf("%v: %w", "failed to get access control", err) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	resolver := ConflictResolver{Users: conflicts, Store: s, userService: userService, ac: acService} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	resolver.BuildConflictBlocks(conflicts, f) | 
					
						
							|  |  |  |  | 	return &resolver, nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-08 22:00:13 +08:00
										 |  |  |  | func getSqlStore(cfg *setting.Cfg, tracer tracing.Tracer, features featuremgmt.FeatureToggles) (*sqlstore.SQLStore, *sqlstore.ReplStore, error) { | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	bus := bus.ProvideBus(tracer) | 
					
						
							| 
									
										
										
										
											2024-07-08 22:00:13 +08:00
										 |  |  |  | 	ss, err := sqlstore.ProvideService(cfg, features, &migrations.OSSMigrations{}, bus, tracer) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, nil, err | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	replStore, err := sqlstore.ProvideServiceWithReadReplica(ss, cfg, features, &migrations.OSSMigrations{}, bus, tracer) | 
					
						
							|  |  |  |  | 	return ss, replStore, err | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func runListConflictUsers() func(context *cli.Context) error { | 
					
						
							|  |  |  |  | 	return func(context *cli.Context) error { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		cmd := &utils.ContextCommandLine{Context: context} | 
					
						
							|  |  |  |  | 		whiteBold := color.New(color.FgWhite).Add(color.Bold) | 
					
						
							|  |  |  |  | 		r, err := initializeConflictResolver(cmd, whiteBold.Sprintf, context) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			return fmt.Errorf("%v: %w", "failed to initialize conflict resolver", err) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		if len(r.Users) < 1 { | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 			logger.Info(color.GreenString("No Conflicting users found.\n\n")) | 
					
						
							|  |  |  |  | 			return nil | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		logger.Infof("\n\nShowing conflicts\n\n") | 
					
						
							|  |  |  |  | 		logger.Infof(r.ToStringPresentation()) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		logger.Infof("\n") | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		if len(r.DiscardedBlocks) != 0 { | 
					
						
							|  |  |  |  | 			r.logDiscardedUsers() | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 		return nil | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func runGenerateConflictUsersFile() func(context *cli.Context) error { | 
					
						
							|  |  |  |  | 	return func(context *cli.Context) error { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		cmd := &utils.ContextCommandLine{Context: context} | 
					
						
							|  |  |  |  | 		r, err := initializeConflictResolver(cmd, fmt.Sprintf, context) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			return fmt.Errorf("%v: %w", "failed to initialize conflict resolver", err) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		if len(r.Users) < 1 { | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 			logger.Info(color.GreenString("No Conflicting users found.\n\n")) | 
					
						
							|  |  |  |  | 			return nil | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		tmpFile, err := generateConflictUsersFile(r) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		if err != nil { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("generating file return error: %w", err) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		logger.Infof("\n\ngenerated file\n") | 
					
						
							|  |  |  |  | 		logger.Infof("%s\n\n", tmpFile.Name()) | 
					
						
							|  |  |  |  | 		logger.Infof("once the file is edited and resolved conflicts, you can either validate or ingest the file\n\n") | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		if len(r.DiscardedBlocks) != 0 { | 
					
						
							|  |  |  |  | 			r.logDiscardedUsers() | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		return nil | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func runValidateConflictUsersFile() func(context *cli.Context) error { | 
					
						
							|  |  |  |  | 	return func(context *cli.Context) error { | 
					
						
							|  |  |  |  | 		cmd := &utils.ContextCommandLine{Context: context} | 
					
						
							|  |  |  |  | 		r, err := initializeConflictResolver(cmd, fmt.Sprintf, context) | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("%v: %w", "failed to initialize conflict resolver", err) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		// read in the file to validate
 | 
					
						
							|  |  |  |  | 		// read in the file to ingest
 | 
					
						
							|  |  |  |  | 		arg := cmd.Args().First() | 
					
						
							|  |  |  |  | 		if arg == "" { | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 			return fmt.Errorf("please specify a absolute path to file to read from") | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 		b, err := os.ReadFile(filepath.Clean(arg)) | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 			logger.Error(color.RedString("validation failed with an error")) | 
					
						
							|  |  |  |  | 			return fmt.Errorf("could not read file with error %s", err) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 		validErr := getValidConflictUsers(r, b) | 
					
						
							|  |  |  |  | 		if validErr != nil { | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 			logger.Error(color.RedString("validation failed with an error")) | 
					
						
							|  |  |  |  | 			return fmt.Errorf("could not validate file with error:\n%s", validErr) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 		logger.Info(color.GreenString("File validation complete.\n")) | 
					
						
							|  |  |  |  | 		logger.Info("File can be used with the `ingest-file` command.\n\n") | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		return nil | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func runIngestConflictUsersFile() func(context *cli.Context) error { | 
					
						
							|  |  |  |  | 	return func(context *cli.Context) error { | 
					
						
							|  |  |  |  | 		cmd := &utils.ContextCommandLine{Context: context} | 
					
						
							|  |  |  |  | 		r, err := initializeConflictResolver(cmd, fmt.Sprintf, context) | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("%v: %w", "failed to initialize conflict resolver", err) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		// read in the file to ingest
 | 
					
						
							|  |  |  |  | 		arg := cmd.Args().First() | 
					
						
							|  |  |  |  | 		if arg == "" { | 
					
						
							|  |  |  |  | 			return errors.New("please specify a absolute path to file to read from") | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		b, err := os.ReadFile(filepath.Clean(arg)) | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("could not read file with error %e", err) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		validErr := getValidConflictUsers(r, b) | 
					
						
							|  |  |  |  | 		if validErr != nil { | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 			return fmt.Errorf("could not validate file with error:\n%s", validErr) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 		// should we rebuild blocks here?
 | 
					
						
							|  |  |  |  | 		// kind of a weird thing maybe?
 | 
					
						
							|  |  |  |  | 		if len(r.ValidUsers) == 0 { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("no users") | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		r.showChanges() | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 		if !confirm("\n\nWe encourage users to create a db backup before running this command. \n Proceed with operation") { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			return fmt.Errorf("user cancelled") | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		err = r.MergeConflictingUsers(context.Context) | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("not able to merge with %e", err) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		logger.Info("\n\nconflicts resolved.\n") | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		return nil | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | func getDocumentationForFile() string { | 
					
						
							|  |  |  |  | 	return `# Conflicts File | 
					
						
							|  |  |  |  | # This file is generated by the grafana-cli command ` + color.CyanString("grafana-cli admin user-manager conflicts generate-file") + `. | 
					
						
							|  |  |  |  | # | 
					
						
							|  |  |  |  | # Commands: | 
					
						
							|  |  |  |  | # +, keep <user> = keep user | 
					
						
							|  |  |  |  | # -, delete <user> = delete user | 
					
						
							|  |  |  |  | # | 
					
						
							|  |  |  |  | # The fields conflict_email and conflict_login | 
					
						
							|  |  |  |  | # indicate that we see a conflict in email and/or login with another user. | 
					
						
							|  |  |  |  | # Both these fields can be true. | 
					
						
							|  |  |  |  | # | 
					
						
							|  |  |  |  | # There needs to be exactly one picked user per conflict block. | 
					
						
							|  |  |  |  | # | 
					
						
							|  |  |  |  | # The lines can be re-ordered. | 
					
						
							|  |  |  |  | # | 
					
						
							|  |  |  |  | # If you feel like you want to wait with a specific block, | 
					
						
							|  |  |  |  | # delete all lines regarding that conflict block. | 
					
						
							| 
									
										
										
										
											2022-11-17 18:24:07 +08:00
										 |  |  |  | # email - the user’s email | 
					
						
							|  |  |  |  | # login - the user’s login/username | 
					
						
							|  |  |  |  | # last_seen_at - the user’s last login | 
					
						
							|  |  |  |  | # auth_module - if the user was created/signed in using an authentication provider | 
					
						
							|  |  |  |  | # conflict_email - a boolean if we consider the email to be a conflict | 
					
						
							|  |  |  |  | # conflict_login - a boolean if we consider the login to be a conflict | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | # | 
					
						
							|  |  |  |  | ` | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | func generateConflictUsersFile(r *ConflictResolver) (*os.File, error) { | 
					
						
							|  |  |  |  | 	tmpFile, err := os.CreateTemp(os.TempDir(), "conflicting_user_*.diff") | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return nil, err | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-05-26 18:08:50 +08:00
										 |  |  |  | 	if _, err := tmpFile.WriteString(getDocumentationForFile()); err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		return nil, err | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-05-26 18:08:50 +08:00
										 |  |  |  | 	if _, err := tmpFile.WriteString(r.ToStringPresentation()); err != nil { | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		return nil, err | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	return tmpFile, nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | func getValidConflictUsers(r *ConflictResolver, b []byte) error { | 
					
						
							|  |  |  |  | 	newConflicts := make(ConflictingUsers, 0) | 
					
						
							|  |  |  |  | 	// need to verify that id or email exists
 | 
					
						
							|  |  |  |  | 	previouslySeenIds := map[string]bool{} | 
					
						
							|  |  |  |  | 	previouslySeenEmails := map[string]bool{} | 
					
						
							| 
									
										
										
										
											2022-10-03 18:24:26 +08:00
										 |  |  |  | 	previouslySeenLogins := map[string]bool{} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	for _, users := range r.Blocks { | 
					
						
							|  |  |  |  | 		for _, u := range users { | 
					
						
							|  |  |  |  | 			previouslySeenIds[strings.ToLower(u.ID)] = true | 
					
						
							|  |  |  |  | 			previouslySeenEmails[strings.ToLower(u.Email)] = true | 
					
						
							| 
									
										
										
										
											2022-10-03 18:24:26 +08:00
										 |  |  |  | 			previouslySeenLogins[strings.ToLower(u.Login)] = true | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	// tested in https://regex101.com/r/una3zC/1
 | 
					
						
							|  |  |  |  | 	diffPattern := `^[+-]` | 
					
						
							|  |  |  |  | 	// compiling since in a loop
 | 
					
						
							|  |  |  |  | 	matchingExpression, err := regexp.Compile(diffPattern) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		return fmt.Errorf("unable to compile regex %s: %w", diffPattern, err) | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 	counterKeepUsersForBlock := map[string]int{} | 
					
						
							|  |  |  |  | 	counterDeleteUsersForBlock := map[string]int{} | 
					
						
							|  |  |  |  | 	currentBlock := "" | 
					
						
							|  |  |  |  | 	for rowNumber, row := range strings.Split(string(b), "\n") { | 
					
						
							|  |  |  |  | 		// end of file
 | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		if row == "" { | 
					
						
							|  |  |  |  | 			break | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		// if the row starts with a #, it is a comment
 | 
					
						
							|  |  |  |  | 		if row[0] == '#' { | 
					
						
							|  |  |  |  | 			continue | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-26 18:08:50 +08:00
										 |  |  |  | 		entryRow := matchingExpression.MatchString(row) | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 		// not an entry row -> is a conflict block row
 | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		if !entryRow { | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 			// check for malformed row
 | 
					
						
							|  |  |  |  | 			// rows should be of the form
 | 
					
						
							|  |  |  |  | 			// conflict: <conflict>
 | 
					
						
							|  |  |  |  | 			// or
 | 
					
						
							|  |  |  |  | 			// + id: <id>
 | 
					
						
							|  |  |  |  | 			// - id: <id>
 | 
					
						
							|  |  |  |  | 			if (row[0] != '-') && (row[0] != '+') && (row[0] != 'c') { | 
					
						
							|  |  |  |  | 				return fmt.Errorf("invalid start character (expected '+,-') found %c for row number %d", row[0], rowNumber+1) | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 			// is a conflict block row
 | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			// conflict: hej
 | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 			currentBlock = row | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			continue | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 		// need to track how many keep users we have for a block
 | 
					
						
							|  |  |  |  | 		if _, ok := counterKeepUsersForBlock[currentBlock]; !ok { | 
					
						
							|  |  |  |  | 			counterKeepUsersForBlock[currentBlock] = 0 | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		if _, ok := counterDeleteUsersForBlock[currentBlock]; !ok { | 
					
						
							|  |  |  |  | 			counterDeleteUsersForBlock[currentBlock] = 0 | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		if row[0] == '+' { | 
					
						
							|  |  |  |  | 			counterKeepUsersForBlock[currentBlock] += 1 | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		if row[0] == '-' { | 
					
						
							|  |  |  |  | 			counterDeleteUsersForBlock[currentBlock] += 1 | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		newUser := &ConflictingUser{} | 
					
						
							|  |  |  |  | 		err := newUser.Marshal(row) | 
					
						
							|  |  |  |  | 		if err != nil { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("could not parse the content of the file with error %e", err) | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-10-03 18:24:26 +08:00
										 |  |  |  | 		if newUser.ConflictEmail != "" && !previouslySeenEmails[strings.ToLower(newUser.Email)] { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("not valid email: %s, email not seen in previous conflicts", newUser.Email) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		if newUser.ConflictLogin != "" && !previouslySeenLogins[strings.ToLower(newUser.Login)] { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("not valid login: %s, login not seen in previous conflicts", newUser.Login) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 		// valid entry
 | 
					
						
							|  |  |  |  | 		newConflicts = append(newConflicts, *newUser) | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 	for block, count := range counterKeepUsersForBlock { | 
					
						
							|  |  |  |  | 		// check if we only have one addition for each block
 | 
					
						
							|  |  |  |  | 		if count != 1 { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("invalid number of users to keep, expected 1, got %d for block: %s", count, block) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	for block, count := range counterDeleteUsersForBlock { | 
					
						
							|  |  |  |  | 		// check if we have at least one deletion for each block
 | 
					
						
							|  |  |  |  | 		if count < 1 { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("invalid number of users to delete, should be at least 1, got %d for block %s", count, block) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	r.ValidUsers = newConflicts | 
					
						
							|  |  |  |  | 	r.BuildConflictBlocks(newConflicts, fmt.Sprintf) | 
					
						
							|  |  |  |  | 	return nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func (r *ConflictResolver) MergeConflictingUsers(ctx context.Context) error { | 
					
						
							|  |  |  |  | 	for block, users := range r.Blocks { | 
					
						
							|  |  |  |  | 		if len(users) < 2 { | 
					
						
							|  |  |  |  | 			return fmt.Errorf("not enough users to perform merge, found %d for id %s, should be at least 2", len(users), block) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		var intoUser user.User | 
					
						
							|  |  |  |  | 		var intoUserId int64 | 
					
						
							|  |  |  |  | 		var fromUserIds []int64 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		// creating a session for each block of users
 | 
					
						
							|  |  |  |  | 		// we want to rollback incase something happens during update / delete
 | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 		if err := r.Store.InTransaction(ctx, func(ctx context.Context) error { | 
					
						
							| 
									
										
										
										
											2022-09-29 22:17:33 +08:00
										 |  |  |  | 			for _, u := range users { | 
					
						
							|  |  |  |  | 				if u.Direction == "+" { | 
					
						
							|  |  |  |  | 					id, err := strconv.ParseInt(u.ID, 10, 64) | 
					
						
							|  |  |  |  | 					if err != nil { | 
					
						
							|  |  |  |  | 						return fmt.Errorf("could not convert id in +") | 
					
						
							|  |  |  |  | 					} | 
					
						
							|  |  |  |  | 					intoUserId = id | 
					
						
							|  |  |  |  | 				} else if u.Direction == "-" { | 
					
						
							|  |  |  |  | 					id, err := strconv.ParseInt(u.ID, 10, 64) | 
					
						
							|  |  |  |  | 					if err != nil { | 
					
						
							|  |  |  |  | 						return fmt.Errorf("could not convert id in -") | 
					
						
							|  |  |  |  | 					} | 
					
						
							|  |  |  |  | 					fromUserIds = append(fromUserIds, id) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-09-29 22:17:33 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 			if _, err := r.userService.GetByID(ctx, &user.GetUserByIDQuery{ID: intoUserId}); err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-29 22:17:33 +08:00
										 |  |  |  | 				return fmt.Errorf("could not find intoUser: %w", err) | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			for _, fromUserId := range fromUserIds { | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 				_, err := r.userService.GetByID(ctx, &user.GetUserByIDQuery{ID: fromUserId}) | 
					
						
							|  |  |  |  | 				if err != nil && errors.Is(err, user.ErrUserNotFound) { | 
					
						
							|  |  |  |  | 					fmt.Printf("user with id %d does not exist, skipping\n", fromUserId) | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 				if err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-29 22:17:33 +08:00
										 |  |  |  | 					return fmt.Errorf("could not find fromUser: %w", err) | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 				//  delete the user
 | 
					
						
							|  |  |  |  | 				delErr := r.userService.Delete(ctx, &user.DeleteUserCommand{UserID: fromUserId}) | 
					
						
							| 
									
										
										
										
											2022-09-29 22:17:33 +08:00
										 |  |  |  | 				if delErr != nil { | 
					
						
							|  |  |  |  | 					return fmt.Errorf("error during deletion of user: %w", delErr) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 				delACErr := r.ac.DeleteUserPermissions(ctx, 0, fromUserId) | 
					
						
							|  |  |  |  | 				if delACErr != nil { | 
					
						
							|  |  |  |  | 					return fmt.Errorf("error during deletion of user access control: %w", delACErr) | 
					
						
							|  |  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-29 22:17:33 +08:00
										 |  |  |  | 			updateMainCommand := &user.UpdateUserCommand{ | 
					
						
							|  |  |  |  | 				UserID: intoUser.ID, | 
					
						
							|  |  |  |  | 				Login:  strings.ToLower(intoUser.Login), | 
					
						
							|  |  |  |  | 				Email:  strings.ToLower(intoUser.Email), | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 			updateErr := r.userService.Update(ctx, updateMainCommand) | 
					
						
							| 
									
										
										
										
											2022-09-29 22:17:33 +08:00
										 |  |  |  | 			if updateErr != nil { | 
					
						
							|  |  |  |  | 				return fmt.Errorf("could not update user: %w", updateErr) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-09-29 22:17:33 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 			return nil | 
					
						
							|  |  |  |  | 		}); err != nil { | 
					
						
							|  |  |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	return nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | /* | 
					
						
							|  |  |  |  | hej@test.com+hej@test.com | 
					
						
							|  |  |  |  | all of the permissions, roles and ownership will be transferred to the user. | 
					
						
							|  |  |  |  | + id: 1, email: hej@test.com, login: hej@test.com | 
					
						
							|  |  |  |  | these user(s) will be deleted and their permissions transferred. | 
					
						
							|  |  |  |  | - id: 2, email: HEJ@TEST.COM, login: HEJ@TEST.COM | 
					
						
							|  |  |  |  | - id: 3, email: hej@TEST.com, login: hej@TEST.com | 
					
						
							|  |  |  |  | */ | 
					
						
							|  |  |  |  | func (r *ConflictResolver) showChanges() { | 
					
						
							|  |  |  |  | 	if len(r.ValidUsers) == 0 { | 
					
						
							|  |  |  |  | 		fmt.Println("no changes will take place as we have no valid users.") | 
					
						
							|  |  |  |  | 		return | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	var b strings.Builder | 
					
						
							|  |  |  |  | 	for block, users := range r.Blocks { | 
					
						
							|  |  |  |  | 		if _, ok := r.DiscardedBlocks[block]; ok { | 
					
						
							|  |  |  |  | 			// skip block
 | 
					
						
							|  |  |  |  | 			continue | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		// looping as we want to can get these out of order (meaning the + and -)
 | 
					
						
							|  |  |  |  | 		var mainUser ConflictingUser | 
					
						
							|  |  |  |  | 		for _, u := range users { | 
					
						
							|  |  |  |  | 			if u.Direction == "+" { | 
					
						
							|  |  |  |  | 				mainUser = u | 
					
						
							|  |  |  |  | 				break | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		b.WriteString("Keep the following user.\n") | 
					
						
							|  |  |  |  | 		b.WriteString(fmt.Sprintf("%s\n", block)) | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 		b.WriteString(color.GreenString(fmt.Sprintf("id: %s, email: %s, login: %s\n", mainUser.ID, mainUser.Email, mainUser.Login))) | 
					
						
							|  |  |  |  | 		for _, r := range fmt.Sprintf("%s%s", mainUser.Email, mainUser.Login) { | 
					
						
							|  |  |  |  | 			if unicode.IsUpper(r) { | 
					
						
							|  |  |  |  | 				b.WriteString("Will be change to:\n") | 
					
						
							|  |  |  |  | 				b.WriteString(color.GreenString(fmt.Sprintf("id: %s, email: %s, login: %s\n", mainUser.ID, strings.ToLower(mainUser.Email), strings.ToLower(mainUser.Login)))) | 
					
						
							|  |  |  |  | 				break | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		b.WriteString("\n\n") | 
					
						
							|  |  |  |  | 		b.WriteString("The following user(s) will be deleted.\n") | 
					
						
							|  |  |  |  | 		for _, user := range users { | 
					
						
							|  |  |  |  | 			if user.ID == mainUser.ID { | 
					
						
							|  |  |  |  | 				continue | 
					
						
							|  |  |  |  | 			} | 
					
						
							|  |  |  |  | 			// mergeable users
 | 
					
						
							| 
									
										
										
										
											2022-11-08 02:12:17 +08:00
										 |  |  |  | 			b.WriteString(color.RedString(fmt.Sprintf("id: %s, email: %s, login: %s\n", user.ID, user.Email, user.Login))) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 		b.WriteString("\n\n") | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	logger.Info("\n\nChanges that will take place\n\n") | 
					
						
							|  |  |  |  | 	logger.Infof(b.String()) | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | // Formatter make it possible for us to write to terminal and to a file
 | 
					
						
							|  |  |  |  | // with different formats depending on the usecase
 | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  |  | type Formatter func(format string, a ...any) string | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | func shouldDiscardBlock(seenUsersInBlock map[string]string, block string, user ConflictingUser) bool { | 
					
						
							|  |  |  |  | 	// loop through users to see if we should skip this block
 | 
					
						
							|  |  |  |  | 	// we have some more tricky scenarios where we have more than two users that can have conflicts with each other
 | 
					
						
							|  |  |  |  | 	// we have made the approach to discard any users that we have seen
 | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	if _, ok := seenUsersInBlock[user.ID]; ok { | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		// we have seen the user in different block than the current block
 | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		if seenUsersInBlock[user.ID] != block { | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 			return true | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	seenUsersInBlock[user.ID] = block | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	return false | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | // BuildConflictBlocks builds blocks of users where each block is a unique email/login
 | 
					
						
							|  |  |  |  | // NOTE: currently this function assumes that the users are in order of grouping already
 | 
					
						
							|  |  |  |  | func (r *ConflictResolver) BuildConflictBlocks(users ConflictingUsers, f Formatter) { | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	discardedBlocks := make(map[string]bool) | 
					
						
							|  |  |  |  | 	seenUsersToBlock := make(map[string]string) | 
					
						
							|  |  |  |  | 	blocks := make(map[string]ConflictingUsers) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	for _, user := range users { | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		// conflict blocks is how we identify a conflict in the user base.
 | 
					
						
							|  |  |  |  | 		var conflictBlock string | 
					
						
							| 
									
										
										
										
											2022-10-24 17:59:26 +08:00
										 |  |  |  | 		// sqlite   generates string : ""/true
 | 
					
						
							|  |  |  |  | 		// postgres generates string : false/true
 | 
					
						
							|  |  |  |  | 		if user.ConflictEmail == "false" { | 
					
						
							|  |  |  |  | 			user.ConflictEmail = "" | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		if user.ConflictLogin == "false" { | 
					
						
							|  |  |  |  | 			user.ConflictLogin = "" | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		if user.ConflictEmail != "" { | 
					
						
							|  |  |  |  | 			conflictBlock = f("conflict: %s", strings.ToLower(user.Email)) | 
					
						
							|  |  |  |  | 		} else if user.ConflictLogin != "" { | 
					
						
							|  |  |  |  | 			conflictBlock = f("conflict: %s", strings.ToLower(user.Login)) | 
					
						
							|  |  |  |  | 		} else if user.ConflictEmail != "" && user.ConflictLogin != "" { | 
					
						
							|  |  |  |  | 			// both conflicts
 | 
					
						
							|  |  |  |  | 			// should not be here unless changed in sql
 | 
					
						
							|  |  |  |  | 			conflictBlock = f("conflict: %s%s", strings.ToLower(user.Email), strings.ToLower(user.Login)) | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		// discard logic
 | 
					
						
							|  |  |  |  | 		if shouldDiscardBlock(seenUsersToBlock, conflictBlock, user) { | 
					
						
							|  |  |  |  | 			discardedBlocks[conflictBlock] = true | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 		// adding users to blocks
 | 
					
						
							|  |  |  |  | 		if _, ok := blocks[conflictBlock]; !ok { | 
					
						
							|  |  |  |  | 			blocks[conflictBlock] = []ConflictingUser{user} | 
					
						
							|  |  |  |  | 			continue | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		// skip user thats already part of the block
 | 
					
						
							|  |  |  |  | 		// since we get duplicate entries
 | 
					
						
							|  |  |  |  | 		if contains(blocks[conflictBlock], user) { | 
					
						
							|  |  |  |  | 			continue | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		blocks[conflictBlock] = append(blocks[conflictBlock], user) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	r.Blocks = blocks | 
					
						
							|  |  |  |  | 	r.DiscardedBlocks = discardedBlocks | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func contains(cu ConflictingUsers, target ConflictingUser) bool { | 
					
						
							|  |  |  |  | 	for _, u := range cu { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		if u.ID == target.ID { | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 			return true | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	return false | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func (r *ConflictResolver) logDiscardedUsers() { | 
					
						
							|  |  |  |  | 	keys := make([]string, 0, len(r.DiscardedBlocks)) | 
					
						
							|  |  |  |  | 	for block := range r.DiscardedBlocks { | 
					
						
							|  |  |  |  | 		for _, u := range r.Blocks[block] { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			keys = append(keys, u.ID) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	warn := color.YellowString("Note: We discarded some conflicts that have multiple conflicting types involved.") | 
					
						
							|  |  |  |  | 	logger.Infof(` | 
					
						
							|  |  |  |  | %s | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | users discarded with more than one conflict: | 
					
						
							|  |  |  |  | ids: %s | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | Solve conflicts and run the command again to see other conflicts. | 
					
						
							|  |  |  |  | `, warn, strings.Join(keys, ",")) | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // handling tricky cases::
 | 
					
						
							|  |  |  |  | // if we have seen a user already
 | 
					
						
							|  |  |  |  | // note the conflict of that user
 | 
					
						
							|  |  |  |  | // discard that conflict for next time that the user runs the command
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // only present one conflict per user
 | 
					
						
							|  |  |  |  | // go through each conflict email/login
 | 
					
						
							|  |  |  |  | // if any has ids that have already been seen
 | 
					
						
							|  |  |  |  | // discard that conflict
 | 
					
						
							|  |  |  |  | // make note to the user to run again after fixing these conflicts
 | 
					
						
							|  |  |  |  | func (r *ConflictResolver) ToStringPresentation() string { | 
					
						
							|  |  |  |  | 	/* | 
					
						
							|  |  |  |  | 		hej@test.com+hej@test.com | 
					
						
							|  |  |  |  | 		+ id: 1, email: hej@test.com, login: hej@test.com | 
					
						
							|  |  |  |  | 		- id: 2, email: HEJ@TEST.COM, login: HEJ@TEST.COM | 
					
						
							|  |  |  |  | 		- id: 3, email: hej@TEST.com, login: hej@TEST.com | 
					
						
							|  |  |  |  | 	*/ | 
					
						
							|  |  |  |  | 	startOfBlock := make(map[string]bool) | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	var b strings.Builder | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	for block, users := range r.Blocks { | 
					
						
							|  |  |  |  | 		if _, ok := r.DiscardedBlocks[block]; ok { | 
					
						
							|  |  |  |  | 			// skip block
 | 
					
						
							|  |  |  |  | 			continue | 
					
						
							|  |  |  |  | 		} | 
					
						
							|  |  |  |  | 		for _, user := range users { | 
					
						
							|  |  |  |  | 			if !startOfBlock[block] { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 				b.WriteString(fmt.Sprintf("%s\n", block)) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 				startOfBlock[block] = true | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 				b.WriteString(fmt.Sprintf("+ id: %s, email: %s, login: %s, last_seen_at: %s, auth_module: %s, conflict_email: %s, conflict_login: %s\n", | 
					
						
							|  |  |  |  | 					user.ID, | 
					
						
							|  |  |  |  | 					user.Email, | 
					
						
							|  |  |  |  | 					user.Login, | 
					
						
							|  |  |  |  | 					user.LastSeenAt, | 
					
						
							|  |  |  |  | 					user.AuthModule, | 
					
						
							|  |  |  |  | 					user.ConflictEmail, | 
					
						
							|  |  |  |  | 					user.ConflictLogin, | 
					
						
							|  |  |  |  | 				)) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 				continue | 
					
						
							|  |  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			// mergeable users
 | 
					
						
							|  |  |  |  | 			b.WriteString(fmt.Sprintf("- id: %s, email: %s, login: %s, last_seen_at: %s, auth_module: %s, conflict_email: %s, conflict_login: %s\n", | 
					
						
							|  |  |  |  | 				user.ID, | 
					
						
							|  |  |  |  | 				user.Email, | 
					
						
							|  |  |  |  | 				user.Login, | 
					
						
							|  |  |  |  | 				user.LastSeenAt, | 
					
						
							|  |  |  |  | 				user.AuthModule, | 
					
						
							|  |  |  |  | 				user.ConflictEmail, | 
					
						
							|  |  |  |  | 				user.ConflictLogin, | 
					
						
							|  |  |  |  | 			)) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		} | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	return b.String() | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | type ConflictResolver struct { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	Store           *sqlstore.SQLStore | 
					
						
							| 
									
										
										
										
											2022-12-01 00:19:28 +08:00
										 |  |  |  | 	userService     user.Service | 
					
						
							|  |  |  |  | 	ac              accesscontrol.Service | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	Config          *setting.Cfg | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	Users           ConflictingUsers | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	ValidUsers      ConflictingUsers | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	Blocks          map[string]ConflictingUsers | 
					
						
							|  |  |  |  | 	DiscardedBlocks map[string]bool | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | type ConflictingUser struct { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	// direction is the +/- which indicates if we should keep or delete the user
 | 
					
						
							|  |  |  |  | 	Direction     string `xorm:"direction"` | 
					
						
							|  |  |  |  | 	ID            string `xorm:"id"` | 
					
						
							|  |  |  |  | 	Email         string `xorm:"email"` | 
					
						
							|  |  |  |  | 	Login         string `xorm:"login"` | 
					
						
							|  |  |  |  | 	LastSeenAt    string `xorm:"last_seen_at"` | 
					
						
							|  |  |  |  | 	AuthModule    string `xorm:"auth_module"` | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	ConflictEmail string `xorm:"conflict_email"` | 
					
						
							|  |  |  |  | 	ConflictLogin string `xorm:"conflict_login"` | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | type ConflictingUsers []ConflictingUser | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func (c *ConflictingUser) Marshal(filerow string) error { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	// example view of the file to ingest
 | 
					
						
							|  |  |  |  | 	// +/- id: 1, email: hej, auth_module: LDAP
 | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	trimmedSpaces := strings.ReplaceAll(filerow, " ", "") | 
					
						
							|  |  |  |  | 	if trimmedSpaces[0] == '+' { | 
					
						
							|  |  |  |  | 		c.Direction = "+" | 
					
						
							|  |  |  |  | 	} else if trimmedSpaces[0] == '-' { | 
					
						
							|  |  |  |  | 		c.Direction = "-" | 
					
						
							|  |  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 		return fmt.Errorf("unable to get which operation was chosen") | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	} | 
					
						
							|  |  |  |  | 	trimmed := strings.TrimLeft(trimmedSpaces, "+-") | 
					
						
							|  |  |  |  | 	values := strings.Split(trimmed, ",") | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	if len(values) < 3 { | 
					
						
							|  |  |  |  | 		return fmt.Errorf("expected at least 3 values in entry row") | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	// expected fields
 | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	id := strings.Split(values[0], ":") | 
					
						
							|  |  |  |  | 	email := strings.Split(values[1], ":") | 
					
						
							|  |  |  |  | 	login := strings.Split(values[2], ":") | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 	c.ID = id[1] | 
					
						
							|  |  |  |  | 	c.Email = email[1] | 
					
						
							|  |  |  |  | 	c.Login = login[1] | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// why trim values, 2022-08-20:19:17:12
 | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	lastSeenAt := strings.TrimPrefix(values[3], "last_seen_at:") | 
					
						
							|  |  |  |  | 	authModule := strings.Split(values[4], ":") | 
					
						
							|  |  |  |  | 	if len(authModule) < 2 { | 
					
						
							|  |  |  |  | 		c.AuthModule = "" | 
					
						
							|  |  |  |  | 	} else { | 
					
						
							|  |  |  |  | 		c.AuthModule = authModule[1] | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	c.LastSeenAt = lastSeenAt | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	// which conflict
 | 
					
						
							|  |  |  |  | 	conflictEmail := strings.Split(values[5], ":") | 
					
						
							|  |  |  |  | 	conflictLogin := strings.Split(values[6], ":") | 
					
						
							|  |  |  |  | 	if len(conflictEmail) < 2 { | 
					
						
							|  |  |  |  | 		c.ConflictEmail = "" | 
					
						
							|  |  |  |  | 	} else { | 
					
						
							|  |  |  |  | 		c.ConflictEmail = conflictEmail[1] | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	if len(conflictLogin) < 2 { | 
					
						
							|  |  |  |  | 		c.ConflictLogin = "" | 
					
						
							|  |  |  |  | 	} else { | 
					
						
							|  |  |  |  | 		c.ConflictLogin = conflictLogin[1] | 
					
						
							|  |  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	return nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func GetUsersWithConflictingEmailsOrLogins(ctx *cli.Context, s *sqlstore.SQLStore) (ConflictingUsers, error) { | 
					
						
							|  |  |  |  | 	queryUsers := make([]ConflictingUser, 0) | 
					
						
							| 
									
										
										
										
											2022-10-19 21:02:15 +08:00
										 |  |  |  | 	outerErr := s.WithDbSession(ctx.Context, func(dbSession *db.Session) error { | 
					
						
							| 
									
										
										
										
											2022-10-24 17:59:26 +08:00
										 |  |  |  | 		var rawSQL string | 
					
						
							|  |  |  |  | 		if s.GetDialect().DriverName() == migrator.Postgres { | 
					
						
							|  |  |  |  | 			rawSQL = conflictUserEntriesSQLPostgres() | 
					
						
							|  |  |  |  | 		} else if s.GetDialect().DriverName() == migrator.SQLite { | 
					
						
							|  |  |  |  | 			rawSQL = conflictingUserEntriesSQL(s) | 
					
						
							|  |  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		err := dbSession.SQL(rawSQL).Find(&queryUsers) | 
					
						
							|  |  |  |  | 		return err | 
					
						
							|  |  |  |  | 	}) | 
					
						
							|  |  |  |  | 	if outerErr != nil { | 
					
						
							|  |  |  |  | 		return queryUsers, outerErr | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	return queryUsers, nil | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // conflictingUserEntriesSQL orders conflicting users by their user_identification
 | 
					
						
							|  |  |  |  | // sorts the users by their useridentification and ids
 | 
					
						
							|  |  |  |  | func conflictingUserEntriesSQL(s *sqlstore.SQLStore) string { | 
					
						
							|  |  |  |  | 	userDialect := db.DB.GetDialect(s).Quote("user") | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 	sqlQuery := ` | 
					
						
							|  |  |  |  | 	SELECT DISTINCT | 
					
						
							|  |  |  |  | 	u1.id, | 
					
						
							|  |  |  |  | 	u1.email, | 
					
						
							|  |  |  |  | 	u1.login, | 
					
						
							|  |  |  |  | 	u1.last_seen_at, | 
					
						
							|  |  |  |  | 	user_auth.auth_module, | 
					
						
							|  |  |  |  | 		( SELECT | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			'true' | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		FROM | 
					
						
							|  |  |  |  | 			` + userDialect + ` | 
					
						
							|  |  |  |  | 		WHERE (LOWER(u1.email) = LOWER(u2.email)) AND(u1.email != u2.email)) AS conflict_email, | 
					
						
							|  |  |  |  | 		( SELECT | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 			'true' | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | 		FROM | 
					
						
							|  |  |  |  | 			` + userDialect + ` | 
					
						
							|  |  |  |  | 		WHERE (LOWER(u1.login) = LOWER(u2.login) AND(u1.login != u2.login))) AS conflict_login | 
					
						
							|  |  |  |  | 	FROM | 
					
						
							|  |  |  |  | 		 ` + userDialect + ` AS u1, ` + userDialect + ` AS u2 | 
					
						
							|  |  |  |  | 	LEFT JOIN user_auth on user_auth.user_id = u1.id | 
					
						
							|  |  |  |  | 	WHERE (conflict_email IS NOT NULL | 
					
						
							|  |  |  |  | 		OR conflict_login IS NOT NULL) | 
					
						
							|  |  |  |  | 		AND (u1.` + notServiceAccount(s) + `) | 
					
						
							|  |  |  |  | 	ORDER BY conflict_email, conflict_login, u1.id` | 
					
						
							|  |  |  |  | 	return sqlQuery | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-24 17:59:26 +08:00
										 |  |  |  | func conflictUserEntriesSQLPostgres() string { | 
					
						
							|  |  |  |  | 	sqlQuery := ` | 
					
						
							|  |  |  |  | SELECT DISTINCT | 
					
						
							|  |  |  |  | 	u1.id, | 
					
						
							|  |  |  |  | 	u1.email, | 
					
						
							|  |  |  |  | 	u1.login, | 
					
						
							|  |  |  |  | 	u1.last_seen_at, | 
					
						
							|  |  |  |  | 	ua.auth_module, | 
					
						
							|  |  |  |  | 	((LOWER(u1.email) = LOWER(u2.email)) | 
					
						
							|  |  |  |  | 		AND(u1.email != u2.email)) AS conflict_email, | 
					
						
							|  |  |  |  | 	((LOWER(u1.login) = LOWER(u2.login)) | 
					
						
							|  |  |  |  | 		AND(u1.login != u2.login)) AS conflict_login | 
					
						
							|  |  |  |  | FROM | 
					
						
							|  |  |  |  | 	"user" AS u1, | 
					
						
							|  |  |  |  | 	"user" AS u2 | 
					
						
							|  |  |  |  | 	LEFT JOIN user_auth AS ua ON ua.user_id = u2.id | 
					
						
							|  |  |  |  | WHERE ((LOWER(u1.email) = LOWER(u2.email)) | 
					
						
							|  |  |  |  | 	AND(u1.email != u2.email)) IS TRUE | 
					
						
							|  |  |  |  | 	OR((LOWER(u1.login) = LOWER(u2.login)) | 
					
						
							|  |  |  |  | 	AND(u1.login != u2.login)) IS TRUE | 
					
						
							|  |  |  |  | 	AND(u1.is_service_account = FALSE) | 
					
						
							|  |  |  |  | ORDER BY | 
					
						
							|  |  |  |  | 	conflict_email, | 
					
						
							|  |  |  |  | 	conflict_login, | 
					
						
							|  |  |  |  | 	u1.id; | 
					
						
							|  |  |  |  | ; | 
					
						
							|  |  |  |  | 	` | 
					
						
							|  |  |  |  | 	return sqlQuery | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | func notServiceAccount(ss *sqlstore.SQLStore) string { | 
					
						
							|  |  |  |  | 	return fmt.Sprintf("is_service_account = %s", | 
					
						
							| 
									
										
										
										
											2024-04-24 16:38:40 +08:00
										 |  |  |  | 		ss.GetDialect().BooleanStr(false)) | 
					
						
							| 
									
										
										
										
											2022-08-12 21:47:31 +08:00
										 |  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-29 20:26:24 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | // confirm function asks for user input
 | 
					
						
							|  |  |  |  | // returns bool
 | 
					
						
							|  |  |  |  | func confirm(confirmPrompt string) bool { | 
					
						
							|  |  |  |  | 	var input string | 
					
						
							|  |  |  |  | 	logger.Infof("%s? [y|n]: ", confirmPrompt) | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	_, err := fmt.Scanln(&input) | 
					
						
							|  |  |  |  | 	if err != nil { | 
					
						
							|  |  |  |  | 		logger.Infof("could not parse input from user for confirmation") | 
					
						
							|  |  |  |  | 		return false | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	input = strings.ToLower(input) | 
					
						
							|  |  |  |  | 	if input == "y" || input == "yes" { | 
					
						
							|  |  |  |  | 		return true | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | 	return false | 
					
						
							|  |  |  |  | } |