*/
package org.sonar.api;
+/**
+ * @since 2.15
+ */
public enum PropertyType {
- STRING, TEXT, PASSWORD, BOOLEAN, INTEGER, FLOAT, SINGLE_SELECT_LIST
+ /**
+ * Basic single line input field
+ */
+ STRING,
+
+ /**
+ * Multiple line text-area
+ */
+ TEXT,
+
+ /**
+ * Variation of {#STRING} with masked characters
+ */
+ PASSWORD,
+
+ /**
+ * True/False
+ */
+ BOOLEAN,
+
+ /**
+ * Integer value, positive or negative
+ */
+ INTEGER,
+
+ /**
+ * Floating point number
+ */
+ FLOAT,
+
+ /**
+ * Single select list with a list of options
+ */
+ SINGLE_SELECT_LIST,
+
+ /**
+ * Sonar Metric
+ */
+ METRIC,
+
+ /**
+ * SonarSource license
+ */
+ LICENSE
}
--- /dev/null
+/*
+ * 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.config;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Maps;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.utils.DateUtils;
+
+import javax.annotation.Nullable;
+import java.io.StringReader;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * SonarSource license. This class aims to extract metadata but not to validate or - of course -
+ * to generate license
+ *
+ * @since 2.15
+ */
+public final class License {
+ private String product;
+ private String organization;
+ private String expirationDate;
+ private String type;
+ private String server;
+
+ private License(Map<String, String> properties) {
+ product = StringUtils.defaultString(properties.get("Product"), properties.get("Plugin"));
+ organization = StringUtils.defaultString(properties.get("Organisation"), properties.get("Name"));
+ expirationDate = StringUtils.defaultString(properties.get("Expiration"), properties.get("Expires"));
+ type = properties.get("Type");
+ server = properties.get("Server");
+ }
+
+ @Nullable
+ public String getProduct() {
+ return product;
+ }
+
+ @Nullable
+ public String getOrganization() {
+ return organization;
+ }
+
+ @Nullable
+ public String getExpirationDateAsString() {
+ return expirationDate;
+ }
+
+ @Nullable
+ public Date getExpirationDate() {
+ return DateUtils.parseDateQuietly(expirationDate);
+ }
+
+ public boolean isExpired() {
+ return isExpired(new Date());
+ }
+
+ @VisibleForTesting
+ boolean isExpired(Date now) {
+ Date date = getExpirationDate();
+ return date != null && !date.after(org.apache.commons.lang.time.DateUtils.truncate(now, Calendar.DATE));
+ }
+
+ @Nullable
+ public String getType() {
+ return type;
+ }
+
+ @Nullable
+ public String getServer() {
+ return server;
+ }
+
+ public static License readBase64(String base64) {
+ return readPlainText(new String(Base64.decodeBase64(base64.getBytes())));
+ }
+
+ @VisibleForTesting
+ static License readPlainText(String data) {
+ Map<String, String> props = Maps.newHashMap();
+ StringReader reader = new StringReader(data);
+ try {
+ List<String> lines = IOUtils.readLines(reader);
+ for (String line : lines) {
+ if (StringUtils.isNotBlank(line) && line.indexOf(':') > 0) {
+ String key = StringUtils.substringBefore(line, ":");
+ String value = StringUtils.substringAfter(line, ":");
+ props.put(StringUtils.trimToEmpty(key), StringUtils.trimToEmpty(value));
+ }
+ }
+
+ } catch (Exception e) {
+ // silently ignore
+
+ } finally {
+ IOUtils.closeQuietly(reader);
+ }
+ return new License(props);
+ }
+}
}
private PropertyType fixType(String key, PropertyType type) {
- // Auto-detect passwords for old versions of plugins that
- // do not declare the type
- if (type==PropertyType.STRING && StringUtils.endsWith(key, ".password.secured")) {
- return PropertyType.PASSWORD;
+ // Auto-detect passwords and licenses for old versions of plugins that
+ // do not declare property types
+ PropertyType fix = type;
+ if (type == PropertyType.STRING) {
+ if (StringUtils.endsWith(key, ".password.secured")) {
+ fix = PropertyType.PASSWORD;
+ } else if (StringUtils.endsWith(key, ".license.secured")) {
+ fix = PropertyType.LICENSE;
+ }
}
- return type;
+ return fix;
}
@VisibleForTesting
--- /dev/null
+/*
+ * 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.config;
+
+import org.apache.commons.codec.binary.Base64;
+import org.hamcrest.core.Is;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+public class LicenseTest {
+
+ private static final String V2_FORMAT = "Foo: bar\n" +
+ "Organisation: ABC \n" +
+ "Server: 12345 \n" +
+ "Product: SQALE\n" +
+ " Expiration: 2012-05-18 \n" +
+ "Type: EVALUATION \n" +
+ "Other: field\n";
+
+ private static final String V1_FORMAT = "Foo: bar\n" +
+ "Name: ABC \n" +
+ "Plugin: SQALE\n" +
+ " Expires: 2012-05-18 \n" +
+ "Other: field\n";
+
+ @Test
+ public void readPlainTest() {
+ License license = License.readPlainText(V2_FORMAT);
+
+ assertThat(license.getOrganization(), Is.is("ABC"));
+ assertThat(license.getServer(), Is.is("12345"));
+ assertThat(license.getProduct(), Is.is("SQALE"));
+ assertThat(license.getExpirationDateAsString(), Is.is("2012-05-18"));
+ assertThat(license.getType(), Is.is("EVALUATION"));
+ }
+
+ @Test
+ public void readPlainText_empty_fields() {
+ License license = License.readPlainText("");
+
+ assertThat(license.getOrganization(), nullValue());
+ assertThat(license.getServer(), nullValue());
+ assertThat(license.getProduct(), nullValue());
+ assertThat(license.getExpirationDateAsString(), nullValue());
+ assertThat(license.getExpirationDate(), nullValue());
+ assertThat(license.getType(), nullValue());
+ }
+
+ @Test
+ public void readPlainText_not_valid_input() {
+ License license = License.readPlainText("old pond ... a frog leaps in water’s sound");
+
+ assertThat(license.getOrganization(), nullValue());
+ assertThat(license.getServer(), nullValue());
+ assertThat(license.getProduct(), nullValue());
+ assertThat(license.getExpirationDateAsString(), nullValue());
+ assertThat(license.getExpirationDate(), nullValue());
+ assertThat(license.getType(), nullValue());
+ }
+
+ @Test
+ public void readPlainTest_version_1() {
+ License license = License.readPlainText(V1_FORMAT);
+
+ assertThat(license.getOrganization(), Is.is("ABC"));
+ assertThat(license.getServer(), nullValue());
+ assertThat(license.getProduct(), Is.is("SQALE"));
+ assertThat(license.getExpirationDateAsString(), Is.is("2012-05-18"));
+ assertThat(license.getType(), nullValue());
+ }
+
+ @Test
+ public void readBase64() {
+ License license = License.readBase64(new String(Base64.encodeBase64(V2_FORMAT.getBytes())));
+
+ assertThat(license.getOrganization(), Is.is("ABC"));
+ assertThat(license.getServer(), Is.is("12345"));
+ assertThat(license.getProduct(), Is.is("SQALE"));
+ assertThat(license.getExpirationDateAsString(), Is.is("2012-05-18"));
+ assertThat(license.getType(), Is.is("EVALUATION"));
+ }
+
+ @Test
+ public void readBase64_not_base64() {
+ License license = License.readBase64("çé '123$@");
+
+ assertThat(license.getOrganization(), nullValue());
+ assertThat(license.getServer(), nullValue());
+ assertThat(license.getProduct(), nullValue());
+ assertThat(license.getExpirationDateAsString(), nullValue());
+ assertThat(license.getExpirationDate(), nullValue());
+ assertThat(license.getType(), nullValue());
+ }
+
+ @Test
+ public void isExpired() {
+ License license = License.readPlainText(V2_FORMAT);
+
+ assertThat(license.isExpired(DateUtils.parseDate("2013-06-23")), is(true));
+ assertThat(license.isExpired(DateUtils.parseDate("2012-05-18")), is(true));
+ assertThat(license.isExpired(DateUtils.parseDateTime("2012-05-18T15:50:45+0100")), is(true));
+ assertThat(license.isExpired(DateUtils.parseDate("2011-01-01")), is(false));
+ }
+}
}
@Test
- public void autodetectPasswordType() {
+ public void autoDetectPasswordType() {
Properties props = AnnotationUtils.getClassAnnotation(OldScmPlugin.class, Properties.class);
Property prop = props.value()[0];
assertThat(def.getKey(), Is.is("scm.password.secured"));
assertThat(def.getType(), Is.is(PropertyType.PASSWORD));
}
+
+ @Properties({
+ @Property(key = "views.license.secured", name = "Views license")
+ })
+ static class ViewsPlugin {
+ }
+
+ @Test
+ public void autoDetectLicenseType() {
+ Properties props = AnnotationUtils.getClassAnnotation(ViewsPlugin.class, Properties.class);
+ Property prop = props.value()[0];
+
+ PropertyDefinition def = PropertyDefinition.create(prop);
+
+ assertThat(def.getKey(), Is.is("views.license.secured"));
+ assertThat(def.getType(), Is.is(PropertyType.LICENSE));
+ }
}
import org.apache.commons.configuration.Configuration;
import org.slf4j.LoggerFactory;
+import org.sonar.api.config.License;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
import org.sonar.api.platform.ComponentContainer;
public String generateRandomSecretKey() {
return getContainer().getComponentByType(Settings.class).getEncryption().generateRandomSecretKey();
}
+
+ public License parseLicense(String base64) {
+ return License.readBase64(base64);
+ }
public ReviewsNotificationManager getReviewsNotificationManager() {
value = Property.value(property.getKey(), (@project ? @project.id : nil), '') unless value
# for backward-compatibility with properties that do not define the type TEXT
- property_type = value.include?("\n") ? 'TEXT' : property.getType()
+ property_type = property.getType()
+ if property_type.to_s=='STRING' && value.include?("\n")
+ property_type = 'TEXT'
+ end
+
%>
<tr class="<%= cycle('even', 'odd', :name => 'properties') -%>">
- <td style="padding: 10px" id="foo_<%= property.getKey() -%>">
+ <td style="padding: 10px" id="block_<%= property.getKey() -%>">
<h3>
<%= message("property.#{property.key()}.name", :default => property.name()) -%>
<br/><span class="note"><%= property.getKey() -%></span>
-<select name="<%= h property.key -%>" id="input_<%= h property.key-%>">
+<select name="<%= h property.getKey() -%>" id="input_<%= h property.getKey() -%>">
<option value="" <%= 'selected' if value.blank? -%>><%= message('default') -%></option>
<option value="true" <%= 'selected' if value=='true' -%>><%= message('true') -%></option>
<option value="false" <%= 'selected' if value=='false' -%>><%= message('false') -%></option>
-<input type="text" name="<%= h property.key -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.key-%>"/>
\ No newline at end of file
+<input type="text" name="<%= h property.getKey() -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.getKey() -%>"/>
\ No newline at end of file
-<input type="text" name="<%= h property.key -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.key-%>"/>
\ No newline at end of file
+<input type="text" name="<%= h property.getKey() -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.getKey()-%>"/>
\ No newline at end of file
--- /dev/null
+<% if !value || value.blank? %>
+ <textarea rows="5" cols="80" class="width100" name="<%= h property.getKey() -%>" id="input_<%= h property.getKey() -%>"></textarea>
+<%
+ else
+ license = controller.java_facade.parseLicense(value)
+ date = license.getExpirationDateAsString()
+%>
+ <div class="width100">
+ <textarea rows="6" name="<%= h property.getKey() -%>" id="input_<%= h property.getKey() -%>" style="float: left;width: 390px"><%= h value -%></textarea>
+
+ <div style="margin-left: 400px">
+ <table>
+ <tr>
+ <td class="form-key-cell">Product:</td>
+ <td class="form-val-cell"><%= license.getProduct() || '-' -%></td>
+ </tr>
+ <tr>
+ <td class="form-key-cell">Organization:</td>
+ <td><%= license.getOrganization() || '-' -%></td>
+ </tr>
+ <tr>
+ <td class="form-key-cell">Expiration:</td>
+ <td>
+ <% if license.getExpirationDateAsString()
+ formatted_date = l(Date.parse(license.getExpirationDateAsString()))
+ %>
+ <%= license.isExpired() ? "<span class='error'>#{formatted_date}</span>" : formatted_date -%>
+ <% else %>
+ -
+ <% end %>
+
+ </td>
+ </tr>
+ <tr>
+ <td class="form-key-cell">Type:</td>
+ <td><%= license.getType() || '-' -%></td>
+ </tr>
+ <tr>
+ <td class="form-key-cell">Server:</td>
+ <td><%= license.getServer() || '-' -%></td>
+ </tr>
+ </table>
+ </div>
+ </div>
+<% end %>
\ No newline at end of file
--- /dev/null
+<select name="<%= h property.getKey() -%>" id="input_<%= h property.getKey() -%>">
+ <option value=""><%= message('default') -%></option>
+ <%
+ metrics_per_domain={}
+ Metric.all.select { |m| m.display? }.sort_by { |m| m.short_name }.each do |metric|
+ domain=metric.domain || ''
+ metrics_per_domain[domain]||=[]
+ metrics_per_domain[domain]<<metric
+ end
+
+ metrics_per_domain.keys.sort.each do |domain|
+%>
+
+ <optgroup label="<%= h domain -%>">
+<%
+ metrics_per_domain[domain].each do |m|
+ selected_attr = (m.key==value || m.id==value) ? " selected='selected'" : ''
+%>
+ <option value="<%= m.key -%>" <%= selected_attr -%>><%= m.short_name -%></option>
+<%
+ end
+%>
+ </optgroup>
+<%
+ end
+%>
+</select>
\ No newline at end of file
-<input type="password" name="<%= h property.key -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.key-%>"/>
\ No newline at end of file
+<input type="password" name="<%= h property.getKey() -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.getKey() -%>"/>
\ No newline at end of file
-<select name="<%= h property.key -%>" id="input_<%= h property.key-%>">
+<select name="<%= h property.getKey() -%>" id="input_<%= h property.key-%>">
<option value=""><%= message('default') -%></option>
<% property.options.each do |option| %>
<option value="<%= h option -%>" <%= 'selected' if value && value==option -%>><%= h option -%></option>
-<input type="text" name="<%= h property.key -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.key-%>"/>
-<%= link_to_function(image_tag('zoom.png'), "enlargeTextInput('#{property.key}')", :class => 'nolink') -%>
\ No newline at end of file
+<input type="text" name="<%= h property.getKey() -%>" value="<%= h value if value -%>" size="50" id="input_<%= h property.getKey() -%>"/>
+<%= link_to_function(image_tag('zoom.png'), "enlargeTextInput('#{property.getKey()}')", :class => 'nolink') -%>
\ No newline at end of file
-<textarea rows="5" cols="80" class="width100" name="<%= h property.key -%>" id="input_<%= h property.key-%>"><%= h value if value -%></textarea>
\ No newline at end of file
+<textarea rows="5" cols="80" class="width100" name="<%= h property.getKey() -%>" id="input_<%= h property.getKey() -%>"><%= h value if value -%></textarea>
\ No newline at end of file