Skip to content

Commit 13723fa

Browse files
authored
feat: managed connection inclusion (#1242)
* fix: managed connection inclusion * fix: remove Username-Password-Authentication from included connections example * chore: resolve comments * feat: add support for included connections in YAML context and update schema * feat: enforce exclusive configuration for AUTH0_INCLUDED_CONNECTIONS and AUTH0_EXCLUDED_CONNECTIONS * fix: update AUTH0 connection handling to respect include list - Adjusted logic to ensure 'enterprise-saml' is not marked for deletion when not in the include list. - Modified tests to reflect that 'google-oauth2' should not be deleted if it's not in the include list. - Updated behavior to only delete managed connections when assets are empty, preserving unmanaged ones. - Ensured that unmanaged connections are ignored during updates, preventing unintended deletions. * fix: update included connections in environment variable example * fix: include rules in initial assets for proper asset management * feat: add filterIncluded function to manage included connections in changes * feat: enhance connection processing to respect included connections and improve test coverage
1 parent 0b45726 commit 13723fa

17 files changed

Lines changed: 7508 additions & 15931 deletions

docs/configuring-the-deploy-cli.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,41 @@ String. Specifies the JWT signing algorithms used by the client when facilitatin
8686

8787
Boolean. When enabled, will allow the tool to delete resources. Default: `false`.
8888

89+
### `AUTH0_INCLUDED_CONNECTIONS`
90+
91+
Array of strings. Specifies which connections should be managed by the Deploy CLI. When configured, only the connections listed by name will be included in export and import operations. All other connections in the tenant will be completely ignored.
92+
93+
This is particularly useful for:
94+
- Managing only specific connections while preserving others (e.g., self-service SSO connections, third-party integrations)
95+
- Preventing accidental modifications to connections managed by other systems
96+
- Isolating connection management to specific subsets of your tenant
97+
98+
**Important:** This setting affects all operations (export and import). Connections not in this list will not appear in exports and will not be modified during imports.
99+
100+
Cannot be used simultaneously with `AUTH0_EXCLUDED_CONNECTIONS`.
101+
102+
#### Example
103+
104+
```json
105+
{
106+
"AUTH0_INCLUDED_CONNECTIONS": ["github", "google-oauth2"]
107+
}
108+
```
109+
110+
In the example above, only the `github` and `google-oauth2` connections will be managed. All other connections in the tenant will be ignored.
111+
112+
#### Environment Variable Format
113+
114+
When passing as an environment variable, use JSON array format:
115+
116+
```shell
117+
# JSON array format
118+
export AUTH0_INCLUDED_CONNECTIONS='["github","google-oauth2"]'
119+
120+
# Or as a single-line array
121+
export AUTH0_INCLUDED_CONNECTIONS='["github"]'
122+
```
123+
89124
### `AUTH0_EXCLUDED`
90125

91126
Array of strings. Excludes entire resource types from being managed, bi-directionally. See also: [excluding resources from management](excluding-from-management.md). Possible values: `actions`, `attackProtection`, `branding`, `clientGrants`, `clients`, `connections`, `customDomains`, `databases`, `emailProvider`, `phoneProviders`, `emailTemplates`, `guardianFactorProviders`, `guardianFactorTemplates`, `guardianFactors`, `guardianPhoneFactorMessageTypes`, `guardianPhoneFactorSelectedProvider`, `guardianPolicies`, `logStreams`, `migrations`, `organizations`, `pages`, `prompts`, `resourceServers`, `roles`, `tenant`, `triggers`, `selfServiceProfiles`.
@@ -175,6 +210,8 @@ Array of strings. Excludes the management of specific databases by name. **Note:
175210

176211
Array of strings. Excludes the management of specific connections by name. **Note:** This configuration may be subject to deprecation in the future. See: [excluding resources from management](excluding-from-management.md).
177212

213+
Cannot be used simultaneously with `AUTH0_INCLUDED_CONNECTIONS`.
214+
178215
### `AUTH0_EXCLUDED_RESOURCE_SERVERS`
179216

180217
Array of strings. Excludes the management of specific resource servers by name. **Note:** This configuration may be subject to deprecation in the future. See: [excluding resources from management](excluding-from-management.md).

src/context/directory/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export default class DirectoryContext {
4040
resourceServers: config.AUTH0_EXCLUDED_RESOURCE_SERVERS || [],
4141
defaults: config.AUTH0_EXCLUDED_DEFAULTS || [],
4242
};
43+
44+
this.assets.include = {
45+
connections: config.AUTH0_INCLUDED_CONNECTIONS || [],
46+
};
4347
}
4448

4549
loadFile(f: string, folder: string) {

src/context/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const nonPrimitiveProps: (keyof Config)[] = [
2424
'AUTH0_INCLUDED_ONLY',
2525
'EXCLUDED_PROPS',
2626
'INCLUDED_PROPS',
27+
'AUTH0_INCLUDED_CONNECTIONS',
2728
];
2829

2930
const EA_FEATURES = [];
@@ -134,6 +135,21 @@ export const setupContext = async (
134135
}
135136
})(config);
136137

