Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/context/directory/handlers/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ async function dump(context: DirectoryContext): Promise<void> {
);
}

// Filter to included connections
const includedConnections = (context.assets.include && context.assets.include.connections) || [];
if (includedConnections.length) {
connections = connections.filter((connection) => includedConnections.includes(connection.name));
}

const connectionsFolder = path.join(context.filePath, constants.CONNECTIONS_DIRECTORY);
fs.ensureDirSync(connectionsFolder);

Expand Down Expand Up @@ -131,11 +137,23 @@ async function dump(context: DirectoryContext): Promise<void> {
if (dumpedConnection.strategy === 'email') expectedFiles.add(`${connectionName}.html`);
});

// With an include list configured, connections outside it are unmanaged, so limit pruning
// to the listed names rather than every file in the folder.
const prunableFiles = includedConnections.length
? new Set(
includedConnections.flatMap((name) => [`${sanitize(name)}.json`, `${sanitize(name)}.html`])
)
: null;

// Remove files that belong to connections no longer present (and not excluded)
if (fs.existsSync(connectionsFolder)) {
for (const existing of fs.readdirSync(connectionsFolder)) {
const fullPath = path.join(connectionsFolder, existing);
if (fs.statSync(fullPath).isFile() && !expectedFiles.has(existing)) {
if (
fs.statSync(fullPath).isFile() &&
!expectedFiles.has(existing) &&
(prunableFiles === null || prunableFiles.has(existing))
) {
fs.removeSync(fullPath);
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/context/yaml/handlers/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ async function dump(context: YAMLContext): Promise<ParsedConnections> {
);
}

// Filter to included connections
const includedConnections = (context.assets.include && context.assets.include.connections) || [];
if (includedConnections.length) {
connections = connections.filter((connection) => includedConnections.includes(connection.name));
}

return {
connections: connections.map((connection) => {
let dumpedConnection = {
Expand Down
63 changes: 63 additions & 0 deletions test/context/directory/connections.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,69 @@ describe('#directory context connections', () => {
expect(fs.existsSync(path.join(connectionsFolder, 'excludedConnection.json'))).to.equal(true);
});

it('should only dump included connections', async () => {
const dir = path.join(testDataDir, 'directory', 'connectionsDumpInclude');
cleanThenMkdir(dir);
const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient());

context.assets.connections = [
{ name: 'includedConnection', strategy: 'waad' },
{ name: 'unmanagedConnection', strategy: 'samlp' },
];
context.assets.include = { connections: ['includedConnection'] };

await handler.dump(context);
const connectionsFolder = path.join(dir, constants.CONNECTIONS_DIRECTORY);

expect(fs.existsSync(path.join(connectionsFolder, 'includedConnection.json'))).to.equal(true);
expect(fs.existsSync(path.join(connectionsFolder, 'unmanagedConnection.json'))).to.equal(false);
});

it('should preserve pre-existing files for connections outside the include list on re-dump', async () => {
const dir = path.join(testDataDir, 'directory', 'connectionsDumpInclude');
cleanThenMkdir(dir);

// Simulate a prior export that wrote a connection now outside the include list
const connectionsFolder = path.join(dir, constants.CONNECTIONS_DIRECTORY);
fs.ensureDirSync(connectionsFolder);
fs.writeFileSync(
path.join(connectionsFolder, 'unmanagedConnection.json'),
'{"name":"unmanagedConnection"}'
);

const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient());
context.assets.connections = [
{ name: 'includedConnection', strategy: 'waad' },
{ name: 'unmanagedConnection', strategy: 'samlp' },
];
context.assets.include = { connections: ['includedConnection'] };

await handler.dump(context);

expect(fs.existsSync(path.join(connectionsFolder, 'includedConnection.json'))).to.equal(true);
expect(fs.existsSync(path.join(connectionsFolder, 'unmanagedConnection.json'))).to.equal(true);
});

it('should remove stale files for included connections no longer present on the tenant', async () => {
const dir = path.join(testDataDir, 'directory', 'connectionsIncludeStaleFiles');
cleanThenMkdir(dir);

// Simulate a previous export of two included connections, one since deleted on the tenant
const connectionsFolder = path.join(dir, constants.CONNECTIONS_DIRECTORY);
fs.ensureDirSync(connectionsFolder);
fs.writeFileSync(path.join(connectionsFolder, 'facebook.json'), '{"name":"facebook"}');
fs.writeFileSync(path.join(connectionsFolder, 'github.json'), '{"name":"github"}');

const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient());
context.assets.connections = [{ name: 'github', strategy: 'github' }];
context.assets.include = { connections: ['facebook', 'github'] };

await handler.dump(context);

expect(fs.existsSync(path.join(connectionsFolder, 'github.json'))).to.equal(true);
expect(fs.existsSync(path.join(connectionsFolder, 'facebook.json'))).to.equal(false);
});

it('should remove stale connection files when connections are removed from source', async () => {
const dir = path.join(testDataDir, 'directory', 'connectionsStaleFiles');
cleanThenMkdir(dir);
Expand Down
20 changes: 20 additions & 0 deletions test/context/yaml/connections.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,24 @@ describe('#YAML context connections', () => {
expect(dumped.connections).to.have.length(1);
expect(dumped.connections[0].name).to.equal('includedConnection');
});

it('should only dump included connections', async () => {
const dir = path.join(testDataDir, 'yaml', 'connectionsDumpInclude');
cleanThenMkdir(dir);
const context = new Context(
{ AUTH0_INPUT_FILE: path.join(dir, 'tenant.yaml') },
mockMgmtClient()
);

context.assets.connections = [
{ name: 'includedConnection', strategy: 'waad' },
{ name: 'unmanagedConnection', strategy: 'samlp' },
];
context.assets.include = { connections: ['includedConnection'] };

const dumped = await handler.dump(context);

expect(dumped.connections).to.have.length(1);
expect(dumped.connections[0].name).to.equal('includedConnection');
});
});