Skip to content

Commit 101d782

Browse files
authored
Add a setting for the fields visibility (#14)
* Add a setting for the fields visibility, and a locker if it is hidden from page config * Add the visibility and signal to the manager interface * Lock the visibility of secret fields only if explicitly provided in the options
1 parent 0749ddf commit 101d782

File tree

6 files changed

+157
-16
lines changed

6 files changed

+157
-16
lines changed

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"files": [
2020
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
2121
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
22-
"src/**/*.{ts,tsx}"
22+
"src/**/*.{ts,tsx}",
23+
"schema/*.json"
2324
],
2425
"main": "lib/index.js",
2526
"types": "lib/index.d.ts",
@@ -58,9 +59,11 @@
5859
"dependencies": {
5960
"@jupyterlab/application": "^4.0.0",
6061
"@jupyterlab/coreutils": "^6.0.0",
62+
"@jupyterlab/settingregistry": "^4.0.0",
6163
"@jupyterlab/statedb": "^4.0.0",
6264
"@lumino/algorithm": "^2.0.0",
63-
"@lumino/coreutils": "^2.1.2"
65+
"@lumino/coreutils": "^2.1.2",
66+
"@lumino/signaling": "^2.1.2"
6467
},
6568
"devDependencies": {
6669
"@jupyterlab/builder": "^4.0.0",
@@ -99,7 +102,8 @@
99102
},
100103
"jupyterlab": {
101104
"extension": true,
102-
"outputDir": "jupyter_secrets_manager/labextension"
105+
"outputDir": "jupyter_secrets_manager/labextension",
106+
"schemaDir": "schema"
103107
},
104108
"eslintIgnore": [
105109
"node_modules",

schema/manager.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"title": "Secrets manager",
3+
"description": "The secrets manager settings",
4+
"type": "object",
5+
"properties": {
6+
"ShowSecretFields": {
7+
"type": "boolean",
8+
"title": "Show secret fields",
9+
"description": "Whether to show the secret fields in the UI or not",
10+
"default": false
11+
}
12+
},
13+
"additionalProperties": false
14+
}

src/index.ts

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import {
22
JupyterFrontEnd,
33
JupyterFrontEndPlugin
44
} from '@jupyterlab/application';
5+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
6+
57
import { SecretsManager } from './manager';
68
import { ISecretsManager } from './token';
79
import { InMemoryConnector } from './connectors';
10+
import { PageConfig } from '@jupyterlab/coreutils';
811

912
/**
1013
* A basic secret connector extension, that should be disabled to provide a new
@@ -23,18 +26,58 @@ const inMemoryConnector: JupyterFrontEndPlugin<void> = {
2326
/**
2427
* The secret manager extension.
2528
*/
26-
const manager: JupyterFrontEndPlugin<ISecretsManager> = {
29+
const managerPlugin: JupyterFrontEndPlugin<ISecretsManager> = {
2730
id: 'jupyter-secrets-manager:manager',
2831
description: 'A JupyterLab extension to manage secrets.',
2932
autoStart: true,
3033
provides: ISecretsManager,
31-
activate: (app: JupyterFrontEnd): ISecretsManager => {
32-
console.log('JupyterLab extension jupyter-secrets-manager is activated!');
33-
return new SecretsManager();
34+
optional: [ISettingRegistry],
35+
activate: (
36+
app: JupyterFrontEnd,
37+
settingRegistry: ISettingRegistry
38+
): ISecretsManager => {
39+
// Check if the fields are hidden from page config.
40+
let showSecretFieldsConfig = true;
41+
if (PageConfig.getOption('secretsManager-showFields') === 'false') {
42+
showSecretFieldsConfig = false;
43+
}
44+
45+
const manager = new SecretsManager({
46+
showSecretFields: showSecretFieldsConfig
47+
});
48+
49+
settingRegistry
50+
.load(managerPlugin.id)
51+
.then(settings => {
52+
// If the fields are hidden from the manager, remove the setting.
53+
if (!showSecretFieldsConfig) {
54+
delete settings.schema.properties?.['ShowSecretFields'];
55+
return;
56+
}
57+
58+
// Otherwise listen to it to update the field visibility.
59+
const updateFieldVisibility = () => {
60+
const showSecretField =
61+
settings.get('ShowSecretFields').composite ?? false;
62+
manager.secretFieldsVisibility = showSecretField as boolean;
63+
};
64+
65+
settings.changed.connect(() => updateFieldVisibility());
66+
updateFieldVisibility();
67+
})
68+
.catch(reason => {
69+
console.error(
70+
`Failed to load settings for ${managerPlugin.id}`,
71+
reason
72+
);
73+
});
74+
75+
console.debug('JupyterLab extension jupyter-secrets-manager is activated!');
76+
return manager;
3477
}
3578
};
3679

3780
export * from './connectors';
3881
export * from './manager';
3982
export * from './token';
40-
export default [inMemoryConnector, manager];
83+
export default [inMemoryConnector, managerPlugin];

src/manager.ts

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,29 @@ import {
88
ISecretsList,
99
ISecretsManager
1010
} from './token';
11+
import { ISignal, Signal } from '@lumino/signaling';
12+
13+
interface IOptions {
14+
showSecretFields?: boolean;
15+
}
1116

1217
/**
1318
* The default secrets manager.
1419
*/
1520
export class SecretsManager implements ISecretsManager {
1621
/**
17-
* the secrets manager constructor.
22+
* The secrets manager constructor.
1823
*/
19-
constructor() {
24+
constructor(options: IOptions) {
2025
this._storing = new PromiseDelegate<void>();
2126
this._storing.resolve();
27+
Private.setSecretFieldsVisibility(options.showSecretFields ?? false);
28+
29+
// If the secret fields are hidden from constructor, this setting comes from
30+
// PageConfig, we need to lock the fields visibility.
31+
if (options.showSecretFields === false) {
32+
Private.lockFieldsVisibility();
33+
}
2234
}
2335

2436
/**
@@ -33,14 +45,44 @@ export class SecretsManager implements ISecretsManager {
3345
this._ready.resolve();
3446
}
3547

48+
/**
49+
* A promise that resolves when the connector is set.
50+
*/
3651
get ready(): Promise<void> {
3752
return this._ready.promise;
3853
}
3954

55+
/**
56+
* A promise that locks the connector access during storage.
57+
*/
4058
protected get storing(): Promise<void> {
4159
return this._storing.promise;
4260
}
4361

62+
/**
63+
* A signal emitting when the field visibility setting has changed.
64+
*/
65+
get fieldVisibilityChanged(): ISignal<this, boolean> {
66+
return this._fieldsVisibilityChanged;
67+
}
68+
69+
/**
70+
* Get the visibility of the secret fields.
71+
*/
72+
get secretFieldsVisibility(): boolean {
73+
return Private.getSecretFieldsVisibility();
74+
}
75+
76+
/**
77+
* Set the visibility of the secret fields.
78+
* The visibility cannot be set if it is locked (from page config).
79+
*/
80+
set secretFieldsVisibility(value: boolean) {
81+
if (Private.setSecretFieldsVisibility(value)) {
82+
this._fieldsVisibilityChanged.emit(Private.getSecretFieldsVisibility());
83+
}
84+
}
85+
4486
/**
4587
* Get a secret given its namespace and ID.
4688
*/
@@ -179,6 +221,7 @@ export class SecretsManager implements ISecretsManager {
179221

180222
private _ready = new PromiseDelegate<void>();
181223
private _storing: PromiseDelegate<void>;
224+
private _fieldsVisibilityChanged = new Signal<this, boolean>(this);
182225
}
183226

184227
/**
@@ -293,7 +336,7 @@ namespace Private {
293336
}
294337

295338
/**
296-
* Actually fetch the secret from the connector.
339+
* Fetch the secret from the connector.
297340
*/
298341
export async function get(id: string): Promise<ISecret | undefined> {
299342
if (!connector?.fetch) {
@@ -303,7 +346,7 @@ namespace Private {
303346
}
304347

305348
/**
306-
* Actually list the secret from the connector.
349+
* List the secret from the connector.
307350
*/
308351
export async function list(
309352
namespace: string
@@ -314,7 +357,7 @@ namespace Private {
314357
return connector.list(namespace);
315358
}
316359
/**
317-
* Actually save the secret using the connector.
360+
* Save the secret using the connector.
318361
*/
319362
export async function set(id: string, secret: ISecret): Promise<any> {
320363
if (!connector?.save) {
@@ -324,7 +367,7 @@ namespace Private {
324367
}
325368

326369
/**
327-
* Actually remove the secrets using the connector.
370+
* Remove the secrets using the connector.
328371
*/
329372
export async function remove(id: string): Promise<void> {
330373
if (!connector?.remove) {
@@ -333,6 +376,32 @@ namespace Private {
333376
return connector.remove(id);
334377
}
335378

379+
/**
380+
* Lock the fields visibility value.
381+
*/
382+
let fieldsVisibilityLocked = false;
383+
export function lockFieldsVisibility() {
384+
fieldsVisibilityLocked = true;
385+
}
386+
387+
/**
388+
* Get/set the fields visibility.
389+
*/
390+
let secretFieldsVisibility = false;
391+
export function getSecretFieldsVisibility(): boolean {
392+
return secretFieldsVisibility;
393+
}
394+
export function setSecretFieldsVisibility(value: boolean): boolean {
395+
if (!fieldsVisibilityLocked && value !== secretFieldsVisibility) {
396+
secretFieldsVisibility = value;
397+
return true;
398+
}
399+
return false;
400+
}
401+
402+
/**
403+
* The secret path type.
404+
*/
336405
export type SecretPath = {
337406
namespace: string;
338407
id: string;

src/token.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
22
import { IDataConnector } from '@jupyterlab/statedb';
33
import { Token } from '@lumino/coreutils';
4+
import { ISignal } from '@lumino/signaling';
45

56
/**
67
* The secret object interface.
@@ -36,6 +37,14 @@ export interface ISecretsManager {
3637
* This is to prevent misconfiguration of competing plugins or MITM attacks.
3738
*/
3839
setConnector(value: ISecretsConnector): void;
40+
/**
41+
* A signal emitting when the field visibility setting has changed.
42+
*/
43+
readonly fieldVisibilityChanged: ISignal<ISecretsManager, boolean>;
44+
/**
45+
* Get the visibility of the secret fields.
46+
*/
47+
readonly secretFieldsVisibility: boolean;
3948
/**
4049
* Get a secret given its namespace and ID.
4150
*/

yarn.lock

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2495,7 +2495,7 @@ __metadata:
24952495
languageName: node
24962496
linkType: hard
24972497

2498-
"@jupyterlab/settingregistry@npm:^4.3.5":
2498+
"@jupyterlab/settingregistry@npm:^4.0.0, @jupyterlab/settingregistry@npm:^4.3.5":
24992499
version: 4.3.5
25002500
resolution: "@jupyterlab/settingregistry@npm:4.3.5"
25012501
dependencies:
@@ -2915,7 +2915,7 @@ __metadata:
29152915
languageName: node
29162916
linkType: hard
29172917

2918-
"@lumino/signaling@npm:^1.10.0 || ^2.0.0, @lumino/signaling@npm:^2.1.3":
2918+
"@lumino/signaling@npm:^1.10.0 || ^2.0.0, @lumino/signaling@npm:^2.1.2, @lumino/signaling@npm:^2.1.3":
29192919
version: 2.1.3
29202920
resolution: "@lumino/signaling@npm:2.1.3"
29212921
dependencies:
@@ -6802,10 +6802,12 @@ __metadata:
68026802
"@jupyterlab/application": ^4.0.0
68036803
"@jupyterlab/builder": ^4.0.0
68046804
"@jupyterlab/coreutils": ^6.0.0
6805+
"@jupyterlab/settingregistry": ^4.0.0
68056806
"@jupyterlab/statedb": ^4.0.0
68066807
"@jupyterlab/testutils": ^4.0.0
68076808
"@lumino/algorithm": ^2.0.0
68086809
"@lumino/coreutils": ^2.1.2
6810+
"@lumino/signaling": ^2.1.2
68096811
"@types/jest": ^29.2.0
68106812
"@types/json-schema": ^7.0.11
68116813
"@types/react": ^18.0.26

0 commit comments

Comments
 (0)