Skip to content

Commit

Permalink
UI: Don't show resultant-ACL banner in nested namespace if ancestor w…
Browse files Browse the repository at this point in the history
…ildcard policy (#27263)

* Refactor hasWildcardAccess to check for ancestors of current namespace in globPaths

* Add extra test coverage

* remove tests for removed getter

* changelog + test update

* update test coverage to also check resulting permissionsBanner state

* rename hasWildcardAccess for clarity, add isDenied check on namespace access check

* Update changelog/27263.txt

Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>

* Remove redundant check

---------

Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com>
  • Loading branch information
hashishaw and hellobontempo authored May 30, 2024
1 parent 5c275e7 commit 83949e8
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 90 deletions.
3 changes: 3 additions & 0 deletions changelog/27263.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: Do not show resultant-ACL banner when ancestor namespace grants wildcard access.
```
51 changes: 32 additions & 19 deletions ui/app/services/permissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export default class PermissionsService extends Service {
@service store;
@service namespace;

get baseNs() {
get fullCurrentNamespace() {
const currentNs = this.namespace.path;
return this.chrootNamespace
? `${sanitizePath(this.chrootNamespace)}/${sanitizePath(currentNs)}`
Expand All @@ -122,24 +122,37 @@ export default class PermissionsService extends Service {
}
}

get wildcardPath() {
const ns = [sanitizePath(this.chrootNamespace), sanitizePath(this.namespace.userRootNamespace)].join('/');
// wildcard path comes back from root namespace as empty string,
// but within a namespace it's the namespace itself ending with a slash
return ns === '/' ? '' : `${sanitizePath(ns)}/`;
}

/**
* hasWildcardAccess checks if the user has a wildcard policy
* hasWildcardNsAccess checks if the user has a wildcard access to target namespace
* via full glob path or any ancestors of the target namespace
* @param {string} targetNs is the current/target namespace that we are checking access for
* @param {object} globPaths key is path, value is object with capabilities
* @returns {boolean} whether the user's policy includes wildcard access to NS
*/
hasWildcardAccess(globPaths = {}) {
// First check if the wildcard path is in the globPaths object
if (!Object.keys(globPaths).includes(this.wildcardPath)) return false;
hasWildcardNsAccess(targetNs, globPaths = {}) {
const nsParts = sanitizePath(targetNs).split('/');
let matchKey = null;
// For each section of the namespace, check if there is a matching wildcard path
while (nsParts.length > 0) {
// glob paths always end in a slash
const test = `${nsParts.join('/')}/`;
if (Object.keys(globPaths).includes(test)) {
matchKey = test;
break;
}
nsParts.pop();
}
// Finally, check if user has wildcard access to the root namespace
// which is represented by an empty string
if (!matchKey && Object.keys(globPaths).includes('')) {
matchKey = '';
}
if (null === matchKey) {
return false;
}

// if so, make sure the current namespace is a child of the wildcard path
return this.namespace.path.startsWith(this.wildcardPath);
// if there is a match make sure the capabilities do not include deny
return !this.isDenied(globPaths[matchKey]);
}

// This method is called to recalculate whether to show the permissionsBanner when the namespace changes
Expand All @@ -148,14 +161,14 @@ export default class PermissionsService extends Service {
this.permissionsBanner = null;
return;
}
const namespace = this.baseNs;
const namespace = this.fullCurrentNamespace;
const allowed =
// check if the user has wildcard access to the relative root namespace
this.hasWildcardAccess(this.globPaths) ||
this.hasWildcardNsAccess(namespace, this.globPaths) ||
// or if any of their glob paths start with the namespace
Object.keys(this.globPaths).any((k) => k.startsWith(namespace)) ||
Object.keys(this.globPaths).any((k) => k.startsWith(namespace) && !this.isDenied(this.globPaths[k])) ||
// or if any of their exact paths start with the namespace
Object.keys(this.exactPaths).any((k) => k.startsWith(namespace));
Object.keys(this.exactPaths).any((k) => k.startsWith(namespace) && !this.isDenied(this.exactPaths[k]));
this.permissionsBanner = allowed ? null : PERMISSIONS_BANNER_STATES.noAccess;
}

Expand Down Expand Up @@ -200,7 +213,7 @@ export default class PermissionsService extends Service {
}

pathNameWithNamespace(pathName) {
const namespace = this.baseNs;
const namespace = this.fullCurrentNamespace;
if (namespace) {
return `${sanitizePath(namespace)}/${sanitizeStart(pathName)}`;
} else {
Expand Down
Loading

0 comments on commit 83949e8

Please sign in to comment.