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

Side by Side Diff: chrome/common/extensions/permissions/permission_set.cc

Issue 10675007: Move each permission classes to its own files in extensions/permissions (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 8 years, 6 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
(Empty)
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/common/extensions/permissions/permission_set.h"
6
7 #include <algorithm>
8 #include <string>
9
10 #include "chrome/common/extensions/extension.h"
11 #include "chrome/common/extensions/permissions/permissions_info.h"
12 #include "chrome/common/extensions/url_pattern.h"
13 #include "chrome/common/extensions/url_pattern_set.h"
14 #include "content/public/common/url_constants.h"
15 #include "grit/generated_resources.h"
16 #include "net/base/registry_controlled_domain.h"
17 #include "ui/base/l10n/l10n_util.h"
18
19 namespace {
20
21 // Helper for GetDistinctHosts(): com > net > org > everything else.
22 bool RcdBetterThan(std::string a, std::string b) {
23 if (a == b)
24 return false;
25 if (a == "com")
26 return true;
27 if (a == "net")
28 return b != "com";
29 if (a == "org")
30 return b != "com" && b != "net";
31 return false;
32 }
33
34 // Names of API modules that can be used without listing it in the
35 // permissions section of the manifest.
36 const char* kNonPermissionModuleNames[] = {
37 "app",
38 "appWindow",
39 "browserAction",
40 "devtools",
41 "events",
42 "extension",
43 "i18n",
44 "omnibox",
45 "pageAction",
46 "pageActions",
47 "permissions",
48 "runtime",
49 "scriptBadge",
50 "test",
51 "types"
52 };
53 const size_t kNumNonPermissionModuleNames =
54 arraysize(kNonPermissionModuleNames);
55
56 // Names of functions (within modules requiring permissions) that can be used
57 // without asking for the module permission. In other words, functions you can
58 // use with no permissions specified.
59 const char* kNonPermissionFunctionNames[] = {
60 "management.getPermissionWarningsByManifest",
61 "tabs.create",
62 "tabs.onRemoved",
63 "tabs.remove",
64 "tabs.update",
65 };
66 const size_t kNumNonPermissionFunctionNames =
67 arraysize(kNonPermissionFunctionNames);
68
69 void AddPatternsAndRemovePaths(const URLPatternSet& set, URLPatternSet* out) {
70 DCHECK(out);
71 for (URLPatternSet::const_iterator i = set.begin(); i != set.end(); ++i) {
72 URLPattern p = *i;
73 p.SetPath("/*");
74 out->AddPattern(p);
75 }
76 }
77
78 // Strips out the API name from a function or event name.
79 // Functions will be of the form api_name.function
80 // Events will be of the form api_name/id or api_name.optional.stuff
81 std::string GetPermissionName(const std::string& function_name) {
82 size_t separator = function_name.find_first_of("./");
83 if (separator != std::string::npos)
84 return function_name.substr(0, separator);
85 else
86 return function_name;
87 }
88
89 } // namespace
90
91 namespace extensions {
92
93 //
94 // PermissionSet
95 //
96
97 PermissionSet::PermissionSet() {}
98
99 PermissionSet::PermissionSet(
100 const extensions::Extension* extension,
101 const APIPermissionSet& apis,
102 const URLPatternSet& explicit_hosts,
103 const OAuth2Scopes& scopes)
104 : apis_(apis),
105 scopes_(scopes) {
106 DCHECK(extension);
107 AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
108 InitImplicitExtensionPermissions(extension);
109 InitEffectiveHosts();
110 }
111
112 PermissionSet::PermissionSet(
113 const APIPermissionSet& apis,
114 const URLPatternSet& explicit_hosts,
115 const URLPatternSet& scriptable_hosts)
116 : apis_(apis),
117 scriptable_hosts_(scriptable_hosts) {
118 AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
119 InitEffectiveHosts();
120 }
121
122 PermissionSet::PermissionSet(
123 const APIPermissionSet& apis,
124 const URLPatternSet& explicit_hosts,
125 const URLPatternSet& scriptable_hosts,
126 const OAuth2Scopes& scopes)
127 : apis_(apis),
128 scriptable_hosts_(scriptable_hosts),
129 scopes_(scopes) {
130 AddPatternsAndRemovePaths(explicit_hosts, &explicit_hosts_);
131 InitEffectiveHosts();
132 }
133
134 PermissionSet::PermissionSet(
135 const OAuth2Scopes& scopes)
136 : scopes_(scopes) {
137 InitEffectiveHosts();
138 }
139
140 // static
141 PermissionSet* PermissionSet::CreateDifference(
142 const PermissionSet* set1,
143 const PermissionSet* set2) {
144 scoped_refptr<PermissionSet> empty = new PermissionSet();
145 const PermissionSet* set1_safe = (set1 == NULL) ? empty : set1;
146 const PermissionSet* set2_safe = (set2 == NULL) ? empty : set2;
147
148 APIPermissionSet apis;
149 std::set_difference(set1_safe->apis().begin(), set1_safe->apis().end(),
150 set2_safe->apis().begin(), set2_safe->apis().end(),
151 std::insert_iterator<APIPermissionSet>(
152 apis, apis.begin()));
153
154 URLPatternSet explicit_hosts;
155 URLPatternSet::CreateDifference(set1_safe->explicit_hosts(),
156 set2_safe->explicit_hosts(),
157 &explicit_hosts);
158
159 URLPatternSet scriptable_hosts;
160 URLPatternSet::CreateDifference(set1_safe->scriptable_hosts(),
161 set2_safe->scriptable_hosts(),
162 &scriptable_hosts);
163
164 OAuth2Scopes scopes;
165 std::set_difference(set1_safe->scopes().begin(), set1_safe->scopes().end(),
166 set2_safe->scopes().begin(), set2_safe->scopes().end(),
167 std::insert_iterator<OAuth2Scopes>(
168 scopes, scopes.begin()));
169
170 return new PermissionSet(
171 apis, explicit_hosts, scriptable_hosts, scopes);
172 }
173
174 // static
175 PermissionSet* PermissionSet::CreateIntersection(
176 const PermissionSet* set1,
177 const PermissionSet* set2) {
178 scoped_refptr<PermissionSet> empty = new PermissionSet();
179 const PermissionSet* set1_safe = (set1 == NULL) ? empty : set1;
180 const PermissionSet* set2_safe = (set2 == NULL) ? empty : set2;
181
182 APIPermissionSet apis;
183 std::set_intersection(set1_safe->apis().begin(), set1_safe->apis().end(),
184 set2_safe->apis().begin(), set2_safe->apis().end(),
185 std::insert_iterator<APIPermissionSet>(
186 apis, apis.begin()));
187 URLPatternSet explicit_hosts;
188 URLPatternSet::CreateIntersection(set1_safe->explicit_hosts(),
189 set2_safe->explicit_hosts(),
190 &explicit_hosts);
191
192 URLPatternSet scriptable_hosts;
193 URLPatternSet::CreateIntersection(set1_safe->scriptable_hosts(),
194 set2_safe->scriptable_hosts(),
195 &scriptable_hosts);
196
197 OAuth2Scopes scopes;
198 std::set_intersection(set1_safe->scopes().begin(), set1_safe->scopes().end(),
199 set2_safe->scopes().begin(), set2_safe->scopes().end(),
200 std::insert_iterator<OAuth2Scopes>(
201 scopes, scopes.begin()));
202
203 return new PermissionSet(
204 apis, explicit_hosts, scriptable_hosts, scopes);
205 }
206
207 // static
208 PermissionSet* PermissionSet::CreateUnion(
209 const PermissionSet* set1,
210 const PermissionSet* set2) {
211 scoped_refptr<PermissionSet> empty = new PermissionSet();
212 const PermissionSet* set1_safe = (set1 == NULL) ? empty : set1;
213 const PermissionSet* set2_safe = (set2 == NULL) ? empty : set2;
214
215 APIPermissionSet apis;
216 std::set_union(set1_safe->apis().begin(), set1_safe->apis().end(),
217 set2_safe->apis().begin(), set2_safe->apis().end(),
218 std::insert_iterator<APIPermissionSet>(
219 apis, apis.begin()));
220
221 URLPatternSet explicit_hosts;
222 URLPatternSet::CreateUnion(set1_safe->explicit_hosts(),
223 set2_safe->explicit_hosts(),
224 &explicit_hosts);
225
226 URLPatternSet scriptable_hosts;
227 URLPatternSet::CreateUnion(set1_safe->scriptable_hosts(),
228 set2_safe->scriptable_hosts(),
229 &scriptable_hosts);
230
231 OAuth2Scopes scopes;
232 std::set_union(set1_safe->scopes().begin(), set1_safe->scopes().end(),
233 set2_safe->scopes().begin(), set2_safe->scopes().end(),
234 std::insert_iterator<OAuth2Scopes>(
235 scopes, scopes.begin()));
236
237 return new PermissionSet(
238 apis, explicit_hosts, scriptable_hosts, scopes);
239 }
240
241 bool PermissionSet::operator==(
242 const PermissionSet& rhs) const {
243 return apis_ == rhs.apis_ &&
244 scriptable_hosts_ == rhs.scriptable_hosts_ &&
245 explicit_hosts_ == rhs.explicit_hosts_ &&
246 scopes_ == rhs.scopes_;
247 }
248
249 bool PermissionSet::Contains(const PermissionSet& set) const {
250 // Every set includes the empty set.
251 if (set.IsEmpty())
252 return true;
253
254 if (!std::includes(apis_.begin(), apis_.end(),
255 set.apis().begin(), set.apis().end()))
256 return false;
257
258 if (!explicit_hosts().Contains(set.explicit_hosts()))
259 return false;
260
261 if (!scriptable_hosts().Contains(set.scriptable_hosts()))
262 return false;
263
264 if (!std::includes(scopes_.begin(), scopes_.end(),
265 set.scopes().begin(), set.scopes().end()))
266 return false;
267
268 return true;
269 }
270
271 std::set<std::string> PermissionSet::GetAPIsAsStrings() const {
272 PermissionsInfo* info = PermissionsInfo::GetInstance();
273 std::set<std::string> apis_str;
274 for (APIPermissionSet::const_iterator i = apis_.begin();
275 i != apis_.end(); ++i) {
276 APIPermission* permission = info->GetByID(*i);
277 if (permission)
278 apis_str.insert(permission->name());
279 }
280 return apis_str;
281 }
282
283 std::set<std::string> PermissionSet::
284 GetAPIsWithAnyAccessAsStrings() const {
285 std::set<std::string> result = GetAPIsAsStrings();
286 for (size_t i = 0; i < kNumNonPermissionModuleNames; ++i)
287 result.insert(kNonPermissionModuleNames[i]);
288 for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i)
289 result.insert(GetPermissionName(kNonPermissionFunctionNames[i]));
290 return result;
291 }
292
293 bool PermissionSet::HasAnyAccessToAPI(
294 const std::string& api_name) const {
295 if (HasAccessToFunction(api_name))
296 return true;
297
298 for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i) {
299 if (api_name == GetPermissionName(kNonPermissionFunctionNames[i]))
300 return true;
301 }
302
303 return false;
304 }
305
306 std::set<std::string>
307 PermissionSet::GetDistinctHostsForDisplay() const {
308 return GetDistinctHosts(effective_hosts_, true, true);
309 }
310
311 PermissionMessages
312 PermissionSet::GetPermissionMessages() const {
313 PermissionMessages messages;
314
315 if (HasEffectiveFullAccess()) {
316 messages.push_back(PermissionMessage(
317 PermissionMessage::kFullAccess,
318 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
319 return messages;
320 }
321
322 if (HasEffectiveAccessToAllHosts()) {
323 messages.push_back(PermissionMessage(
324 PermissionMessage::kHostsAll,
325 l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS)));
326 } else {
327 std::set<std::string> hosts = GetDistinctHostsForDisplay();
328 if (!hosts.empty())
329 messages.push_back(PermissionMessage::CreateFromHostList(hosts));
330 }
331
332 std::set<PermissionMessage> simple_msgs =
333 GetSimplePermissionMessages();
334 messages.insert(messages.end(), simple_msgs.begin(), simple_msgs.end());
335
336 return messages;
337 }
338
339 std::vector<string16> PermissionSet::GetWarningMessages() const {
340 std::vector<string16> messages;
341 PermissionMessages permissions = GetPermissionMessages();
342
343 bool audio_capture = false;
344 bool video_capture = false;
345 for (PermissionMessages::const_iterator i = permissions.begin();
346 i != permissions.end(); ++i) {
347 if (i->id() == PermissionMessage::kAudioCapture)
348 audio_capture = true;
349 if (i->id() == PermissionMessage::kVideoCapture)
350 video_capture = true;
351 }
352
353 for (PermissionMessages::const_iterator i = permissions.begin();
354 i != permissions.end(); ++i) {
355 if (audio_capture && video_capture) {
356 if (i->id() == PermissionMessage::kAudioCapture) {
357 messages.push_back(l10n_util::GetStringUTF16(
358 IDS_EXTENSION_PROMPT_WARNING_AUDIO_AND_VIDEO_CAPTURE));
359 continue;
360 } else if (i->id() == PermissionMessage::kVideoCapture) {
361 // The combined message will be pushed above.
362 continue;
363 }
364 }
365
366 messages.push_back(i->message());
367 }
368
369 return messages;
370 }
371
372 bool PermissionSet::IsEmpty() const {
373 // Not default if any host permissions are present.
374 if (!(explicit_hosts().is_empty() && scriptable_hosts().is_empty()))
375 return false;
376
377 // Or if it has no api permissions.
378 return apis().empty();
379 }
380
381 bool PermissionSet::HasAPIPermission(
382 APIPermission::ID permission) const {
383 return apis().find(permission) != apis().end();
384 }
385
386 bool PermissionSet::HasAccessToFunction(
387 const std::string& function_name) const {
388 // TODO(jstritar): Embed this information in each permission and add a method
389 // like GrantsAccess(function_name) to APIPermission. A "default"
390 // permission can then handle the modules and functions that everyone can
391 // access.
392 for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i) {
393 if (function_name == kNonPermissionFunctionNames[i])
394 return true;
395 }
396
397 std::string permission_name = GetPermissionName(function_name);
398 APIPermission* permission =
399 PermissionsInfo::GetInstance()->GetByName(permission_name);
400 if (permission && apis_.count(permission->id()))
401 return true;
402
403 for (size_t i = 0; i < kNumNonPermissionModuleNames; ++i) {
404 if (permission_name == kNonPermissionModuleNames[i]) {
405 return true;
406 }
407 }
408
409 return false;
410 }
411
412 bool PermissionSet::HasExplicitAccessToOrigin(
413 const GURL& origin) const {
414 return explicit_hosts().MatchesURL(origin);
415 }
416
417 bool PermissionSet::HasScriptableAccessToURL(
418 const GURL& origin) const {
419 // We only need to check our host list to verify access. The host list should
420 // already reflect any special rules (such as chrome://favicon, all hosts
421 // access, etc.).
422 return scriptable_hosts().MatchesURL(origin);
423 }
424
425 bool PermissionSet::HasEffectiveAccessToAllHosts() const {
426 // There are two ways this set can have effective access to all hosts:
427 // 1) it has an <all_urls> URL pattern.
428 // 2) it has a named permission with implied full URL access.
429 for (URLPatternSet::const_iterator host = effective_hosts().begin();
430 host != effective_hosts().end(); ++host) {
431 if (host->match_all_urls() ||
432 (host->match_subdomains() && host->host().empty()))
433 return true;
434 }
435
436 PermissionsInfo* info = PermissionsInfo::GetInstance();
437 for (APIPermissionSet::const_iterator i = apis().begin();
438 i != apis().end(); ++i) {
439 APIPermission* permission = info->GetByID(*i);
440 if (permission->implies_full_url_access())
441 return true;
442 }
443 return false;
444 }
445
446 bool PermissionSet::HasEffectiveAccessToURL(
447 const GURL& url) const {
448 return effective_hosts().MatchesURL(url);
449 }
450
451 bool PermissionSet::HasEffectiveFullAccess() const {
452 PermissionsInfo* info = PermissionsInfo::GetInstance();
453 for (APIPermissionSet::const_iterator i = apis().begin();
454 i != apis().end(); ++i) {
455 APIPermission* permission = info->GetByID(*i);
456 if (permission->implies_full_access())
457 return true;
458 }
459 return false;
460 }
461
462 bool PermissionSet::HasLessPrivilegesThan(
463 const PermissionSet* permissions) const {
464 // Things can't get worse than native code access.
465 if (HasEffectiveFullAccess())
466 return false;
467
468 // Otherwise, it's a privilege increase if the new one has full access.
469 if (permissions->HasEffectiveFullAccess())
470 return true;
471
472 if (HasLessHostPrivilegesThan(permissions))
473 return true;
474
475 if (HasLessAPIPrivilegesThan(permissions))
476 return true;
477
478 if (HasLessScopesThan(permissions))
479 return true;
480
481 return false;
482 }
483
484 PermissionSet::~PermissionSet() {}
485
486 // static
487 std::set<std::string> PermissionSet::GetDistinctHosts(
488 const URLPatternSet& host_patterns,
489 bool include_rcd,
490 bool exclude_file_scheme) {
491 // Use a vector to preserve order (also faster than a map on small sets).
492 // Each item is a host split into two parts: host without RCDs and
493 // current best RCD.
494 typedef std::vector<std::pair<std::string, std::string> > HostVector;
495 HostVector hosts_best_rcd;
496 for (URLPatternSet::const_iterator i = host_patterns.begin();
497 i != host_patterns.end(); ++i) {
498 if (exclude_file_scheme && i->scheme() == chrome::kFileScheme)
499 continue;
500
501 std::string host = i->host();
502
503 // Add the subdomain wildcard back to the host, if necessary.
504 if (i->match_subdomains())
505 host = "*." + host;
506
507 // If the host has an RCD, split it off so we can detect duplicates.
508 std::string rcd;
509 size_t reg_len = net::RegistryControlledDomainService::GetRegistryLength(
510 host, false);
511 if (reg_len && reg_len != std::string::npos) {
512 if (include_rcd) // else leave rcd empty
513 rcd = host.substr(host.size() - reg_len);
514 host = host.substr(0, host.size() - reg_len);
515 }
516
517 // Check if we've already seen this host.
518 HostVector::iterator it = hosts_best_rcd.begin();
519 for (; it != hosts_best_rcd.end(); ++it) {
520 if (it->first == host)
521 break;
522 }
523 // If this host was found, replace the RCD if this one is better.
524 if (it != hosts_best_rcd.end()) {
525 if (include_rcd && RcdBetterThan(rcd, it->second))
526 it->second = rcd;
527 } else { // Previously unseen host, append it.
528 hosts_best_rcd.push_back(std::make_pair(host, rcd));
529 }
530 }
531
532 // Build up the final vector by concatenating hosts and RCDs.
533 std::set<std::string> distinct_hosts;
534 for (HostVector::iterator it = hosts_best_rcd.begin();
535 it != hosts_best_rcd.end(); ++it)
536 distinct_hosts.insert(it->first + it->second);
537 return distinct_hosts;
538 }
539
540 void PermissionSet::InitImplicitExtensionPermissions(
541 const extensions::Extension* extension) {
542 // Add the implied permissions.
543 if (!extension->plugins().empty())
544 apis_.insert(APIPermission::kPlugin);
545
546 if (!extension->devtools_url().is_empty())
547 apis_.insert(APIPermission::kDevtools);
548
549 // The webRequest permission implies the internal version as well.
550 if (apis_.find(APIPermission::kWebRequest) != apis_.end())
551 apis_.insert(APIPermission::kWebRequestInternal);
552
553 // The fileBrowserHandler permission implies the internal version as well.
554 if (apis_.find(APIPermission::kFileBrowserHandler) != apis_.end())
555 apis_.insert(APIPermission::kFileBrowserHandlerInternal);
556
557 // Add the scriptable hosts.
558 for (UserScriptList::const_iterator content_script =
559 extension->content_scripts().begin();
560 content_script != extension->content_scripts().end(); ++content_script) {
561 URLPatternSet::const_iterator pattern =
562 content_script->url_patterns().begin();
563 for (; pattern != content_script->url_patterns().end(); ++pattern)
564 scriptable_hosts_.AddPattern(*pattern);
565 }
566 }
567
568 void PermissionSet::InitEffectiveHosts() {
569 effective_hosts_.ClearPatterns();
570
571 URLPatternSet::CreateUnion(
572 explicit_hosts(), scriptable_hosts(), &effective_hosts_);
573 }
574
575 std::set<PermissionMessage>
576 PermissionSet::GetSimplePermissionMessages() const {
577 std::set<PermissionMessage> messages;
578 PermissionsInfo* info = PermissionsInfo::GetInstance();
579 for (APIPermissionSet::const_iterator i = apis_.begin();
580 i != apis_.end(); ++i) {
581 DCHECK_GT(PermissionMessage::kNone,
582 PermissionMessage::kUnknown);
583 APIPermission* perm = info->GetByID(*i);
584 if (perm && perm->message_id() > PermissionMessage::kNone)
585 messages.insert(perm->GetMessage());
586 }
587 return messages;
588 }
589
590 bool PermissionSet::HasLessAPIPrivilegesThan(
591 const PermissionSet* permissions) const {
592 if (permissions == NULL)
593 return false;
594
595 std::set<PermissionMessage> current_warnings =
596 GetSimplePermissionMessages();
597 std::set<PermissionMessage> new_warnings =
598 permissions->GetSimplePermissionMessages();
599 std::set<PermissionMessage> delta_warnings;
600 std::set_difference(new_warnings.begin(), new_warnings.end(),
601 current_warnings.begin(), current_warnings.end(),
602 std::inserter(delta_warnings, delta_warnings.begin()));
603
604 // We have less privileges if there are additional warnings present.
605 return !delta_warnings.empty();
606 }
607
608 bool PermissionSet::HasLessHostPrivilegesThan(
609 const PermissionSet* permissions) const {
610 // If this permission set can access any host, then it can't be elevated.
611 if (HasEffectiveAccessToAllHosts())
612 return false;
613
614 // Likewise, if the other permission set has full host access, then it must be
615 // a privilege increase.
616 if (permissions->HasEffectiveAccessToAllHosts())
617 return true;
618
619 const URLPatternSet& old_list = effective_hosts();
620 const URLPatternSet& new_list = permissions->effective_hosts();
621
622 // TODO(jstritar): This is overly conservative with respect to subdomains.
623 // For example, going from *.google.com to www.google.com will be
624 // considered an elevation, even though it is not (http://crbug.com/65337).
625 std::set<std::string> new_hosts_set(GetDistinctHosts(new_list, false, false));
626 std::set<std::string> old_hosts_set(GetDistinctHosts(old_list, false, false));
627 std::set<std::string> new_hosts_only;
628
629 std::set_difference(new_hosts_set.begin(), new_hosts_set.end(),
630 old_hosts_set.begin(), old_hosts_set.end(),
631 std::inserter(new_hosts_only, new_hosts_only.begin()));
632
633 return !new_hosts_only.empty();
634 }
635
636 bool PermissionSet::HasLessScopesThan(
637 const PermissionSet* permissions) const {
638 if (permissions == NULL)
639 return false;
640
641 OAuth2Scopes current_scopes = scopes();
642 OAuth2Scopes new_scopes = permissions->scopes();
643 OAuth2Scopes delta_scopes;
644 std::set_difference(new_scopes.begin(), new_scopes.end(),
645 current_scopes.begin(), current_scopes.end(),
646 std::inserter(delta_scopes, delta_scopes.begin()));
647
648 // We have less privileges if there are additional scopes present.
649 return !delta_scopes.empty();
650 }
651
652 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698