Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

CvalChecker.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.tools;
  17. import static java.lang.Integer.parseInt;
  18. import java.io.File;
  19. import java.io.FileNotFoundException;
  20. import java.io.IOException;
  21. import java.net.MalformedURLException;
  22. import java.net.URL;
  23. import java.net.URLConnection;
  24. import java.text.MessageFormat;
  25. import java.util.Arrays;
  26. import java.util.Date;
  27. import java.util.List;
  28. import java.util.Locale;
  29. import java.util.ResourceBundle;
  30. import java.util.prefs.Preferences;
  31. import elemental.json.JsonException;
  32. import elemental.json.JsonObject;
  33. import elemental.json.impl.JsonUtil;
  34. import org.apache.commons.io.IOUtils;
  35. /**
  36. * This class is able to validate the vaadin CVAL license.
  37. *
  38. * It reads the developer license file and asks the server to validate the
  39. * licenseKey. If the license is invalid it throws an exception with the
  40. * information about the problem and the server response.
  41. *
  42. * @since 7.3
  43. */
  44. public final class CvalChecker {
  45. /*
  46. * Class used for binding the JSON gotten from server.
  47. *
  48. * It is not in a separate f le, so as it is easier to copy into any product
  49. * which does not depend on vaadin core.
  50. *
  51. * We are using org.json in order not to use additional dependency like
  52. * auto-beans, gson, etc.
  53. */
  54. public static class CvalInfo {
  55. public static class Product {
  56. private JsonObject o;
  57. public Product(JsonObject o) {
  58. this.o = o;
  59. }
  60. public String getName() {
  61. return get(o, "name", String.class);
  62. }
  63. public Integer getVersion() {
  64. return get(o, "version", Integer.class);
  65. }
  66. }
  67. @SuppressWarnings("unchecked")
  68. private static <T> T get(JsonObject o, String k, Class<T> clz) {
  69. Object ret = null;
  70. try {
  71. if (clz == String.class) {
  72. ret = o.getString(k);
  73. } else if (clz == JsonObject.class) {
  74. ret = o.getObject(k);
  75. } else if (clz == Integer.class) {
  76. ret = Integer.valueOf((int) o.getNumber(k));
  77. } else if (clz == Date.class) {
  78. ret = new Date((long) o.getNumber(k));
  79. } else if (clz == Boolean.class) {
  80. ret = o.getBoolean(k);
  81. }
  82. } catch (JsonException e) {
  83. }
  84. return (T) ret;
  85. }
  86. private JsonObject o;
  87. private Product product;
  88. public CvalInfo(JsonObject o) {
  89. this.o = o;
  90. product = new Product(get(o, "product", JsonObject.class));
  91. }
  92. public Boolean getExpired() {
  93. return get(o, "expired", Boolean.class);
  94. }
  95. public Date getExpiredEpoch() {
  96. return get(o, "expiredEpoch", Date.class);
  97. }
  98. public String getLicensee() {
  99. return get(o, "licensee", String.class);
  100. }
  101. public String getLicenseKey() {
  102. return get(o, "licenseKey", String.class);
  103. }
  104. public String getMessage() {
  105. return get(o, "message", String.class);
  106. }
  107. public Product getProduct() {
  108. return product;
  109. }
  110. public String getType() {
  111. return get(o, "type", String.class);
  112. }
  113. public void setExpiredEpoch(Date expiredEpoch) {
  114. o.put("expiredEpoch", expiredEpoch.getTime());
  115. }
  116. public void setMessage(String msg) {
  117. o.put("message", msg);
  118. }
  119. @Override
  120. public String toString() {
  121. return o.toString();
  122. }
  123. public boolean isLicenseExpired() {
  124. return (getExpired() != null && getExpired())
  125. || (getExpiredEpoch() != null && getExpiredEpoch().before(
  126. new Date()));
  127. }
  128. public boolean isValidVersion(int majorVersion) {
  129. return getProduct().getVersion() == null
  130. || getProduct().getVersion() >= majorVersion;
  131. }
  132. private boolean isValidInfo(String name, String key) {
  133. return getProduct() != null && getProduct().getName() != null
  134. && getLicenseKey() != null
  135. && getProduct().getName().equals(name)
  136. && getLicenseKey().equals(key);
  137. }
  138. }
  139. /*
  140. * The class with the method for getting json from server side. It is here
  141. * and protected just for replacing it in tests.
  142. */
  143. public static class CvalServer {
  144. protected String licenseUrl = LICENSE_URL_PROD;
  145. String askServer(String productName, String productKey, int timeoutMs)
  146. throws IOException {
  147. String url = licenseUrl + productKey;
  148. URLConnection con;
  149. try {
  150. // Send some additional info in the User-Agent string.
  151. String ua = "Cval " + productName + " " + productKey + " "
  152. + getFirstLaunch();
  153. for (String prop : Arrays.asList("java.vendor.url",
  154. "java.version", "os.name", "os.version", "os.arch")) {
  155. ua += " " + System.getProperty(prop, "-").replace(" ", "_");
  156. }
  157. con = new URL(url).openConnection();
  158. con.setRequestProperty("User-Agent", ua);
  159. con.setConnectTimeout(timeoutMs);
  160. con.setReadTimeout(timeoutMs);
  161. String r = IOUtils.toString(con.getInputStream());
  162. return r;
  163. } catch (MalformedURLException e) {
  164. e.printStackTrace();
  165. return null;
  166. }
  167. }
  168. /*
  169. * Get the GWT firstLaunch timestamp.
  170. */
  171. String getFirstLaunch() {
  172. try {
  173. Class<?> clz = Class
  174. .forName("com.google.gwt.dev.shell.CheckForUpdates");
  175. return Preferences.userNodeForPackage(clz).get("firstLaunch",
  176. "-");
  177. } catch (ClassNotFoundException e) {
  178. return "-";
  179. }
  180. }
  181. }
  182. /**
  183. * Exception thrown when the user does not have a valid cval license.
  184. */
  185. public static class InvalidCvalException extends Exception {
  186. private static final long serialVersionUID = 1L;
  187. public final CvalInfo info;
  188. public final String name;
  189. public final String key;
  190. public final String version;
  191. public final String title;
  192. public InvalidCvalException(String name, String version, String title,
  193. String key, CvalInfo info) {
  194. super(composeMessage(title, version, key, info));
  195. this.info = info;
  196. this.name = name;
  197. this.key = key;
  198. this.version = version;
  199. this.title = title;
  200. }
  201. static String composeMessage(String title, String version, String key,
  202. CvalInfo info) {
  203. String msg = "";
  204. int majorVers = computeMajorVersion(version);
  205. if (info != null && info.getMessage() != null) {
  206. msg = info.getMessage().replace("\\n", "\n");
  207. } else if (info != null && info.isLicenseExpired()) {
  208. String type = "evaluation".equals(info.getType()) ? "Evaluation license"
  209. : "License";
  210. msg = getErrorMessage("expired", title, majorVers, type);
  211. } else if (key == null) {
  212. msg = getErrorMessage("none", title, majorVers);
  213. } else {
  214. msg = getErrorMessage("invalid", title, majorVers);
  215. }
  216. return msg;
  217. }
  218. }
  219. /**
  220. * Exception thrown when the license server is unreachable
  221. */
  222. public static class UnreachableCvalServerException extends Exception {
  223. private static final long serialVersionUID = 1L;
  224. public final String name;
  225. public UnreachableCvalServerException(String name, Exception e) {
  226. super(e);
  227. this.name = name;
  228. }
  229. }
  230. public static final String LINE = "----------------------------------------------------------------------------------------------------------------------";
  231. static final int GRACE_DAYS_MSECS = 2 * 24 * 60 * 60 * 1000;
  232. private static final String LICENSE_URL_PROD = "https://tools.vaadin.com/vaadin-license-server/licenses/";
  233. /*
  234. * used in tests
  235. */
  236. static void cacheLicenseInfo(CvalInfo info) {
  237. if (info != null) {
  238. Preferences p = Preferences.userNodeForPackage(CvalInfo.class);
  239. if (info.toString().length() > Preferences.MAX_VALUE_LENGTH) {
  240. // This should never happen since MAX_VALUE_LENGTH is big
  241. // enough.
  242. // But server could eventually send a very big message, so we
  243. // discard it in cache and would use hard-coded messages.
  244. info.setMessage(null);
  245. }
  246. p.put(info.getProduct().getName(), info.toString());
  247. }
  248. }
  249. /*
  250. * used in tests
  251. */
  252. static void deleteCache(String productName) {
  253. Preferences p = Preferences.userNodeForPackage(CvalInfo.class);
  254. p.remove(productName);
  255. }
  256. /**
  257. * Given a product name returns the name of the file with the license key.
  258. *
  259. * Traditionally we have delivered license keys with a name like
  260. * 'vaadin.touchkit.developer.license' but our database product name is
  261. * 'vaadin-touchkit' so we have to replace '-' by '.' to maintain
  262. * compatibility.
  263. */
  264. static final String computeLicenseName(String productName) {
  265. return productName.replace("-", ".") + ".developer.license";
  266. }
  267. static final int computeMajorVersion(String productVersion) {
  268. return productVersion == null || productVersion.isEmpty() ? 0
  269. : parseInt(productVersion.replaceFirst("[^\\d]+.*$", ""));
  270. }
  271. /*
  272. * used in tests
  273. */
  274. static CvalInfo parseJson(String json) {
  275. if (json == null) {
  276. return null;
  277. }
  278. try {
  279. JsonObject o = JsonUtil.parse(json);
  280. return new CvalInfo(o);
  281. } catch (JsonException e) {
  282. return null;
  283. }
  284. }
  285. private CvalServer provider;
  286. /**
  287. * The constructor.
  288. */
  289. public CvalChecker() {
  290. setLicenseProvider(new CvalServer());
  291. }
  292. /**
  293. * Validate whether there is a valid license key for a product.
  294. *
  295. * @param productName
  296. * for example vaadin-touchkit
  297. * @param productVersion
  298. * for instance 4.0.1
  299. * @return CvalInfo Server response or cache response if server is offline
  300. * @throws InvalidCvalException
  301. * when there is no a valid license for the product
  302. * @throws UnreachableCvalServerException
  303. * when we have license key but server is unreachable
  304. */
  305. public CvalInfo validateProduct(String productName, String productVersion,
  306. String productTitle) throws InvalidCvalException,
  307. UnreachableCvalServerException {
  308. String key = getDeveloperLicenseKey(productName, productVersion,
  309. productTitle);
  310. CvalInfo info = null;
  311. if (key != null && !key.isEmpty()) {
  312. info = getCachedLicenseInfo(productName);
  313. if (info != null && !info.isValidInfo(productName, key)) {
  314. deleteCache(productName);
  315. info = null;
  316. }
  317. info = askLicenseServer(productName, key, productVersion, info);
  318. if (info != null && info.isValidInfo(productName, key)
  319. && info.isValidVersion(computeMajorVersion(productVersion))
  320. && !info.isLicenseExpired()) {
  321. return info;
  322. }
  323. }
  324. throw new InvalidCvalException(productName, productVersion,
  325. productTitle, key, info);
  326. }
  327. /*
  328. * Change the license provider, only used in tests.
  329. */
  330. final CvalChecker setLicenseProvider(CvalServer p) {
  331. provider = p;
  332. return this;
  333. }
  334. private CvalInfo askLicenseServer(String productName, String productKey,
  335. String productVersion, CvalInfo info)
  336. throws UnreachableCvalServerException {
  337. int majorVersion = computeMajorVersion(productVersion);
  338. // If we have a valid license info here, it means that we got it from
  339. // cache.
  340. // We add a grace time when so as if the server is unreachable
  341. // we allow the user to use the product.
  342. if (info != null && info.getExpiredEpoch() != null
  343. && !"evaluation".equals(info.getType())) {
  344. long ts = info.getExpiredEpoch().getTime() + GRACE_DAYS_MSECS;
  345. info.setExpiredEpoch(new Date(ts));
  346. }
  347. boolean validCache = info != null
  348. && info.isValidInfo(productName, productKey)
  349. && info.isValidVersion(majorVersion)
  350. && !info.isLicenseExpired();
  351. // if we have a validCache we set the timeout smaller
  352. int timeout = validCache ? 2000 : 10000;
  353. try {
  354. CvalInfo srvinfo = parseJson(provider.askServer(productName + "-"
  355. + productVersion, productKey, timeout));
  356. if (srvinfo != null && srvinfo.isValidInfo(productName, productKey)
  357. && srvinfo.isValidVersion(majorVersion)) {
  358. // We always cache the info if it is valid although it is
  359. // expired
  360. cacheLicenseInfo(srvinfo);
  361. info = srvinfo;
  362. }
  363. } catch (FileNotFoundException e) {
  364. // 404
  365. return null;
  366. } catch (Exception e) {
  367. if (info == null) {
  368. throw new UnreachableCvalServerException(productName, e);
  369. }
  370. }
  371. return info;
  372. }
  373. private CvalInfo getCachedLicenseInfo(String productName) {
  374. Preferences p = Preferences.userNodeForPackage(CvalInfo.class);
  375. String json = p.get(productName, "");
  376. if (!json.isEmpty()) {
  377. CvalInfo info = parseJson(json);
  378. if (info != null) {
  379. return info;
  380. }
  381. }
  382. return null;
  383. }
  384. private String getDeveloperLicenseKey(String productName,
  385. String productVersion, String productTitle)
  386. throws InvalidCvalException {
  387. String licenseName = computeLicenseName(productName);
  388. String key = System.getProperty(licenseName);
  389. if (key != null && !key.isEmpty()) {
  390. return key;
  391. }
  392. try {
  393. String dotLicenseName = "." + licenseName;
  394. String userHome = System.getProperty("user.home");
  395. for (URL url : new URL[] {
  396. new File(userHome, dotLicenseName).toURI().toURL(),
  397. new File(userHome, licenseName).toURI().toURL(),
  398. URL.class.getResource("/" + dotLicenseName),
  399. URL.class.getResource("/" + licenseName) }) {
  400. if (url != null) {
  401. try {
  402. key = readKeyFromFile(url,
  403. computeMajorVersion(productVersion));
  404. if (key != null && !(key = key.trim()).isEmpty()) {
  405. return key;
  406. }
  407. } catch (IOException ignored) {
  408. }
  409. }
  410. }
  411. } catch (Exception e) {
  412. e.printStackTrace();
  413. }
  414. throw new InvalidCvalException(productName, productVersion,
  415. productTitle, null, null);
  416. }
  417. String readKeyFromFile(URL url, int majorVersion) throws IOException {
  418. String majorVersionStr = String.valueOf(majorVersion);
  419. List<String> lines = IOUtils.readLines(url.openStream());
  420. String defaultKey = null;
  421. for (String line : lines) {
  422. String[] parts = line.split("\\s*=\\s*");
  423. if (parts.length < 2) {
  424. defaultKey = parts[0].trim();
  425. }
  426. if (parts[0].equals(majorVersionStr)) {
  427. return parts[1].trim();
  428. }
  429. }
  430. return defaultKey;
  431. }
  432. static String getErrorMessage(String key, Object... pars) {
  433. Locale loc = Locale.getDefault();
  434. ResourceBundle res = ResourceBundle.getBundle(
  435. CvalChecker.class.getName(), loc);
  436. String msg = res.getString(key);
  437. return new MessageFormat(msg, loc).format(pars);
  438. }
  439. }