138+
((config: Config) => {
139+
const hasIncludedConnections =
140+
config.AUTH0_INCLUDED_CONNECTIONS !== undefined &&
141+
config.AUTH0_INCLUDED_CONNECTIONS.length > 0;
142+
const hasExcludedConnections =
143+
config.AUTH0_EXCLUDED_CONNECTIONS !== undefined &&
144+
config.AUTH0_EXCLUDED_CONNECTIONS.length > 0;
145+
146+
if (hasIncludedConnections && hasExcludedConnections) {
147+
throw new Error(
148+
'Both AUTH0_INCLUDED_CONNECTIONS and AUTH0_EXCLUDED_CONNECTIONS configuration values are defined, only one can be configured at a time.'
149+
);
150+
}
151+
})(config);
152+
137153
((config: Config) => {
138154
// Check if experimental early access features are enabled
139155
if (config.AUTH0_EXPERIMENTAL_EA) {

src/context/yaml/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ export default class YAMLContext {
4646
defaults: config.AUTH0_EXCLUDED_DEFAULTS || [],
4747
};
4848

49+
this.assets.include = {
50+
connections: config.AUTH0_INCLUDED_CONNECTIONS || [],
51+
};
52+
4953
this.basePath = (() => {
5054
if (!!config.AUTH0_BASE_PATH) return config.AUTH0_BASE_PATH;
5155
//@ts-ignore because this looks to be a bug, but do not want to introduce regression; more investigation needed
@@ -100,6 +104,7 @@ export default class YAMLContext {
100104

101105
const initialAssets: Assets = {
102106
exclude: this.assets.exclude, // Keep the exclude rules in result assets
107+
include: this.assets.include, // Keep the include rules in result assets
103108
};
104109
this.assets = Object.keys(this.assets).reduce((acc: Assets, key: AssetTypes) => {
105110
// Get the list of asset types to include
@@ -202,6 +207,7 @@ export default class YAMLContext {
202207

203208
// Delete exclude as it's not part of the auth0 tenant config
204209
delete cleaned.exclude;
210+
delete cleaned.include;
205211

206212
// Optionally Strip identifiers
207213
if (!this.config.AUTH0_EXPORT_IDENTIFIERS) {

src/tools/auth0/handlers/connections.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import dotProp from 'dot-prop';
22
import { chunk, keyBy } from 'lodash';
33
import { Management, ManagementError } from 'auth0';
44
import DefaultAPIHandler, { order } from './default';
5-
import { filterExcluded, convertClientNameToId, getEnabledClients, sleep } from '../../utils';
5+
import {
6+
filterExcluded,
7+
convertClientNameToId,
8+
getEnabledClients,
9+
sleep,
10+
filterIncluded,
11+
} from '../../utils';
612
import { CalculatedChanges, Asset, Assets, Auth0APIClient } from '../../../types';
713
import { ConfigFunction } from '../../../configFactory';
814
import { paginate } from '../client';
@@ -621,6 +627,7 @@ export default class ConnectionsHandler extends DefaultAPIHandler {
621627
...this.getFormattedOptions(connection, clients),
622628
enabled_clients: getEnabledClients(assets, connection, existingConnections, clients),
623629
}));
630+
624631
const proposedChanges = await super.calcChanges({ ...assets, connections: formatted });
625632

626633
const proposedChangesWithExcludedProperties = addExcludedConnectionPropertiesToChanges({
@@ -648,20 +655,20 @@ export default class ConnectionsHandler extends DefaultAPIHandler {
648655
);
649656
}
650657

658+
const includedConnections = (assets.include && assets.include.connections) || [];
651659
const excludedConnections = (assets.exclude && assets.exclude.connections) || [];
652660

653-
const changes = await this.calcChanges(assets);
661+
let changes = await this.calcChanges(assets);
662+
663+
changes = filterExcluded(changes, excludedConnections);
664+
changes = filterIncluded(changes, includedConnections);
654665

655-
await super.processChanges(assets, filterExcluded(changes, excludedConnections));
666+
await super.processChanges(assets, changes);
656667

657668
// process enabled clients
658-
await processConnectionEnabledClients(
659-
this.client,
660-
this.type,
661-
filterExcluded(changes, excludedConnections)
662-
);
669+
await processConnectionEnabledClients(this.client, this.type, changes);
663670

664671
// process directory provisioning
665-
await this.processConnectionDirectoryProvisioning(filterExcluded(changes, excludedConnections));
672+
await this.processConnectionDirectoryProvisioning(changes);
666673
}
667674
}

src/tools/auth0/handlers/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,10 @@ const auth0ApiHandlers: { [key in AssetTypes]: any } = {
8585
};
8686

8787
export default auth0ApiHandlers as {
88-
[key in AssetTypes]: { default: typeof APIHandler; excludeSchema?: any; schema: any };
88+
[key in AssetTypes]: {
89+
default: typeof APIHandler;
90+
excludeSchema?: any;
91+
schema: any;
92+
includeSchema?: any;
93+
};
8994
}; // TODO: apply stronger types to schema properties

src/tools/auth0/schema.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ const excludeSchema = Object.entries(handlers).reduce(
1818
{}
1919
);
2020

21+
const includeSchema = Object.entries(handlers).reduce(
22+
(map: { [key: string]: Object }, [name, obj]) => {
23+
if (obj.includeSchema) {
24+
map[name] = obj.includeSchema;
25+
}
26+
return map;
27+
},
28+
{}
29+
);
30+
2131
export default {
2232
type: 'object',
2333
$schema: 'http://json-schema.org/draft-07/schema#',
@@ -28,6 +38,11 @@ export default {
2838
properties: { ...excludeSchema },
2939
default: {},
3040
},
41+
include: {
42+
type: 'object',
43+
properties: { ...includeSchema },
44+
default: {},
45+
},
3146
},
3247
additionalProperties: false,
3348
};

src/tools/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,23 @@ export function filterExcluded(changes: CalculatedChanges, exclude: string[]): C
199199
};
200200
}
201201

202+
export function filterIncluded(changes: CalculatedChanges, include: string[]): CalculatedChanges {
203+
const { del, update, create, conflicts } = changes;
204+
205+
if (!include || !include.length) {
206+
return changes;
207+
}
208+
209+
const filter = (list: Asset[]) => list.filter((item) => include.includes(item.name));
210+
211+
return {
212+
del: filter(del),
213+
update: filter(update),
214+
create: filter(create),
215+
conflicts: filter(conflicts),
216+
};
217+
}
218+
202219
export function areArraysEquals(x: any[], y: any[]): boolean {
203220
return _.isEqual(x && x.sort(), y && y.sort());
204221
}

src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export type Config = {
8282
INCLUDED_PROPS?: {
8383
[key: string]: string[];
8484
};
85+
AUTH0_INCLUDED_CONNECTIONS?: string[];
8586
AUTH0_IGNORE_UNAVAILABLE_MIGRATIONS?: boolean;
8687
// Eventually deprecate. See: https://github.com/auth0/auth0-deploy-cli/issues/451#user-content-deprecated-exclusion-props
8788
AUTH0_EXCLUDED_RULES?: string[];
@@ -138,6 +139,9 @@ export type Assets = Partial<{
138139
exclude?: {
139140
[key: string]: string[];
140141
};
142+
include?: {
143+
[key: string]: string[];
144+
};
141145
clientsOrig: Asset[] | null;
142146
themes: Theme[] | null;
143147
forms: Form[] | null;

test/context/context.test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,49 @@ describe('#context loader validation', async () => {
255255
'Need to define at least one resource type in AUTH0_INCLUDED_ONLY configuration. See: https://github.com/auth0/auth0-deploy-cli/blob/master/docs/configuring-the-deploy-cli.md#auth0_included_only'
256256
);
257257
});
258+
259+
it('should error if trying to configure AUTH0_INCLUDED_CONNECTIONS and AUTH0_EXCLUDED_CONNECTIONS simultaneously', async () => {
260+
const dir = path.resolve(testDataDir, 'context');
261+
cleanThenMkdir(dir);
262+
const yaml = path.join(dir, 'empty.yaml');
263+
fs.writeFileSync(yaml, '');
264+
265+
await expect(
266+
setupContext(
267+
{
268+
...config,
269+
AUTH0_INPUT_FILE: yaml,
270+
AUTH0_INCLUDED_CONNECTIONS: ['github', 'google-oauth2'],
271+
AUTH0_EXCLUDED_CONNECTIONS: ['facebook'],
272+
},
273+
'import'
274+
)
275+
).to.be.rejectedWith(
276+
'Both AUTH0_INCLUDED_CONNECTIONS and AUTH0_EXCLUDED_CONNECTIONS configuration values are defined, only one can be configured at a time.'
277+
);
278+
279+
await expect(
280+
setupContext(
281+
{
282+
...config,
283+
AUTH0_INCLUDED_CONNECTIONS: ['github', 'google-oauth2'],
284+
AUTH0_INPUT_FILE: yaml,
285+
},
286+
'import'
287+
)
288+
).to.be.not.rejected;
289+
290+
await expect(
291+
setupContext(
292+
{
293+
...config,
294+
AUTH0_EXCLUDED_CONNECTIONS: ['facebook'],
295+
AUTH0_INPUT_FILE: yaml,
296+
},
297+
'import'
298+
)
299+
).to.be.not.rejected;
300+
});
258301
});
259302

260303
describe('#filterOnlyIncludedResourceTypes', async () => {

0 commit comments

Comments
 (0)