diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-12-06 12:44:56 +0100 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2016-12-07 14:36:18 +0100 |
commit | b117943f3efa541d6c8cd8e62ad157c4f8194211 (patch) | |
tree | 825fa577b1943d1188b0e4a7d0237b7690687951 /server/sonar-server | |
parent | 160109946acd018e7c3b79dbe008c39d51235ddf (diff) | |
download | sonarqube-b117943f3efa541d6c8cd8e62ad157c4f8194211.tar.gz sonarqube-b117943f3efa541d6c8cd8e62ad157c4f8194211.zip |
SONAR-8498 Make WS api/l10n/index accessible without authentication or migration
Diffstat (limited to 'server/sonar-server')
8 files changed, 244 insertions, 208 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java index af14f73165f..b0fcf8a7a72 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java +++ b/server/sonar-server/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java @@ -65,7 +65,7 @@ public class UserSessionInitializer { "/sessions/*", "/api/system/db_migration_status", "/api/system/status", "/api/system/migrate_db", "/api/server/index", "/api/server/setup", "/api/server/version", - "/api/users/identity_providers", + "/api/users/identity_providers", "/api/l10n/index", LOGIN_URL, LOGOUT_URL, VALIDATE_URL); private static final UrlPattern URL_PATTERN = UrlPattern.builder() diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 4064e747a88..5613a5ff655 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -346,6 +346,7 @@ public class PlatformLevel4 extends PlatformLevel { // localization L10nWs.class, + org.sonar.server.platform.ws.IndexAction.class, // authentication AuthenticationModule.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java index 91cf5b83310..3571d7b8d77 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java @@ -23,6 +23,8 @@ import org.sonar.server.organization.NoopDefaultOrganizationCache; import org.sonar.server.platform.ServerImpl; import org.sonar.server.platform.web.WebPagesFilter; import org.sonar.server.platform.ws.DbMigrationStatusAction; +import org.sonar.server.platform.ws.IndexAction; +import org.sonar.server.platform.ws.L10nWs; import org.sonar.server.platform.ws.MigrateDbAction; import org.sonar.server.platform.ws.StatusAction; import org.sonar.server.platform.ws.SystemWs; @@ -41,6 +43,10 @@ public class PlatformLevelSafeMode extends PlatformLevel { ServerImpl.class, WebPagesFilter.class, + // l10n WS + L10nWs.class, + IndexAction.class, + // Server WS StatusAction.class, MigrateDbAction.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/IndexAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/IndexAction.java new file mode 100644 index 00000000000..9aaa0a23836 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/IndexAction.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.platform.ws; + +import java.util.Date; +import java.util.Locale; +import org.sonar.api.platform.Server; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.core.i18n.DefaultI18n; +import org.sonar.server.ws.WsAction; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED; +import static java.util.Locale.ENGLISH; + +public class IndexAction implements WsAction { + + private static final String LOCALE_PARAM = "locale"; + private static final String TS_PARAM = "ts"; + private final DefaultI18n i18n; + private final Server server; + + public IndexAction(DefaultI18n i18n, Server server) { + this.i18n = i18n; + this.server = server; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction indexAction = context.createAction("index") + .setInternal(true) + .setDescription("Get all localization messages for a given locale") + .setResponseExample(getClass().getResource("l10n-index-example.json")) + .setSince("4.4") + .setHandler(this); + indexAction.createParam(LOCALE_PARAM) + .setDescription("BCP47 language tag, used to override the browser Accept-Language header") + .setExampleValue("fr-CH") + .setDefaultValue(ENGLISH.toLanguageTag()); + indexAction.createParam(TS_PARAM) + .setDescription("Date of the last cache update.") + .setExampleValue("2014-06-04T09:31:42+0000"); + } + + @Override + public void handle(Request request, Response response) throws Exception { + Date timestamp = request.paramAsDateTime(TS_PARAM); + if (timestamp != null && timestamp.after(server.getStartedAt())) { + response.stream().setStatus(HTTP_NOT_MODIFIED).output().close(); + return; + } + String localeParam = request.mandatoryParam(LOCALE_PARAM); + Locale locale = Locale.forLanguageTag(localeParam); + checkArgument(!locale.getISO3Language().isEmpty(), "'%s' cannot be parsed as a BCP47 language tag", localeParam); + JsonWriter json = response.newJsonWriter().beginObject(); + for (String messageKey : i18n.getPropertyKeys()) { + json.prop(messageKey, i18n.message(locale, messageKey, messageKey)); + } + json.endObject().close(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/L10nWs.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/L10nWs.java index 33b75875f25..8d7f1519e87 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/L10nWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/L10nWs.java @@ -19,31 +19,14 @@ */ package org.sonar.server.platform.ws; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.util.Date; -import java.util.Locale; -import org.sonar.api.platform.Server; -import org.sonar.api.server.ws.Request; -import org.sonar.api.server.ws.RequestHandler; -import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; -import org.sonar.api.utils.text.JsonWriter; -import org.sonar.core.i18n.DefaultI18n; -import org.sonar.server.user.UserSession; - -import static com.google.common.base.Preconditions.checkArgument; public class L10nWs implements WebService { - private final DefaultI18n i18n; - private final Server server; - private final UserSession userSession; + private final IndexAction indexAction; - public L10nWs(DefaultI18n i18n, Server server, UserSession userSession) { - this.i18n = i18n; - this.server = server; - this.userSession = userSession; + public L10nWs(IndexAction indexAction) { + this.indexAction = indexAction; } @Override @@ -51,43 +34,7 @@ public class L10nWs implements WebService { NewController l10n = context.createController("api/l10n"); l10n.setDescription("Manage localization.") .setSince("4.4"); - NewAction indexAction = l10n.createAction("index") - .setInternal(true) - .setDescription("Get all localization messages for a given locale") - .setResponseExample(getClass().getResource("l10n-index-example.json")) - .setSince("4.4") - .setHandler(new RequestHandler() { - @Override - public void handle(Request request, Response response) throws Exception { - serializeMessages(request, response); - } - }); - indexAction.createParam("locale") - .setDescription("BCP47 language tag, used to override the browser Accept-Language header") - .setExampleValue("fr-CH"); - indexAction.createParam("ts") - .setDescription("Date of the last cache update.") - .setExampleValue("2014-06-04T09:31:42+0000"); - + indexAction.define(l10n); l10n.done(); } - - private void serializeMessages(Request request, Response response) throws IOException { - Date timestamp = request.paramAsDateTime("ts"); - if (timestamp != null && timestamp.after(server.getStartedAt())) { - response.stream().setStatus(HttpURLConnection.HTTP_NOT_MODIFIED).output().close(); - } else { - Locale locale = userSession.locale(); - String localeParam = request.param("locale"); - if (localeParam != null) { - locale = Locale.forLanguageTag(localeParam); - checkArgument(!locale.getISO3Language().isEmpty(), "'%s' cannot be parsed as a BCP47 language tag", localeParam); - } - JsonWriter json = response.newJsonWriter().beginObject(); - for (String messageKey : i18n.getPropertyKeys()) { - json.prop(messageKey, i18n.message(locale, messageKey, messageKey)); - } - json.endObject().close(); - } - } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java index 94acef3059f..30857db4154 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java @@ -111,6 +111,7 @@ public class UserSessionInitializerTest { assertPathIsIgnored("/api/server/setup"); assertPathIsIgnored("/api/server/version"); assertPathIsIgnored("/api/users/identity_providers"); + assertPathIsIgnored("/api/l10n/index"); // exclude static resources assertPathIsIgnored("/css/style.css"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/IndexActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/IndexActionTest.java new file mode 100644 index 00000000000..5bd43a2554e --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/IndexActionTest.java @@ -0,0 +1,149 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.platform.ws; + +import com.google.common.collect.ImmutableSet; +import java.net.HttpURLConnection; +import java.util.Date; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.platform.Server; +import org.sonar.api.utils.DateUtils; +import org.sonar.core.i18n.DefaultI18n; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.TestResponse; +import org.sonar.server.ws.WsActionTester; + +import static java.util.Locale.ENGLISH; +import static java.util.Locale.PRC; +import static java.util.Locale.UK; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.test.JsonAssert.assertJson; + +public class IndexActionTest { + + private static final String KEY_1 = "key1"; + private static final String KEY_2 = "key2"; + private static final String KEY_3 = "key3"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private DefaultI18n i18n = mock(DefaultI18n.class); + private Server server = mock(Server.class); + + private IndexAction underTest = new IndexAction(i18n, server); + + private WsActionTester ws = new WsActionTester(underTest); + + @Test + public void allow_client_to_cache_messages() throws Exception { + Date now = new Date(); + Date aBitLater = new Date(now.getTime() + 1000); + when(server.getStartedAt()).thenReturn(now); + + TestResponse result = call(null, DateUtils.formatDateTime(aBitLater)); + + verifyZeroInteractions(i18n); + verify(server).getStartedAt(); + assertThat(result.getStatus()).isEqualTo(HttpURLConnection.HTTP_NOT_MODIFIED); + } + + @Test + public void return_all_l10n_messages_using_accept_header_with_cache_expired() throws Exception { + Date now = new Date(); + Date aBitEarlier = new Date(now.getTime() - 1000); + when(server.getStartedAt()).thenReturn(now); + when(i18n.getPropertyKeys()).thenReturn(ImmutableSet.of(KEY_1, KEY_2, KEY_3)); + when(i18n.message(PRC, KEY_1, KEY_1)).thenReturn(KEY_1); + when(i18n.message(PRC, KEY_2, KEY_2)).thenReturn(KEY_2); + when(i18n.message(PRC, KEY_3, KEY_3)).thenReturn(KEY_3); + + TestResponse result = call(PRC.toLanguageTag(), DateUtils.formatDateTime(aBitEarlier)); + + verify(i18n).getPropertyKeys(); + verify(i18n).message(PRC, KEY_1, KEY_1); + verify(i18n).message(PRC, KEY_2, KEY_2); + verify(i18n).message(PRC, KEY_3, KEY_3); + assertJson(result.getInput()).isSimilarTo("{\"key1\":\"key1\",\"key2\":\"key2\",\"key3\":\"key3\"}"); + } + + @Test + public void default_locale_is_english() throws Exception { + String key1 = "key1"; + String key2 = "key2"; + String key3 = "key3"; + when(i18n.getPropertyKeys()).thenReturn(ImmutableSet.of(key1, key2, key3)); + when(i18n.message(ENGLISH, key1, key1)).thenReturn(key1); + when(i18n.message(ENGLISH, key2, key2)).thenReturn(key2); + when(i18n.message(ENGLISH, key3, key3)).thenReturn(key3); + + TestResponse result = call(null, null); + + verify(i18n).getPropertyKeys(); + verify(i18n).message(ENGLISH, key1, key1); + verify(i18n).message(ENGLISH, key2, key2); + verify(i18n).message(ENGLISH, key3, key3); + assertJson(result.getInput()).isSimilarTo("{\"key1\":\"key1\",\"key2\":\"key2\",\"key3\":\"key3\"}"); + } + + @Test + public void support_BCP47_formatted_language_tags() throws Exception { + String key1 = "key1"; + when(i18n.getPropertyKeys()).thenReturn(ImmutableSet.of(key1)); + when(i18n.message(UK, key1, key1)).thenReturn(key1); + + TestResponse result = call("en-GB", null); + + verify(i18n).getPropertyKeys(); + verify(i18n).message(UK, key1, key1); + assertJson(result.getInput()).isSimilarTo("{\"key1\":\"key1\"}"); + } + + @Test + public void fail_when_java_formatted_language_tags() throws Exception { + String key1 = "key1"; + when(i18n.getPropertyKeys()).thenReturn(ImmutableSet.of(key1)); + when(i18n.message(UK, key1, key1)).thenReturn(key1); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("'en_GB' cannot be parsed as a BCP47 language tag"); + call("en_GB", null); + } + + private TestResponse call(@Nullable String locale, @Nullable String timestamp) { + TestRequest request = ws.newRequest(); + if (locale != null) { + request.setParam("locale", locale); + } + if (timestamp != null) { + request.setParam("ts", timestamp); + } + return request.execute(); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/L10nWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/L10nWsTest.java deleted file mode 100644 index 13e7c8d752a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/L10nWsTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.platform.ws; - -import com.google.common.collect.ImmutableSet; -import java.util.Date; -import java.util.Locale; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.platform.Server; -import org.sonar.api.utils.DateUtils; -import org.sonar.core.i18n.DefaultI18n; -import org.sonar.server.tester.UserSessionRule; -import org.sonar.server.ws.WsTester; -import org.sonar.server.ws.WsTester.Result; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -public class L10nWsTest { - @Rule - public UserSessionRule userSessionRule = UserSessionRule.standalone(); - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - DefaultI18n i18n = mock(DefaultI18n.class); - Server server = mock(Server.class); - - @Test - public void should_allow_client_to_cache_messages() throws Exception { - Locale locale = Locale.PRC; - userSessionRule.setLocale(locale); - - Date now = new Date(); - Date aBitLater = new Date(now.getTime() + 1000); - when(server.getStartedAt()).thenReturn(now); - - Result result = new WsTester(new L10nWs(i18n, server, userSessionRule)).newGetRequest("api/l10n", "index").setParam("ts", DateUtils.formatDateTime(aBitLater)).execute(); - verifyZeroInteractions(i18n); - verify(server).getStartedAt(); - - result.assertNotModified(); - } - - @Test - public void should_return_all_l10n_messages_using_accept_header_with_cache_expired() throws Exception { - Locale locale = Locale.PRC; - userSessionRule.setLocale(locale); - - Date now = new Date(); - Date aBitEarlier = new Date(now.getTime() - 1000); - when(server.getStartedAt()).thenReturn(now); - - String key1 = "key1"; - String key2 = "key2"; - String key3 = "key3"; - - when(i18n.getPropertyKeys()).thenReturn(ImmutableSet.of(key1, key2, key3)); - when(i18n.message(locale, key1, key1)).thenReturn(key1); - when(i18n.message(locale, key2, key2)).thenReturn(key2); - when(i18n.message(locale, key3, key3)).thenReturn(key3); - - Result result = new WsTester(new L10nWs(i18n, server, userSessionRule)).newGetRequest("api/l10n", "index").setParam("ts", DateUtils.formatDateTime(aBitEarlier)).execute(); - verify(i18n).getPropertyKeys(); - verify(i18n).message(locale, key1, key1); - verify(i18n).message(locale, key2, key2); - verify(i18n).message(locale, key3, key3); - - result.assertJson("{\"key1\":\"key1\",\"key2\":\"key2\",\"key3\":\"key3\"}"); - } - - @Test - public void should_override_locale_when_locale_param_is_set() throws Exception { - Locale locale = Locale.PRC; - userSessionRule.setLocale(locale); - Locale override = Locale.JAPANESE; - - String key1 = "key1"; - String key2 = "key2"; - String key3 = "key3"; - - when(i18n.getPropertyKeys()).thenReturn(ImmutableSet.of(key1, key2, key3)); - when(i18n.message(override, key1, key1)).thenReturn(key1); - when(i18n.message(override, key2, key2)).thenReturn(key2); - when(i18n.message(override, key3, key3)).thenReturn(key3); - - Result result = new WsTester(new L10nWs(i18n, server, userSessionRule)).newGetRequest("api/l10n", "index").setParam("locale", override.toString()).execute(); - verify(i18n).getPropertyKeys(); - verify(i18n).message(override, key1, key1); - verify(i18n).message(override, key2, key2); - verify(i18n).message(override, key3, key3); - - result.assertJson("{\"key1\":\"key1\",\"key2\":\"key2\",\"key3\":\"key3\"}"); - } - - @Test - public void support_BCP47_formatted_language_tags() throws Exception { - Locale locale = Locale.PRC; - userSessionRule.setLocale(locale); - Locale override = Locale.UK; - - String key1 = "key1"; - - when(i18n.getPropertyKeys()).thenReturn(ImmutableSet.of(key1)); - when(i18n.message(override, key1, key1)).thenReturn(key1); - - Result result = new WsTester(new L10nWs(i18n, server, userSessionRule)).newGetRequest("api/l10n", "index").setParam("locale", "en-GB").execute(); - verify(i18n).getPropertyKeys(); - verify(i18n).message(override, key1, key1); - - result.assertJson("{\"key1\":\"key1\"}"); - } - - @Test - public void fail_when_java_formatted_language_tags() throws Exception { - Locale locale = Locale.PRC; - userSessionRule.setLocale(locale); - Locale override = Locale.UK; - - String key1 = "key1"; - - when(i18n.getPropertyKeys()).thenReturn(ImmutableSet.of(key1)); - when(i18n.message(override, key1, key1)).thenReturn(key1); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("'en_GB' cannot be parsed as a BCP47 language tag"); - - new WsTester(new L10nWs(i18n, server, userSessionRule)).newGetRequest("api/l10n", "index").setParam("locale", "en_GB").execute(); - } -} |