diff options
12 files changed, 125 insertions, 82 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java b/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java index c6f7c501f22..0410b66cc90 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalGroupsProvider.java @@ -19,17 +19,20 @@ */ package org.sonar.api.security; -import com.google.common.annotations.Beta; -import org.sonar.api.ServerExtension; - import java.util.Collection; /** + * Note that prefix "do" for names of methods is reserved for future enhancements, thus should not be used in subclasses. + * * @since 2.14 + * @see SecurityRealm */ -@Beta -public interface ExternalGroupsProvider extends ServerExtension { +public abstract class ExternalGroupsProvider { - Collection<String> doGetGroups(String username); + /** + * @return list of groups associated with specified user, or null if such user doesn't exist + * @throws RuntimeException in case of unexpected error such as connection failure + */ + public abstract Collection<String> doGetGroups(String username); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalUsersProvider.java b/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalUsersProvider.java index 5e49ef8116f..00f7dfb4dac 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalUsersProvider.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/security/ExternalUsersProvider.java @@ -19,15 +19,18 @@ */ package org.sonar.api.security; -import com.google.common.annotations.Beta; -import org.sonar.api.ServerExtension; - /** + * Note that prefix "do" for names of methods is reserved for future enhancements, thus should not be used in subclasses. + * * @since 2.14 + * @see SecurityRealm */ -@Beta -public interface ExternalUsersProvider extends ServerExtension { +public abstract class ExternalUsersProvider { - UserDetails doGetUserDetails(String username); + /** + * @return details for specified user, or null if such user doesn't exist + * @throws RuntimeException in case of unexpected error such as connection failure + */ + public abstract UserDetails doGetUserDetails(String username); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/security/LoginPasswordAuthenticator.java b/sonar-plugin-api/src/main/java/org/sonar/api/security/LoginPasswordAuthenticator.java index 7160746c8e5..8ca6f97864f 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/security/LoginPasswordAuthenticator.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/security/LoginPasswordAuthenticator.java @@ -23,14 +23,21 @@ import org.sonar.api.ServerExtension; /** * @since 1.12 + * @see SecurityRealm */ public interface LoginPasswordAuthenticator extends ServerExtension { /** - * Throws a runtime exception if the authenticator can not be initialized at sonar server startup, eg. if the connection to LDAP server is refused. + * @throws RuntimeException if the authenticator can not be initialized at sonar server startup, eg. if the connection to LDAP server is refused + * @deprecated in 2.14, but was left for backward compatibility - when this authenticator is not a part of {@link SecurityRealm}, otherwise has no effect and not invoked */ + @Deprecated void init(); - boolean authenticate(String login, String password); - -}
\ No newline at end of file + /** + * @return true, if user was successfully authenticated with specified username and password, false otherwise + * @throws RuntimeException in case of unexpected error such as connection failure + */ + boolean authenticate(String username, String password); + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/security/Realm.java b/sonar-plugin-api/src/main/java/org/sonar/api/security/SecurityRealm.java index 5ef15b9238c..f21d4d26946 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/security/Realm.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/security/SecurityRealm.java @@ -19,14 +19,12 @@ */ package org.sonar.api.security; -import com.google.common.annotations.Beta; import org.sonar.api.ServerExtension; /** * @since 2.14 */ -@Beta -public abstract class Realm implements ServerExtension { +public abstract class SecurityRealm implements ServerExtension { /** * @return unique name of this realm, e.g. "LDAP" @@ -35,13 +33,16 @@ public abstract class Realm implements ServerExtension { return getClass().getSimpleName(); } + /** + * Invoked during server startup and can be used to initialize internal state. + */ public void init() { } /** * @return {@link LoginPasswordAuthenticator} associated with this realm, never null */ - public abstract LoginPasswordAuthenticator getAuthenticator(); + public abstract LoginPasswordAuthenticator getLoginPasswordAuthenticator(); /** * @return {@link ExternalUsersProvider} associated with this realm, null if not supported diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/security/UserDetails.java b/sonar-plugin-api/src/main/java/org/sonar/api/security/UserDetails.java index e5a4db4b40f..e3519866a13 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/security/UserDetails.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/security/UserDetails.java @@ -19,18 +19,18 @@ */ package org.sonar.api.security; -import com.google.common.annotations.Beta; +import com.google.common.base.Objects; /** * This class is not intended to be subclassed by clients. * * @since 2.14 + * @see ExternalUsersProvider */ -@Beta -public class UserDetails { +public final class UserDetails { - private String name; - private String email; + private String name = ""; + private String email = ""; public UserDetails() { } @@ -51,4 +51,11 @@ public class UserDetails { return name; } + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("name", name) + .add("email", email) + .toString(); + } } diff --git a/sonar-server/src/dev/postgresql/conf/logback.xml b/sonar-server/src/dev/postgresql/conf/logback.xml index 7605ec46890..3c2b26eb555 100644 --- a/sonar-server/src/dev/postgresql/conf/logback.xml +++ b/sonar-server/src/dev/postgresql/conf/logback.xml @@ -1,5 +1,4 @@ <?xml version="1.0" encoding="UTF-8" ?> - <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> @@ -9,34 +8,33 @@ </pattern> </encoder> </appender> - + <logger name="org.hibernate.SQL"> - <level value="ERROR"/> + <level value="ERROR"/> </logger> - + <logger name="org.hibernate.cache.ReadWriteCache"> <!-- removing "An item was expired by the cache while it was locked (increase your cache timeout)" msg --> - <level value="ERROR"/> + <level value="ERROR"/> </logger> - + <logger name="org.hibernate.cache.EhCacheProvider"> <!-- removing "org.hibernate.cache.EhCacheProvider - Could not find configuratio)" message --> - <level value="ERROR"/> + <level value="ERROR"/> </logger> - + <logger name="org.sonar.INFO"> - <level value="INFO"/> + <level value="INFO"/> </logger> - + <logger name="net.sf.ehcache"> - <level value="INFO"/> + <level value="INFO"/> </logger> - - + <logger name="rails.sonar"> - <level value="INFO"/> + <level value="INFO"/> </logger> - + <root> <level value="INFO"/> <appender-ref ref="STDOUT"/> diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index 17049e551b1..f14f10043f7 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -68,7 +68,7 @@ import org.sonar.server.rules.RulesConsole; import org.sonar.server.startup.*; import org.sonar.server.ui.CodeColorizers; import org.sonar.server.ui.JRubyI18n; -import org.sonar.server.ui.RealmFactory; +import org.sonar.server.ui.SecurityRealmFactory; import org.sonar.server.ui.Views; import javax.servlet.ServletContext; @@ -192,7 +192,7 @@ public final class Platform { servicesContainer.addComponent(DefaultRulesManager.class, false); servicesContainer.addComponent(ProfilesManager.class, false); servicesContainer.addComponent(Backup.class, false); - servicesContainer.addSingleton(RealmFactory.class); + servicesContainer.addSingleton(SecurityRealmFactory.class); servicesContainer.addSingleton(ServerLifecycleNotifier.class); servicesContainer.addSingleton(AnnotationProfileParser.class); servicesContainer.addSingleton(XMLProfileParser.class); diff --git a/sonar-server/src/main/java/org/sonar/server/ui/CompatibilityRealm.java b/sonar-server/src/main/java/org/sonar/server/ui/CompatibilityRealm.java index 45d72239373..77ef6b6140f 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/CompatibilityRealm.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/CompatibilityRealm.java @@ -21,14 +21,14 @@ package org.sonar.server.ui; import org.sonar.api.CoreProperties; import org.sonar.api.security.LoginPasswordAuthenticator; -import org.sonar.api.security.Realm; +import org.sonar.api.security.SecurityRealm; /** * Provides backward compatibility for {@link CoreProperties#CORE_AUTHENTICATOR_CLASS}. * * @since 2.14 */ -class CompatibilityRealm extends Realm { +class CompatibilityRealm extends SecurityRealm { private final LoginPasswordAuthenticator authenticator; public CompatibilityRealm(LoginPasswordAuthenticator authenticator) { @@ -46,7 +46,7 @@ class CompatibilityRealm extends Realm { } @Override - public LoginPasswordAuthenticator getAuthenticator() { + public LoginPasswordAuthenticator getLoginPasswordAuthenticator() { return authenticator; } } diff --git a/sonar-server/src/main/java/org/sonar/server/ui/RealmFactory.java b/sonar-server/src/main/java/org/sonar/server/ui/SecurityRealmFactory.java index e3187b5929c..2761f575399 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/RealmFactory.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/SecurityRealmFactory.java @@ -26,26 +26,26 @@ import org.sonar.api.CoreProperties; import org.sonar.api.ServerComponent; import org.sonar.api.config.Settings; import org.sonar.api.security.LoginPasswordAuthenticator; -import org.sonar.api.security.Realm; +import org.sonar.api.security.SecurityRealm; /** * @since 2.14 */ -public class RealmFactory implements ServerComponent { +public class SecurityRealmFactory implements ServerComponent { private static final Logger INFO = LoggerFactory.getLogger("org.sonar.INFO"); - private static final Logger LOG = LoggerFactory.getLogger(RealmFactory.class); + private static final Logger LOG = LoggerFactory.getLogger(SecurityRealmFactory.class); private final boolean ignoreStartupFailure; - private final Realm realm; + private final SecurityRealm realm; static final String REALM_PROPERTY = "sonar.security.realm"; - public RealmFactory(Settings settings, Realm[] realms, LoginPasswordAuthenticator[] authenticators) { + public SecurityRealmFactory(Settings settings, SecurityRealm[] realms, LoginPasswordAuthenticator[] authenticators) { ignoreStartupFailure = settings.getBoolean(CoreProperties.CORE_AUTHENTICATOR_IGNORE_STARTUP_FAILURE); String realmName = settings.getString(REALM_PROPERTY); String className = settings.getString(CoreProperties.CORE_AUTHENTICATOR_CLASS); - Realm selectedRealm = null; + SecurityRealm selectedRealm = null; if (!StringUtils.isEmpty(realmName)) { selectedRealm = selectRealm(realms, realmName); if (selectedRealm == null) { @@ -65,15 +65,15 @@ public class RealmFactory implements ServerComponent { realm = selectedRealm; } - public RealmFactory(Settings settings, LoginPasswordAuthenticator[] authenticators) { + public SecurityRealmFactory(Settings settings, LoginPasswordAuthenticator[] authenticators) { this(settings, null, authenticators); } - public RealmFactory(Settings settings, Realm[] realms) { + public SecurityRealmFactory(Settings settings, SecurityRealm[] realms) { this(settings, realms, null); } - public RealmFactory(Settings settings) { + public SecurityRealmFactory(Settings settings) { this(settings, null, null); } @@ -94,13 +94,13 @@ public class RealmFactory implements ServerComponent { } } - public Realm getRealm() { + public SecurityRealm getRealm() { return realm; } - private static Realm selectRealm(Realm[] realms, String realmName) { + private static SecurityRealm selectRealm(SecurityRealm[] realms, String realmName) { if (realms != null) { - for (Realm realm : realms) { + for (SecurityRealm realm : realms) { if (StringUtils.equals(realmName, realm.getName())) { return realm; } diff --git a/sonar-server/src/main/webapp/WEB-INF/lib/need_authentication.rb b/sonar-server/src/main/webapp/WEB-INF/lib/need_authentication.rb index 94e7d62223d..e7335d80369 100644 --- a/sonar-server/src/main/webapp/WEB-INF/lib/need_authentication.rb +++ b/sonar-server/src/main/webapp/WEB-INF/lib/need_authentication.rb @@ -43,8 +43,9 @@ end # class PluginRealm def initialize(java_realm) - @java_authenticator = java_realm.getAuthenticator() + @java_authenticator = java_realm.getLoginPasswordAuthenticator() @java_users_provider = java_realm.getUsersProvider() + @java_groups_provider = java_realm.getGroupsProvider() end def authenticate?(login, password) @@ -70,12 +71,34 @@ class PluginRealm else if details user.update_attributes(:name => details.getName(), :email => details.getEmail(), :password => password, :password_confirmation => password) + # Synchronize groups only when succeeded to synchronize details + synchronize_groups(user) user.save end end end end + def synchronize_groups(user) + if @java_groups_provider + begin + groups = @java_groups_provider.doGetGroups(user.login) + rescue Exception => e + Java::OrgSonarServerUi::JRubyFacade.new.logError("Error from external groups provider: #{e.message}") + else + if groups + user.groups = [] + for group_name in groups + group = Group.find_by_name(group_name) + if group + user.groups << group + end + end + end + end + end + end + def editable_password? false end @@ -89,7 +112,7 @@ class RealmFactory def self.realm if @@realm.nil? - realm_factory = Java::OrgSonarServerUi::JRubyFacade.new.getCoreComponentByClassname('org.sonar.server.ui.RealmFactory') + realm_factory = Java::OrgSonarServerUi::JRubyFacade.new.getCoreComponentByClassname('org.sonar.server.ui.SecurityRealmFactory') component = realm_factory.getRealm() @@realm = component ? PluginRealm.new(component) : DefaultRealm.new end diff --git a/sonar-server/src/test/java/org/sonar/server/ui/CompatibilityRealmTest.java b/sonar-server/src/test/java/org/sonar/server/ui/CompatibilityRealmTest.java index 84febf3eb0e..136407aeb04 100644 --- a/sonar-server/src/test/java/org/sonar/server/ui/CompatibilityRealmTest.java +++ b/sonar-server/src/test/java/org/sonar/server/ui/CompatibilityRealmTest.java @@ -35,7 +35,7 @@ public class CompatibilityRealmTest { CompatibilityRealm realm = new CompatibilityRealm(authenticator); realm.init(); verify(authenticator).init(); - assertThat(realm.getAuthenticator(), is(authenticator)); + assertThat(realm.getLoginPasswordAuthenticator(), is(authenticator)); assertThat(realm.getName(), is("CompatibilityRealm[" + authenticator.getClass().getName() + "]")); } diff --git a/sonar-server/src/test/java/org/sonar/server/ui/RealmFactoryTest.java b/sonar-server/src/test/java/org/sonar/server/ui/SecurityRealmFactoryTest.java index 9f27ba0ee7b..38ba8cd2662 100644 --- a/sonar-server/src/test/java/org/sonar/server/ui/RealmFactoryTest.java +++ b/sonar-server/src/test/java/org/sonar/server/ui/SecurityRealmFactoryTest.java @@ -24,14 +24,14 @@ import org.junit.Test; import org.sonar.api.CoreProperties; import org.sonar.api.config.Settings; import org.sonar.api.security.LoginPasswordAuthenticator; -import org.sonar.api.security.Realm; +import org.sonar.api.security.SecurityRealm; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -public class RealmFactoryTest { +public class SecurityRealmFactoryTest { private Settings settings; @@ -45,10 +45,10 @@ public class RealmFactoryTest { */ @Test public void shouldSelectRealmAndStart() { - Realm realm = spy(new FakeRealm()); - settings.setProperty(RealmFactory.REALM_PROPERTY, realm.getName()); + SecurityRealm realm = spy(new FakeRealm()); + settings.setProperty(SecurityRealmFactory.REALM_PROPERTY, realm.getName()); - RealmFactory factory = new RealmFactory(settings, new Realm[] {realm}); + SecurityRealmFactory factory = new SecurityRealmFactory(settings, new SecurityRealm[] {realm}); factory.start(); assertThat(factory.getRealm(), is(realm)); verify(realm).init(); @@ -56,16 +56,16 @@ public class RealmFactoryTest { @Test public void doNotFailIfNoRealms() { - RealmFactory factory = new RealmFactory(settings); + SecurityRealmFactory factory = new SecurityRealmFactory(settings); factory.start(); assertThat(factory.getRealm(), nullValue()); } @Test(expected = AuthenticatorNotFoundException.class) public void realmNotFound() { - settings.setProperty(RealmFactory.REALM_PROPERTY, "Fake"); + settings.setProperty(SecurityRealmFactory.REALM_PROPERTY, "Fake"); - new RealmFactory(settings); + new SecurityRealmFactory(settings); } @Test @@ -73,19 +73,20 @@ public class RealmFactoryTest { settings.setProperty(CoreProperties.CORE_AUTHENTICATOR_CLASS, FakeAuthenticator.class.getName()); LoginPasswordAuthenticator authenticator = new FakeAuthenticator(); - RealmFactory factory = new RealmFactory(settings, new LoginPasswordAuthenticator[] {authenticator}); - Realm realm = factory.getRealm(); + SecurityRealmFactory factory = new SecurityRealmFactory(settings, new LoginPasswordAuthenticator[] {authenticator}); + SecurityRealm realm = factory.getRealm(); assertThat(realm, instanceOf(CompatibilityRealm.class)); } @Test public void shouldTakePrecedenceOverAuthenticator() { - Realm realm = new FakeRealm(); - settings.setProperty(RealmFactory.REALM_PROPERTY, realm.getName()); + SecurityRealm realm = new FakeRealm(); + settings.setProperty(SecurityRealmFactory.REALM_PROPERTY, realm.getName()); LoginPasswordAuthenticator authenticator = new FakeAuthenticator(); settings.setProperty(CoreProperties.CORE_AUTHENTICATOR_CLASS, FakeAuthenticator.class.getName()); - RealmFactory factory = new RealmFactory(settings, new Realm[] {realm}, new LoginPasswordAuthenticator[] {authenticator}); + SecurityRealmFactory factory = new SecurityRealmFactory(settings, new SecurityRealm[] {realm}, + new LoginPasswordAuthenticator[] {authenticator}); assertThat(factory.getRealm(), is(realm)); } @@ -93,25 +94,25 @@ public class RealmFactoryTest { public void authenticatorNotFound() { settings.setProperty(CoreProperties.CORE_AUTHENTICATOR_CLASS, "Fake"); - new RealmFactory(settings); + new SecurityRealmFactory(settings); } @Test public void ignoreStartupFailure() { - Realm realm = spy(new AlwaysFailsRealm()); - settings.setProperty(RealmFactory.REALM_PROPERTY, realm.getName()); + SecurityRealm realm = spy(new AlwaysFailsRealm()); + settings.setProperty(SecurityRealmFactory.REALM_PROPERTY, realm.getName()); settings.setProperty(CoreProperties.CORE_AUTHENTICATOR_IGNORE_STARTUP_FAILURE, true); - new RealmFactory(settings, new Realm[] {realm}).start(); + new SecurityRealmFactory(settings, new SecurityRealm[] {realm}).start(); verify(realm).init(); } @Test(expected = IllegalStateException.class) public void shouldFail() { - Realm realm = spy(new AlwaysFailsRealm()); - settings.setProperty(RealmFactory.REALM_PROPERTY, realm.getName()); + SecurityRealm realm = spy(new AlwaysFailsRealm()); + settings.setProperty(SecurityRealmFactory.REALM_PROPERTY, realm.getName()); - new RealmFactory(settings, new Realm[] {realm}).start(); + new SecurityRealmFactory(settings, new SecurityRealm[] {realm}).start(); } private static class AlwaysFailsRealm extends FakeRealm { @@ -121,9 +122,9 @@ public class RealmFactoryTest { } } - private static class FakeRealm extends Realm { + private static class FakeRealm extends SecurityRealm { @Override - public LoginPasswordAuthenticator getAuthenticator() { + public LoginPasswordAuthenticator getLoginPasswordAuthenticator() { return null; } } |