File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\model\UsageStatistics.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.model;
25
26 import com.trilead.ssh2.crypto.Base64;
27 import hudson.PluginWrapper;
28 import hudson.Util;
29 import hudson.Extension;
30 import hudson.node_monitors.ArchitectureMonitor.DescriptorImpl;
31 import hudson.util.Secret;
32 import static hudson.util.TimeUnit2.DAYS;
33
34 import jenkins.model.Jenkins;
35 import net.sf.json.JSONObject;
36 import org.apache.commons.io.output.ByteArrayOutputStream;
37 import org.kohsuke.stapler.StaplerRequest;
38
39 import javax.crypto.Cipher;
40 import javax.crypto.CipherOutputStream;
41 import javax.crypto.KeyGenerator;
42 import javax.crypto.SecretKey;
43 import javax.crypto.CipherInputStream;
44 import javax.crypto.spec.IvParameterSpec;
45 import javax.crypto.spec.SecretKeySpec;
46 import java.io.IOException;
47 import java.io.OutputStreamWriter;
48 import java.io.FilterOutputStream;
49 import java.io.OutputStream;
50 import java.io.FilterInputStream;
51 import java.io.InputStream;
52 import java.io.DataInputStream;
53 import java.security.GeneralSecurityException;
54 import java.security.Key;
55 import java.security.KeyFactory;
56 import java.security.PublicKey;
57 import java.security.interfaces.RSAKey;
58 import java.security.interfaces.RSAPublicKey;
59 import java.security.spec.X509EncodedKeySpec;
60 import java.util.ArrayList;
61 import java.util.List;
62 import java.util.zip.GZIPOutputStream;
63
64 /**
65 * @author Kohsuke Kawaguchi
66 */
67 @Extension
68 public class UsageStatistics extends PageDecorator {
69 private final String keyImage;
70
71 /**
72 * Lazily computed {@link PublicKey} representation of {@link #keyImage}.
73 */
74 private volatile transient RSAPublicKey key;
75
76 /**
77 * When was the last time we asked a browser to send the usage stats for us?
78 */
79 private volatile transient long lastAttempt = -1;
80
81 public UsageStatistics() {
82 this(DEFAULT_KEY_BYTES);
83 }
84
85 /**
86 * Creates an instance with a specific public key image.
87 */
88 public UsageStatistics(String keyImage) {
89 this.keyImage = keyImage;
90 load();
91 }
92
93 /**
94 * Returns true if it's time for us to check for new version.
95 */
96 public boolean isDue() {
97 // user opted out. no data collection.
98 if(!Jenkins.getInstance().isUsageStatisticsCollected() || DISABLED) return false;
99
100 long now = System.currentTimeMillis();
101 if(now - lastAttempt > DAY) {
102 lastAttempt = now;
103 return true;
104 }
105 return false;
106 }
107
108 private RSAPublicKey getKey() {
109 try {
110 if (key == null) {
111 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
112 key = (RSAPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(Util.fromHexString(keyImage)));
113 }
114 return key;
115 } catch (GeneralSecurityException e) {
116 throw new Error(e); // impossible
117 }
118 }
119
120 /**
121 * Gets the encrypted usage stat data to be sent to the Hudson server.
122 */
123 public String getStatData() throws IOException {
124 Jenkins j = Jenkins.getInstance();
125
126 JSONObject o = new JSONObject();
127 o.put("stat",1);
128 o.put("install", j.getLegacyInstanceId());
129 o.put("servletContainer", j.servletContext.getServerInfo());
130 o.put("version", Jenkins.VERSION);
131
132 List<JSONObject> nodes = new ArrayList<JSONObject>();
133 for( Computer c : j.getComputers() ) {
134 JSONObject n = new JSONObject();
135 if(c.getNode()==j) {
136 n.put("master",true);
137 n.put("jvm-vendor", System.getProperty("java.vm.vendor"));
138 n.put("jvm-version", System.getProperty("java.version"));
139 }
140 n.put("executors",c.getNumExecutors());
141 DescriptorImpl descriptor = j.getDescriptorByType(DescriptorImpl.class);
142 n.put("os", descriptor.get(c));
143 nodes.add(n);
144 }
145 o.put("nodes",nodes);
146
147 List<JSONObject> plugins = new ArrayList<JSONObject>();
148 for( PluginWrapper pw : j.getPluginManager().getPlugins() ) {
149 if(!pw.isActive()) continue; // treat disabled plugins as if they are uninstalled
150 JSONObject p = new JSONObject();
151 p.put("name",pw.getShortName());
152 p.put("version",pw.getVersion());
153 plugins.add(p);
154 }
155 o.put("plugins",plugins);
156
157 JSONObject jobs = new JSONObject();
158 List<TopLevelItem> items = j.getItems();
159 for (TopLevelItemDescriptor d : Items.all()) {
160 int cnt=0;
161 for (TopLevelItem item : items) {
162 if(item.getDescriptor()==d)
163 cnt++;
164 }
165 jobs.put(d.getJsonSafeClassName(),cnt);
166 }
167 o.put("jobs",jobs);
168
169 try {
170 ByteArrayOutputStream baos = new ByteArrayOutputStream();
171
172 // json -> UTF-8 encode -> gzip -> encrypt -> base64 -> string
173 OutputStreamWriter w = new OutputStreamWriter(new GZIPOutputStream(new CombinedCipherOutputStream(baos,getKey(),"AES")), "UTF-8");
174 o.write(w);
175 w.close();
176
177 return new String(Base64.encode(baos.toByteArray()));
178 } catch (GeneralSecurityException e) {
179 throw new Error(e); // impossible
180 }
181 }
182
183 @Override
184 public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
185 try {
186 // for backward compatibility reasons, this configuration is stored in Jenkins
187 Jenkins.getInstance().setNoUsageStatistics(json.has("usageStatisticsCollected") ? null : true);
188 return true;
189 } catch (IOException e) {
190 throw new FormException(e,"usageStatisticsCollected");
191 }
192 }
193
194 /**
195 * Assymetric cipher is slow and in case of Sun RSA implementation it can only encyrypt the first block.
196 *
197 * So first create a symmetric key, then place this key in the beginning of the stream by encrypting it
198 * with the assymetric cipher. The rest of the stream will be encrypted by a symmetric cipher.
199 */
200 public static final class CombinedCipherOutputStream extends FilterOutputStream {
201 public CombinedCipherOutputStream(OutputStream out, Cipher asym, String algorithm) throws IOException, GeneralSecurityException {
202 super(out);
203
204 // create a new symmetric cipher key used for this stream
205 String keyAlgorithm = getKeyAlgorithm(algorithm);
206 SecretKey symKey = KeyGenerator.getInstance(keyAlgorithm).generateKey();
207
208 // place the symmetric key by encrypting it with asymmetric cipher
209 out.write(asym.doFinal(symKey.getEncoded()));
210
211 // the rest of the data will be encrypted by this symmetric cipher
212 Cipher sym = Secret.getCipher(algorithm);
213 sym.init(Cipher.ENCRYPT_MODE,symKey, keyAlgorithm.equals(algorithm) ? null : new IvParameterSpec(symKey.getEncoded()));
214 super.out = new CipherOutputStream(out,sym);
215 }
216
217 public CombinedCipherOutputStream(OutputStream out, RSAKey key, String algorithm) throws IOException, GeneralSecurityException {
218 this(out,toCipher(key,Cipher.ENCRYPT_MODE),algorithm);
219 }
220 }
221
222 /**
223 * The opposite of the {@link CombinedCipherOutputStream}.
224 */
225 public static final class CombinedCipherInputStream extends FilterInputStream {
226 /**
227 * @param keyLength
228 * Block size of the asymmetric cipher, in bits. I thought I can get it from {@code asym.getBlockSize()}
229 * but that doesn't work with Sun's implementation.
230 */
231 public CombinedCipherInputStream(InputStream in, Cipher asym, String algorithm, int keyLength) throws IOException, GeneralSecurityException {
232 super(in);
233
234 String keyAlgorithm = getKeyAlgorithm(algorithm);
235
236 // first read the symmetric key cipher
237 byte[] symKeyBytes = new byte[keyLength/8];
238 new DataInputStream(in).readFully(symKeyBytes);
239 SecretKey symKey = new SecretKeySpec(asym.doFinal(symKeyBytes),keyAlgorithm);
240
241 // the rest of the data will be decrypted by this symmetric cipher
242 Cipher sym = Secret.getCipher(algorithm);
243 sym.init(Cipher.DECRYPT_MODE,symKey, keyAlgorithm.equals(algorithm) ? null : new IvParameterSpec(symKey.getEncoded()));
244 super.in = new CipherInputStream(in,sym);
245 }
246
247 public CombinedCipherInputStream(InputStream in, RSAKey key, String algorithm) throws IOException, GeneralSecurityException {
248 this(in,toCipher(key,Cipher.DECRYPT_MODE),algorithm,key.getModulus().bitLength());
249 }
250 }
251
252 private static String getKeyAlgorithm(String algorithm) {
253 int index = algorithm.indexOf('/');
254 return (index>0)?algorithm.substring(0,index):algorithm;
255 }
256
257 private static Cipher toCipher(RSAKey key, int mode) throws GeneralSecurityException {
258 Cipher cipher = Cipher.getInstance("RSA");
259 cipher.init(mode, (Key)key);
260 return cipher;
261 }
262
263 /**
264 * Public key to encrypt the usage statistics
265 */
266 private static final String DEFAULT_KEY_BYTES = "30819f300d06092a864886f70d010101050003818d0030818902818100c14970473bd90fd1f2d20e4fa6e36ea21f7d46db2f4104a3a8f2eb097d6e26278dfadf3fe9ed05bbbb00a4433f4b7151e6683a169182e6ff2f6b4f2bb6490b2cddef73148c37a2a7421fc75f99fb0fadab46f191806599a208652f4829fd6f76e13195fb81ff3f2fce15a8e9a85ebe15c07c90b34ebdb416bd119f0d74105f3b0203010001";
267
268 private static final long DAY = DAYS.toMillis(1);
269
270 public static boolean DISABLED = Boolean.getBoolean(UsageStatistics.class.getName()+".disabled");
271 }
272