Skip to content

Commit 916b792

Browse files
committed
add mention of conda-store default mapping
1 parent 8eb09f0 commit 916b792

File tree

3 files changed

+134
-0
lines changed

3 files changed

+134
-0
lines changed

docs/docs/how-tos/fine-grained-permissions.mdx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,32 @@ role-mappings](https://conda.store/conda-store/explanations/conda-store-concepts
340340

341341
</div>
342342

343+
:::note Conda-Store defaults and Nebari’s internal group handling
344+
Nebari grants some Conda-Store permissions **automatically**, regardless of how your
345+
Keycloak groups are configured. The authentication class applies these defaults at
346+
login so users can work even if the Keycloak groups don’t define any `conda_store_*`
347+
client roles.
348+
349+
**What Nebari applies by default**
350+
- **Personal namespace** → always **admin**
351+
`"{username}/*" → {"admin"}` (cannot be downgraded by scopes).
352+
- **Deployment default namespace** → **viewer**
353+
`"{default_namespace}/*" → {"viewer"}`.
354+
- **Nebari default groups** (`analyst`, `developer`, `admin`) → **[handled internally](https://github.com/nebari-dev/nebari/blob/main/src/_nebari/stages/kubernetes_services/template/modules/kubernetes/services/conda-store/config/conda_store_config.py#L105)**
355+
If a user is in one of these groups, Nebari binds a **same-named namespace** to the
356+
user with that group’s effective Conda-Store role (e.g., group `analyst` ⇒ `analyst/*`),
357+
**even when the Keycloak group itself has no client roles**.
358+
- **SuperAdmin** (`conda_store_superadmin`) → full wildcard
359+
`"*/*" → {"admin"}` (includes delete & service-token privileges).
360+
361+
**How it reconciles with Keycloak scopes**
362+
- On each login, Nebari **removes non-default bindings** and re-applies permissions from
363+
Keycloak role **scopes** (e.g., `admin!namespace=analyst`).
364+
- If multiple grants target the same namespace, the **highest** permission wins.
365+
- Order of precedence (highest → lowest): **SuperAdmin** → **explicit scopes** → **internal defaults**.
366+
- Changes take effect after the user signs in again.
367+
:::
368+
343369
### Creating a Role
344370

345371
In the following example, we create a new role for the `JupyterHub` client granting the

docs/src/theme/TOCItems/Tree.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import Link from '@docusaurus/Link';
3+
import type { Props } from '@theme/TOCItems/Tree';
4+
5+
function TOCItemTree({
6+
toc,
7+
className,
8+
linkClassName,
9+
isChild,
10+
}: Props): JSX.Element | null {
11+
if (!toc.length) {
12+
return null;
13+
}
14+
15+
return (
16+
<ul className={isChild ? undefined : className}>
17+
{toc.map((heading) => {
18+
// Parse the heading ID to check if it has "provider::actualId"
19+
let providerQuery = null;
20+
let actualId = heading.id;
21+
22+
if (heading.id.includes('::')) {
23+
const [provider, ...rest] = heading.id.split('::');
24+
providerQuery = provider;
25+
actualId = rest.join('::'); // In case there's more than one '::', join back the rest.
26+
}
27+
28+
// If a provider is found, build the URL with the query param
29+
const linkHref = providerQuery
30+
? `?provider=${providerQuery}#${heading.id}`
31+
: `#${heading.id}`;
32+
33+
return (
34+
<li key={heading.id}>
35+
<Link
36+
to={linkHref}
37+
className={linkClassName ?? undefined}
38+
// Developer provided the HTML, so assume it's safe.
39+
dangerouslySetInnerHTML={{ __html: heading.value }}
40+
/>
41+
<TOCItemTree
42+
isChild
43+
toc={heading.children}
44+
className={className}
45+
linkClassName={linkClassName}
46+
/>
47+
</li>
48+
);
49+
})}
50+
</ul>
51+
);
52+
}
53+
54+
export default React.memo(TOCItemTree);

docs/src/theme/TOCItems/index.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, {useMemo} from 'react';
2+
import {useThemeConfig} from '@docusaurus/theme-common';
3+
import {
4+
useTOCHighlight,
5+
useFilteredAndTreeifiedTOC,
6+
type TOCHighlightConfig,
7+
} from '@docusaurus/theme-common/internal';
8+
import TOCItemTree from '@theme/TOCItems/Tree';
9+
import type {Props} from '@theme/TOCItems';
10+
11+
export default function TOCItems({
12+
toc,
13+
className = 'table-of-contents table-of-contents__left-border',
14+
linkClassName = 'table-of-contents__link',
15+
linkActiveClassName = undefined,
16+
minHeadingLevel: minHeadingLevelOption,
17+
maxHeadingLevel: maxHeadingLevelOption,
18+
...props
19+
}: Props): JSX.Element | null {
20+
const themeConfig = useThemeConfig();
21+
22+
const minHeadingLevel =
23+
minHeadingLevelOption ?? themeConfig.tableOfContents.minHeadingLevel;
24+
const maxHeadingLevel =
25+
maxHeadingLevelOption ?? themeConfig.tableOfContents.maxHeadingLevel;
26+
27+
const tocTree = useFilteredAndTreeifiedTOC({
28+
toc,
29+
minHeadingLevel,
30+
maxHeadingLevel,
31+
});
32+
33+
const tocHighlightConfig: TOCHighlightConfig | undefined = useMemo(() => {
34+
if (linkClassName && linkActiveClassName) {
35+
return {
36+
linkClassName,
37+
linkActiveClassName,
38+
minHeadingLevel,
39+
maxHeadingLevel,
40+
};
41+
}
42+
return undefined;
43+
}, [linkClassName, linkActiveClassName, minHeadingLevel, maxHeadingLevel]);
44+
useTOCHighlight(tocHighlightConfig);
45+
46+
return (
47+
<TOCItemTree
48+
toc={tocTree}
49+
className={className}
50+
linkClassName={linkClassName}
51+
{...props}
52+
/>
53+
);
54+
}

0 commit comments

Comments
 (0)