import org.junit.Test;
import org.sonar.wsclient.services.Server;
import org.sonar.wsclient.services.ServerQuery;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.ServerId.ShowWsResponse;
import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.WsResponse;
+import pageobjects.Navigation;
+import pageobjects.ServerIdPage;
import util.ItUtils;
import util.selenium.SeleneseTest;
@Test
public void get_sonarqube_version() {
String version = orchestrator.getServer().getWsClient().find(new ServerQuery()).getVersion();
- if (!StringUtils.startsWithAny(version, new String[] {"5.", "6."})) {
+ if (!StringUtils.startsWithAny(version, new String[]{"5.", "6."})) {
fail("Bad version: " + version);
}
}
}
@Test
- public void generate_server_id() {
- Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("server_id",
- "/serverSystem/ServerSystemTest/missing_ip.html",
- // SONAR-4102
- "/serverSystem/ServerSystemTest/organisation_must_not_accept_special_chars.html",
- "/serverSystem/ServerSystemTest/valid_id.html").build();
- new SeleneseTest(selenese).runOn(orchestrator);
+ public void generate_server_id() throws IOException {
+ Navigation nav = Navigation.get(orchestrator).openHomepage().logIn().asAdmin();
+ String validIpAddress = getValidIpAddress();
+
+ nav.openServerId()
+ .setOrganization("Name with invalid chars like $")
+ .setIpAddress(validIpAddress)
+ .submitForm()
+ .assertError();
+
+ nav.openServerId()
+ .setOrganization("DEMO")
+ .setIpAddress("invalid_address")
+ .submitForm()
+ .assertError();
+
+ ServerIdPage page = nav.openServerId()
+ .setOrganization("DEMO")
+ .setIpAddress(validIpAddress)
+ .submitForm();
+
+ String serverId = page.serverIdInput().val();
+ assertThat(serverId).isNotEmpty();
}
@Test
httpclient.getConnectionManager().shutdown();
}
}
+
+ private String getValidIpAddress() throws IOException {
+ WsClient adminWsClient = newAdminWsClient(orchestrator);
+ ShowWsResponse response = ShowWsResponse.parseFrom(adminWsClient.wsConnector().call(
+ new GetRequest("api/server_id/show").setMediaType(MediaTypes.PROTOBUF)).contentStream());
+ assertThat(response.getValidIpAdressesCount()).isGreaterThan(0);
+ return response.getValidIpAdresses(0);
+ }
}
return open("/settings/encryption", EncryptionPage.class);
}
+ public ServerIdPage openServerId() {
+ return open("/settings/server_id", ServerIdPage.class);
+ }
+
public void open(String relativeUrl) {
Selenide.open(relativeUrl);
}
--- /dev/null
+/*
+ * 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 pageobjects;
+
+import com.codeborne.selenide.SelenideElement;
+
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
+
+public class ServerIdPage {
+
+ public ServerIdPage() {
+ $("#server-id-page").shouldBe(visible);
+ }
+
+ public SelenideElement serverIdInput() {
+ return $("#server-id-result").shouldBe(visible);
+ }
+
+ private SelenideElement organizationInput() {
+ return $("#server-id-organization").shouldBe(visible);
+ }
+
+ private SelenideElement ipAddressInput() {
+ return $("#server-id-ip").shouldBe(visible);
+ }
+
+ public ServerIdPage assertError() {
+ $(".alert-danger").shouldBe(visible);
+ return this;
+ }
+
+ public ServerIdPage setOrganization(String organization) {
+ organizationInput().val(organization);
+ return this;
+ }
+
+ public ServerIdPage setIpAddress(String ipAddress) {
+ ipAddressInput().val(ipAddress);
+ return this;
+ }
+
+ public ServerIdPage submitForm() {
+ $("#server-id-form").submit();
+ return this;
+ }
+}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- ~ 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.
- -->
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>missing_ip</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">missing_ip</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>login</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>type</td>
- <td>password</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>commit</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
- </tr>
- <tr>
- <td>open</td>
- <td>/server_id_configuration</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>name=organisation</td>
- <td>DEMO</td>
- </tr>
- <tr>
- <td>type</td>
- <td>name=address</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>generate-button</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>error</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>server_id</td>
- <td></td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>organisation_must_not_accept_special_chars</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>login</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>type</td>
- <td>password</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>commit</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
- </tr>
- <tr>
- <td>open</td>
- <td>/server_id_configuration</td>
- <td></td>
- </tr>
- <tr>
- <td>storeText</td>
- <td>address_0</td>
- <td>valid_ip</td>
- </tr>
- <tr>
- <td>type</td>
- <td>name=organisation</td>
- <td>Name with invalid chars like $</td>
- </tr>
- <tr>
- <td>type</td>
- <td>name=address</td>
- <td>${valid_ip}</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>generate-button</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>error</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>error</td>
- <td>*Organisation does not match the required pattern.*</td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>server_id</td>
- <td></td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>valid_id</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">valid_id</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>login</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>type</td>
- <td>password</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>commit</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
- </tr>
- <tr>
- <td>open</td>
- <td>/server_id_configuration</td>
- <td></td>
- </tr>
- <tr>
- <td>storeText</td>
- <td>address_0</td>
- <td>valid_ip</td>
- </tr>
- <tr>
- <td>type</td>
- <td>name=organisation</td>
- <td>DEMO</td>
- </tr>
- <tr>
- <td>type</td>
- <td>name=address</td>
- <td>${valid_ip}</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>generate-button</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>server_id</td>
- <td></td>
- </tr>
- <tr>
- <td>storeText</td>
- <td>server_id</td>
- <td>server_id</td>
- </tr>
- <tr>
- <td>open</td>
- <td>/system</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>id=content</td>
- <td>glob:*Server ID*${server_id}*</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
export function encryptValue (value) {
return postJSON('/api/settings/encrypt', { value });
}
+
+export function getServerId () {
+ return getJSON('/api/server_id/show');
+}
+
+export function generateServerId (organization, ip) {
+ return postJSON('/api/server_id/generate', { organization, ip });
+}
import App from './components/App';
import LicensesApp from './licenses/LicensesApp';
import EncryptionAppContainer from './encryption/EncryptionAppContainer';
+import ServerIdAppContainer from './serverId/ServerIdAppContainer';
import rootReducer from './store/rootReducer';
import configureStore from '../../components/store/configureStore';
<Route path="/" component={withComponent(App)}/>
<Route path="/licenses" component={LicensesApp}/>
<Route path="/encryption" component={EncryptionAppContainer}/>
+ <Route path="/server_id" component={ServerIdAppContainer}/>
</Router>
</Provider>
), el);
--- /dev/null
+/*
+ * 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.
+ */
+import React from 'react';
+import GlobalMessagesContainer from '../components/GlobalMessagesContainer';
+import { translate } from '../../../helpers/l10n';
+import { getServerId, generateServerId } from '../../../api/settings';
+import { parseError } from '../../code/utils';
+
+export default class ServerIdApp extends React.Component {
+ static propTypes = {
+ addGlobalErrorMessage: React.PropTypes.func.isRequired,
+ closeAllGlobalMessages: React.PropTypes.func.isRequired
+ };
+
+ state = {
+ loading: true,
+ organization: '',
+ ip: '',
+ validIpAdresses: []
+ };
+
+ componentDidMount () {
+ this.mounted = true;
+ this.fetchServerId();
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ handleError (error) {
+ this.setState({ loading: false });
+ parseError(error).then(message => this.props.addGlobalErrorMessage(message));
+ }
+
+ fetchServerId () {
+ this.setState({ loading: true });
+ getServerId().then(data => {
+ if (this.mounted) {
+ this.setState({ ...data, loading: false });
+ }
+ }).catch(error => this.handleError(error));
+ }
+
+ handleSubmit (e) {
+ e.preventDefault();
+ this.setState({ loading: true });
+ this.props.closeAllGlobalMessages();
+ generateServerId(this.state.organization, this.state.ip).then(data => {
+ if (this.mounted) {
+ this.setState({ serverId: data.serverId, invalidServerId: false, loading: false });
+ }
+ }).catch(error => this.handleError(error));
+ }
+
+ render () {
+ return (
+ <div id="server-id-page" className="page page-limited">
+ <header className="page-header">
+ <h1 className="page-title">{translate('property.category.server_id')}</h1>
+ {this.state.loading && <i className="spinner"/>}
+ <div className="page-description">{translate('server_id_configuration.information')}</div>
+ </header>
+
+ <GlobalMessagesContainer/>
+
+ {this.state.serverId != null && (
+ <div className={this.state.invalidServerId ? 'panel panel-danger' : 'panel'}>
+ Server ID:
+ <input
+ id="server-id-result"
+ className="spacer-left input-large"
+ type="text"
+ readOnly={true}
+ value={this.state.serverId}/>
+ {!!this.state.invalidServerId && (
+ <span className="spacer-left">{translate('server_id_configuration.bad_key')}</span>
+ )}
+ </div>
+ )}
+
+ <div className="panel">
+ <form id="server-id-form" onSubmit={e => this.handleSubmit(e)}>
+ <div className="modal-field">
+ <label htmlFor="server-id-organization">
+ {translate('server_id_configuration.organisation.title')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ id="server-id-organization"
+ type="text"
+ required
+ value={this.state.organization}
+ disabled={this.state.loading}
+ onChange={e => this.setState({ organization: e.target.value })}/>
+ <div className="modal-field-description">
+ {translate('server_id_configuration.organisation.desc')}
+ {'. '}
+ {translate('server_id_configuration.organisation.pattern')}
+ </div>
+ </div>
+
+ <div className="modal-field">
+ <label htmlFor="server-id-ip">
+ {translate('server_id_configuration.ip.title')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ id="server-id-ip"
+ type="text"
+ required
+ value={this.state.ip}
+ disabled={this.state.loading}
+ onChange={e => this.setState({ ip: e.target.value })}/>
+ <div className="modal-field-description">
+ {translate('server_id_configuration.ip.desc')}
+ <ul className="list-styled">
+ {this.state.validIpAdresses.map(ip => (
+ <li key={ip} className="little-spacer-top">{ip}</li>
+ ))}
+ </ul>
+ </div>
+ </div>
+
+ <div className="modal-field">
+ <button disabled={this.state.loading}>{translate('server_id_configuration.generate_button')}</button>
+ </div>
+ </form>
+ </div>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+import { connect } from 'react-redux';
+import ServerIdApp from './ServerIdApp';
+import { addGlobalErrorMessage, closeAllGlobalMessages } from '../../../components/store/globalMessages';
+
+export default connect(
+ () => ({}),
+ { addGlobalErrorMessage, closeAllGlobalMessages }
+)(ServerIdApp);
{this.renderLink('/settings', translate('settings.page'), url => window.location.pathname === url)}
{this.renderLink('/settings/licenses', translate('property.category.licenses'))}
{this.renderLink('/settings/encryption', translate('property.category.security.encryption'))}
+ {this.renderLink('/settings/server_id', translate('property.category.server_id'))}
{this.renderLink('/metrics', 'Custom Metrics')}
{this.renderLink('/admin_dashboards', translate('default_dashboards.page'))}
{this.props.extensions.map(e => this.renderLink(e.url, e.name))}
+++ /dev/null
-#
-# 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.
-#
-class ServerIdConfigurationController < ApplicationController
-
- SECTION=Navigation::SECTION_CONFIGURATION
- PROPERTY_SERVER_ID = 'sonar.server_id'
- PROPERTY_IP_ADDRESS = 'sonar.server_id.ip_address'
- PROPERTY_ORGANISATION = 'sonar.organisation'
-
- before_filter :admin_required
-
- def index
- @server_id = Property.value(PROPERTY_SERVER_ID)
- @organisation = Property.value(PROPERTY_ORGANISATION) || ''
- @address = Property.value(PROPERTY_IP_ADDRESS) || ''
- @valid_addresses = java_facade.getValidInetAddressesForServerId()
- @bad_id = false
-
- if @server_id.present?
- id = java_facade.generateServerId(@organisation, @address)
- @bad_id = (@server_id != id)
- end
- params[:layout]='false'
- end
-
- def generate
- verify_post_request
- organisation = params[:organisation].strip
- Property.set(PROPERTY_ORGANISATION, organisation)
- ip_address=params[:address].strip
- Property.set(PROPERTY_IP_ADDRESS, ip_address)
-
- if organisation.blank? || ip_address.blank?
- flash[:error] = Api::Utils.message('server_id_configuration.fields_cannot_be_blank')
- elsif !(organisation =~ /^[a-zA-Z0-9]+[a-zA-Z0-9 ]*$/)
- flash[:error] = Api::Utils.message('server_id_configuration.does_not_match_organisation_pattern')
- else
- id = java_facade.generateServerId(organisation, ip_address)
- if id
- Java::OrgSlf4j::LoggerFactory::getLogger('root').info("Generated new server ID=" + id);
- # Success!
- Property.set(PROPERTY_SERVER_ID, id)
- else
- # Something unexpected happened during the generation
- Property.clear(PROPERTY_SERVER_ID)
- flash[:error] = Api::Utils.message('server_id_configuration.generation_error')
- end
- end
-
- redirect_to :action => 'index'
- end
-end
def encryption
render :action => 'index'
end
+
+ def server_id
+ render :action => 'index'
+ end
end
+++ /dev/null
-<form method="post" action="<%= ApplicationController.root_context -%>/server_id_configuration/generate">
- <table class="marginbottom10">
- <thead>
- <tr>
- <td style="padding: 10px" colspan="2">
- <p><%= message('server_id_configuration.information') -%></p>
-
- <% if @server_id %>
- <p>
- <br/>
- <span class="<%= @bad_id ? 'error' : 'notice' -%>" id="server_id"><big><%= h @server_id -%></big></span>
- <% if @bad_id %>
- <span class="error"><%= message('server_id_configuration.bad_key') -%></span>
- <% end %>
- </p>
- <% end %>
- </td>
- </tr>
- </thead>
- <tbody>
- <tr class="property">
- <th>
- <h3><%= message('server_id_configuration.organisation.title') -%></h3>
- </th>
- <td>
- <input type="text" name="organisation" value="<%= h @organisation -%>" size="50"/>
- <br/>
- <p class="marginbottom10"><%= message('server_id_configuration.organisation.desc') -%></p>
- <span class="note"><%= message('server_id_configuration.organisation.pattern') -%></span>
- </td>
- </tr>
- <tr class="property">
- <th>
- <h3><%= message('server_id_configuration.ip.title') -%></h3>
- </th>
- <td>
- <input type="text" name="address" value="<%= h @address -%>"/>
- <p class="marginbottom10"><%= message('server_id_configuration.ip.desc') -%></p>
- <ul class="marginbottom10 bullet">
- <% @valid_addresses.each_with_index do |ip_address, index| %>
- <li><span id="address_<%= index -%>"><%= ip_address.getHostAddress() -%></span></li>
- <% end %>
- </ul>
- </td>
- </tr>
- </tbody>
- </table>
-
- <%= submit_tag message('server_id_configuration.generate_button'), :disable_with => message('server_id_configuration.generating_button'), :id => 'generate-button' -%>
-</form>
-
-