Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(385)

Side by Side Diff: Source/devtools/front_end/security/SecurityPanel.js

Issue 1301833003: Add origin views to the Security panel. (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @constructor 6 * @constructor
7 * @extends {WebInspector.PanelWithSidebar} 7 * @extends {WebInspector.PanelWithSidebar}
8 * @implements {WebInspector.TargetManager.Observer} 8 * @implements {WebInspector.TargetManager.Observer}
9 */ 9 */
10 WebInspector.SecurityPanel = function() { 10 WebInspector.SecurityPanel = function()
11 {
11 WebInspector.PanelWithSidebar.call(this, "security"); 12 WebInspector.PanelWithSidebar.call(this, "security");
12 this.registerRequiredCSS("security/securityPanel.css"); 13 this.registerRequiredCSS("security/securityPanel.css");
13 this.registerRequiredCSS("security/lockIcon.css"); 14 this.registerRequiredCSS("security/lockIcon.css");
14 15
15 var sidebarTree = new TreeOutlineInShadow(); 16 var sidebarTree = new TreeOutlineInShadow();
16 sidebarTree.element.classList.add("sidebar-tree"); 17 sidebarTree.element.classList.add("sidebar-tree");
17 this.panelSidebarElement().appendChild(sidebarTree.element); 18 this.panelSidebarElement().appendChild(sidebarTree.element);
19 sidebarTree.registerRequiredCSS("security/sidebar.css");
18 sidebarTree.registerRequiredCSS("security/lockIcon.css"); 20 sidebarTree.registerRequiredCSS("security/lockIcon.css");
19 this.setDefaultFocusedElement(sidebarTree.element); 21 this.setDefaultFocusedElement(sidebarTree.element);
20 22
21 this._sidebarMainViewElement = new WebInspector.SecurityMainViewSidebarTreeE lement(this); 23 this._sidebarMainViewElement = new WebInspector.SecurityMainViewSidebarTreeE lement(this);
22 sidebarTree.appendChild(this._sidebarMainViewElement); 24 sidebarTree.appendChild(this._sidebarMainViewElement);
23 25
26 // TODO(lgarron): Add a section for the main origin.
27 this._sidebarOriginSection = new WebInspector.SidebarSectionTreeElement(WebI nspector.UIString("Origins"));
28 this._sidebarOriginSection.listItemElement.classList.add("security-sidebar-o rigins");
29 sidebarTree.appendChild(this._sidebarOriginSection);
30
24 this._mainView = new WebInspector.SecurityMainView(); 31 this._mainView = new WebInspector.SecurityMainView();
25 this.showMainView();
26 32
27 /** @type {!Map<string, !{securityState: !SecurityAgent.SecurityState, secur ityDetails: ?NetworkAgent.SecurityDetails}>} */ 33 /** @type {!Map<string, !{securityState: !SecurityAgent.SecurityState, secur ityDetails: ?NetworkAgent.SecurityDetails}>} */
28 this._origins = new Map(); 34 this._origins = new Map();
29 WebInspector.targetManager.addEventListener(WebInspector.ResourceTreeModel.E ventTypes.InspectedURLChanged, this._clear, this); 35 // TODO(lgarron): Until we can clear the panel properly (https://crbug.com/5 22762), don't trigger _clear().
30 WebInspector.targetManager.addEventListener(WebInspector.ResourceTreeModel.E ventTypes.WillReloadPage, this._clear, this); 36 // WebInspector.targetManager.addEventListener(WebInspector.ResourceTreeMode l.EventTypes.MainFrameNavigated, this._clear, this);
dgozman 2015/08/21 19:44:36 Do not commit commented code. Better mention in co
lgarron 2015/08/21 21:50:06 I don't want to uncomment the MainFrameNavigated e
31 WebInspector.targetManager.addEventListener(WebInspector.ResourceTreeModel.E ventTypes.MainFrameNavigated, this._clear, this);
32 37
33 WebInspector.targetManager.observeTargets(this); 38 WebInspector.targetManager.observeTargets(this);
34 } 39 }
35 40
36 WebInspector.SecurityPanel.prototype = { 41 WebInspector.SecurityPanel.prototype = {
37 42
38 /** 43 /**
39 * @param {!SecurityAgent.SecurityState} newSecurityState 44 * @param {!SecurityAgent.SecurityState} newSecurityState
40 * @param {!Array<!SecurityAgent.SecurityStateExplanation>} explanations 45 * @param {!Array<!SecurityAgent.SecurityStateExplanation>} explanations
41 */ 46 */
(...skipping 12 matching lines...) Expand all
54 var explanations = /** @type {!Array<!SecurityAgent.SecurityStateExplana tion>} */ (event.data.explanations); 59 var explanations = /** @type {!Array<!SecurityAgent.SecurityStateExplana tion>} */ (event.data.explanations);
55 this._updateSecurityState(securityState, explanations); 60 this._updateSecurityState(securityState, explanations);
56 }, 61 },
57 62
58 showMainView: function() 63 showMainView: function()
59 { 64 {
60 this._setVisibleView(this._mainView); 65 this._setVisibleView(this._mainView);
61 }, 66 },
62 67
63 /** 68 /**
69 * @param {!WebInspector.SecurityPanel.Origin} origin
70 */
71 showOrigin: function(origin)
72 {
73 var originData = this._origins.get(origin);
74 if (!originData.originView)
75 originData.originView = new WebInspector.SecurityOriginView(this, or igin, originData.securityState, originData.securityDetails);
76
77 this._setVisibleView(originData.originView);
78 },
79
80 wasShown: function()
81 {
82 WebInspector.Panel.prototype.wasShown.call(this);
83 if (!this._visibleView)
84 this._sidebarMainViewElement.select();
85 },
86
87 /**
64 * @param {!WebInspector.VBox} view 88 * @param {!WebInspector.VBox} view
65 */ 89 */
66 _setVisibleView: function(view) 90 _setVisibleView: function(view)
67 { 91 {
68 if (this._visibleView === view) 92 if (this._visibleView === view)
69 return; 93 return;
70 94
71 if (this._visibleView) 95 if (this._visibleView)
72 this._visibleView.detach(); 96 this._visibleView.detach();
73 97
74 this._visibleView = view; 98 this._visibleView = view;
75 99
76 if (view) 100 if (view)
77 this.splitWidget().setMainWidget(view); 101 this.splitWidget().setMainWidget(view);
78 }, 102 },
79 103
80 /** 104 /**
81 * @param {!WebInspector.Event} event 105 * @param {!WebInspector.Event} event
82 */ 106 */
83 _onResponseReceivedSecurityDetails: function(event) 107 _onResponseReceivedSecurityDetails: function(event)
84 { 108 {
85 var data = event.data; 109 var data = event.data;
86 var origin = /** @type {string} */ (data.origin); 110 var origin = /** @type {string} */ (data.origin);
87 var securityState = /** @type {!SecurityAgent.SecurityState} */ (data.se curityState); 111 var securityState = /** @type {!SecurityAgent.SecurityState} */ (data.se curityState);
88 112
89 if (this._origins.has(origin)) { 113 if (this._origins.has(origin)) {
90 var originData = this._origins.get(origin); 114 var originData = this._origins.get(origin);
91 originData.securityState = this._securityStateMin(originData.securit yState, securityState); 115 var oldSecurityState = originData.securityState;
116 originData.securityState = this._securityStateMin(oldSecurityState, securityState);
117 if (oldSecurityState != originData.securityState) {
118 originData.sidebarElement.setSecurityState(securityState);
119 if (originData.originView)
120 originData.originView.setSecurityState(securityState);
121 }
92 } else { 122 } else {
93 // TODO(lgarron): Store a (deduplicated) list of different security details we have seen. 123 // TODO(lgarron): Store a (deduplicated) list of different security details we have seen.
94 var originData = {}; 124 var originData = {};
95 originData.securityState = securityState; 125 originData.securityState = securityState;
96 if (data.securityDetails) 126 if (data.securityDetails)
97 originData.securityDetails = data.securityDetails; 127 originData.securityDetails = data.securityDetails;
98 128
99 this._origins.set(origin, originData); 129 this._origins.set(origin, originData);
130
131 originData.sidebarElement = new WebInspector.SecurityOriginViewSideb arTreeElement(this, origin);
132 this._sidebarOriginSection.appendChild(originData.sidebarElement)
dgozman 2015/08/21 19:44:37 nit: semicolon
lgarron 2015/08/21 21:50:06 Done.
133 originData.sidebarElement.setSecurityState(securityState);
134
135 // Don't construct the origin view yet (let it happen lazily).
100 } 136 }
101 }, 137 },
102 138
103 /** 139 /**
104 * @param {!SecurityAgent.SecurityState} stateA 140 * @param {!SecurityAgent.SecurityState} stateA
105 * @param {!SecurityAgent.SecurityState} stateB 141 * @param {!SecurityAgent.SecurityState} stateB
106 * @return {!SecurityAgent.SecurityState} 142 * @return {!SecurityAgent.SecurityState}
107 */ 143 */
108 _securityStateMin: function(stateA, stateB) 144 _securityStateMin: function(stateA, stateB)
109 { 145 {
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
141 this._networkManager.removeEventListener(WebInspector.NetworkManager .EventTypes.ResponseReceivedSecurityDetails, this._onResponseReceivedSecurityDet ails, this); 177 this._networkManager.removeEventListener(WebInspector.NetworkManager .EventTypes.ResponseReceivedSecurityDetails, this._onResponseReceivedSecurityDet ails, this);
142 delete this._networkManager; 178 delete this._networkManager;
143 delete this._target; 179 delete this._target;
144 this._clear(); 180 this._clear();
145 } 181 }
146 }, 182 },
147 183
148 _clear: function() 184 _clear: function()
149 { 185 {
150 this._updateSecurityState(SecurityAgent.SecurityState.Unknown, []); 186 this._updateSecurityState(SecurityAgent.SecurityState.Unknown, []);
187 this._sidebarMainViewElement.select();
188 this._sidebarOriginSection.removeChildren();
151 this._origins.clear(); 189 this._origins.clear();
152 }, 190 },
153 191
154 __proto__: WebInspector.PanelWithSidebar.prototype 192 __proto__: WebInspector.PanelWithSidebar.prototype
155 } 193 }
156 194
157 /** 195 /**
158 * @return {!WebInspector.SecurityPanel} 196 * @return {!WebInspector.SecurityPanel}
159 */ 197 */
160 WebInspector.SecurityPanel._instance = function() 198 WebInspector.SecurityPanel._instance = function()
161 { 199 {
162 if (!WebInspector.SecurityPanel._instanceObject) 200 if (!WebInspector.SecurityPanel._instanceObject)
163 WebInspector.SecurityPanel._instanceObject = new WebInspector.SecurityPa nel(); 201 WebInspector.SecurityPanel._instanceObject = new WebInspector.SecurityPa nel();
164 return WebInspector.SecurityPanel._instanceObject; 202 return WebInspector.SecurityPanel._instanceObject;
165 } 203 }
166 204
205 /** @typedef {string} */
206 WebInspector.SecurityPanel.Origin;
dgozman 2015/08/21 19:44:36 nit: move to the top (before SecurityPanel.prototy
lgarron 2015/08/21 21:50:06 Done.
207
167 /** 208 /**
168 * @constructor 209 * @constructor
169 * @extends {WebInspector.SidebarTreeElement} 210 * @extends {WebInspector.SidebarTreeElement}
170 * @param {!WebInspector.SecurityPanel} panel 211 * @param {!WebInspector.SecurityPanel} panel
171 */ 212 */
172 WebInspector.SecurityMainViewSidebarTreeElement = function(panel) 213 WebInspector.SecurityMainViewSidebarTreeElement = function(panel)
173 { 214 {
174 this._panel = panel; 215 this._panel = panel;
175 this.small = true; 216 WebInspector.SidebarTreeElement.call(this, "security-main-view-sidebar-tree- item", WebInspector.UIString("Overview"));
176 WebInspector.SidebarTreeElement.call(this, "security-sidebar-tree-item", Web Inspector.UIString("Overview"));
177 this.iconElement.classList.add("lock-icon"); 217 this.iconElement.classList.add("lock-icon");
178 } 218 }
179 219
180 WebInspector.SecurityMainViewSidebarTreeElement.prototype = { 220 WebInspector.SecurityMainViewSidebarTreeElement.prototype = {
181 onattach: function() 221 onattach: function()
182 { 222 {
183 WebInspector.SidebarTreeElement.prototype.onattach.call(this); 223 WebInspector.SidebarTreeElement.prototype.onattach.call(this);
184 }, 224 },
185 225
186 /** 226 /**
(...skipping 16 matching lines...) Expand all
203 { 243 {
204 this._panel.showMainView(); 244 this._panel.showMainView();
205 return true; 245 return true;
206 }, 246 },
207 247
208 __proto__: WebInspector.SidebarTreeElement.prototype 248 __proto__: WebInspector.SidebarTreeElement.prototype
209 } 249 }
210 250
211 /** 251 /**
212 * @constructor 252 * @constructor
253 * @extends {WebInspector.SidebarTreeElement}
254 * @param {!WebInspector.SecurityPanel} panel
255 * @param {!WebInspector.SecurityPanel.Origin} origin
256 */
257 WebInspector.SecurityOriginViewSidebarTreeElement = function(panel, origin)
258 {
259 this._panel = panel;
260 this._origin = origin;
261 this.small = true
dgozman 2015/08/21 19:44:37 nit: semicolon
lgarron 2015/08/21 21:50:06 Done.
262 WebInspector.SidebarTreeElement.call(this, "security-sidebar-tree-item", Web Inspector.UIString(origin));
dgozman 2015/08/21 19:44:37 Don't use UIString for origin. It's meant for stri
lgarron 2015/08/21 21:50:06 Okay. So on user-originated strings, we don't do a
dgozman 2015/08/24 21:44:54 Using |textContent| is safe, it's not like |innerH
lgarron 2015/08/24 23:42:16 Cool, that matches what I expect. :-)
263 this.iconElement.classList.add("security-property");
264 }
265
266 WebInspector.SecurityOriginViewSidebarTreeElement.prototype = {
267 /**
268 * @override
269 * @return {boolean}
270 */
271 onselect: function()
272 {
273 this._panel.showOrigin(this._origin);
274 return true;
275 },
276
277 /**
278 * @param {!SecurityAgent.SecurityState} newSecurityState
279 */
280 setSecurityState: function(newSecurityState)
281 {
282 for (var className of this.iconElement.classList)
dgozman 2015/08/21 19:44:37 nit: use {} for more-than-one-liners
lgarron 2015/08/21 21:50:06 :-D (Done.)
283 if (className.indexOf("security-property-") === 0)
dgozman 2015/08/21 19:44:37 startsWith
lgarron 2015/08/21 21:50:06 Done.
284 this.iconElement.classList.remove(className);
dgozman 2015/08/21 19:44:37 Removing while iterating could be dangerous. Make
lgarron 2015/08/21 21:50:06 Done.
285
286 this.iconElement.classList.add("security-property-" + newSecurityState);
287 },
288
289 __proto__: WebInspector.SidebarTreeElement.prototype
290 }
291
292 /**
293 * @constructor
213 * @implements {WebInspector.PanelFactory} 294 * @implements {WebInspector.PanelFactory}
214 */ 295 */
215 WebInspector.SecurityPanelFactory = function() 296 WebInspector.SecurityPanelFactory = function()
216 { 297 {
217 } 298 }
218 299
219 WebInspector.SecurityPanelFactory.prototype = { 300 WebInspector.SecurityPanelFactory.prototype = {
220 /** 301 /**
221 * @override 302 * @override
222 * @return {!WebInspector.Panel} 303 * @return {!WebInspector.Panel}
(...skipping 27 matching lines...) Expand all
250 WebInspector.SecurityMainView.prototype = { 331 WebInspector.SecurityMainView.prototype = {
251 /** 332 /**
252 * @param {!SecurityAgent.SecurityStateExplanation} explanation 333 * @param {!SecurityAgent.SecurityStateExplanation} explanation
253 */ 334 */
254 _addExplanation: function(explanation) 335 _addExplanation: function(explanation)
255 { 336 {
256 var explanationDiv = this._securityExplanations.createChild("div", "secu rity-explanation"); 337 var explanationDiv = this._securityExplanations.createChild("div", "secu rity-explanation");
257 338
258 var explanationLockIcon = explanationDiv.createChild("div", "lock-icon") ; 339 var explanationLockIcon = explanationDiv.createChild("div", "lock-icon") ;
259 explanationLockIcon.classList.add("lock-icon-" + explanation.securitySta te); 340 explanationLockIcon.classList.add("lock-icon-" + explanation.securitySta te);
260 explanationDiv.createChild("div", "explanation-title").textContent = exp lanation.summary; 341 explanationDiv.createChild("div", "explanation-title").textContent = Web Inspector.UIString(explanation.summary);
261 explanationDiv.createChild("div", "explanation-text").textContent = expl anation.description; 342 explanationDiv.createChild("div", "explanation-text").textContent = WebI nspector.UIString(explanation.description);
262 }, 343 },
263 344
264 /** 345 /**
265 * @param {!SecurityAgent.SecurityState} newSecurityState 346 * @param {!SecurityAgent.SecurityState} newSecurityState
266 * @param {!Array<!SecurityAgent.SecurityStateExplanation>} explanations 347 * @param {!Array<!SecurityAgent.SecurityStateExplanation>} explanations
267 */ 348 */
268 updateSecurityState: function(newSecurityState, explanations) 349 updateSecurityState: function(newSecurityState, explanations)
269 { 350 {
270 // Remove old state. 351 // Remove old state.
271 // It's safe to call this even when this._securityState is undefined. 352 // It's safe to call this even when this._securityState is undefined.
272 this._lockIcon.classList.remove("lock-icon-" + this._securityState); 353 this._lockIcon.classList.remove("lock-icon-" + this._securityState);
273 354
274 // Add new state. 355 // Add new state.
275 this._securityState = newSecurityState; 356 this._securityState = newSecurityState;
276 this._lockIcon.classList.add("lock-icon-" + this._securityState); 357 this._lockIcon.classList.add("lock-icon-" + this._securityState);
277 this._securityStateText.textContent = WebInspector.UIString("Page securi ty state: %s", this._securityState); 358 this._securityStateText.textContent = WebInspector.UIString("Page securi ty state: %s", this._securityState);
278 359
279 this._securityExplanations.removeChildren(); 360 this._securityExplanations.removeChildren();
280 for (var explanation of explanations) 361 for (var explanation of explanations)
281 this._addExplanation(explanation); 362 this._addExplanation(explanation);
282 }, 363 },
283 364
284 __proto__: WebInspector.VBox.prototype 365 __proto__: WebInspector.VBox.prototype
285 } 366 }
367
368 /**
369 * @constructor
370 * @extends {WebInspector.VBox}
371 */
372 WebInspector.SecurityOriginView = function(panel, origin, securityState, securit yDetails)
dgozman 2015/08/21 19:44:37 - JSDoc for params; - why pass securityState and d
lgarron 2015/08/21 21:50:06 JSDoc: Done. Why pass? I wanted to keep all the b
373 {
374 this._panel = panel;
375 WebInspector.VBox.call(this);
376 this.setMinimumSize(200, 100);
377
378 this.element.classList.add("security-origin-view");
379 this.registerRequiredCSS("security/originView.css");
380 this.registerRequiredCSS("security/lockIcon.css");
381
382 var titleSection = this.element.createChild("div", "origin-view-section titl e-section");
383 titleSection.createChild("h1").textContent = WebInspector.UIString("Origin") ;
384 var originDisplay = titleSection.createChild("div", "origin-display");
385 this._originLockIcon = originDisplay.createChild("span", "security-property" );
386 this._originLockIcon.classList.add("security-property-" + securityState);
387 // TODO(lgarron): Highlight the origin scheme.
388 originDisplay.createChild("span", "origin").textContent = WebInspector.UIStr ing(origin);
389
390 if (securityDetails && securityDetails.certificateDetails) {
391 var connectionSection = this.element.createChild("div", "origin-view-sec tion");
392 connectionSection.createChild("h2").textContent = "Connection";
393
394 var table = connectionSection.createChild("table", "details-table");
395 this._addTableRow(table, "Protocol", securityDetails.protocol);
396 this._addTableRow(table, "Cipher Suite", securityDetails.cipher + (secur ityDetails.mac ? " with " + securityDetails.mac : ""));
397 this._addTableRow(table, "Key Exchange", securityDetails.keyExchange);
398 }
399
400 if (securityDetails) {
401 var certificateSection = this.element.createChild("div", "origin-view-se ction");
402 certificateSection.createChild("h2").textContent = "Certificate";
403
404 var sanDiv = this._createSanDiv(securityDetails);
405 var validFromString = new Date(1000 * securityDetails.certificateDetails .validFrom).toUTCString();
406 var validUntilString = new Date(1000 * securityDetails.certificateDetail s.validTo).toUTCString();
407
408 var table = certificateSection.createChild("table", "details-table");
409 this._addTableRow(table, "Subject", securityDetails.certificateDetails.s ubject.name);
410 this._addTableRow(table, "SAN", sanDiv);
411 this._addTableRow(table, "Valid From", validFromString);
412 this._addTableRow(table, "Valid Until", validUntilString);
413 this._addTableRow(table, "Issuer", securityDetails.certificateDetails.is suer);
414 // TODO(lgarron): Make SCT status available in certificate details and s how it here.
415
416 // TODO(lgarron): Implement a link to get certificateDetails
417
418 var noteSection = this.element.createChild("div", "origin-view-section") ;
419 noteSection.createChild("h2").textContent = "Development Note";
420 // TODO(lgarron): Fix the issue and then remove this section. See commen t in _onResponseReceivedSecurityDetails
421 noteSection.createChild("div").textContent = WebInspector.UIString("At t he moment, this view only shows security details from the first connection made to %s", origin);
422 }
423
424 if (!securityDetails) {
425 var notSecureSection = this.element.createChild("div", "origin-view-sect ion");
426 notSecureSection.createChild("h2").textContent = "Not Secure";
427 notSecureSection.createChild("div").textContent = WebInspector.UIString( "Your connection to this origin is not secure.");
428 }
429 }
430
431 WebInspector.SecurityOriginView.prototype = {
432 /**
433 * @param {!Element} table
434 * @param {string} key
435 * @param {string|!HTMLDivElement} value
436 */
437 _addTableRow: function(table, key, value)
438 {
439 var row = table.createChild("tr");
440 row.createChild("td").textContent = WebInspector.UIString(key);
441
442 var valueTd = row.createChild("td");
443 if (value instanceof HTMLDivElement) {
444 valueTd.appendChild(value);
445 } else {
446 valueTd.textContent = WebInspector.UIString(value);
447 }
448 },
449
450 /**
451 * @param {!NetworkAgent.SecurityDetails} securityDetails
452 * *return {!Element}
453 */
454 _createSanDiv: function(securityDetails)
455 {
456 // TODO(lgarron): Truncate the display of SAN entries and add a button t o toggle the full list.
457 var sanDiv = createElement("div");
458 var sanList = securityDetails.certificateDetails.subject.sanDnsNames.con cat(securityDetails.certificateDetails.subject.sanIpAddresses);
459 if (sanList.length === 0) {
460 sanDiv.textContent = WebInspector.UIString("(N/A)");
461 } else {
462 for (var sanEntry of sanList) {
463 var span = sanDiv.createChild("span", "san-entry");
464 span.textContent = WebInspector.UIString(sanEntry);
465 }
466 }
467 return sanDiv;
468 },
469
470 /**
471 * @param {!SecurityAgent.SecurityState} newSecurityState
472 */
473 setSecurityState: function(newSecurityState)
474 {
475 for (var className of this._originLockIcon.classList)
476 if (className.indexOf("security-property-") === 0)
477 this._originLockIcon.classList.remove(className);
478
479 this._originLockIcon.classList.add("security-property-" + newSecuritySta te);
480 },
481
482 __proto__: WebInspector.VBox.prototype
483 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698