github.com/cs3org/reva/v2@v2.27.7/pkg/conversions/role.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 // Package conversions sits between CS3 type definitions and OCS API Responses 20 package conversions 21 22 import ( 23 "fmt" 24 "strings" 25 26 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 27 "github.com/cs3org/reva/v2/pkg/storage/utils/grants" 28 ) 29 30 // Role is a set of ocs permissions and cs3 resource permissions under a common name. 31 type Role struct { 32 Name string 33 cS3ResourcePermissions *provider.ResourcePermissions 34 ocsPermissions Permissions 35 } 36 37 const ( 38 // RoleViewer grants non-editor role on a resource. 39 RoleViewer = "viewer" 40 // RoleViewerListGrants grants non-editor role on a resource. 41 RoleViewerListGrants = "viewer-list-grants" 42 // RoleSpaceViewer grants non-editor role on a space. 43 RoleSpaceViewer = "spaceviewer" 44 // RoleEditor grants editor permission on a resource, including folders. 45 RoleEditor = "editor" 46 // RoleEditorListGrants grants editor permission on a resource, including folders. 47 RoleEditorListGrants = "editor-list-grants" 48 // RoleEditorListGrantsWithVersions grants editor permission on a resource, including folders. 49 RoleEditorListGrantsWithVersions = "editor-list-grants-with-versions" 50 // RoleSpaceEditor grants editor permission on a space. 51 RoleSpaceEditor = "spaceeditor" 52 // RoleSpaceEditorWithoutVersions grants editor permission without list/restore versions on a space. 53 RoleSpaceEditorWithoutVersions = "spaceeditor-without-versions" 54 // RoleFileEditor grants editor permission on a single file. 55 RoleFileEditor = "file-editor" 56 // RoleFileEditorListGrants grants editor permission on a single file. 57 RoleFileEditorListGrants = "file-editor-list-grants" 58 // RoleFileEditorListGrantsWithVersions grants editor permission on a single file. 59 RoleFileEditorListGrantsWithVersions = "file-editor-list-grants-with-versions" 60 // RoleCoowner grants co-owner permissions on a resource. 61 RoleCoowner = "coowner" 62 // RoleEditorLite grants permission to upload and download to a resource. 63 RoleEditorLite = "editor-lite" 64 // RoleUploader grants uploader permission to upload onto a resource (no download). 65 RoleUploader = "uploader" 66 // RoleManager grants manager permissions on a resource. Semantically equivalent to co-owner. 67 RoleManager = "manager" 68 // RoleSecureViewer grants secure view permissions on a resource or space. 69 RoleSecureViewer = "secure-viewer" 70 71 // RoleUnknown is used for unknown roles. 72 RoleUnknown = "unknown" 73 // RoleLegacy provides backwards compatibility. 74 RoleLegacy = "legacy" 75 // RoleDenied grants no permission at all on a resource 76 RoleDenied = "denied" 77 ) 78 79 // CS3ResourcePermissions for the role 80 func (r *Role) CS3ResourcePermissions() *provider.ResourcePermissions { 81 return r.cS3ResourcePermissions 82 } 83 84 // OCSPermissions for the role 85 func (r *Role) OCSPermissions() Permissions { 86 return r.ocsPermissions 87 } 88 89 // WebDAVPermissions returns the webdav permissions used in propfinds, eg. "WCKDNVR" 90 /* 91 from https://github.com/owncloud/core/blob/10715e2b1c85fc3855a38d2b1fe4426b5e3efbad/apps/dav/lib/Files/PublicFiles/SharedNodeTrait.php#L196-L215 92 93 $p = ''; 94 if ($node->isDeletable() && $this->checkSharePermissions(Constants::PERMISSION_DELETE)) { 95 $p .= 'D'; 96 } 97 if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { 98 $p .= 'NV'; // Renameable, Moveable 99 } 100 if ($node->getType() === \OCP\Files\FileInfo::TYPE_FILE) { 101 if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) { 102 $p .= 'W'; 103 } 104 } else { 105 if ($node->isCreatable() && $this->checkSharePermissions(Constants::PERMISSION_CREATE)) { 106 $p .= 'CK'; 107 } 108 } 109 110 */ 111 // D = delete 112 // NV = update (renameable moveable) 113 // W = update (files only) 114 // CK = create (folders only) 115 // S = Shared 116 // R = Shareable 117 // M = Mounted 118 // Z = Deniable (NEW) 119 // P = Purge from trashbin 120 // X = SecureViewable 121 func (r *Role) WebDAVPermissions(isDir, isShared, isMountpoint, isPublic bool) string { 122 var b strings.Builder 123 if !isPublic && isShared { 124 fmt.Fprintf(&b, "S") 125 } 126 if r.ocsPermissions.Contain(PermissionShare) { 127 fmt.Fprintf(&b, "R") 128 } 129 if !isPublic && isMountpoint { 130 fmt.Fprintf(&b, "M") 131 } 132 if r.ocsPermissions.Contain(PermissionDelete) { 133 fmt.Fprintf(&b, "D") // TODO oc10 shows received shares as deletable 134 } 135 if r.ocsPermissions.Contain(PermissionWrite) { 136 // Single file public link shares cannot be renamed 137 if !isPublic || (isPublic && r.cS3ResourcePermissions != nil && r.cS3ResourcePermissions.Move) { 138 fmt.Fprintf(&b, "NV") 139 } 140 if !isDir { 141 fmt.Fprintf(&b, "W") 142 } 143 } 144 if isDir && r.ocsPermissions.Contain(PermissionCreate) { 145 fmt.Fprintf(&b, "CK") 146 } 147 148 if r.CS3ResourcePermissions().DenyGrant { 149 fmt.Fprintf(&b, "Z") 150 } 151 152 if r.CS3ResourcePermissions().PurgeRecycle { 153 fmt.Fprintf(&b, "P") 154 } 155 156 if r.Name == RoleSecureViewer { 157 fmt.Fprintf(&b, "X") 158 } 159 160 return b.String() 161 } 162 163 // RoleFromName creates a role from the name 164 func RoleFromName(name string) *Role { 165 switch name { 166 case RoleDenied: 167 return NewDeniedRole() 168 case RoleViewer: 169 return NewViewerRole() 170 case RoleViewerListGrants: 171 return NewViewerListGrantsRole() 172 case RoleSpaceViewer: 173 return NewSpaceViewerRole() 174 case RoleEditor: 175 return NewEditorRole() 176 case RoleEditorListGrants: 177 return NewEditorListGrantsRole() 178 case RoleEditorListGrantsWithVersions: 179 return NewEditorListGrantsWithVersionsRole() 180 case RoleSpaceEditor: 181 return NewSpaceEditorRole() 182 case RoleFileEditor: 183 return NewFileEditorRole() 184 case RoleFileEditorListGrants: 185 return NewFileEditorListGrantsRole() 186 case RoleFileEditorListGrantsWithVersions: 187 return NewFileEditorListGrantsWithVersionsRole() 188 case RoleUploader: 189 return NewUploaderRole() 190 case RoleManager: 191 return NewManagerRole() 192 case RoleSecureViewer: 193 return NewSecureViewerRole() 194 default: 195 return NewUnknownRole() 196 } 197 } 198 199 // NewUnknownRole creates an unknown role. An Unknown role has no permissions over a cs3 resource nor any ocs endpoint. 200 func NewUnknownRole() *Role { 201 return &Role{ 202 Name: RoleUnknown, 203 cS3ResourcePermissions: &provider.ResourcePermissions{}, 204 ocsPermissions: PermissionInvalid, 205 } 206 } 207 208 // NewDeniedRole creates a fully denied role 209 func NewDeniedRole() *Role { 210 return &Role{ 211 Name: RoleDenied, 212 cS3ResourcePermissions: &provider.ResourcePermissions{}, 213 ocsPermissions: PermissionsNone, 214 } 215 } 216 217 // NewViewerRole creates a viewer role. `sharing` indicates if sharing permission should be added 218 func NewViewerRole() *Role { 219 p := PermissionRead 220 return &Role{ 221 Name: RoleViewer, 222 cS3ResourcePermissions: &provider.ResourcePermissions{ 223 GetPath: true, 224 GetQuota: true, 225 InitiateFileDownload: true, 226 ListContainer: true, 227 ListRecycle: true, 228 Stat: true, 229 }, 230 ocsPermissions: p, 231 } 232 } 233 234 // NewViewerListGrantsRole creates a viewer role. `sharing` indicates if sharing permission should be added 235 func NewViewerListGrantsRole() *Role { 236 role := NewViewerRole() 237 role.cS3ResourcePermissions.ListGrants = true 238 return role 239 } 240 241 // NewSpaceViewerRole creates a spaceviewer role 242 func NewSpaceViewerRole() *Role { 243 return &Role{ 244 Name: RoleSpaceViewer, 245 cS3ResourcePermissions: &provider.ResourcePermissions{ 246 GetPath: true, 247 GetQuota: true, 248 InitiateFileDownload: true, 249 ListContainer: true, 250 ListGrants: true, 251 ListRecycle: true, 252 Stat: true, 253 }, 254 ocsPermissions: PermissionRead, 255 } 256 } 257 258 // NewEditorRole creates an editor role. `sharing` indicates if sharing permission should be added 259 func NewEditorRole() *Role { 260 p := PermissionRead | PermissionCreate | PermissionWrite | PermissionDelete 261 return &Role{ 262 Name: RoleEditor, 263 cS3ResourcePermissions: &provider.ResourcePermissions{ 264 CreateContainer: true, 265 Delete: true, 266 GetPath: true, 267 GetQuota: true, 268 InitiateFileDownload: true, 269 InitiateFileUpload: true, 270 ListContainer: true, 271 ListRecycle: true, 272 Move: true, 273 RestoreRecycleItem: true, 274 Stat: true, 275 }, 276 ocsPermissions: p, 277 } 278 } 279 280 // NewEditorListGrantsRole creates an editor role. `sharing` indicates if sharing permission should be added 281 func NewEditorListGrantsRole() *Role { 282 role := NewEditorRole() 283 role.cS3ResourcePermissions.ListGrants = true 284 return role 285 } 286 287 // NewEditorListGrantsWithVersionsRole creates an editor role. `sharing` indicates if sharing permission should be added 288 func NewEditorListGrantsWithVersionsRole() *Role { 289 role := NewEditorListGrantsRole() 290 role.cS3ResourcePermissions.ListFileVersions = true 291 return role 292 } 293 294 // NewSpaceEditorRole creates an editor role 295 func NewSpaceEditorRole() *Role { 296 return &Role{ 297 Name: RoleSpaceEditor, 298 cS3ResourcePermissions: &provider.ResourcePermissions{ 299 CreateContainer: true, 300 Delete: true, 301 GetPath: true, 302 GetQuota: true, 303 InitiateFileDownload: true, 304 InitiateFileUpload: true, 305 ListContainer: true, 306 ListFileVersions: true, 307 ListGrants: true, 308 ListRecycle: true, 309 Move: true, 310 RestoreFileVersion: true, 311 RestoreRecycleItem: true, 312 Stat: true, 313 }, 314 ocsPermissions: PermissionRead | PermissionCreate | PermissionWrite | PermissionDelete, 315 } 316 } 317 318 // NewSpaceEditorWithoutVersionsRole creates an editor without list/restore versions role 319 func NewSpaceEditorWithoutVersionsRole() *Role { 320 return &Role{ 321 Name: RoleSpaceEditorWithoutVersions, 322 cS3ResourcePermissions: &provider.ResourcePermissions{ 323 CreateContainer: true, 324 Delete: true, 325 GetPath: true, 326 GetQuota: true, 327 InitiateFileDownload: true, 328 InitiateFileUpload: true, 329 ListContainer: true, 330 ListGrants: true, 331 ListRecycle: true, 332 Move: true, 333 RestoreRecycleItem: true, 334 Stat: true, 335 }, 336 ocsPermissions: PermissionRead | PermissionCreate | PermissionWrite | PermissionDelete, 337 } 338 } 339 340 // NewFileEditorRole creates a file-editor role 341 func NewFileEditorRole() *Role { 342 p := PermissionRead | PermissionWrite 343 return &Role{ 344 Name: RoleEditor, 345 cS3ResourcePermissions: &provider.ResourcePermissions{ 346 GetPath: true, 347 GetQuota: true, 348 InitiateFileDownload: true, 349 ListContainer: true, 350 ListRecycle: true, 351 Stat: true, 352 InitiateFileUpload: true, 353 RestoreRecycleItem: true, 354 }, 355 ocsPermissions: p, 356 } 357 } 358 359 // NewFileEditorListGrantsRole creates a file-editor role 360 func NewFileEditorListGrantsRole() *Role { 361 role := NewFileEditorRole() 362 role.cS3ResourcePermissions.ListGrants = true 363 return role 364 } 365 366 // NewFileEditorListGrantsWithVersionsRole creates a file-editor role 367 func NewFileEditorListGrantsWithVersionsRole() *Role { 368 role := NewFileEditorListGrantsRole() 369 role.cS3ResourcePermissions.ListFileVersions = true 370 return role 371 } 372 373 // NewCoownerRole creates a coowner role. 374 func NewCoownerRole() *Role { 375 return &Role{ 376 Name: RoleCoowner, 377 cS3ResourcePermissions: &provider.ResourcePermissions{ 378 GetPath: true, 379 GetQuota: true, 380 InitiateFileDownload: true, 381 ListGrants: true, 382 ListContainer: true, 383 ListFileVersions: true, 384 ListRecycle: true, 385 Stat: true, 386 InitiateFileUpload: true, 387 RestoreFileVersion: true, 388 RestoreRecycleItem: true, 389 CreateContainer: true, 390 Delete: true, 391 Move: true, 392 PurgeRecycle: true, 393 AddGrant: true, 394 UpdateGrant: true, 395 RemoveGrant: true, 396 }, 397 ocsPermissions: PermissionAll, 398 } 399 } 400 401 // NewEditorLiteRole creates an editor-lite role 402 func NewEditorLiteRole() *Role { 403 return &Role{ 404 Name: RoleEditorLite, 405 cS3ResourcePermissions: &provider.ResourcePermissions{ 406 Stat: true, 407 GetPath: true, 408 CreateContainer: true, 409 InitiateFileUpload: true, 410 InitiateFileDownload: true, 411 ListContainer: true, 412 Move: true, 413 }, 414 ocsPermissions: PermissionCreate, 415 } 416 } 417 418 // NewUploaderRole creates an uploader role with no download permissions 419 func NewUploaderRole() *Role { 420 return &Role{ 421 Name: RoleUploader, 422 cS3ResourcePermissions: &provider.ResourcePermissions{ 423 Stat: true, 424 GetPath: true, 425 CreateContainer: true, 426 InitiateFileUpload: true, 427 }, 428 ocsPermissions: PermissionCreate, 429 } 430 } 431 432 // NewNoneRole creates a role with no permissions 433 func NewNoneRole() *Role { 434 return &Role{ 435 Name: "none", 436 cS3ResourcePermissions: &provider.ResourcePermissions{}, 437 ocsPermissions: PermissionInvalid, 438 } 439 } 440 441 // NewManagerRole creates an manager role 442 func NewManagerRole() *Role { 443 return &Role{ 444 Name: RoleManager, 445 cS3ResourcePermissions: &provider.ResourcePermissions{ 446 GetPath: true, 447 GetQuota: true, 448 InitiateFileDownload: true, 449 ListGrants: true, 450 ListContainer: true, 451 ListFileVersions: true, 452 ListRecycle: true, 453 Stat: true, 454 InitiateFileUpload: true, 455 RestoreFileVersion: true, 456 RestoreRecycleItem: true, 457 Move: true, 458 CreateContainer: true, 459 Delete: true, 460 PurgeRecycle: true, 461 462 // these permissions only make sense to enforce them in the root of the storage space. 463 AddGrant: true, // managers can add users to the space 464 RemoveGrant: true, // managers can remove users from the space 465 UpdateGrant: true, 466 DenyGrant: true, // managers can deny access to sub folders 467 }, 468 ocsPermissions: PermissionAll, 469 } 470 } 471 472 // NewSecureViewerRole creates a secure viewer role 473 func NewSecureViewerRole() *Role { 474 return &Role{ 475 Name: RoleSecureViewer, 476 cS3ResourcePermissions: &provider.ResourcePermissions{ 477 GetPath: true, 478 ListContainer: true, 479 Stat: true, 480 }, 481 } 482 } 483 484 // RoleFromOCSPermissions tries to map ocs permissions to a role 485 // TODO: rethink using this. ocs permissions cannot be assigned 1:1 to roles 486 func RoleFromOCSPermissions(p Permissions, ri *provider.ResourceInfo) *Role { 487 switch { 488 // Invalid 489 case p == PermissionInvalid: 490 return NewNoneRole() 491 // Uploader 492 case p == PermissionCreate: 493 return NewUploaderRole() 494 // Viewer/SpaceViewer 495 case p == PermissionRead: 496 if isSpaceRoot(ri) { 497 return NewSpaceViewerRole() 498 } 499 return NewViewerRole() 500 // Editor/SpaceEditor 501 case p.Contain(PermissionRead) && p.Contain(PermissionWrite) && p.Contain(PermissionCreate) && p.Contain(PermissionDelete) && !p.Contain(PermissionShare): 502 if isSpaceRoot(ri) { 503 return NewSpaceEditorRole() 504 } 505 506 return NewEditorRole() 507 // Custom 508 default: 509 return NewLegacyRoleFromOCSPermissions(p) 510 } 511 } 512 513 func isSpaceRoot(ri *provider.ResourceInfo) bool { 514 if ri == nil { 515 return false 516 } 517 if ri.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER { 518 return false 519 } 520 521 if ri.GetId().GetOpaqueId() != ri.GetSpace().GetRoot().GetOpaqueId() || 522 ri.GetId().GetSpaceId() != ri.GetSpace().GetRoot().GetSpaceId() || 523 ri.GetId().GetStorageId() != ri.GetSpace().GetRoot().GetStorageId() { 524 return false 525 } 526 return true 527 } 528 529 // NewLegacyRoleFromOCSPermissions tries to map a legacy combination of ocs permissions to cs3 resource permissions as a legacy role 530 func NewLegacyRoleFromOCSPermissions(p Permissions) *Role { 531 r := &Role{ 532 Name: RoleLegacy, // TODO custom role? 533 ocsPermissions: p, 534 cS3ResourcePermissions: &provider.ResourcePermissions{}, 535 } 536 if p.Contain(PermissionRead) { 537 r.cS3ResourcePermissions.ListContainer = true 538 // r.cS3ResourcePermissions.ListGrants = true 539 r.cS3ResourcePermissions.ListRecycle = true 540 r.cS3ResourcePermissions.Stat = true 541 r.cS3ResourcePermissions.GetPath = true 542 r.cS3ResourcePermissions.GetQuota = true 543 r.cS3ResourcePermissions.InitiateFileDownload = true 544 } 545 if p.Contain(PermissionWrite) { 546 r.cS3ResourcePermissions.InitiateFileUpload = true 547 r.cS3ResourcePermissions.RestoreRecycleItem = true 548 } 549 if p.Contain(PermissionCreate) { 550 r.cS3ResourcePermissions.Stat = true 551 r.cS3ResourcePermissions.CreateContainer = true 552 // FIXME permissions mismatch: double check ocs create vs update file 553 // - if the file exists the ocs api needs to check update permission, 554 // - if the file does not exist the ocs api needs to check update permission 555 r.cS3ResourcePermissions.InitiateFileUpload = true 556 if p.Contain(PermissionWrite) { 557 r.cS3ResourcePermissions.Move = true // TODO move only when create and write? 558 } 559 } 560 if p.Contain(PermissionDelete) { 561 r.cS3ResourcePermissions.Delete = true 562 } 563 if p.Contain(PermissionShare) { 564 r.cS3ResourcePermissions.AddGrant = true 565 // r.cS3ResourcePermissions.RemoveGrant = true // TODO when are you able to unshare / delete 566 // r.cS3ResourcePermissions.UpdateGrant = true 567 } 568 return r 569 } 570 571 // RoleFromResourcePermissions tries to map cs3 resource permissions to a role 572 // It needs to know whether this is a link or not, because empty permissions on links mean "INTERNAL LINK" 573 // while empty permissions on other resources mean "DENIAL". Obviously this is not optimal. 574 func RoleFromResourcePermissions(rp *provider.ResourcePermissions, islink bool) *Role { 575 r := &Role{ 576 Name: RoleUnknown, 577 ocsPermissions: PermissionInvalid, 578 cS3ResourcePermissions: rp, 579 } 580 if rp == nil { 581 return r 582 } 583 if grants.PermissionsEqual(rp, &provider.ResourcePermissions{}) { 584 if !islink { 585 r.ocsPermissions = PermissionsNone 586 r.Name = RoleDenied 587 } 588 return r 589 } 590 if rp.ListContainer && 591 rp.ListRecycle && 592 rp.Stat && 593 rp.GetPath && 594 rp.GetQuota && 595 rp.InitiateFileDownload { 596 r.ocsPermissions |= PermissionRead 597 } 598 if rp.InitiateFileUpload && 599 rp.RestoreRecycleItem { 600 r.ocsPermissions |= PermissionWrite 601 } 602 if rp.Stat && 603 rp.CreateContainer && 604 rp.InitiateFileUpload { 605 r.ocsPermissions |= PermissionCreate 606 } 607 if rp.Delete { 608 r.ocsPermissions |= PermissionDelete 609 } 610 if rp.AddGrant { 611 r.ocsPermissions |= PermissionShare 612 } 613 614 if r.ocsPermissions.Contain(PermissionRead) { 615 if r.ocsPermissions.Contain(PermissionWrite) && r.ocsPermissions.Contain(PermissionCreate) && r.ocsPermissions.Contain(PermissionDelete) && r.ocsPermissions.Contain(PermissionShare) { 616 r.Name = RoleEditor 617 if rp.ListGrants { 618 r.Name = RoleEditorListGrants 619 } 620 if rp.ListGrants && rp.ListFileVersions { 621 r.Name = RoleEditorListGrantsWithVersions 622 } 623 if rp.RemoveGrant { 624 r.Name = RoleManager 625 } 626 return r // editor or manager 627 } 628 if r.ocsPermissions == PermissionRead|PermissionShare { 629 r.Name = RoleViewer 630 if rp.ListGrants { 631 r.Name = RoleViewerListGrants 632 } 633 return r 634 } 635 } else if rp.Stat && rp.GetPath && rp.ListContainer && !rp.InitiateFileUpload && !rp.Delete && !rp.AddGrant { 636 r.Name = RoleSecureViewer 637 return r 638 } 639 if r.ocsPermissions == PermissionCreate { 640 if rp.GetPath && rp.InitiateFileDownload && rp.ListContainer && rp.Move { 641 r.Name = RoleEditorLite 642 return r 643 } 644 r.Name = RoleUploader 645 return r 646 } 647 r.Name = RoleLegacy 648 // at this point other ocs permissions may have been mapped. 649 // TODO what about even more granular cs3 permissions?, eg. only stat 650 return r 651 } 652 653 // SufficientCS3Permissions returns true if the `existing` permissions contain the `requested` permissions 654 func SufficientCS3Permissions(existing, requested *provider.ResourcePermissions) bool { 655 if existing == nil || requested == nil { 656 return false 657 } 658 // empty permissions represent a denial 659 if grants.PermissionsEqual(requested, &provider.ResourcePermissions{}) { 660 return existing.DenyGrant 661 } 662 663 numFields := existing.ProtoReflect().Descriptor().Fields().Len() 664 for i := 0; i < numFields; i++ { 665 field := existing.ProtoReflect().Descriptor().Fields().Get(i) 666 existingPermission := existing.ProtoReflect().Get(field).Bool() 667 requestedPermission := requested.ProtoReflect().Get(field).Bool() 668 // every requested permission needs to exist for the creator 669 if requestedPermission && !existingPermission { 670 return false 671 } 672 } 673 return true 674 }