From 9b40c00c9562c14c159e302e900dfce6fc4b4c81 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Tue, 16 Dec 2014 09:36:37 +0100 Subject: [PATCH] SONAR-5934 Create Java user service to create a user --- .../java/org/sonar/server/db/DbClient.java | 2 +- .../org/sonar/server/exceptions/Message.java | 31 ++ .../server/platform/ServerComponents.java | 192 +------- .../java/org/sonar/server/user/NewUser.java | 114 +++++ .../server/user/ReactivationException.java | 35 ++ .../org/sonar/server/user/UserCreator.java | 219 +++++++++ .../org/sonar/server/user/UserService.java | 17 +- .../org/sonar/server/user/db/UserDao.java | 55 +++ .../sonar/server/user/db/UserGroupDao.java | 38 ++ .../org/sonar/server/util/Validation.java | 1 + .../sonar/server/user/UserCreatorTest.java | 431 ++++++++++++++++++ .../server/user/UserServiceMediumTest.java | 45 +- .../org/sonar/server/user/db/UserDaoTest.java | 93 ++++ .../server/user/db/UserGroupDaoTest.java | 58 +++ ..._default_groups_when_reactivating_user.xml | 10 + .../fail_to_create_user_if_already_exists.xml | 6 + ...te_user_if_already_exists_but_inactive.xml | 6 + .../user/UserCreatorTest/reactivate_user.xml | 6 + .../user/db/UserDaoTest/select_by_login.xml | 8 + .../db/UserGroupDaoTest/insert-result.xml | 5 + .../app/controllers/users_controller.rb | 1 - .../org/sonar/core/persistence/MyBatis.java | 3 +- .../java/org/sonar/core/user/UserDao.java | 10 + .../java/org/sonar/core/user/UserDto.java | 52 ++- .../org/sonar/core/user/UserGroupDto.java | 45 ++ .../org/sonar/core/user/UserGroupMapper.java | 27 ++ .../java/org/sonar/core/user/UserMapper.java | 5 + .../org/sonar/core/user/UserGroupMapper.xml | 11 + .../org/sonar/core/user/UserMapper.xml | 29 +- .../resources/org/sonar/l10n/core.properties | 8 + .../java/org/sonar/core/user/UserDaoTest.java | 51 ++- .../core/user/UserDaoTest/update_user.xml | 3 + 32 files changed, 1421 insertions(+), 196 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/user/NewUser.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/user/ReactivationException.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/user/UserCreator.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/user/db/UserDao.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/user/db/UserGroupDao.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/user/UserCreatorTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/user/db/UserDaoTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/user/db/UserGroupDaoTest.java create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/associate_default_groups_when_reactivating_user.xml create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists.xml create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists_but_inactive.xml create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/reactivate_user.xml create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/db/UserDaoTest/select_by_login.xml create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/user/db/UserGroupDaoTest/insert-result.xml create mode 100644 sonar-core/src/main/java/org/sonar/core/user/UserGroupDto.java create mode 100644 sonar-core/src/main/java/org/sonar/core/user/UserGroupMapper.java create mode 100644 sonar-core/src/main/resources/org/sonar/core/user/UserGroupMapper.xml create mode 100644 sonar-core/src/test/resources/org/sonar/core/user/UserDaoTest/update_user.xml diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java b/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java index e40789d9a81..4689180ce02 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java @@ -32,7 +32,6 @@ import org.sonar.core.source.db.FileSourceDao; import org.sonar.core.technicaldebt.db.CharacteristicDao; import org.sonar.core.template.LoadedTemplateDao; import org.sonar.core.user.AuthorizationDao; -import org.sonar.core.user.UserDao; import org.sonar.server.activity.db.ActivityDao; import org.sonar.server.component.db.ComponentDao; import org.sonar.server.component.db.SnapshotDao; @@ -46,6 +45,7 @@ import org.sonar.server.measure.persistence.MetricDao; import org.sonar.server.qualityprofile.db.ActiveRuleDao; import org.sonar.server.rule.db.RuleDao; import org.sonar.server.user.db.GroupDao; +import org.sonar.server.user.db.UserDao; import java.sql.Connection; import java.sql.PreparedStatement; diff --git a/server/sonar-server/src/main/java/org/sonar/server/exceptions/Message.java b/server/sonar-server/src/main/java/org/sonar/server/exceptions/Message.java index d821b6f246a..38e6a6379f4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/exceptions/Message.java +++ b/server/sonar-server/src/main/java/org/sonar/server/exceptions/Message.java @@ -22,6 +22,8 @@ package org.sonar.server.exceptions; import com.google.common.base.Objects; import org.apache.commons.lang.StringUtils; +import java.util.Arrays; + public class Message { private final String key; @@ -44,6 +46,35 @@ public class Message { return new Message(StringUtils.defaultString(l10nKey), l10nParams); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Message message = (Message) o; + + if (!key.equals(message.key)) { + return false; + } + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(params, message.params)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = key.hashCode(); + result = 31 * result + (params != null ? Arrays.hashCode(params) : 0); + return result; + } + @Override public String toString() { return Objects.toStringHelper(this) diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index 306c3a007d1..ef0bdb16177 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -54,13 +54,7 @@ import org.sonar.core.measure.db.MeasureFilterDao; import org.sonar.core.metric.DefaultMetricFinder; import org.sonar.core.notification.DefaultNotificationManager; import org.sonar.core.permission.PermissionFacade; -import org.sonar.core.persistence.DaoUtils; -import org.sonar.core.persistence.DatabaseVersion; -import org.sonar.core.persistence.DefaultDatabase; -import org.sonar.core.persistence.MyBatis; -import org.sonar.core.persistence.PreviewDatabaseFactory; -import org.sonar.core.persistence.SemaphoreUpdater; -import org.sonar.core.persistence.SemaphoresImpl; +import org.sonar.core.persistence.*; import org.sonar.core.preview.PreviewCache; import org.sonar.core.profiling.Profiling; import org.sonar.core.purge.PurgeProfiler; @@ -86,11 +80,7 @@ import org.sonar.server.activity.index.ActivityNormalizer; import org.sonar.server.activity.ws.ActivitiesWebService; import org.sonar.server.activity.ws.ActivityMapping; import org.sonar.server.authentication.ws.AuthenticationWs; -import org.sonar.server.batch.BatchIndex; -import org.sonar.server.batch.BatchWs; -import org.sonar.server.batch.GlobalReferentialsAction; -import org.sonar.server.batch.ProjectReferentialsAction; -import org.sonar.server.batch.UploadReportAction; +import org.sonar.server.batch.*; import org.sonar.server.charts.ChartFactory; import org.sonar.server.component.ComponentCleanerService; import org.sonar.server.component.ComponentService; @@ -98,25 +88,10 @@ import org.sonar.server.component.DefaultComponentFinder; import org.sonar.server.component.DefaultRubyComponentService; import org.sonar.server.component.db.ComponentDao; import org.sonar.server.component.db.SnapshotDao; -import org.sonar.server.component.ws.ComponentAppAction; -import org.sonar.server.component.ws.ComponentsWs; -import org.sonar.server.component.ws.EventsWs; -import org.sonar.server.component.ws.ProjectsWs; -import org.sonar.server.component.ws.ResourcesWs; -import org.sonar.server.computation.AnalysisReportQueue; -import org.sonar.server.computation.AnalysisReportService; -import org.sonar.server.computation.AnalysisReportTaskCleaner; -import org.sonar.server.computation.AnalysisReportTaskLauncher; -import org.sonar.server.computation.ComputationService; +import org.sonar.server.component.ws.*; +import org.sonar.server.computation.*; import org.sonar.server.computation.db.AnalysisReportDao; -import org.sonar.server.computation.step.ComponentIndexationInDatabaseStep; -import org.sonar.server.computation.step.ComputationStepRegistry; -import org.sonar.server.computation.step.DataCleanerStep; -import org.sonar.server.computation.step.DigestReportStep; -import org.sonar.server.computation.step.IndexProjectIssuesStep; -import org.sonar.server.computation.step.InvalidatePreviewCacheStep; -import org.sonar.server.computation.step.SwitchSnapshotStep; -import org.sonar.server.computation.step.SynchronizeProjectPermissionsStep; +import org.sonar.server.computation.step.*; import org.sonar.server.computation.ws.ActiveAnalysisReportsAction; import org.sonar.server.computation.ws.AnalysisReportHistorySearchAction; import org.sonar.server.computation.ws.AnalysisReportWebService; @@ -132,14 +107,7 @@ import org.sonar.server.db.DbClient; import org.sonar.server.db.EmbeddedDatabaseFactory; import org.sonar.server.db.migrations.DatabaseMigrations; import org.sonar.server.db.migrations.DatabaseMigrator; -import org.sonar.server.debt.DebtCharacteristicsXMLImporter; -import org.sonar.server.debt.DebtModelBackup; -import org.sonar.server.debt.DebtModelLookup; -import org.sonar.server.debt.DebtModelOperations; -import org.sonar.server.debt.DebtModelPluginRepository; -import org.sonar.server.debt.DebtModelService; -import org.sonar.server.debt.DebtModelXMLExporter; -import org.sonar.server.debt.DebtRulesXMLImporter; +import org.sonar.server.debt.*; import org.sonar.server.design.FileDesignWidget; import org.sonar.server.duplication.ws.DuplicationsJsonWriter; import org.sonar.server.duplication.ws.DuplicationsParser; @@ -147,33 +115,14 @@ import org.sonar.server.duplication.ws.DuplicationsWs; import org.sonar.server.es.EsClient; import org.sonar.server.es.IndexCreator; import org.sonar.server.es.IndexRegistry; -import org.sonar.server.issue.ActionService; -import org.sonar.server.issue.AddTagsAction; -import org.sonar.server.issue.AssignAction; -import org.sonar.server.issue.CommentAction; -import org.sonar.server.issue.InternalRubyIssueService; -import org.sonar.server.issue.IssueBulkChangeService; -import org.sonar.server.issue.IssueChangelogFormatter; -import org.sonar.server.issue.IssueChangelogService; -import org.sonar.server.issue.IssueCommentService; -import org.sonar.server.issue.IssueQueryService; -import org.sonar.server.issue.IssueService; -import org.sonar.server.issue.PlanAction; -import org.sonar.server.issue.RemoveTagsAction; -import org.sonar.server.issue.ServerIssueStorage; -import org.sonar.server.issue.SetSeverityAction; -import org.sonar.server.issue.TransitionAction; +import org.sonar.server.issue.*; import org.sonar.server.issue.actionplan.ActionPlanService; import org.sonar.server.issue.actionplan.ActionPlanWs; import org.sonar.server.issue.db.IssueDao; import org.sonar.server.issue.filter.IssueFilterService; import org.sonar.server.issue.filter.IssueFilterWriter; import org.sonar.server.issue.filter.IssueFilterWs; -import org.sonar.server.issue.index.IssueAuthorizationIndexer; -import org.sonar.server.issue.index.IssueIndex; -import org.sonar.server.issue.index.IssueIndexDefinition; -import org.sonar.server.issue.index.IssueIndexer; -import org.sonar.server.issue.index.IssueNormalizer; +import org.sonar.server.issue.index.*; import org.sonar.server.issue.ws.IssueActionsWriter; import org.sonar.server.issue.ws.IssueShowAction; import org.sonar.server.issue.ws.IssuesWs; @@ -196,118 +145,34 @@ import org.sonar.server.platform.ws.L10nWs; import org.sonar.server.platform.ws.RestartHandler; import org.sonar.server.platform.ws.ServerWs; import org.sonar.server.platform.ws.SystemWs; -import org.sonar.server.plugins.InstalledPluginReferentialFactory; -import org.sonar.server.plugins.PluginDownloader; -import org.sonar.server.plugins.ServerExtensionInstaller; -import org.sonar.server.plugins.ServerPluginJarInstaller; -import org.sonar.server.plugins.ServerPluginJarsInstaller; -import org.sonar.server.plugins.ServerPluginRepository; -import org.sonar.server.plugins.UpdateCenterClient; -import org.sonar.server.plugins.UpdateCenterMatrixFactory; +import org.sonar.server.plugins.*; import org.sonar.server.properties.ProjectSettingsFactory; import org.sonar.server.qualitygate.QgateProjectFinder; import org.sonar.server.qualitygate.QualityGates; import org.sonar.server.qualitygate.RegisterQualityGates; -import org.sonar.server.qualitygate.ws.QGatesAppAction; -import org.sonar.server.qualitygate.ws.QGatesCopyAction; -import org.sonar.server.qualitygate.ws.QGatesCreateAction; -import org.sonar.server.qualitygate.ws.QGatesCreateConditionAction; -import org.sonar.server.qualitygate.ws.QGatesDeleteConditionAction; -import org.sonar.server.qualitygate.ws.QGatesDeselectAction; -import org.sonar.server.qualitygate.ws.QGatesDestroyAction; -import org.sonar.server.qualitygate.ws.QGatesListAction; -import org.sonar.server.qualitygate.ws.QGatesRenameAction; -import org.sonar.server.qualitygate.ws.QGatesSearchAction; -import org.sonar.server.qualitygate.ws.QGatesSelectAction; -import org.sonar.server.qualitygate.ws.QGatesSetAsDefaultAction; -import org.sonar.server.qualitygate.ws.QGatesShowAction; -import org.sonar.server.qualitygate.ws.QGatesUnsetDefaultAction; -import org.sonar.server.qualitygate.ws.QGatesUpdateConditionAction; -import org.sonar.server.qualitygate.ws.QGatesWs; -import org.sonar.server.qualityprofile.BuiltInProfiles; -import org.sonar.server.qualityprofile.QProfileBackuper; -import org.sonar.server.qualityprofile.QProfileCopier; -import org.sonar.server.qualityprofile.QProfileExporters; -import org.sonar.server.qualityprofile.QProfileFactory; -import org.sonar.server.qualityprofile.QProfileLoader; -import org.sonar.server.qualityprofile.QProfileLookup; -import org.sonar.server.qualityprofile.QProfileProjectLookup; -import org.sonar.server.qualityprofile.QProfileProjectOperations; -import org.sonar.server.qualityprofile.QProfileReset; -import org.sonar.server.qualityprofile.QProfileService; -import org.sonar.server.qualityprofile.QProfiles; -import org.sonar.server.qualityprofile.RegisterQualityProfiles; -import org.sonar.server.qualityprofile.RuleActivator; -import org.sonar.server.qualityprofile.RuleActivatorContextFactory; +import org.sonar.server.qualitygate.ws.*; +import org.sonar.server.qualityprofile.*; import org.sonar.server.qualityprofile.db.ActiveRuleDao; import org.sonar.server.qualityprofile.index.ActiveRuleIndex; import org.sonar.server.qualityprofile.index.ActiveRuleNormalizer; -import org.sonar.server.qualityprofile.ws.BulkRuleActivationActions; -import org.sonar.server.qualityprofile.ws.ProfilesWs; -import org.sonar.server.qualityprofile.ws.QProfileRestoreBuiltInAction; -import org.sonar.server.qualityprofile.ws.QProfilesWs; -import org.sonar.server.qualityprofile.ws.RuleActivationActions; -import org.sonar.server.rule.DefaultRuleFinder; -import org.sonar.server.rule.DeprecatedRulesDefinitionLoader; -import org.sonar.server.rule.RegisterRules; -import org.sonar.server.rule.RubyRuleService; -import org.sonar.server.rule.RuleCreator; -import org.sonar.server.rule.RuleDefinitionsLoader; -import org.sonar.server.rule.RuleDeleter; -import org.sonar.server.rule.RuleOperations; -import org.sonar.server.rule.RuleRepositories; -import org.sonar.server.rule.RuleService; -import org.sonar.server.rule.RuleUpdater; +import org.sonar.server.qualityprofile.ws.*; +import org.sonar.server.rule.*; import org.sonar.server.rule.db.RuleDao; import org.sonar.server.rule.index.RuleIndex; import org.sonar.server.rule.index.RuleNormalizer; -import org.sonar.server.rule.ws.ActiveRuleCompleter; -import org.sonar.server.rule.ws.AppAction; -import org.sonar.server.rule.ws.DeleteAction; -import org.sonar.server.rule.ws.RuleMapping; -import org.sonar.server.rule.ws.RulesWebService; -import org.sonar.server.rule.ws.SearchAction; -import org.sonar.server.rule.ws.TagsAction; -import org.sonar.server.rule.ws.UpdateAction; -import org.sonar.server.search.IndexClient; -import org.sonar.server.search.IndexQueue; -import org.sonar.server.search.IndexSynchronizer; -import org.sonar.server.search.SearchClient; -import org.sonar.server.search.SearchHealth; +import org.sonar.server.rule.ws.*; +import org.sonar.server.search.*; import org.sonar.server.source.HtmlSourceDecorator; import org.sonar.server.source.IndexSourceLinesStep; import org.sonar.server.source.SourceService; import org.sonar.server.source.index.SourceLineIndex; import org.sonar.server.source.index.SourceLineIndexDefinition; import org.sonar.server.source.index.SourceLineIndexer; -import org.sonar.server.source.ws.HashAction; -import org.sonar.server.source.ws.IndexAction; -import org.sonar.server.source.ws.LinesAction; -import org.sonar.server.source.ws.RawAction; -import org.sonar.server.source.ws.ScmAction; -import org.sonar.server.source.ws.ScmWriter; +import org.sonar.server.source.ws.*; import org.sonar.server.source.ws.ShowAction; -import org.sonar.server.source.ws.SourcesWs; -import org.sonar.server.startup.CleanPreviewAnalysisCache; -import org.sonar.server.startup.CopyRequirementsFromCharacteristicsToRules; -import org.sonar.server.startup.GeneratePluginIndex; -import org.sonar.server.startup.JdbcDriverDeployer; -import org.sonar.server.startup.LogServerId; -import org.sonar.server.startup.RegisterDashboards; -import org.sonar.server.startup.RegisterDebtModel; -import org.sonar.server.startup.RegisterMetrics; -import org.sonar.server.startup.RegisterNewMeasureFilters; -import org.sonar.server.startup.RegisterPermissionTemplates; -import org.sonar.server.startup.RegisterServletFilters; -import org.sonar.server.startup.RenameDeprecatedPropertyKeys; -import org.sonar.server.startup.ServerMetadataPersister; +import org.sonar.server.startup.*; import org.sonar.server.test.CoverageService; -import org.sonar.server.test.ws.CoverageShowAction; -import org.sonar.server.test.ws.CoverageWs; -import org.sonar.server.test.ws.TestsCoveredFilesAction; -import org.sonar.server.test.ws.TestsShowAction; -import org.sonar.server.test.ws.TestsTestCasesAction; -import org.sonar.server.test.ws.TestsWs; +import org.sonar.server.test.ws.*; import org.sonar.server.text.MacroInterpreter; import org.sonar.server.text.RubyTextService; import org.sonar.server.ui.JRubyI18n; @@ -315,26 +180,16 @@ import org.sonar.server.ui.JRubyProfiling; import org.sonar.server.ui.PageDecorations; import org.sonar.server.ui.Views; import org.sonar.server.updatecenter.ws.UpdateCenterWs; -import org.sonar.server.user.DefaultUserService; -import org.sonar.server.user.DoPrivileged; -import org.sonar.server.user.GroupMembershipFinder; -import org.sonar.server.user.GroupMembershipService; -import org.sonar.server.user.NewUserNotifier; -import org.sonar.server.user.SecurityRealmFactory; -import org.sonar.server.user.UserService; +import org.sonar.server.user.*; import org.sonar.server.user.db.GroupDao; +import org.sonar.server.user.db.UserDao; +import org.sonar.server.user.db.UserGroupDao; import org.sonar.server.user.index.UserIndexDefinition; import org.sonar.server.user.index.UserIndexer; import org.sonar.server.user.ws.FavoritesWs; import org.sonar.server.user.ws.UserPropertiesWs; import org.sonar.server.user.ws.UsersWs; -import org.sonar.server.util.BooleanTypeValidation; -import org.sonar.server.util.FloatTypeValidation; -import org.sonar.server.util.IntegerTypeValidation; -import org.sonar.server.util.StringListTypeValidation; -import org.sonar.server.util.StringTypeValidation; -import org.sonar.server.util.TextTypeValidation; -import org.sonar.server.util.TypeValidations; +import org.sonar.server.util.*; import org.sonar.server.ws.ListingWs; import org.sonar.server.ws.WebServiceEngine; @@ -397,6 +252,8 @@ class ServerComponents { // users GroupDao.class, + UserDao.class, + UserGroupDao.class, // dashboards DashboardDao.class, @@ -616,6 +473,7 @@ class ServerComponents { pico.addSingleton(UserIndexDefinition.class); pico.addSingleton(UserIndexer.class); pico.addSingleton(UserService.class); + pico.addSingleton(UserCreator.class); // groups pico.addSingleton(GroupMembershipService.class); diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/NewUser.java b/server/sonar-server/src/main/java/org/sonar/server/user/NewUser.java new file mode 100644 index 00000000000..833ecea8461 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/user/NewUser.java @@ -0,0 +1,114 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.user; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import java.util.List; + +public class NewUser { + + private String login; + private String name; + private String email; + private List scmAccounts; + + private String password; + private String passwordConfirmation; + + private boolean preventReactivation = false; + + private NewUser() { + // No direct call to this constructor + } + + public NewUser setLogin(String login) { + this.login = login; + return this; + } + + public String login() { + return login; + } + + public String name() { + return name; + } + + public NewUser setName(String name) { + this.name = name; + return this; + } + + @CheckForNull + public String email() { + return email; + } + + public NewUser setEmail(@Nullable String email) { + this.email = email; + return this; + } + + public List scmAccounts() { + return scmAccounts; + } + + public NewUser setScmAccounts(List scmAccounts) { + this.scmAccounts = scmAccounts; + return this; + } + + public String password() { + return password; + } + + public NewUser setPassword(String password) { + this.password = password; + return this; + } + + public String passwordConfirmation() { + return passwordConfirmation; + } + + public NewUser setPasswordConfirmation(String passwordConfirmation) { + this.passwordConfirmation = passwordConfirmation; + return this; + } + + public boolean isPreventReactivation() { + return preventReactivation; + } + + /** + * When true, if the user already exists in status disabled, an {@link org.sonar.server.user.ReactivationException} will be thrown + */ + public NewUser setPreventReactivation(boolean preventReactivation) { + this.preventReactivation = preventReactivation; + return this; + } + + public static NewUser create() { + return new NewUser(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ReactivationException.java b/server/sonar-server/src/main/java/org/sonar/server/user/ReactivationException.java new file mode 100644 index 00000000000..92b9fdebdba --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ReactivationException.java @@ -0,0 +1,35 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.user; + +public class ReactivationException extends RuntimeException { + + private String login; + + public ReactivationException(String message, String login) { + super(message); + this.login = login; + } + + public String login() { + return login; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserCreator.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserCreator.java new file mode 100644 index 00000000000..fa185658951 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserCreator.java @@ -0,0 +1,219 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.user; + +import com.google.common.base.Predicate; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import org.sonar.api.platform.NewUserHandler; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.text.CsvWriter; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.user.GroupDto; +import org.sonar.core.user.UserDto; +import org.sonar.core.user.UserGroupDto; +import org.sonar.server.db.DbClient; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.exceptions.Message; +import org.sonar.server.user.db.UserGroupDao; +import org.sonar.server.util.Validation; + +import javax.annotation.Nullable; + +import java.io.StringWriter; +import java.security.SecureRandom; +import java.util.List; +import java.util.Random; + +import static com.google.common.collect.Lists.newArrayList; + +public class UserCreator { + + private static final String LOGIN = "Login"; + private static final String PASSWORD_CONFIRMATION = "Password confirmation"; + private static final String PASSWORD = "Password"; + private static final String NAME = "Name"; + private static final String EMAIL = "Email"; + + private final NewUserNotifier newUserNotifier; + private final Settings settings; + private final UserGroupDao userGroupDao; + private final DbClient dbClient; + private final System2 system2; + + public UserCreator(NewUserNotifier newUserNotifier, Settings settings, UserGroupDao userGroupDao, DbClient dbClient, System2 system2) { + this.newUserNotifier = newUserNotifier; + this.settings = settings; + this.userGroupDao = userGroupDao; + this.dbClient = dbClient; + this.system2 = system2; + } + + public void create(NewUser newUser) { + validate(newUser); + + DbSession dbSession = dbClient.openSession(false); + try { + String login = newUser.login(); + UserDto existingUser = dbClient.userDao().selectNullableByLogin(dbSession, login); + if (existingUser != null) { + updateExistingUser(dbSession, newUser); + } else { + createNewUser(dbSession, newUser); + } + dbSession.commit(); + notifyNewUser(newUser); + } finally { + dbSession.close(); + } + } + + private static void validate(NewUser newUser) { + List messages = newArrayList(); + + validateLogin(newUser.login(), messages); + validateName(newUser.name(), messages); + if (newUser.email().length() >= 100) { + messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, EMAIL, 100)); + } + validatePassword(newUser, messages); + + if (!messages.isEmpty()) { + throw new BadRequestException(messages); + } + } + + private static void validateLogin(@Nullable String login, List messages) { + if (Strings.isNullOrEmpty(login)) { + messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, LOGIN)); + } else if (!login.matches("\\A\\w[\\w\\.\\-_@\\s]+\\z")) { + messages.add(Message.of("user.bad_login")); + } else if (login.length() <= 2) { + messages.add(Message.of(Validation.IS_TOO_SHORT_MESSAGE, LOGIN, 2)); + } else if (login.length() >= 255) { + messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, LOGIN, 255)); + } + } + + private static void validateName(@Nullable String name, List messages) { + if (Strings.isNullOrEmpty(name)) { + messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, NAME)); + } else if (name.length() >= 200) { + messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, NAME, 200)); + } + } + + private static void validatePassword(NewUser newUser, List messages) { + if (Strings.isNullOrEmpty(newUser.password())) { + messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, PASSWORD)); + } + if (Strings.isNullOrEmpty(newUser.passwordConfirmation())) { + messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, PASSWORD_CONFIRMATION)); + } + + if (!Strings.isNullOrEmpty(newUser.password()) && !Strings.isNullOrEmpty(newUser.passwordConfirmation()) + && !StringUtils.equals(newUser.password(), newUser.passwordConfirmation())) { + messages.add(Message.of("user.password_doesnt_match_confirmation")); + } + } + + private void createNewUser(DbSession dbSession, NewUser newUser) { + long now = system2.now(); + UserDto userDto = new UserDto() + .setLogin(newUser.login()) + .setName(newUser.name()) + .setEmail(newUser.email()) + .setActive(true) + .setScmAccounts(convertScmAccountsToCsv(newUser)) + .setCreatedAt(now) + .setUpdatedAt(now); + setEncryptedPassWord(newUser, userDto); + dbClient.userDao().insert(dbSession, userDto); + addDefaultGroup(dbSession, userDto); + } + + private void updateExistingUser(DbSession dbSession, NewUser newUser) { + String login = newUser.login(); + UserDto existingUser = dbClient.userDao().selectNullableByLogin(dbSession, login); + if (existingUser != null) { + if (!existingUser.isActive()) { + if (newUser.isPreventReactivation()) { + throw new ReactivationException(String.format("A disabled user with the login '%s' already exists", login), login); + } else { + existingUser.setActive(true); + existingUser.setUpdatedAt(system2.now()); + dbClient.userDao().update(dbSession, existingUser); + addDefaultGroup(dbSession, existingUser); + } + } else { + throw new IllegalArgumentException(String.format("A user with the login '%s' already exists", login)); + } + } + } + + private static void setEncryptedPassWord(NewUser newUser, UserDto userDto) { + Random random = new SecureRandom(); + byte[] salt = new byte[32]; + random.nextBytes(salt); + String saltHex = DigestUtils.sha1Hex(salt); + userDto.setSalt(saltHex); + userDto.setCryptedPassword(DigestUtils.sha1Hex("--" + saltHex + "--" + newUser.password() + "--")); + } + + private static String convertScmAccountsToCsv(NewUser newUser) { + int size = newUser.scmAccounts().size(); + StringWriter writer = new StringWriter(size); + CsvWriter csv = CsvWriter.of(writer); + csv.values(newUser.scmAccounts().toArray(new String[size])); + csv.close(); + return writer.toString(); + } + + private void notifyNewUser(NewUser newUser) { + newUserNotifier.onNewUser(NewUserHandler.Context.builder() + .setLogin(newUser.login()) + .setName(newUser.name()) + .setEmail(newUser.email()) + .build()); + } + + private void addDefaultGroup(DbSession dbSession, UserDto userDto) { + final String defaultGroup = settings.getString(CoreProperties.CORE_DEFAULT_GROUP); + if (defaultGroup == null) { + throw new IllegalStateException(String.format("The default group property '%s' is null", CoreProperties.CORE_DEFAULT_GROUP)); + } + List userGroups = dbClient.groupDao().findByUserLogin(dbSession, userDto.getLogin()); + if (!Iterables.any(userGroups, new Predicate() { + @Override + public boolean apply(@Nullable GroupDto input) { + return input != null && input.getKey().equals(defaultGroup); + } + })) { + GroupDto groupDto = dbClient.groupDao().getByKey(dbSession, defaultGroup); + userGroupDao.insert(dbSession, new UserGroupDto().setUserId(userDto.getId()).setGroupId(groupDto.getId())); + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserService.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserService.java index 8cf3ca10af8..e7f504f64a2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UserService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserService.java @@ -21,17 +21,32 @@ package org.sonar.server.user; import org.sonar.api.ServerComponent; +import org.sonar.core.permission.GlobalPermissions; import org.sonar.server.user.index.UserIndexer; public class UserService implements ServerComponent { private final UserIndexer userIndexer; + private final UserCreator userCreator; - public UserService(UserIndexer userIndexer) { + public UserService(UserIndexer userIndexer, UserCreator userCreator) { this.userIndexer = userIndexer; + this.userCreator = userCreator; + } + + public void create(NewUser newUser) { + checkPermission(); + userCreator.create(newUser); + userIndexer.index(); } public void index() { userIndexer.index(); } + + private void checkPermission() { + UserSession userSession = UserSession.get(); + userSession.checkLoggedIn(); + userSession.checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/db/UserDao.java b/server/sonar-server/src/main/java/org/sonar/server/user/db/UserDao.java new file mode 100644 index 00000000000..977c3c9cf44 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/user/db/UserDao.java @@ -0,0 +1,55 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.user.db; + +import org.sonar.api.utils.System2; +import org.sonar.core.persistence.DaoComponent; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.MyBatis; +import org.sonar.core.user.UserDto; +import org.sonar.core.user.UserMapper; +import org.sonar.server.exceptions.NotFoundException; + +import javax.annotation.CheckForNull; + +public class UserDao extends org.sonar.core.user.UserDao implements DaoComponent { + + public UserDao(MyBatis mybatis, System2 system2) { + super(mybatis, system2); + } + + @CheckForNull + public UserDto selectNullableByLogin(DbSession session, String login) { + return mapper(session).selectByLogin(login); + } + + public UserDto selectByLogin(DbSession session, String login) { + UserDto user = selectNullableByLogin(session, login); + if (user == null) { + throw new NotFoundException(String.format("User with login '%s' has not been found", login)); + } + return user; + } + + protected UserMapper mapper(DbSession session) { + return session.getMapper(UserMapper.class); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/db/UserGroupDao.java b/server/sonar-server/src/main/java/org/sonar/server/user/db/UserGroupDao.java new file mode 100644 index 00000000000..60e29f3c1e1 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/user/db/UserGroupDao.java @@ -0,0 +1,38 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.user.db; + +import org.sonar.core.persistence.DaoComponent; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.user.UserGroupDto; +import org.sonar.core.user.UserGroupMapper; + +public class UserGroupDao implements DaoComponent { + + public UserGroupDto insert(DbSession session, UserGroupDto dto) { + mapper(session).insert(dto); + return dto; + } + + protected UserGroupMapper mapper(DbSession session) { + return session.getMapper(UserGroupMapper.class); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/Validation.java b/server/sonar-server/src/main/java/org/sonar/server/util/Validation.java index 6d9f1f471d4..92b31ddde48 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/util/Validation.java +++ b/server/sonar-server/src/main/java/org/sonar/server/util/Validation.java @@ -25,6 +25,7 @@ import org.sonar.server.exceptions.BadRequestException; public class Validation { public static final String CANT_BE_EMPTY_MESSAGE = "errors.cant_be_empty"; + public static final String IS_TOO_SHORT_MESSAGE = "errors.is_too_short"; public static final String IS_TOO_LONG_MESSAGE = "errors.is_too_long"; public static final String IS_ALREADY_USED_MESSAGE = "errors.is_already_used"; diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserCreatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserCreatorTest.java new file mode 100644 index 00000000000..2b7ab1a6d4d --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/user/UserCreatorTest.java @@ -0,0 +1,431 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.user; + +import com.google.common.base.Strings; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import org.sonar.api.platform.NewUserHandler; +import org.sonar.api.utils.System2; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.DbTester; +import org.sonar.core.user.GroupDto; +import org.sonar.core.user.GroupMembershipDao; +import org.sonar.core.user.GroupMembershipQuery; +import org.sonar.core.user.UserDto; +import org.sonar.server.db.DbClient; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.exceptions.Message; +import org.sonar.server.user.db.GroupDao; +import org.sonar.server.user.db.UserDao; +import org.sonar.server.user.db.UserGroupDao; +import org.sonar.server.util.Validation; +import org.sonar.test.DbTests; + +import static com.google.common.collect.Lists.newArrayList; +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@Category(DbTests.class) +@RunWith(MockitoJUnitRunner.class) +public class UserCreatorTest { + + @Rule + public DbTester db = new DbTester(); + + @Mock + System2 system2; + + @Mock + NewUserNotifier newUserNotifier; + + @Captor + ArgumentCaptor newUserHandler; + + Settings settings; + UserDao userDao; + GroupDao groupDao; + GroupMembershipFinder groupMembershipFinder; + DbSession session; + + UserCreator userCreator; + + @Before + public void setUp() throws Exception { + settings = new Settings(); + session = db.myBatis().openSession(false); + userDao = new UserDao(db.myBatis(), system2); + groupDao = new GroupDao(system2); + UserGroupDao userGroupDao = new UserGroupDao(); + GroupMembershipDao groupMembershipDao = new GroupMembershipDao(db.myBatis()); + groupMembershipFinder = new GroupMembershipFinder(userDao, groupMembershipDao); + + userCreator = new UserCreator(newUserNotifier, settings, userGroupDao, new DbClient(db.database(), db.myBatis(), userDao, groupDao), system2); + } + + @After + public void tearDown() throws Exception { + session.close(); + } + + @Test + public void create_user() throws Exception { + when(system2.now()).thenReturn(1418215735482L); + createDefaultGroup(); + + userCreator.create(NewUser.create() + .setLogin("user") + .setName("User") + .setEmail("user@mail.com") + .setPassword("password") + .setPasswordConfirmation("password") + .setScmAccounts(newArrayList("u1", "u_1"))); + + UserDto dto = userDao.selectNullableByLogin(session, "user"); + assertThat(dto.getId()).isNotNull(); + assertThat(dto.getLogin()).isEqualTo("user"); + assertThat(dto.getName()).isEqualTo("User"); + assertThat(dto.getEmail()).isEqualTo("user@mail.com"); + assertThat(dto.getScmAccounts()).contains("u1,u_1"); + assertThat(dto.isActive()).isTrue(); + + assertThat(dto.getSalt()).isNotNull(); + assertThat(dto.getCryptedPassword()).isNotNull(); + assertThat(dto.getCreatedAt()).isEqualTo(1418215735482L); + assertThat(dto.getUpdatedAt()).isEqualTo(1418215735482L); + } + + @Test + public void fail_to_create_user_if_login_already_exists() throws Exception { + db.prepareDbUnit(getClass(), "fail_to_create_user_if_already_exists.xml"); + + try { + userCreator.create(NewUser.create() + .setLogin("marius") + .setName("Marius") + .setEmail("marius@mail.com") + .setPassword("password") + .setPasswordConfirmation("password")); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("A user with the login 'marius' already exists"); + } + } + + @Test + public void fail_to_create_user_if_login_already_exists_but_inactive() throws Exception { + db.prepareDbUnit(getClass(), "fail_to_create_user_if_already_exists_but_inactive.xml"); + + try { + userCreator.create(NewUser.create() + .setLogin("marius") + .setName("Marius") + .setEmail("marius@mail.com") + .setPassword("password") + .setPasswordConfirmation("password") + .setPreventReactivation(true)); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(ReactivationException.class).hasMessage("A disabled user with the login 'marius' already exists"); + } + } + + @Test + public void fail_to_create_user_with_missing_login() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin(null) + .setName("Marius") + .setEmail("marius@mail.com") + .setPassword("password") + .setPasswordConfirmation("password")); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).containsOnly(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Login")); + } + } + + @Test + public void fail_to_create_user_with_invalid_login() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin("/marius/") + .setName("Marius") + .setEmail("marius@mail.com") + .setPassword("password") + .setPasswordConfirmation("password")); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).containsOnly(Message.of("user.bad_login")); + } + } + + @Test + public void fail_to_create_user_with_too_short_login() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin("ma") + .setName("Marius") + .setEmail("marius@mail.com") + .setPassword("password") + .setPasswordConfirmation("password")); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_SHORT_MESSAGE, "Login", 2)); + } + } + + @Test + public void fail_to_create_user_with_too_long_login() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin(Strings.repeat("m", 256)) + .setName("Marius") + .setEmail("marius@mail.com") + .setPassword("password") + .setPasswordConfirmation("password")); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_LONG_MESSAGE, "Login", 255)); + } + } + + @Test + public void fail_to_create_user_with_missing_name() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin("marius") + .setName(null) + .setEmail("marius@mail.com") + .setPassword("password") + .setPasswordConfirmation("password")); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).containsOnly(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Name")); + } + } + + @Test + public void fail_to_create_user_with_too_long_name() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin("marius") + .setName(Strings.repeat("m", 201)) + .setEmail("marius@mail.com") + .setPassword("password") + .setPasswordConfirmation("password")); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_LONG_MESSAGE, "Name", 200)); + } + } + + @Test + public void fail_to_create_user_with_too_long_email() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin("marius") + .setName("Marius") + .setEmail(Strings.repeat("m", 101)) + .setPassword("password") + .setPasswordConfirmation("password")); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_LONG_MESSAGE, "Email", 100)); + } + } + + @Test + public void fail_to_create_user_with_missing_password() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin("marius") + .setName("Marius") + .setEmail("marius@mail.com") + .setPassword(null) + .setPasswordConfirmation("password")); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).containsOnly(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Password")); + } + } + + @Test + public void fail_to_create_user_with_missing_password_confirmation() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin("marius") + .setName("Marius") + .setEmail("marius@mail.com") + .setPassword("password") + .setPasswordConfirmation(null)); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).containsOnly(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Password confirmation")); + } + } + + @Test + public void fail_to_create_user_with_password_not_matching_password_confirmation() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin("marius") + .setName("Marius") + .setEmail("marius@mail.com") + .setPassword("password") + .setPasswordConfirmation("password2")); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).containsOnly(Message.of("user.password_doesnt_match_confirmation")); + } + } + + @Test + public void fail_to_create_user_with_many_errors() throws Exception { + try { + userCreator.create(NewUser.create() + .setLogin("") + .setName("") + .setEmail("marius@mail.com") + .setPassword("") + .setPasswordConfirmation("")); + fail(); + } catch (BadRequestException e) { + assertThat(e.errors().messages()).hasSize(4); + } + } + + @Test + public void notify_new_user() throws Exception { + createDefaultGroup(); + + userCreator.create(NewUser.create() + .setLogin("user") + .setName("User") + .setEmail("user@mail.com") + .setPassword("password") + .setPasswordConfirmation("password") + .setScmAccounts(newArrayList("u1", "u_1"))); + + verify(newUserNotifier).onNewUser(newUserHandler.capture()); + assertThat(newUserHandler.getValue().getLogin()).isEqualTo("user"); + assertThat(newUserHandler.getValue().getName()).isEqualTo("User"); + assertThat(newUserHandler.getValue().getEmail()).isEqualTo("user@mail.com"); + } + + @Test + public void associate_default_groups_when_creating_user() throws Exception { + createDefaultGroup(); + + userCreator.create(NewUser.create() + .setLogin("user") + .setName("User") + .setEmail("user@mail.com") + .setPassword("password") + .setPasswordConfirmation("password") + .setScmAccounts(newArrayList("u1", "u_1"))); + + GroupMembershipFinder.Membership membership = groupMembershipFinder.find(GroupMembershipQuery.builder().login("user").build()); + assertThat(membership.groups()).hasSize(1); + assertThat(membership.groups().get(0).name()).isEqualTo("sonar-users"); + assertThat(membership.groups().get(0).isMember()).isTrue(); + } + + @Test + public void fail_to_associate_default_groups_to_user_if_no_default_group() throws Exception { + settings.setProperty(CoreProperties.CORE_DEFAULT_GROUP, (String) null); + + try { + userCreator.create(NewUser.create() + .setLogin("user") + .setName("User") + .setEmail("user@mail.com") + .setPassword("password") + .setPasswordConfirmation("password") + .setScmAccounts(newArrayList("u1", "u_1"))); + } catch (Exception e) { + assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("The default group property 'sonar.defaultGroup' is null"); + } + } + + @Test + public void reactivate_user() throws Exception { + db.prepareDbUnit(getClass(), "reactivate_user.xml"); + when(system2.now()).thenReturn(1418215735486L); + createDefaultGroup(); + + userCreator.create(NewUser.create() + .setLogin("marius") + .setName("Marius2") + .setEmail("marius2@mail.com") + .setPassword("password2") + .setPasswordConfirmation("password2") + .setPreventReactivation(false)); + + UserDto dto = userDao.selectNullableByLogin(session, "marius"); + assertThat(dto.isActive()).isTrue(); + assertThat(dto.getName()).isEqualTo("Marius"); + assertThat(dto.getEmail()).isEqualTo("marius@lesbronzes.fr"); + assertThat(dto.getScmAccounts()).contains("ma,marius33"); + + assertThat(dto.getSalt()).isEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365"); + assertThat(dto.getCryptedPassword()).isEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"); + assertThat(dto.getCreatedAt()).isEqualTo(1418215735482L); + assertThat(dto.getUpdatedAt()).isEqualTo(1418215735486L); + } + + @Test + public void associate_default_groups_when_reactivating_user() throws Exception { + db.prepareDbUnit(getClass(), "associate_default_groups_when_reactivating_user.xml"); + createDefaultGroup(); + + userCreator.create(NewUser.create() + .setLogin("marius") + .setName("Marius2") + .setEmail("marius2@mail.com") + .setPassword("password2") + .setPasswordConfirmation("password2") + .setPreventReactivation(false)); + + GroupMembershipFinder.Membership membership = groupMembershipFinder.find(GroupMembershipQuery.builder().login("marius").groupSearch("sonar-users").build()); + assertThat(membership.groups()).hasSize(1); + assertThat(membership.groups().get(0).name()).isEqualTo("sonar-users"); + assertThat(membership.groups().get(0).isMember()).isTrue(); + } + + private void createDefaultGroup() { + settings.setProperty(CoreProperties.CORE_DEFAULT_GROUP, "sonar-users"); + groupDao.insert(session, new GroupDto().setName("sonar-users").setDescription("Sonar Users")); + session.commit(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserServiceMediumTest.java index 0503b1aabfb..c7a61cc6d92 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/UserServiceMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/UserServiceMediumTest.java @@ -24,14 +24,19 @@ import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; +import org.sonar.api.CoreProperties; +import org.sonar.core.permission.GlobalPermissions; import org.sonar.core.persistence.DbSession; +import org.sonar.core.user.GroupDto; import org.sonar.core.user.UserDto; import org.sonar.server.db.DbClient; import org.sonar.server.es.EsClient; +import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.tester.ServerTester; +import org.sonar.server.user.db.UserDao; import org.sonar.server.user.index.UserIndexDefinition; -import org.sonar.server.user.index.UserIndexer; +import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.Assertions.assertThat; public class UserServiceMediumTest { @@ -43,7 +48,7 @@ public class UserServiceMediumTest { DbClient dbClient; DbSession session; - UserIndexer indexer; + UserService service; @Before public void setUp() throws Exception { @@ -51,7 +56,7 @@ public class UserServiceMediumTest { dbClient = tester.get(DbClient.class); esClient = tester.get(EsClient.class); session = dbClient.openSession(false); - indexer = tester.get(UserIndexer.class); + service = tester.get(UserService.class); } @After @@ -59,6 +64,38 @@ public class UserServiceMediumTest { session.close(); } + @Test + public void create_user() throws Exception { + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + GroupDto userGroup = new GroupDto().setName(CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE); + dbClient.groupDao().insert(session, userGroup); + session.commit(); + + service.create(NewUser.create() + .setLogin("user") + .setName("User") + .setEmail("user@mail.com") + .setPassword("password") + .setPasswordConfirmation("password") + .setScmAccounts(newArrayList("u1", "u_1"))); + + assertThat(tester.get(UserDao.class).selectNullableByLogin(session, "user")).isNotNull(); + assertThat(esClient.prepareGet(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, "user").get().isExists()).isTrue(); + } + + @Test(expected = ForbiddenException.class) + public void fail_to_create_user_without_sys_admin_permission() throws Exception { + MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.DASHBOARD_SHARING); + + service.create(NewUser.create() + .setLogin("user") + .setName("User") + .setEmail("user@mail.com") + .setPassword("password") + .setPasswordConfirmation("password") + .setScmAccounts(newArrayList("u1", "u_1"))); + } + @Test public void index() throws Exception { UserDto userDto = new UserDto().setLogin("user").setEmail("user@mail.com").setCreatedAt(System.currentTimeMillis()).setUpdatedAt(System.currentTimeMillis()); @@ -66,7 +103,7 @@ public class UserServiceMediumTest { session.commit(); assertThat(esClient.prepareGet(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, "user").get().isExists()).isFalse(); - indexer.index(); + service.index(); assertThat(esClient.prepareGet(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, "user").get().isExists()).isTrue(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/db/UserDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/db/UserDaoTest.java new file mode 100644 index 00000000000..dcb7ca99dcd --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/user/db/UserDaoTest.java @@ -0,0 +1,93 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.user.db; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.DbTester; +import org.sonar.core.user.UserDto; +import org.sonar.server.exceptions.NotFoundException; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; +import static org.mockito.Mockito.mock; + +public class UserDaoTest { + + @Rule + public DbTester db = new DbTester(); + + private UserDao dao; + private DbSession session; + + @Before + public void before() throws Exception { + this.session = db.myBatis().openSession(false); + System2 system2 = mock(System2.class); + this.dao = new UserDao(db.myBatis(), system2); + } + + @After + public void after() { + this.session.close(); + } + + @Test + public void select_by_login() { + db.prepareDbUnit(getClass(), "select_by_login.xml"); + + UserDto dto = dao.selectByLogin(session, "marius"); + assertThat(dto.getId()).isEqualTo(101); + assertThat(dto.getLogin()).isEqualTo("marius"); + assertThat(dto.getName()).isEqualTo("Marius"); + assertThat(dto.getEmail()).isEqualTo("marius@lesbronzes.fr"); + assertThat(dto.isActive()).isTrue(); + assertThat(dto.getScmAccounts()).isEqualTo("ma,marius33"); + assertThat(dto.getSalt()).isEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365"); + assertThat(dto.getCryptedPassword()).isEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"); + assertThat(dto.getCreatedAt()).isEqualTo(1418215735482L); + assertThat(dto.getUpdatedAt()).isEqualTo(1418215735485L); + } + + @Test + public void select_by_login_with_unknown_login() { + try { + dao.selectByLogin(session, "unknown"); + fail(); + } catch (Exception e) { + assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("User with login 'unknown' has not been found"); + } + } + + @Test + public void select_nullable_by_login() { + db.prepareDbUnit(getClass(), "select_by_login.xml"); + + assertThat(dao.selectNullableByLogin(session, "marius")).isNotNull(); + + assertThat(dao.selectNullableByLogin(session, "unknown")).isNull(); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/db/UserGroupDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/db/UserGroupDaoTest.java new file mode 100644 index 00000000000..6c898f66894 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/user/db/UserGroupDaoTest.java @@ -0,0 +1,58 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.user.db; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.DbTester; +import org.sonar.core.user.UserGroupDto; + +public class UserGroupDaoTest { + + @Rule + public DbTester db = new DbTester(); + + private UserGroupDao dao; + private DbSession session; + + @Before + public void before() throws Exception { + this.session = db.myBatis().openSession(false); + this.dao = new UserGroupDao(); + } + + @After + public void after() { + this.session.close(); + } + + @Test + public void insert() { + UserGroupDto userGroupDto = new UserGroupDto().setUserId(1L).setGroupId(2L); + dao.insert(session, userGroupDto); + session.commit(); + + db.assertDbUnit(getClass(), "insert-result.xml", "groups_users"); + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/associate_default_groups_when_reactivating_user.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/associate_default_groups_when_reactivating_user.xml new file mode 100644 index 00000000000..d4b47674bdf --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/associate_default_groups_when_reactivating_user.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists.xml new file mode 100644 index 00000000000..3c22b1ebf61 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists_but_inactive.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists_but_inactive.xml new file mode 100644 index 00000000000..24fd96bdfc7 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists_but_inactive.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/reactivate_user.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/reactivate_user.xml new file mode 100644 index 00000000000..24fd96bdfc7 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/reactivate_user.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/db/UserDaoTest/select_by_login.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/db/UserDaoTest/select_by_login.xml new file mode 100644 index 00000000000..0b245535234 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/user/db/UserDaoTest/select_by_login.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/db/UserGroupDaoTest/insert-result.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/db/UserGroupDaoTest/insert-result.xml new file mode 100644 index 00000000000..2e5bb1ada57 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/user/db/UserGroupDaoTest/insert-result.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/users_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/users_controller.rb index 56c4d53cb1a..6b7bc5504b5 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/users_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/users_controller.rb @@ -52,7 +52,6 @@ class UsersController < ApplicationController render :text => 'ok', :status => 200 else # case user: don't exist, WITH ERRORS when create - # case user: exist and ACTIVE, whith or without errors when create @user = user user.errors.full_messages.each { |msg| @errors< 'users/create_form', :status => 400 diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java index 7785eba83c8..ea38fbd742f 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java @@ -154,6 +154,7 @@ public class MyBatis implements BatchComponent, ServerComponent { loadAlias(conf, "SchemaMigration", SchemaMigrationDto.class); loadAlias(conf, "User", UserDto.class); loadAlias(conf, "UserRole", UserRoleDto.class); + loadAlias(conf, "UserGroup", UserGroupDto.class); loadAlias(conf, "Widget", WidgetDto.class); loadAlias(conf, "WidgetProperty", WidgetPropertyDto.class); loadAlias(conf, "MeasureModel", MeasureModel.class); @@ -191,7 +192,7 @@ public class MyBatis implements BatchComponent, ServerComponent { IssueMapper.class, IssueChangeMapper.class, IssueFilterMapper.class, IssueFilterFavouriteMapper.class, LoadedTemplateMapper.class, MeasureFilterMapper.class, Migration44Mapper.class, PermissionTemplateMapper.class, PropertiesMapper.class, PurgeMapper.class, ResourceKeyUpdaterMapper.class, ResourceIndexerMapper.class, ResourceSnapshotMapper.class, RoleMapper.class, RuleMapper.class, - SchemaMigrationMapper.class, SemaphoreMapper.class, UserMapper.class, GroupMapper.class, WidgetMapper.class, WidgetPropertyMapper.class, + SchemaMigrationMapper.class, SemaphoreMapper.class, UserMapper.class, GroupMapper.class, UserGroupMapper.class, WidgetMapper.class, WidgetPropertyMapper.class, org.sonar.api.database.model.MeasureMapper.class, SnapshotDataMapper.class, FileSourceMapper.class, ActionPlanMapper.class, ActionPlanStatsMapper.class, NotificationQueueMapper.class, CharacteristicMapper.class, diff --git a/sonar-core/src/main/java/org/sonar/core/user/UserDao.java b/sonar-core/src/main/java/org/sonar/core/user/UserDao.java index 0f0ab61adb1..0cff129eb8a 100644 --- a/sonar-core/src/main/java/org/sonar/core/user/UserDao.java +++ b/sonar-core/src/main/java/org/sonar/core/user/UserDao.java @@ -117,6 +117,11 @@ public class UserDao implements BatchComponent, ServerComponent, DaoComponent { return dto; } + public UserDto update(SqlSession session, UserDto dto) { + session.getMapper(UserMapper.class).update(dto); + return dto; + } + /** * Deactivate a user and drops all his preferences. * @return false if the user does not exist, true if the existing user has been deactivated @@ -152,6 +157,8 @@ public class UserDao implements BatchComponent, ServerComponent, DaoComponent { * Search for group by name. * * @return the group, null if group not found + * + * TODO should be moved to GroupDao */ @CheckForNull public GroupDto selectGroupByName(String name, DbSession session) { @@ -159,6 +166,9 @@ public class UserDao implements BatchComponent, ServerComponent, DaoComponent { return mapper.selectGroupByName(name); } + /** + * TODO should be moved to GroupDao + */ @CheckForNull public GroupDto selectGroupByName(String name) { DbSession session = mybatis.openSession(false); diff --git a/sonar-core/src/main/java/org/sonar/core/user/UserDto.java b/sonar-core/src/main/java/org/sonar/core/user/UserDto.java index 4fc269fc0fa..14a174a8d98 100644 --- a/sonar-core/src/main/java/org/sonar/core/user/UserDto.java +++ b/sonar-core/src/main/java/org/sonar/core/user/UserDto.java @@ -19,6 +19,7 @@ */ package org.sonar.core.user; +import javax.annotation.CheckForNull; import javax.annotation.Nullable; /** @@ -29,9 +30,12 @@ public class UserDto { private String login; private String name; private String email; + private boolean active = true; + private String scmAccounts; + private String cryptedPassword; + private String salt; private Long createdAt; private Long updatedAt; - private boolean active = true; public Long getId() { return id; @@ -60,6 +64,7 @@ public class UserDto { return this; } + @CheckForNull public String getEmail() { return email; } @@ -69,6 +74,42 @@ public class UserDto { return this; } + public boolean isActive() { + return active; + } + + public UserDto setActive(boolean b) { + this.active = b; + return this; + } + + public String getScmAccounts() { + return scmAccounts; + } + + public UserDto setScmAccounts(String scmAccounts) { + this.scmAccounts = scmAccounts; + return this; + } + + public String getCryptedPassword() { + return cryptedPassword; + } + + public UserDto setCryptedPassword(String cryptedPassword) { + this.cryptedPassword = cryptedPassword; + return this; + } + + public String getSalt() { + return salt; + } + + public UserDto setSalt(String salt) { + this.salt = salt; + return this; + } + public Long getCreatedAt() { return createdAt; } @@ -87,15 +128,6 @@ public class UserDto { return this; } - public boolean isActive() { - return active; - } - - public UserDto setActive(boolean b) { - this.active = b; - return this; - } - public DefaultUser toUser() { return new DefaultUser() .setLogin(login) diff --git a/sonar-core/src/main/java/org/sonar/core/user/UserGroupDto.java b/sonar-core/src/main/java/org/sonar/core/user/UserGroupDto.java new file mode 100644 index 00000000000..99d300c0574 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/user/UserGroupDto.java @@ -0,0 +1,45 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.core.user; + +public class UserGroupDto { + + private Long userId; + private Long groupId; + + public Long getUserId() { + return userId; + } + + public UserGroupDto setUserId(Long userId) { + this.userId = userId; + return this; + } + + public Long getGroupId() { + return groupId; + } + + public UserGroupDto setGroupId(Long groupId) { + this.groupId = groupId; + return this; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/user/UserGroupMapper.java b/sonar-core/src/main/java/org/sonar/core/user/UserGroupMapper.java new file mode 100644 index 00000000000..328abc4d1b8 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/user/UserGroupMapper.java @@ -0,0 +1,27 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.core.user; + +public interface UserGroupMapper { + + void insert(UserGroupDto userGroupDto); + +} diff --git a/sonar-core/src/main/java/org/sonar/core/user/UserMapper.java b/sonar-core/src/main/java/org/sonar/core/user/UserMapper.java index 4e18104c46d..329fd09beeb 100644 --- a/sonar-core/src/main/java/org/sonar/core/user/UserMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/user/UserMapper.java @@ -28,6 +28,9 @@ import java.util.List; public interface UserMapper { + @CheckForNull + UserDto selectByLogin(String login); + @CheckForNull UserDto selectUser(long userId); @@ -46,6 +49,8 @@ public interface UserMapper { void insert(UserDto userDto); + void update(UserDto userDto); + void removeUserFromGroups(long userId); void deleteUserActiveDashboards(long userId); diff --git a/sonar-core/src/main/resources/org/sonar/core/user/UserGroupMapper.xml b/sonar-core/src/main/resources/org/sonar/core/user/UserGroupMapper.xml new file mode 100644 index 00000000000..ce48eeae261 --- /dev/null +++ b/sonar-core/src/main/resources/org/sonar/core/user/UserGroupMapper.xml @@ -0,0 +1,11 @@ + + + + + + + INSERT INTO groups_users (user_id, group_id) + VALUES (#{userId}, #{groupId}) + + + diff --git a/sonar-core/src/main/resources/org/sonar/core/user/UserMapper.xml b/sonar-core/src/main/resources/org/sonar/core/user/UserMapper.xml index 69a559d0002..588c91f0c1d 100644 --- a/sonar-core/src/main/resources/org/sonar/core/user/UserMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/user/UserMapper.xml @@ -8,12 +8,21 @@ u.login as login, u.name as name, u.email as email, + u.active as "active", + u.scm_accounts as "scmAccounts", + u.salt as "salt", + u.crypted_password as "cryptedPassword", u.created_at as "createdAt", - u.updated_at as "updatedAt", - u.active as "active" + u.updated_at as "updatedAt" - + SELECT + + FROM users u WHERE u.login=#{login} + + + -