diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2012-05-08 09:14:29 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2012-05-08 10:23:02 +0200 |
commit | 9e46b4bb203a60742f0cb58cd621a58496b7ed33 (patch) | |
tree | ce4a6aac43db33c25c4bf48b6c22a9f63e88e42c /sonar-plugin-api | |
parent | bd0abcb3f754f4c123f9ce3145e2cf814210b91e (diff) | |
download | sonarqube-9e46b4bb203a60742f0cb58cd621a58496b7ed33.tar.gz sonarqube-9e46b4bb203a60742f0cb58cd621a58496b7ed33.zip |
SONAR-2950 Single Sign On with external authentication mechanism
Diffstat (limited to 'sonar-plugin-api')
11 files changed, 443 insertions, 5 deletions
diff --git a/sonar-plugin-api/pom.xml b/sonar-plugin-api/pom.xml index 22a6a4fdef2..93e5a4a7522 100644 --- a/sonar-plugin-api/pom.xml +++ b/sonar-plugin-api/pom.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.codehaus.sonar</groupId> @@ -131,6 +132,12 @@ <groupId>xalan</groupId> <artifactId>xalan</artifactId> </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + <version>2.4</version> + <optional>true</optional> + </dependency> <!-- unit tests --> <dependency> @@ -169,6 +176,12 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.easytesting</groupId> + <artifactId>fest-assert</artifactId> + <scope>test</scope> + </dependency> + + <dependency> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty-servlet-tester</artifactId> <scope>test</scope> diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/security/Authenticator.java b/sonar-plugin-api/src/main/java/org/sonar/api/security/Authenticator.java new file mode 100644 index 00000000000..2420eaf3d8f --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/security/Authenticator.java @@ -0,0 +1,70 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.security; + +import com.google.common.base.Preconditions; +import org.sonar.api.ServerExtension; + +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + +/** + * @see SecurityRealm + * @since 3.1 + */ +public abstract class Authenticator implements ServerExtension { + + /** + * @return true if user was successfully authenticated with specified credentials, false otherwise + * @throws RuntimeException in case of unexpected error such as connection failure + */ + public abstract boolean doAuthenticate(Context context); + + public static final class Context { + private String username; + private String password; + private HttpServletRequest request; + + public Context(@Nullable String username, @Nullable String password, HttpServletRequest request) { + Preconditions.checkNotNull(request); + this.request = request; + this.username = username; + this.password = password; + } + + /** + * Username can be null, for example when using <a href="http://www.jasig.org/cas">CAS</a>. + */ + public String getUsername() { + return username; + } + + /** + * Password can be null, for example when using <a href="http://www.jasig.org/cas">CAS</a>. + */ + public String getPassword() { + return password; + } + + public HttpServletRequest getRequest() { + return request; + } + } +} 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 00f7dfb4dac..b7c93c59fb2 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,18 +19,58 @@ */ package org.sonar.api.security; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; + /** * 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 + * @since 2.14 */ public abstract class ExternalUsersProvider { /** + * This method is overridden by old versions of plugins such as LDAP 1.1. It should be overridden anymore. + * * @return details for specified user, or null if such user doesn't exist * @throws RuntimeException in case of unexpected error such as connection failure + * @deprecated replaced by {@link #doGetUserDetails(org.sonar.api.security.ExternalUsersProvider.Context)} since v. 3.1 */ - public abstract UserDetails doGetUserDetails(String username); + @Deprecated + public UserDetails doGetUserDetails(@Nullable String username) { + return null; + } + + /** + * Override this method in order load user information. + * + * @return the user, or null if user doesn't exist + * @throws RuntimeException in case of unexpected error such as connection failure + * @since 3.1 + */ + public UserDetails doGetUserDetails(Context context) { + return doGetUserDetails(context.getUsername()); + } + + public static final class Context { + private String username; + private HttpServletRequest request; + + public Context(@Nullable String username, HttpServletRequest request) { + this.username = username; + this.request = request; + } + + public String getUsername() { + return username; + } + public HttpServletRequest getRequest() { + return request; + } + } } 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 8ca6f97864f..2883275a24e 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 @@ -24,6 +24,7 @@ import org.sonar.api.ServerExtension; /** * @since 1.12 * @see SecurityRealm + * @deprecated replaced by Authenticator in version 3.1 */ public interface LoginPasswordAuthenticator extends ServerExtension { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/security/SecurityRealm.java b/sonar-plugin-api/src/main/java/org/sonar/api/security/SecurityRealm.java index f21d4d26946..16a122933ef 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/security/SecurityRealm.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/security/SecurityRealm.java @@ -41,8 +41,27 @@ public abstract class SecurityRealm implements ServerExtension { /** * @return {@link LoginPasswordAuthenticator} associated with this realm, never null + * @deprecated replaced by doGetAuthenticator in version 3.1 */ - public abstract LoginPasswordAuthenticator getLoginPasswordAuthenticator(); + @Deprecated + public LoginPasswordAuthenticator getLoginPasswordAuthenticator() { + return null; + } + + /** + * @since 3.1 + */ + public Authenticator doGetAuthenticator() { + if (getLoginPasswordAuthenticator() == null) { + return null; + } + return new Authenticator() { + @Override + public boolean doAuthenticate(Context context) { + return getLoginPasswordAuthenticator().authenticate(context.getUsername(), context.getPassword()); + } + }; + } /** * @return {@link ExternalUsersProvider} associated with this realm, null if not supported @@ -57,5 +76,4 @@ public abstract class SecurityRealm implements ServerExtension { public ExternalGroupsProvider getGroupsProvider() { return null; } - } 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 e3519866a13..2d4afc0524c 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 @@ -21,6 +21,8 @@ package org.sonar.api.security; import com.google.common.base.Objects; +import javax.annotation.Nullable; + /** * This class is not intended to be subclassed by clients. * diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/security/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/security/package-info.java new file mode 100644 index 00000000000..8658aba99c6 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/security/package-info.java @@ -0,0 +1,23 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +@ParametersAreNonnullByDefault +package org.sonar.api.security; + +import javax.annotation.ParametersAreNonnullByDefault;
\ No newline at end of file diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/web/ServletFilter.java b/sonar-plugin-api/src/main/java/org/sonar/api/web/ServletFilter.java new file mode 100644 index 00000000000..983f7fcfcfc --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/web/ServletFilter.java @@ -0,0 +1,84 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.web; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import org.sonar.api.ServerExtension; + +import javax.servlet.Filter; + +/** + * @since 3.1 + */ +@Beta +public abstract class ServletFilter implements ServerExtension, Filter { + + /** + * Override to change URL. Default is /* + */ + public UrlPattern doGetPattern() { + return UrlPattern.create("/*"); + } + + public static final class UrlPattern { + private int code; + private String url; + private String urlToMatch; + + public static UrlPattern create(String pattern) { + return new UrlPattern(pattern); + } + + private UrlPattern(String url) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(url), "Empty url"); + this.url = url; + this.urlToMatch = url.replaceAll("/?\\*", ""); + if ("/*".equals(url)) { + code = 1; + } else if (url.startsWith("*")) { + code = 2; + } else if (url.endsWith("*")) { + code = 3; + } else { + code = 4; + } + } + + public boolean matches(String path) { + switch (code) { + case 1: + return true; + case 2: + return path.endsWith(urlToMatch); + case 3: + return path.startsWith(urlToMatch); + default: + return path.equals(urlToMatch); + } + } + + @Override + public String toString() { + return url; + } + } +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/security/ExternalUsersProviderTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/security/ExternalUsersProviderTest.java new file mode 100644 index 00000000000..f1ed1a023e1 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/security/ExternalUsersProviderTest.java @@ -0,0 +1,67 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.security; + +import com.google.common.base.Preconditions; +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class ExternalUsersProviderTest { + + @Test + public void doGetUserDetails() { + ExternalUsersProvider provider = new ExternalUsersProvider() { + @Override + public UserDetails doGetUserDetails(Context context) { + Preconditions.checkNotNull(context.getUsername()); + Preconditions.checkNotNull(context.getRequest()); + UserDetails user = new UserDetails(); + user.setName(context.getUsername()); + user.setEmail("foo@bar.com"); + return user; + } + }; + UserDetails user = provider.doGetUserDetails(new ExternalUsersProvider.Context("foo", mock(HttpServletRequest.class))); + + assertThat(user.getName()).isEqualTo("foo"); + assertThat(user.getEmail()).isEqualTo("foo@bar.com"); + } + + @Test + public void doGetUserDetails_deprecated_api() { + ExternalUsersProvider provider = new ExternalUsersProvider() { + @Override + public UserDetails doGetUserDetails(String username) { + UserDetails user = new UserDetails(); + user.setName(username); + user.setEmail("foo@bar.com"); + return user; + } + }; + UserDetails user = provider.doGetUserDetails(new ExternalUsersProvider.Context("foo", mock(HttpServletRequest.class))); + + assertThat(user.getName()).isEqualTo("foo"); + assertThat(user.getEmail()).isEqualTo("foo@bar.com"); + } +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/security/SecurityRealmTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/security/SecurityRealmTest.java new file mode 100644 index 00000000000..e22eabc34a2 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/security/SecurityRealmTest.java @@ -0,0 +1,59 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.security; + +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class SecurityRealmTest { + + @Test + public void doGetAuthenticator() { + final Authenticator authenticator = mock(Authenticator.class); + SecurityRealm realm = new SecurityRealm() { + @Override + public Authenticator doGetAuthenticator() { + return authenticator; + } + }; + assertThat(realm.doGetAuthenticator()).isSameAs(authenticator); + } + + @Test + public void getLoginPasswordAuthenticator_deprecated_method_replaced_by_getAuthenticator() { + final LoginPasswordAuthenticator deprecatedAuthenticator = mock(LoginPasswordAuthenticator.class); + SecurityRealm realm = new SecurityRealm() { + @Override + public LoginPasswordAuthenticator getLoginPasswordAuthenticator() { + return deprecatedAuthenticator; + } + }; + Authenticator proxy = realm.doGetAuthenticator(); + Authenticator.Context context = new Authenticator.Context("foo", "bar", mock(HttpServletRequest.class)); + proxy.doAuthenticate(context); + + verify(deprecatedAuthenticator).authenticate("foo", "bar"); + } +} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/web/ServletFilterTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/web/ServletFilterTest.java new file mode 100644 index 00000000000..398cbb6d247 --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/web/ServletFilterTest.java @@ -0,0 +1,61 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.api.web; + +import org.junit.Test; + +import static org.fest.assertions.Assertions.assertThat; + +public class ServletFilterTest { + @Test + public void matchAll() { + ServletFilter.UrlPattern pattern = ServletFilter.UrlPattern.create("/*"); + assertThat(pattern.matches("/")).isTrue(); + assertThat(pattern.matches("/foo/ooo")).isTrue(); + } + + @Test + public void matchEndOfUrl() { + ServletFilter.UrlPattern pattern = ServletFilter.UrlPattern.create("*foo"); + assertThat(pattern.matches("/")).isFalse(); + assertThat(pattern.matches("/hello/foo")).isTrue(); + assertThat(pattern.matches("/hello/bar")).isFalse(); + assertThat(pattern.matches("/foo")).isTrue(); + assertThat(pattern.matches("/foo2")).isFalse(); + } + + @Test + public void matchBeginningOfUrl() { + ServletFilter.UrlPattern pattern = ServletFilter.UrlPattern.create("/foo/*"); + assertThat(pattern.matches("/")).isFalse(); + assertThat(pattern.matches("/foo")).isTrue(); + assertThat(pattern.matches("/foo/bar")).isTrue(); + assertThat(pattern.matches("/bar")).isFalse(); + } + + @Test + public void matchExactUrl() { + ServletFilter.UrlPattern pattern = ServletFilter.UrlPattern.create("/foo"); + assertThat(pattern.matches("/")).isFalse(); + assertThat(pattern.matches("/foo")).isTrue(); + assertThat(pattern.matches("/foo/")).isFalse(); + assertThat(pattern.matches("/bar")).isFalse(); + } +} |