@backstage-community/plugin-catalog-backend-module-keycloak 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,239 @@
1
+ 'use strict';
2
+
3
+ var constants = require('./constants.cjs.js');
4
+ var transformers = require('./transformers.cjs.js');
5
+
6
+ const parseGroup = async (keycloakGroup, realm, groupTransformer) => {
7
+ const transformer = groupTransformer ?? transformers.noopGroupTransformer;
8
+ const entity = {
9
+ apiVersion: "backstage.io/v1beta1",
10
+ kind: "Group",
11
+ metadata: {
12
+ name: keycloakGroup.name,
13
+ annotations: {
14
+ [constants.KEYCLOAK_ID_ANNOTATION]: keycloakGroup.id,
15
+ [constants.KEYCLOAK_REALM_ANNOTATION]: realm
16
+ }
17
+ },
18
+ spec: {
19
+ type: "group",
20
+ profile: {
21
+ displayName: keycloakGroup.name
22
+ },
23
+ // children, parent and members are updated again after all group and user transformers applied.
24
+ children: keycloakGroup.subGroups?.map((g) => g.name) ?? [],
25
+ parent: keycloakGroup.parent,
26
+ members: keycloakGroup.members
27
+ }
28
+ };
29
+ return await transformer(entity, keycloakGroup, realm);
30
+ };
31
+ const parseUser = async (user, realm, keycloakGroups, userTransformer) => {
32
+ const transformer = userTransformer ?? transformers.noopUserTransformer;
33
+ const entity = {
34
+ apiVersion: "backstage.io/v1beta1",
35
+ kind: "User",
36
+ metadata: {
37
+ name: user.username,
38
+ annotations: {
39
+ [constants.KEYCLOAK_ID_ANNOTATION]: user.id,
40
+ [constants.KEYCLOAK_REALM_ANNOTATION]: realm
41
+ }
42
+ },
43
+ spec: {
44
+ profile: {
45
+ email: user.email,
46
+ ...user.firstName || user.lastName ? {
47
+ displayName: [user.firstName, user.lastName].filter(Boolean).join(" ")
48
+ } : {}
49
+ },
50
+ memberOf: keycloakGroups.filter((g) => g.members?.includes(user.username)).map((g) => g.entity.metadata.name)
51
+ }
52
+ };
53
+ return await transformer(entity, user, realm, keycloakGroups);
54
+ };
55
+ async function getEntities(entities, config, logger, entityQuerySize = constants.KEYCLOAK_ENTITY_QUERY_SIZE) {
56
+ const rawEntityCount = await entities.count({ realm: config.realm });
57
+ const entityCount = typeof rawEntityCount === "number" ? rawEntityCount : rawEntityCount.count;
58
+ const pageCount = Math.ceil(entityCount / entityQuerySize);
59
+ const entityPromises = Array.from(
60
+ { length: pageCount },
61
+ (_, i) => entities.find({
62
+ realm: config.realm,
63
+ max: entityQuerySize,
64
+ first: i * entityQuerySize
65
+ }).catch(
66
+ (err) => logger.warn("Failed to retieve Keycloak entities.", err)
67
+ )
68
+ );
69
+ const entityResults = (await Promise.all(entityPromises)).flat();
70
+ return entityResults;
71
+ }
72
+ async function getAllGroupMembers(groups, groupId, config, options) {
73
+ const querySize = options?.userQuerySize || 100;
74
+ let allMembers = [];
75
+ let page = 0;
76
+ let totalMembers = 0;
77
+ do {
78
+ const members = await groups.listMembers({
79
+ id: groupId,
80
+ max: querySize,
81
+ realm: config.realm,
82
+ first: page * querySize
83
+ });
84
+ if (members.length > 0) {
85
+ allMembers = allMembers.concat(members.map((m) => m.username));
86
+ totalMembers = members.length;
87
+ } else {
88
+ totalMembers = 0;
89
+ }
90
+ page++;
91
+ } while (totalMembers > 0);
92
+ return allMembers;
93
+ }
94
+ async function processGroupsRecursively(topLevelGroups, entities, realm) {
95
+ const allGroups = [];
96
+ for (const group of topLevelGroups) {
97
+ allGroups.push(group);
98
+ if (group.subGroupCount > 0) {
99
+ const subgroups = await entities.listSubGroups({
100
+ parentId: group.id,
101
+ first: 0,
102
+ max: group.subGroupCount,
103
+ briefRepresentation: true,
104
+ realm
105
+ });
106
+ const subGroupResults = await processGroupsRecursively(
107
+ subgroups,
108
+ entities,
109
+ realm
110
+ );
111
+ allGroups.push(...subGroupResults);
112
+ }
113
+ }
114
+ return allGroups;
115
+ }
116
+ function* traverseGroups(group) {
117
+ yield group;
118
+ for (const g of group.subGroups ?? []) {
119
+ g.parent = group.name;
120
+ yield* traverseGroups(g);
121
+ }
122
+ }
123
+ const readKeycloakRealm = async (client, config, logger, options) => {
124
+ const kUsers = await getEntities(
125
+ client.users,
126
+ config,
127
+ logger,
128
+ options?.userQuerySize
129
+ );
130
+ const topLevelKGroups = await getEntities(
131
+ client.groups,
132
+ config,
133
+ logger,
134
+ options?.groupQuerySize
135
+ );
136
+ let serverVersion;
137
+ try {
138
+ const serverInfo = await client.serverInfo.find();
139
+ serverVersion = parseInt(
140
+ serverInfo.systemInfo?.version?.slice(0, 2) || "",
141
+ 10
142
+ );
143
+ } catch (error) {
144
+ throw new Error(`Failed to retrieve Keycloak server information: ${error}`);
145
+ }
146
+ const isVersion23orHigher = serverVersion >= 23;
147
+ let rawKGroups = [];
148
+ if (isVersion23orHigher) {
149
+ rawKGroups = await processGroupsRecursively(
150
+ topLevelKGroups,
151
+ client.groups,
152
+ config.realm
153
+ );
154
+ } else {
155
+ rawKGroups = topLevelKGroups.reduce(
156
+ (acc, g) => acc.concat(...traverseGroups(g)),
157
+ []
158
+ );
159
+ }
160
+ const kGroups = await Promise.all(
161
+ rawKGroups.map(async (g) => {
162
+ g.members = await getAllGroupMembers(
163
+ client.groups,
164
+ g.id,
165
+ config,
166
+ options
167
+ );
168
+ if (isVersion23orHigher) {
169
+ if (g.subGroupCount > 0) {
170
+ g.subGroups = await client.groups.listSubGroups({
171
+ parentId: g.id,
172
+ first: 0,
173
+ max: g.subGroupCount,
174
+ briefRepresentation: false,
175
+ realm: config.realm
176
+ });
177
+ }
178
+ if (g.parentId) {
179
+ const groupParent = await client.groups.findOne({
180
+ id: g.parentId,
181
+ realm: config.realm
182
+ });
183
+ g.parent = groupParent?.name;
184
+ }
185
+ }
186
+ return g;
187
+ })
188
+ );
189
+ const parsedGroups = await kGroups.reduce(async (promise, g) => {
190
+ const partial = await promise;
191
+ const entity = await parseGroup(g, config.realm, options?.groupTransformer);
192
+ if (entity) {
193
+ const group = {
194
+ ...g,
195
+ entity
196
+ };
197
+ partial.push(group);
198
+ }
199
+ return partial;
200
+ }, Promise.resolve([]));
201
+ const parsedUsers = await kUsers.reduce(async (promise, u) => {
202
+ const partial = await promise;
203
+ const entity = await parseUser(
204
+ u,
205
+ config.realm,
206
+ parsedGroups,
207
+ options?.userTransformer
208
+ );
209
+ if (entity) {
210
+ const user = { ...u, entity };
211
+ partial.push(user);
212
+ }
213
+ return partial;
214
+ }, Promise.resolve([]));
215
+ const groups = parsedGroups.map((g) => {
216
+ const entity = g.entity;
217
+ entity.spec.members = g.entity.spec.members?.flatMap((m) => {
218
+ const name = parsedUsers.find((p) => p.username === m)?.entity.metadata.name;
219
+ return name ? [name] : [];
220
+ }) ?? [];
221
+ entity.spec.children = g.entity.spec.children?.flatMap((c) => {
222
+ const child = parsedGroups.find((p) => p.name === c)?.entity.metadata.name;
223
+ return child ? [child] : [];
224
+ }) ?? [];
225
+ entity.spec.parent = parsedGroups.find(
226
+ (p) => p.name === entity.spec.parent
227
+ )?.entity.metadata.name;
228
+ return entity;
229
+ });
230
+ return { users: parsedUsers.map((u) => u.entity), groups };
231
+ };
232
+
233
+ exports.getEntities = getEntities;
234
+ exports.parseGroup = parseGroup;
235
+ exports.parseUser = parseUser;
236
+ exports.processGroupsRecursively = processGroupsRecursively;
237
+ exports.readKeycloakRealm = readKeycloakRealm;
238
+ exports.traverseGroups = traverseGroups;
239
+ //# sourceMappingURL=read.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.cjs.js","sources":["../../src/lib/read.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { LoggerService } from '@backstage/backend-plugin-api';\nimport type { GroupEntity, UserEntity } from '@backstage/catalog-model';\n\nimport type KeycloakAdminClient from '@keycloak/keycloak-admin-client';\nimport type GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation';\nimport type UserRepresentation from '@keycloak/keycloak-admin-client/lib/defs/userRepresentation';\nimport type { Groups } from '@keycloak/keycloak-admin-client/lib/resources/groups';\nimport type { Users } from '@keycloak/keycloak-admin-client/lib/resources/users';\n\nimport { KeycloakProviderConfig } from './config';\nimport {\n KEYCLOAK_ENTITY_QUERY_SIZE,\n KEYCLOAK_ID_ANNOTATION,\n KEYCLOAK_REALM_ANNOTATION,\n} from './constants';\nimport { noopGroupTransformer, noopUserTransformer } from './transformers';\nimport {\n GroupRepresentationWithParent,\n GroupRepresentationWithParentAndEntity,\n GroupTransformer,\n UserRepresentationWithEntity,\n UserTransformer,\n} from './types';\n\nexport const parseGroup = async (\n keycloakGroup: GroupRepresentationWithParent,\n realm: string,\n groupTransformer?: GroupTransformer,\n): Promise<GroupEntity | undefined> => {\n const transformer = groupTransformer ?? noopGroupTransformer;\n const entity: GroupEntity = {\n apiVersion: 'backstage.io/v1beta1',\n kind: 'Group',\n metadata: {\n name: keycloakGroup.name!,\n annotations: {\n [KEYCLOAK_ID_ANNOTATION]: keycloakGroup.id!,\n [KEYCLOAK_REALM_ANNOTATION]: realm,\n },\n },\n spec: {\n type: 'group',\n profile: {\n displayName: keycloakGroup.name!,\n },\n // children, parent and members are updated again after all group and user transformers applied.\n children: keycloakGroup.subGroups?.map(g => g.name!) ?? [],\n parent: keycloakGroup.parent,\n members: keycloakGroup.members,\n },\n };\n\n return await transformer(entity, keycloakGroup, realm);\n};\n\nexport const parseUser = async (\n user: UserRepresentation,\n realm: string,\n keycloakGroups: GroupRepresentationWithParentAndEntity[],\n\n userTransformer?: UserTransformer,\n): Promise<UserEntity | undefined> => {\n const transformer = userTransformer ?? noopUserTransformer;\n const entity: UserEntity = {\n apiVersion: 'backstage.io/v1beta1',\n kind: 'User',\n metadata: {\n name: user.username!,\n annotations: {\n [KEYCLOAK_ID_ANNOTATION]: user.id!,\n [KEYCLOAK_REALM_ANNOTATION]: realm,\n },\n },\n spec: {\n profile: {\n email: user.email,\n ...(user.firstName || user.lastName\n ? {\n displayName: [user.firstName, user.lastName]\n .filter(Boolean)\n .join(' '),\n }\n : {}),\n },\n memberOf: keycloakGroups\n .filter(g => g.members?.includes(user.username!))\n .map(g => g.entity.metadata.name),\n },\n };\n\n return await transformer(entity, user, realm, keycloakGroups);\n};\n\nexport async function getEntities<T extends Users | Groups>(\n entities: T,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n entityQuerySize: number = KEYCLOAK_ENTITY_QUERY_SIZE,\n): Promise<Awaited<ReturnType<T['find']>>> {\n const rawEntityCount = await entities.count({ realm: config.realm });\n const entityCount =\n typeof rawEntityCount === 'number' ? rawEntityCount : rawEntityCount.count;\n\n const pageCount = Math.ceil(entityCount / entityQuerySize);\n\n // The next line acts like range in python\n const entityPromises = Array.from(\n { length: pageCount },\n (_, i) =>\n entities\n .find({\n realm: config.realm,\n max: entityQuerySize,\n first: i * entityQuerySize,\n })\n .catch(err =>\n logger.warn('Failed to retieve Keycloak entities.', err),\n ) as ReturnType<T['find']>,\n );\n\n const entityResults = (await Promise.all(entityPromises)).flat() as Awaited<\n ReturnType<T['find']>\n >;\n\n return entityResults;\n}\n\nasync function getAllGroupMembers<T extends Groups>(\n groups: T,\n groupId: string,\n config: KeycloakProviderConfig,\n options?: { userQuerySize?: number },\n): Promise<string[]> {\n const querySize = options?.userQuerySize || 100;\n\n let allMembers: string[] = [];\n let page = 0;\n let totalMembers = 0;\n\n do {\n const members = await groups.listMembers({\n id: groupId,\n max: querySize,\n realm: config.realm,\n first: page * querySize,\n });\n\n if (members.length > 0) {\n allMembers = allMembers.concat(members.map(m => m.username!));\n totalMembers = members.length; // Get the number of members retrieved\n } else {\n totalMembers = 0; // No members retrieved\n }\n\n page++;\n } while (totalMembers > 0);\n\n return allMembers;\n}\n\nexport async function processGroupsRecursively(\n topLevelGroups: GroupRepresentationWithParent[],\n entities: Groups,\n realm: string,\n) {\n const allGroups: GroupRepresentationWithParent[] = [];\n for (const group of topLevelGroups) {\n allGroups.push(group);\n\n if (group.subGroupCount! > 0) {\n const subgroups = await entities.listSubGroups({\n parentId: group.id!,\n first: 0,\n max: group.subGroupCount,\n briefRepresentation: true,\n realm,\n });\n const subGroupResults = await processGroupsRecursively(\n subgroups,\n entities,\n realm,\n );\n allGroups.push(...subGroupResults);\n }\n }\n\n return allGroups;\n}\n\nexport function* traverseGroups(\n group: GroupRepresentation,\n): IterableIterator<GroupRepresentationWithParent> {\n yield group;\n for (const g of group.subGroups ?? []) {\n (g as GroupRepresentationWithParent).parent = group.name!;\n yield* traverseGroups(g);\n }\n}\n\nexport const readKeycloakRealm = async (\n client: KeycloakAdminClient,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n options?: {\n userQuerySize?: number;\n groupQuerySize?: number;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n): Promise<{\n users: UserEntity[];\n groups: GroupEntity[];\n}> => {\n const kUsers = await getEntities(\n client.users,\n config,\n logger,\n options?.userQuerySize,\n );\n\n const topLevelKGroups = (await getEntities(\n client.groups,\n config,\n logger,\n options?.groupQuerySize,\n )) as GroupRepresentationWithParent[];\n\n let serverVersion: number;\n\n try {\n const serverInfo = await client.serverInfo.find();\n serverVersion = parseInt(\n serverInfo.systemInfo?.version?.slice(0, 2) || '',\n 10,\n );\n } catch (error) {\n throw new Error(`Failed to retrieve Keycloak server information: ${error}`);\n }\n\n const isVersion23orHigher = serverVersion >= 23;\n\n let rawKGroups: GroupRepresentationWithParent[] = [];\n\n if (isVersion23orHigher) {\n rawKGroups = await processGroupsRecursively(\n topLevelKGroups,\n client.groups as Groups,\n config.realm,\n );\n } else {\n rawKGroups = topLevelKGroups.reduce(\n (acc, g) => acc.concat(...traverseGroups(g)),\n [] as GroupRepresentationWithParent[],\n );\n }\n const kGroups = await Promise.all(\n rawKGroups.map(async g => {\n g.members = await getAllGroupMembers(\n client.groups as Groups,\n g.id!,\n config,\n options,\n );\n\n if (isVersion23orHigher) {\n if (g.subGroupCount! > 0) {\n g.subGroups = await client.groups.listSubGroups({\n parentId: g.id!,\n first: 0,\n max: g.subGroupCount,\n briefRepresentation: false,\n realm: config.realm,\n });\n }\n if (g.parentId) {\n const groupParent = await client.groups.findOne({\n id: g.parentId,\n realm: config.realm,\n });\n g.parent = groupParent?.name;\n }\n }\n\n return g;\n }),\n );\n\n const parsedGroups = await kGroups.reduce(async (promise, g) => {\n const partial = await promise;\n const entity = await parseGroup(g, config.realm, options?.groupTransformer);\n if (entity) {\n const group = {\n ...g,\n entity,\n } as GroupRepresentationWithParentAndEntity;\n partial.push(group);\n }\n return partial;\n }, Promise.resolve([] as GroupRepresentationWithParentAndEntity[]));\n\n const parsedUsers = await kUsers.reduce(async (promise, u) => {\n const partial = await promise;\n const entity = await parseUser(\n u,\n config.realm,\n parsedGroups,\n options?.userTransformer,\n );\n if (entity) {\n const user = { ...u, entity } as UserRepresentationWithEntity;\n partial.push(user);\n }\n return partial;\n }, Promise.resolve([] as UserRepresentationWithEntity[]));\n\n const groups = parsedGroups.map(g => {\n const entity = g.entity;\n entity.spec.members =\n g.entity.spec.members?.flatMap(m => {\n const name = parsedUsers.find(p => p.username === m)?.entity.metadata\n .name;\n return name ? [name] : [];\n }) ?? [];\n entity.spec.children =\n g.entity.spec.children?.flatMap(c => {\n const child = parsedGroups.find(p => p.name === c)?.entity.metadata\n .name;\n return child ? [child] : [];\n }) ?? [];\n entity.spec.parent = parsedGroups.find(\n p => p.name === entity.spec.parent,\n )?.entity.metadata.name;\n return entity;\n });\n\n return { users: parsedUsers.map(u => u.entity), groups };\n};\n"],"names":["noopGroupTransformer","KEYCLOAK_ID_ANNOTATION","KEYCLOAK_REALM_ANNOTATION","noopUserTransformer","KEYCLOAK_ENTITY_QUERY_SIZE"],"mappings":";;;;;AAwCO,MAAM,UAAa,GAAA,OACxB,aACA,EAAA,KAAA,EACA,gBACqC,KAAA;AACrC,EAAA,MAAM,cAAc,gBAAoB,IAAAA,iCAAA,CAAA;AACxC,EAAA,MAAM,MAAsB,GAAA;AAAA,IAC1B,UAAY,EAAA,sBAAA;AAAA,IACZ,IAAM,EAAA,OAAA;AAAA,IACN,QAAU,EAAA;AAAA,MACR,MAAM,aAAc,CAAA,IAAA;AAAA,MACpB,WAAa,EAAA;AAAA,QACX,CAACC,gCAAsB,GAAG,aAAc,CAAA,EAAA;AAAA,QACxC,CAACC,mCAAyB,GAAG,KAAA;AAAA,OAC/B;AAAA,KACF;AAAA,IACA,IAAM,EAAA;AAAA,MACJ,IAAM,EAAA,OAAA;AAAA,MACN,OAAS,EAAA;AAAA,QACP,aAAa,aAAc,CAAA,IAAA;AAAA,OAC7B;AAAA;AAAA,MAEA,QAAA,EAAU,cAAc,SAAW,EAAA,GAAA,CAAI,OAAK,CAAE,CAAA,IAAK,KAAK,EAAC;AAAA,MACzD,QAAQ,aAAc,CAAA,MAAA;AAAA,MACtB,SAAS,aAAc,CAAA,OAAA;AAAA,KACzB;AAAA,GACF,CAAA;AAEA,EAAA,OAAO,MAAM,WAAA,CAAY,MAAQ,EAAA,aAAA,EAAe,KAAK,CAAA,CAAA;AACvD,EAAA;AAEO,MAAM,SAAY,GAAA,OACvB,IACA,EAAA,KAAA,EACA,gBAEA,eACoC,KAAA;AACpC,EAAA,MAAM,cAAc,eAAmB,IAAAC,gCAAA,CAAA;AACvC,EAAA,MAAM,MAAqB,GAAA;AAAA,IACzB,UAAY,EAAA,sBAAA;AAAA,IACZ,IAAM,EAAA,MAAA;AAAA,IACN,QAAU,EAAA;AAAA,MACR,MAAM,IAAK,CAAA,QAAA;AAAA,MACX,WAAa,EAAA;AAAA,QACX,CAACF,gCAAsB,GAAG,IAAK,CAAA,EAAA;AAAA,QAC/B,CAACC,mCAAyB,GAAG,KAAA;AAAA,OAC/B;AAAA,KACF;AAAA,IACA,IAAM,EAAA;AAAA,MACJ,OAAS,EAAA;AAAA,QACP,OAAO,IAAK,CAAA,KAAA;AAAA,QACZ,GAAI,IAAA,CAAK,SAAa,IAAA,IAAA,CAAK,QACvB,GAAA;AAAA,UACE,WAAA,EAAa,CAAC,IAAA,CAAK,SAAW,EAAA,IAAA,CAAK,QAAQ,CAAA,CACxC,MAAO,CAAA,OAAO,CACd,CAAA,IAAA,CAAK,GAAG,CAAA;AAAA,YAEb,EAAC;AAAA,OACP;AAAA,MACA,UAAU,cACP,CAAA,MAAA,CAAO,CAAK,CAAA,KAAA,CAAA,CAAE,SAAS,QAAS,CAAA,IAAA,CAAK,QAAS,CAAC,EAC/C,GAAI,CAAA,CAAA,CAAA,KAAK,CAAE,CAAA,MAAA,CAAO,SAAS,IAAI,CAAA;AAAA,KACpC;AAAA,GACF,CAAA;AAEA,EAAA,OAAO,MAAM,WAAA,CAAY,MAAQ,EAAA,IAAA,EAAM,OAAO,cAAc,CAAA,CAAA;AAC9D,EAAA;AAEA,eAAsB,WACpB,CAAA,QAAA,EACA,MACA,EAAA,MAAA,EACA,kBAA0BE,oCACe,EAAA;AACzC,EAAM,MAAA,cAAA,GAAiB,MAAM,QAAS,CAAA,KAAA,CAAM,EAAE,KAAO,EAAA,MAAA,CAAO,OAAO,CAAA,CAAA;AACnE,EAAA,MAAM,WACJ,GAAA,OAAO,cAAmB,KAAA,QAAA,GAAW,iBAAiB,cAAe,CAAA,KAAA,CAAA;AAEvE,EAAA,MAAM,SAAY,GAAA,IAAA,CAAK,IAAK,CAAA,WAAA,GAAc,eAAe,CAAA,CAAA;AAGzD,EAAA,MAAM,iBAAiB,KAAM,CAAA,IAAA;AAAA,IAC3B,EAAE,QAAQ,SAAU,EAAA;AAAA,IACpB,CAAC,CAAA,EAAG,CACF,KAAA,QAAA,CACG,IAAK,CAAA;AAAA,MACJ,OAAO,MAAO,CAAA,KAAA;AAAA,MACd,GAAK,EAAA,eAAA;AAAA,MACL,OAAO,CAAI,GAAA,eAAA;AAAA,KACZ,CACA,CAAA,KAAA;AAAA,MAAM,CACL,GAAA,KAAA,MAAA,CAAO,IAAK,CAAA,sCAAA,EAAwC,GAAG,CAAA;AAAA,KACzD;AAAA,GACN,CAAA;AAEA,EAAA,MAAM,iBAAiB,MAAM,OAAA,CAAQ,GAAI,CAAA,cAAc,GAAG,IAAK,EAAA,CAAA;AAI/D,EAAO,OAAA,aAAA,CAAA;AACT,CAAA;AAEA,eAAe,kBACb,CAAA,MAAA,EACA,OACA,EAAA,MAAA,EACA,OACmB,EAAA;AACnB,EAAM,MAAA,SAAA,GAAY,SAAS,aAAiB,IAAA,GAAA,CAAA;AAE5C,EAAA,IAAI,aAAuB,EAAC,CAAA;AAC5B,EAAA,IAAI,IAAO,GAAA,CAAA,CAAA;AACX,EAAA,IAAI,YAAe,GAAA,CAAA,CAAA;AAEnB,EAAG,GAAA;AACD,IAAM,MAAA,OAAA,GAAU,MAAM,MAAA,CAAO,WAAY,CAAA;AAAA,MACvC,EAAI,EAAA,OAAA;AAAA,MACJ,GAAK,EAAA,SAAA;AAAA,MACL,OAAO,MAAO,CAAA,KAAA;AAAA,MACd,OAAO,IAAO,GAAA,SAAA;AAAA,KACf,CAAA,CAAA;AAED,IAAI,IAAA,OAAA,CAAQ,SAAS,CAAG,EAAA;AACtB,MAAA,UAAA,GAAa,WAAW,MAAO,CAAA,OAAA,CAAQ,IAAI,CAAK,CAAA,KAAA,CAAA,CAAE,QAAS,CAAC,CAAA,CAAA;AAC5D,MAAA,YAAA,GAAe,OAAQ,CAAA,MAAA,CAAA;AAAA,KAClB,MAAA;AACL,MAAe,YAAA,GAAA,CAAA,CAAA;AAAA,KACjB;AAEA,IAAA,IAAA,EAAA,CAAA;AAAA,WACO,YAAe,GAAA,CAAA,EAAA;AAExB,EAAO,OAAA,UAAA,CAAA;AACT,CAAA;AAEsB,eAAA,wBAAA,CACpB,cACA,EAAA,QAAA,EACA,KACA,EAAA;AACA,EAAA,MAAM,YAA6C,EAAC,CAAA;AACpD,EAAA,KAAA,MAAW,SAAS,cAAgB,EAAA;AAClC,IAAA,SAAA,CAAU,KAAK,KAAK,CAAA,CAAA;AAEpB,IAAI,IAAA,KAAA,CAAM,gBAAiB,CAAG,EAAA;AAC5B,MAAM,MAAA,SAAA,GAAY,MAAM,QAAA,CAAS,aAAc,CAAA;AAAA,QAC7C,UAAU,KAAM,CAAA,EAAA;AAAA,QAChB,KAAO,EAAA,CAAA;AAAA,QACP,KAAK,KAAM,CAAA,aAAA;AAAA,QACX,mBAAqB,EAAA,IAAA;AAAA,QACrB,KAAA;AAAA,OACD,CAAA,CAAA;AACD,MAAA,MAAM,kBAAkB,MAAM,wBAAA;AAAA,QAC5B,SAAA;AAAA,QACA,QAAA;AAAA,QACA,KAAA;AAAA,OACF,CAAA;AACA,MAAU,SAAA,CAAA,IAAA,CAAK,GAAG,eAAe,CAAA,CAAA;AAAA,KACnC;AAAA,GACF;AAEA,EAAO,OAAA,SAAA,CAAA;AACT,CAAA;AAEO,UAAU,eACf,KACiD,EAAA;AACjD,EAAM,MAAA,KAAA,CAAA;AACN,EAAA,KAAA,MAAW,CAAK,IAAA,KAAA,CAAM,SAAa,IAAA,EAAI,EAAA;AACrC,IAAC,CAAA,CAAoC,SAAS,KAAM,CAAA,IAAA,CAAA;AACpD,IAAA,OAAO,eAAe,CAAC,CAAA,CAAA;AAAA,GACzB;AACF,CAAA;AAEO,MAAM,iBAAoB,GAAA,OAC/B,MACA,EAAA,MAAA,EACA,QACA,OASI,KAAA;AACJ,EAAA,MAAM,SAAS,MAAM,WAAA;AAAA,IACnB,MAAO,CAAA,KAAA;AAAA,IACP,MAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAS,EAAA,aAAA;AAAA,GACX,CAAA;AAEA,EAAA,MAAM,kBAAmB,MAAM,WAAA;AAAA,IAC7B,MAAO,CAAA,MAAA;AAAA,IACP,MAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAS,EAAA,cAAA;AAAA,GACX,CAAA;AAEA,EAAI,IAAA,aAAA,CAAA;AAEJ,EAAI,IAAA;AACF,IAAA,MAAM,UAAa,GAAA,MAAM,MAAO,CAAA,UAAA,CAAW,IAAK,EAAA,CAAA;AAChD,IAAgB,aAAA,GAAA,QAAA;AAAA,MACd,WAAW,UAAY,EAAA,OAAA,EAAS,KAAM,CAAA,CAAA,EAAG,CAAC,CAAK,IAAA,EAAA;AAAA,MAC/C,EAAA;AAAA,KACF,CAAA;AAAA,WACO,KAAO,EAAA;AACd,IAAA,MAAM,IAAI,KAAA,CAAM,CAAmD,gDAAA,EAAA,KAAK,CAAE,CAAA,CAAA,CAAA;AAAA,GAC5E;AAEA,EAAA,MAAM,sBAAsB,aAAiB,IAAA,EAAA,CAAA;AAE7C,EAAA,IAAI,aAA8C,EAAC,CAAA;AAEnD,EAAA,IAAI,mBAAqB,EAAA;AACvB,IAAA,UAAA,GAAa,MAAM,wBAAA;AAAA,MACjB,eAAA;AAAA,MACA,MAAO,CAAA,MAAA;AAAA,MACP,MAAO,CAAA,KAAA;AAAA,KACT,CAAA;AAAA,GACK,MAAA;AACL,IAAA,UAAA,GAAa,eAAgB,CAAA,MAAA;AAAA,MAC3B,CAAC,KAAK,CAAM,KAAA,GAAA,CAAI,OAAO,GAAG,cAAA,CAAe,CAAC,CAAC,CAAA;AAAA,MAC3C,EAAC;AAAA,KACH,CAAA;AAAA,GACF;AACA,EAAM,MAAA,OAAA,GAAU,MAAM,OAAQ,CAAA,GAAA;AAAA,IAC5B,UAAA,CAAW,GAAI,CAAA,OAAM,CAAK,KAAA;AACxB,MAAA,CAAA,CAAE,UAAU,MAAM,kBAAA;AAAA,QAChB,MAAO,CAAA,MAAA;AAAA,QACP,CAAE,CAAA,EAAA;AAAA,QACF,MAAA;AAAA,QACA,OAAA;AAAA,OACF,CAAA;AAEA,MAAA,IAAI,mBAAqB,EAAA;AACvB,QAAI,IAAA,CAAA,CAAE,gBAAiB,CAAG,EAAA;AACxB,UAAA,CAAA,CAAE,SAAY,GAAA,MAAM,MAAO,CAAA,MAAA,CAAO,aAAc,CAAA;AAAA,YAC9C,UAAU,CAAE,CAAA,EAAA;AAAA,YACZ,KAAO,EAAA,CAAA;AAAA,YACP,KAAK,CAAE,CAAA,aAAA;AAAA,YACP,mBAAqB,EAAA,KAAA;AAAA,YACrB,OAAO,MAAO,CAAA,KAAA;AAAA,WACf,CAAA,CAAA;AAAA,SACH;AACA,QAAA,IAAI,EAAE,QAAU,EAAA;AACd,UAAA,MAAM,WAAc,GAAA,MAAM,MAAO,CAAA,MAAA,CAAO,OAAQ,CAAA;AAAA,YAC9C,IAAI,CAAE,CAAA,QAAA;AAAA,YACN,OAAO,MAAO,CAAA,KAAA;AAAA,WACf,CAAA,CAAA;AACD,UAAA,CAAA,CAAE,SAAS,WAAa,EAAA,IAAA,CAAA;AAAA,SAC1B;AAAA,OACF;AAEA,MAAO,OAAA,CAAA,CAAA;AAAA,KACR,CAAA;AAAA,GACH,CAAA;AAEA,EAAA,MAAM,eAAe,MAAM,OAAA,CAAQ,MAAO,CAAA,OAAO,SAAS,CAAM,KAAA;AAC9D,IAAA,MAAM,UAAU,MAAM,OAAA,CAAA;AACtB,IAAA,MAAM,SAAS,MAAM,UAAA,CAAW,GAAG,MAAO,CAAA,KAAA,EAAO,SAAS,gBAAgB,CAAA,CAAA;AAC1E,IAAA,IAAI,MAAQ,EAAA;AACV,MAAA,MAAM,KAAQ,GAAA;AAAA,QACZ,GAAG,CAAA;AAAA,QACH,MAAA;AAAA,OACF,CAAA;AACA,MAAA,OAAA,CAAQ,KAAK,KAAK,CAAA,CAAA;AAAA,KACpB;AACA,IAAO,OAAA,OAAA,CAAA;AAAA,GACN,EAAA,OAAA,CAAQ,OAAQ,CAAA,EAA8C,CAAC,CAAA,CAAA;AAElE,EAAA,MAAM,cAAc,MAAM,MAAA,CAAO,MAAO,CAAA,OAAO,SAAS,CAAM,KAAA;AAC5D,IAAA,MAAM,UAAU,MAAM,OAAA,CAAA;AACtB,IAAA,MAAM,SAAS,MAAM,SAAA;AAAA,MACnB,CAAA;AAAA,MACA,MAAO,CAAA,KAAA;AAAA,MACP,YAAA;AAAA,MACA,OAAS,EAAA,eAAA;AAAA,KACX,CAAA;AACA,IAAA,IAAI,MAAQ,EAAA;AACV,MAAA,MAAM,IAAO,GAAA,EAAE,GAAG,CAAA,EAAG,MAAO,EAAA,CAAA;AAC5B,MAAA,OAAA,CAAQ,KAAK,IAAI,CAAA,CAAA;AAAA,KACnB;AACA,IAAO,OAAA,OAAA,CAAA;AAAA,GACN,EAAA,OAAA,CAAQ,OAAQ,CAAA,EAAoC,CAAC,CAAA,CAAA;AAExD,EAAM,MAAA,MAAA,GAAS,YAAa,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA;AACnC,IAAA,MAAM,SAAS,CAAE,CAAA,MAAA,CAAA;AACjB,IAAA,MAAA,CAAO,KAAK,OACV,GAAA,CAAA,CAAE,OAAO,IAAK,CAAA,OAAA,EAAS,QAAQ,CAAK,CAAA,KAAA;AAClC,MAAM,MAAA,IAAA,GAAO,YAAY,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,QAAa,KAAA,CAAC,CAAG,EAAA,MAAA,CAAO,QAC1D,CAAA,IAAA,CAAA;AACH,MAAA,OAAO,IAAO,GAAA,CAAC,IAAI,CAAA,GAAI,EAAC,CAAA;AAAA,KACzB,KAAK,EAAC,CAAA;AACT,IAAA,MAAA,CAAO,KAAK,QACV,GAAA,CAAA,CAAE,OAAO,IAAK,CAAA,QAAA,EAAU,QAAQ,CAAK,CAAA,KAAA;AACnC,MAAM,MAAA,KAAA,GAAQ,aAAa,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,IAAS,KAAA,CAAC,CAAG,EAAA,MAAA,CAAO,QACxD,CAAA,IAAA,CAAA;AACH,MAAA,OAAO,KAAQ,GAAA,CAAC,KAAK,CAAA,GAAI,EAAC,CAAA;AAAA,KAC3B,KAAK,EAAC,CAAA;AACT,IAAO,MAAA,CAAA,IAAA,CAAK,SAAS,YAAa,CAAA,IAAA;AAAA,MAChC,CAAK,CAAA,KAAA,CAAA,CAAE,IAAS,KAAA,MAAA,CAAO,IAAK,CAAA,MAAA;AAAA,KAC9B,EAAG,OAAO,QAAS,CAAA,IAAA,CAAA;AACnB,IAAO,OAAA,MAAA,CAAA;AAAA,GACR,CAAA,CAAA;AAED,EAAO,OAAA,EAAE,OAAO,WAAY,CAAA,GAAA,CAAI,OAAK,CAAE,CAAA,MAAM,GAAG,MAAO,EAAA,CAAA;AACzD;;;;;;;;;"}
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const noopGroupTransformer = async (entity, _user, _realm) => entity;
4
+ const noopUserTransformer = async (entity, _user, _realm, _groups) => entity;
5
+ const sanitizeEmailTransformer = async (entity, _user, _realm, _groups) => {
6
+ entity.metadata.name = entity.metadata.name.replace(/[^a-zA-Z0-9]/g, "-");
7
+ return entity;
8
+ };
9
+
10
+ exports.noopGroupTransformer = noopGroupTransformer;
11
+ exports.noopUserTransformer = noopUserTransformer;
12
+ exports.sanitizeEmailTransformer = sanitizeEmailTransformer;
13
+ //# sourceMappingURL=transformers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transformers.cjs.js","sources":["../../src/lib/transformers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { GroupTransformer, UserTransformer } from './types';\n\nexport const noopGroupTransformer: GroupTransformer = async (\n entity,\n _user,\n _realm,\n) => entity;\n\nexport const noopUserTransformer: UserTransformer = async (\n entity,\n _user,\n _realm,\n _groups,\n) => entity;\n\n/**\n * User transformer that sanitizes .metadata.name from email address to a valid name\n */\nexport const sanitizeEmailTransformer: UserTransformer = async (\n entity,\n _user,\n _realm,\n _groups,\n) => {\n entity.metadata.name = entity.metadata.name.replace(/[^a-zA-Z0-9]/g, '-');\n return entity;\n};\n"],"names":[],"mappings":";;AAiBO,MAAM,oBAAyC,GAAA,OACpD,MACA,EAAA,KAAA,EACA,MACG,KAAA,OAAA;AAEE,MAAM,mBAAuC,GAAA,OAClD,MACA,EAAA,KAAA,EACA,QACA,OACG,KAAA,OAAA;AAKE,MAAM,wBAA4C,GAAA,OACvD,MACA,EAAA,KAAA,EACA,QACA,OACG,KAAA;AACH,EAAA,MAAA,CAAO,SAAS,IAAO,GAAA,MAAA,CAAO,SAAS,IAAK,CAAA,OAAA,CAAQ,iBAAiB,GAAG,CAAA,CAAA;AACxE,EAAO,OAAA,MAAA,CAAA;AACT;;;;;;"}
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var errors = require('@backstage/errors');
5
+ var alpha = require('@backstage/plugin-catalog-node/alpha');
6
+ var extensions = require('../extensions.cjs.js');
7
+ var KeycloakOrgEntityProvider = require('../providers/KeycloakOrgEntityProvider.cjs.js');
8
+
9
+ const catalogModuleKeycloakEntityProvider = backendPluginApi.createBackendModule({
10
+ pluginId: "catalog",
11
+ moduleId: "catalog-backend-module-keycloak",
12
+ register(env) {
13
+ let userTransformer;
14
+ let groupTransformer;
15
+ env.registerExtensionPoint(extensions.keycloakTransformerExtensionPoint, {
16
+ setUserTransformer(transformer) {
17
+ if (userTransformer) {
18
+ throw new errors.InputError("User transformer may only be set once");
19
+ }
20
+ userTransformer = transformer;
21
+ },
22
+ setGroupTransformer(transformer) {
23
+ if (groupTransformer) {
24
+ throw new errors.InputError("Group transformer may only be set once");
25
+ }
26
+ groupTransformer = transformer;
27
+ }
28
+ });
29
+ env.registerInit({
30
+ deps: {
31
+ catalog: alpha.catalogProcessingExtensionPoint,
32
+ config: backendPluginApi.coreServices.rootConfig,
33
+ logger: backendPluginApi.coreServices.logger,
34
+ scheduler: backendPluginApi.coreServices.scheduler
35
+ },
36
+ async init({ catalog, config, logger, scheduler }) {
37
+ catalog.addEntityProvider(
38
+ KeycloakOrgEntityProvider.KeycloakOrgEntityProvider.fromConfig(
39
+ { config, logger },
40
+ {
41
+ scheduler,
42
+ schedule: scheduler.createScheduledTaskRunner({
43
+ frequency: { minutes: 30 },
44
+ timeout: { minutes: 3 }
45
+ }),
46
+ userTransformer,
47
+ groupTransformer
48
+ }
49
+ )
50
+ );
51
+ }
52
+ });
53
+ }
54
+ });
55
+
56
+ exports.catalogModuleKeycloakEntityProvider = catalogModuleKeycloakEntityProvider;
57
+ //# sourceMappingURL=catalogModuleKeycloakEntityProvider.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogModuleKeycloakEntityProvider.cjs.js","sources":["../../src/module/catalogModuleKeycloakEntityProvider.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { InputError } from '@backstage/errors';\nimport { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';\n\nimport { keycloakTransformerExtensionPoint } from '../extensions';\nimport type { GroupTransformer, UserTransformer } from '../lib/types';\nimport { KeycloakOrgEntityProvider } from '../providers';\n\n/**\n * Registers the `KeycloakEntityProvider` with the catalog processing extension point.\n *\n * @alpha\n */\nexport const catalogModuleKeycloakEntityProvider = createBackendModule({\n pluginId: 'catalog',\n moduleId: 'catalog-backend-module-keycloak',\n register(env) {\n let userTransformer: UserTransformer | undefined;\n let groupTransformer: GroupTransformer | undefined;\n\n env.registerExtensionPoint(keycloakTransformerExtensionPoint, {\n setUserTransformer(transformer) {\n if (userTransformer) {\n throw new InputError('User transformer may only be set once');\n }\n userTransformer = transformer;\n },\n setGroupTransformer(transformer) {\n if (groupTransformer) {\n throw new InputError('Group transformer may only be set once');\n }\n groupTransformer = transformer;\n },\n });\n env.registerInit({\n deps: {\n catalog: catalogProcessingExtensionPoint,\n config: coreServices.rootConfig,\n logger: coreServices.logger,\n scheduler: coreServices.scheduler,\n },\n async init({ catalog, config, logger, scheduler }) {\n catalog.addEntityProvider(\n KeycloakOrgEntityProvider.fromConfig(\n { config, logger },\n {\n scheduler: scheduler,\n schedule: scheduler.createScheduledTaskRunner({\n frequency: { minutes: 30 },\n timeout: { minutes: 3 },\n }),\n userTransformer: userTransformer,\n groupTransformer: groupTransformer,\n },\n ),\n );\n },\n });\n },\n});\n"],"names":["createBackendModule","keycloakTransformerExtensionPoint","InputError","catalogProcessingExtensionPoint","coreServices","KeycloakOrgEntityProvider"],"mappings":";;;;;;;;AAgCO,MAAM,sCAAsCA,oCAAoB,CAAA;AAAA,EACrE,QAAU,EAAA,SAAA;AAAA,EACV,QAAU,EAAA,iCAAA;AAAA,EACV,SAAS,GAAK,EAAA;AACZ,IAAI,IAAA,eAAA,CAAA;AACJ,IAAI,IAAA,gBAAA,CAAA;AAEJ,IAAA,GAAA,CAAI,uBAAuBC,4CAAmC,EAAA;AAAA,MAC5D,mBAAmB,WAAa,EAAA;AAC9B,QAAA,IAAI,eAAiB,EAAA;AACnB,UAAM,MAAA,IAAIC,kBAAW,uCAAuC,CAAA,CAAA;AAAA,SAC9D;AACA,QAAkB,eAAA,GAAA,WAAA,CAAA;AAAA,OACpB;AAAA,MACA,oBAAoB,WAAa,EAAA;AAC/B,QAAA,IAAI,gBAAkB,EAAA;AACpB,UAAM,MAAA,IAAIA,kBAAW,wCAAwC,CAAA,CAAA;AAAA,SAC/D;AACA,QAAmB,gBAAA,GAAA,WAAA,CAAA;AAAA,OACrB;AAAA,KACD,CAAA,CAAA;AACD,IAAA,GAAA,CAAI,YAAa,CAAA;AAAA,MACf,IAAM,EAAA;AAAA,QACJ,OAAS,EAAAC,qCAAA;AAAA,QACT,QAAQC,6BAAa,CAAA,UAAA;AAAA,QACrB,QAAQA,6BAAa,CAAA,MAAA;AAAA,QACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,OAC1B;AAAA,MACA,MAAM,IAAK,CAAA,EAAE,SAAS,MAAQ,EAAA,MAAA,EAAQ,WAAa,EAAA;AACjD,QAAQ,OAAA,CAAA,iBAAA;AAAA,UACNC,mDAA0B,CAAA,UAAA;AAAA,YACxB,EAAE,QAAQ,MAAO,EAAA;AAAA,YACjB;AAAA,cACE,SAAA;AAAA,cACA,QAAA,EAAU,UAAU,yBAA0B,CAAA;AAAA,gBAC5C,SAAA,EAAW,EAAE,OAAA,EAAS,EAAG,EAAA;AAAA,gBACzB,OAAA,EAAS,EAAE,OAAA,EAAS,CAAE,EAAA;AAAA,eACvB,CAAA;AAAA,cACD,eAAA;AAAA,cACA,gBAAA;AAAA,aACF;AAAA,WACF;AAAA,SACF,CAAA;AAAA,OACF;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AACF,CAAC;;;;"}
@@ -0,0 +1,201 @@
1
+ 'use strict';
2
+
3
+ var catalogModel = require('@backstage/catalog-model');
4
+ var errors = require('@backstage/errors');
5
+ var inclusion = require('inclusion');
6
+ var lodash = require('lodash');
7
+ var uuid = require('uuid');
8
+ var constants = require('../lib/constants.cjs.js');
9
+ var config = require('../lib/config.cjs.js');
10
+ var read = require('../lib/read.cjs.js');
11
+
12
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
13
+
14
+ function _interopNamespaceCompat(e) {
15
+ if (e && typeof e === 'object' && 'default' in e) return e;
16
+ var n = Object.create(null);
17
+ if (e) {
18
+ Object.keys(e).forEach(function (k) {
19
+ if (k !== 'default') {
20
+ var d = Object.getOwnPropertyDescriptor(e, k);
21
+ Object.defineProperty(n, k, d.get ? d : {
22
+ enumerable: true,
23
+ get: function () { return e[k]; }
24
+ });
25
+ }
26
+ });
27
+ }
28
+ n.default = e;
29
+ return Object.freeze(n);
30
+ }
31
+
32
+ var inclusion__default = /*#__PURE__*/_interopDefaultCompat(inclusion);
33
+ var uuid__namespace = /*#__PURE__*/_interopNamespaceCompat(uuid);
34
+
35
+ const withLocations = (baseUrl, realm, entity) => {
36
+ const kind = entity.kind === "Group" ? "groups" : "users";
37
+ const location = `url:${baseUrl}/admin/realms/${realm}/${kind}/${entity.metadata.annotations?.[constants.KEYCLOAK_ID_ANNOTATION]}`;
38
+ return lodash.merge(
39
+ {
40
+ metadata: {
41
+ annotations: {
42
+ [catalogModel.ANNOTATION_LOCATION]: location,
43
+ [catalogModel.ANNOTATION_ORIGIN_LOCATION]: location
44
+ }
45
+ }
46
+ },
47
+ entity
48
+ );
49
+ };
50
+ class KeycloakOrgEntityProvider {
51
+ constructor(options) {
52
+ this.options = options;
53
+ this.schedule(options.taskRunner);
54
+ }
55
+ connection;
56
+ scheduleFn;
57
+ static fromConfig(deps, options) {
58
+ const { config: config$1, logger } = deps;
59
+ return config.readProviderConfigs(config$1).map((providerConfig) => {
60
+ let taskRunner;
61
+ if ("scheduler" in options && providerConfig.schedule) {
62
+ taskRunner = options.scheduler.createScheduledTaskRunner(
63
+ providerConfig.schedule
64
+ );
65
+ } else if ("schedule" in options) {
66
+ taskRunner = options.schedule;
67
+ } else {
68
+ throw new errors.InputError(
69
+ `No schedule provided via config for KeycloakOrgEntityProvider:${providerConfig.id}.`
70
+ );
71
+ }
72
+ const provider = new KeycloakOrgEntityProvider({
73
+ id: providerConfig.id,
74
+ provider: providerConfig,
75
+ logger,
76
+ taskRunner,
77
+ userTransformer: options.userTransformer,
78
+ groupTransformer: options.groupTransformer
79
+ });
80
+ return provider;
81
+ });
82
+ }
83
+ getProviderName() {
84
+ return `KeycloakOrgEntityProvider:${this.options.id}`;
85
+ }
86
+ async connect(connection) {
87
+ this.connection = connection;
88
+ await this.scheduleFn?.();
89
+ }
90
+ /**
91
+ * Runs one complete ingestion loop. Call this method regularly at some
92
+ * appropriate cadence.
93
+ */
94
+ async read(options) {
95
+ if (!this.connection) {
96
+ throw new errors.NotFoundError("Not initialized");
97
+ }
98
+ const logger = options?.logger ?? this.options.logger;
99
+ const provider = this.options.provider;
100
+ const { markReadComplete } = trackProgress(logger);
101
+ const KeyCloakAdminClientModule = await inclusion__default.default(
102
+ "@keycloak/keycloak-admin-client"
103
+ );
104
+ const KeyCloakAdminClient = KeyCloakAdminClientModule.default;
105
+ const kcAdminClient = new KeyCloakAdminClient({
106
+ baseUrl: provider.baseUrl,
107
+ realmName: provider.loginRealm
108
+ });
109
+ let credentials;
110
+ if (provider.username && provider.password) {
111
+ credentials = {
112
+ grantType: "password",
113
+ clientId: provider.clientId ?? "admin-cli",
114
+ username: provider.username,
115
+ password: provider.password
116
+ };
117
+ } else if (provider.clientId && provider.clientSecret) {
118
+ credentials = {
119
+ grantType: "client_credentials",
120
+ clientId: provider.clientId,
121
+ clientSecret: provider.clientSecret
122
+ };
123
+ } else {
124
+ throw new errors.InputError(
125
+ `username and password or clientId and clientSecret must be provided.`
126
+ );
127
+ }
128
+ await kcAdminClient.auth(credentials);
129
+ const { users, groups } = await read.readKeycloakRealm(
130
+ kcAdminClient,
131
+ provider,
132
+ logger,
133
+ {
134
+ userQuerySize: provider.userQuerySize,
135
+ groupQuerySize: provider.groupQuerySize,
136
+ userTransformer: this.options.userTransformer,
137
+ groupTransformer: this.options.groupTransformer
138
+ }
139
+ );
140
+ const { markCommitComplete } = markReadComplete({ users, groups });
141
+ await this.connection.applyMutation({
142
+ type: "full",
143
+ entities: [...users, ...groups].map((entity) => ({
144
+ locationKey: `keycloak-org-provider:${this.options.id}`,
145
+ entity: withLocations(provider.baseUrl, provider.realm, entity)
146
+ }))
147
+ });
148
+ markCommitComplete();
149
+ }
150
+ schedule(taskRunner) {
151
+ this.scheduleFn = async () => {
152
+ const id = `${this.getProviderName()}:refresh`;
153
+ await taskRunner.run({
154
+ id,
155
+ fn: async () => {
156
+ const logger = this.options.logger.child({
157
+ class: KeycloakOrgEntityProvider.prototype.constructor.name,
158
+ taskId: id,
159
+ taskInstanceId: uuid__namespace.v4()
160
+ });
161
+ try {
162
+ await this.read({ logger });
163
+ } catch (error) {
164
+ if (errors.isError(error)) {
165
+ logger.error("Error while syncing Keycloak users and groups", {
166
+ // Default Error properties:
167
+ name: error.name,
168
+ cause: error.cause,
169
+ message: error.message,
170
+ stack: error.stack,
171
+ // Additional status code if available:
172
+ status: error.response?.status
173
+ });
174
+ }
175
+ }
176
+ }
177
+ });
178
+ };
179
+ }
180
+ }
181
+ function trackProgress(logger) {
182
+ let timestamp = Date.now();
183
+ let summary;
184
+ logger.info("Reading Keycloak users and groups");
185
+ function markReadComplete(read) {
186
+ summary = `${read.users.length} Keycloak users and ${read.groups.length} Keycloak groups`;
187
+ const readDuration = ((Date.now() - timestamp) / 1e3).toFixed(1);
188
+ timestamp = Date.now();
189
+ logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);
190
+ return { markCommitComplete };
191
+ }
192
+ function markCommitComplete() {
193
+ const commitDuration = ((Date.now() - timestamp) / 1e3).toFixed(1);
194
+ logger.info(`Committed ${summary} in ${commitDuration} seconds.`);
195
+ }
196
+ return { markReadComplete };
197
+ }
198
+
199
+ exports.KeycloakOrgEntityProvider = KeycloakOrgEntityProvider;
200
+ exports.withLocations = withLocations;
201
+ //# sourceMappingURL=KeycloakOrgEntityProvider.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeycloakOrgEntityProvider.cjs.js","sources":["../../src/providers/KeycloakOrgEntityProvider.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n LoggerService,\n SchedulerService,\n SchedulerServiceTaskRunner,\n} from '@backstage/backend-plugin-api';\nimport {\n ANNOTATION_LOCATION,\n ANNOTATION_ORIGIN_LOCATION,\n type Entity,\n} from '@backstage/catalog-model';\nimport type { Config } from '@backstage/config';\nimport { InputError, isError, NotFoundError } from '@backstage/errors';\nimport type {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\n\nimport type { Credentials } from '@keycloak/keycloak-admin-client/lib/utils/auth';\n// @ts-ignore\nimport inclusion from 'inclusion';\nimport { merge } from 'lodash';\nimport * as uuid from 'uuid';\n\nimport {\n GroupTransformer,\n KEYCLOAK_ID_ANNOTATION,\n KeycloakProviderConfig,\n UserTransformer,\n} from '../lib';\nimport { readProviderConfigs } from '../lib/config';\nimport { readKeycloakRealm } from '../lib/read';\n\n/**\n * Options for {@link KeycloakOrgEntityProvider}.\n *\n * @public\n */\nexport interface KeycloakOrgEntityProviderOptions {\n /**\n * A unique, stable identifier for this provider.\n *\n * @example \"production\"\n */\n id: string;\n\n /**\n * The refresh schedule to use.\n * @remarks\n *\n * You can pass in the result of\n * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner}\n * to enable automatic scheduling of tasks.\n */\n schedule?: SchedulerServiceTaskRunner;\n\n /**\n * Scheduler used to schedule refreshes based on\n * the schedule config.\n */\n scheduler?: SchedulerService;\n\n /**\n * The logger to use.\n */\n logger: LoggerService;\n\n /**\n * The function that transforms a user entry in LDAP to an entity.\n */\n userTransformer?: UserTransformer;\n\n /**\n * The function that transforms a group entry in LDAP to an entity.\n */\n groupTransformer?: GroupTransformer;\n}\n\n// Makes sure that emitted entities have a proper location\nexport const withLocations = (\n baseUrl: string,\n realm: string,\n entity: Entity,\n): Entity => {\n const kind = entity.kind === 'Group' ? 'groups' : 'users';\n const location = `url:${baseUrl}/admin/realms/${realm}/${kind}/${entity.metadata.annotations?.[KEYCLOAK_ID_ANNOTATION]}`;\n return merge(\n {\n metadata: {\n annotations: {\n [ANNOTATION_LOCATION]: location,\n [ANNOTATION_ORIGIN_LOCATION]: location,\n },\n },\n },\n entity,\n ) as Entity;\n};\n\n/**\n * Ingests org data (users and groups) from GitHub.\n *\n * @public\n */\nexport class KeycloakOrgEntityProvider implements EntityProvider {\n private connection?: EntityProviderConnection;\n private scheduleFn?: () => Promise<void>;\n\n static fromConfig(\n deps: {\n config: Config;\n logger: LoggerService;\n },\n options: (\n | { schedule: SchedulerServiceTaskRunner }\n | { scheduler: SchedulerService }\n ) & {\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ): KeycloakOrgEntityProvider[] {\n const { config, logger } = deps;\n return readProviderConfigs(config).map(providerConfig => {\n let taskRunner: SchedulerServiceTaskRunner | string;\n if ('scheduler' in options && providerConfig.schedule) {\n // Create a scheduled task runner using the provided scheduler and schedule configuration\n taskRunner = options.scheduler.createScheduledTaskRunner(\n providerConfig.schedule,\n );\n } else if ('schedule' in options) {\n // Use the provided schedule directly\n taskRunner = options.schedule;\n } else {\n throw new InputError(\n `No schedule provided via config for KeycloakOrgEntityProvider:${providerConfig.id}.`,\n );\n }\n\n const provider = new KeycloakOrgEntityProvider({\n id: providerConfig.id,\n provider: providerConfig,\n logger: logger,\n taskRunner: taskRunner,\n userTransformer: options.userTransformer,\n groupTransformer: options.groupTransformer,\n });\n\n return provider;\n });\n }\n\n constructor(\n private options: {\n id: string;\n provider: KeycloakProviderConfig;\n logger: LoggerService;\n taskRunner: SchedulerServiceTaskRunner;\n userTransformer?: UserTransformer;\n groupTransformer?: GroupTransformer;\n },\n ) {\n this.schedule(options.taskRunner);\n }\n\n getProviderName(): string {\n return `KeycloakOrgEntityProvider:${this.options.id}`;\n }\n\n async connect(connection: EntityProviderConnection) {\n this.connection = connection;\n await this.scheduleFn?.();\n }\n\n /**\n * Runs one complete ingestion loop. Call this method regularly at some\n * appropriate cadence.\n */\n async read(options?: { logger?: LoggerService }) {\n if (!this.connection) {\n throw new NotFoundError('Not initialized');\n }\n\n const logger = options?.logger ?? this.options.logger;\n const provider = this.options.provider;\n\n const { markReadComplete } = trackProgress(logger);\n const KeyCloakAdminClientModule = await inclusion(\n '@keycloak/keycloak-admin-client',\n );\n const KeyCloakAdminClient = KeyCloakAdminClientModule.default;\n\n const kcAdminClient = new KeyCloakAdminClient({\n baseUrl: provider.baseUrl,\n realmName: provider.loginRealm,\n });\n\n let credentials: Credentials;\n\n if (provider.username && provider.password) {\n credentials = {\n grantType: 'password',\n clientId: provider.clientId ?? 'admin-cli',\n username: provider.username,\n password: provider.password,\n };\n } else if (provider.clientId && provider.clientSecret) {\n credentials = {\n grantType: 'client_credentials',\n clientId: provider.clientId,\n clientSecret: provider.clientSecret,\n };\n } else {\n throw new InputError(\n `username and password or clientId and clientSecret must be provided.`,\n );\n }\n\n await kcAdminClient.auth(credentials);\n\n const { users, groups } = await readKeycloakRealm(\n kcAdminClient,\n provider,\n logger,\n {\n userQuerySize: provider.userQuerySize,\n groupQuerySize: provider.groupQuerySize,\n userTransformer: this.options.userTransformer,\n groupTransformer: this.options.groupTransformer,\n },\n );\n\n const { markCommitComplete } = markReadComplete({ users, groups });\n\n await this.connection.applyMutation({\n type: 'full',\n entities: [...users, ...groups].map(entity => ({\n locationKey: `keycloak-org-provider:${this.options.id}`,\n entity: withLocations(provider.baseUrl, provider.realm, entity),\n })),\n });\n\n markCommitComplete();\n }\n\n schedule(taskRunner: SchedulerServiceTaskRunner) {\n this.scheduleFn = async () => {\n const id = `${this.getProviderName()}:refresh`;\n await taskRunner.run({\n id,\n fn: async () => {\n const logger = this.options.logger.child({\n class: KeycloakOrgEntityProvider.prototype.constructor.name,\n taskId: id,\n taskInstanceId: uuid.v4(),\n });\n\n try {\n await this.read({ logger });\n } catch (error) {\n if (isError(error)) {\n // Ensure that we don't log any sensitive internal data:\n logger.error('Error while syncing Keycloak users and groups', {\n // Default Error properties:\n name: error.name,\n cause: error.cause,\n message: error.message,\n stack: error.stack,\n // Additional status code if available:\n status: (error.response as { status?: string })?.status,\n });\n }\n }\n },\n });\n };\n }\n}\n\n// Helps wrap the timing and logging behaviors\nfunction trackProgress(logger: LoggerService) {\n let timestamp = Date.now();\n let summary: string;\n\n logger.info('Reading Keycloak users and groups');\n\n function markReadComplete(read: { users: unknown[]; groups: unknown[] }) {\n summary = `${read.users.length} Keycloak users and ${read.groups.length} Keycloak groups`;\n const readDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n timestamp = Date.now();\n logger.info(`Read ${summary} in ${readDuration} seconds. Committing...`);\n return { markCommitComplete };\n }\n\n function markCommitComplete() {\n const commitDuration = ((Date.now() - timestamp) / 1000).toFixed(1);\n logger.info(`Committed ${summary} in ${commitDuration} seconds.`);\n }\n\n return { markReadComplete };\n}\n"],"names":["KEYCLOAK_ID_ANNOTATION","merge","ANNOTATION_LOCATION","ANNOTATION_ORIGIN_LOCATION","config","readProviderConfigs","InputError","NotFoundError","inclusion","readKeycloakRealm","uuid","isError"],"mappings":"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"}