@backstage-community/plugin-catalog-backend-module-keycloak 3.19.4 → 3.20.0
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.
- package/CHANGELOG.md +6 -0
- package/dist/extensions.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/lib/authenticate.cjs.js.map +1 -1
- package/dist/lib/config.cjs.js +1 -1
- package/dist/lib/config.cjs.js.map +1 -1
- package/dist/lib/constants.cjs.js.map +1 -1
- package/dist/lib/read.cjs.js.map +1 -1
- package/dist/lib/transformers.cjs.js.map +1 -1
- package/dist/module/catalogModuleKeycloakEntityProvider.cjs.js.map +1 -1
- package/dist/providers/KeycloakOrgEntityProvider.cjs.js.map +1 -1
- package/package.json +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extensions.cjs.js","sources":["../src/extensions.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 { createExtensionPoint } from '@backstage/backend-plugin-api';\n\nimport type { GroupTransformer, UserTransformer } from './lib/types';\n\n/**\n * An extension point that exposes the ability to implement user and group transformer functions for keycloak.\n *\n * @public\n */\nexport const keycloakTransformerExtensionPoint =\n createExtensionPoint<KeycloakTransformerExtensionPoint>({\n id: 'keycloak.transformer',\n });\n\n/**\n * The interface for {@link keycloakTransformerExtensionPoint}.\n *\n * @public\n */\nexport type KeycloakTransformerExtensionPoint = {\n setUserTransformer(userTransformer: UserTransformer): void;\n setGroupTransformer(groupTransformer: GroupTransformer): void;\n};\n"],"names":["createExtensionPoint"],"mappings":";;;;AAwBO,MAAM,oCACXA,
|
|
1
|
+
{"version":3,"file":"extensions.cjs.js","sources":["../src/extensions.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 { createExtensionPoint } from '@backstage/backend-plugin-api';\n\nimport type { GroupTransformer, UserTransformer } from './lib/types';\n\n/**\n * An extension point that exposes the ability to implement user and group transformer functions for keycloak.\n *\n * @public\n */\nexport const keycloakTransformerExtensionPoint =\n createExtensionPoint<KeycloakTransformerExtensionPoint>({\n id: 'keycloak.transformer',\n });\n\n/**\n * The interface for {@link keycloakTransformerExtensionPoint}.\n *\n * @public\n */\nexport type KeycloakTransformerExtensionPoint = {\n setUserTransformer(userTransformer: UserTransformer): void;\n setGroupTransformer(groupTransformer: GroupTransformer): void;\n};\n"],"names":["createExtensionPoint"],"mappings":";;;;AAwBO,MAAM,oCACXA,qCAAA,CAAwD;AAAA,EACtD,EAAA,EAAI;AACN,CAAC;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
|
|
2
|
-
import { SchedulerServiceTaskScheduleDefinition, SchedulerServiceTaskRunner, SchedulerService
|
|
2
|
+
import { SchedulerServiceTaskScheduleDefinition, LoggerService, SchedulerServiceTaskRunner, SchedulerService } from '@backstage/backend-plugin-api';
|
|
3
3
|
import { Config } from '@backstage/config';
|
|
4
4
|
import { EntityProvider, EntityProviderConnection } from '@backstage/plugin-catalog-node';
|
|
5
5
|
import { GroupEntity, UserEntity } from '@backstage/catalog-model';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authenticate.cjs.js","sources":["../../src/lib/authenticate.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 KeycloakAdminClient from '@keycloak/keycloak-admin-client';\nimport { KeycloakProviderConfig } from './config';\nimport { Credentials } from '@keycloak/keycloak-admin-client/lib/utils/auth';\nimport { InputError } from '@backstage/errors';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport jwt from 'jsonwebtoken';\n\nlet refreshTokenPromise: Promise<void> | null = null;\n\nexport async function ensureTokenValid(\n kcAdminClient: KeycloakAdminClient,\n provider: KeycloakProviderConfig,\n logger: LoggerService,\n) {\n if (!kcAdminClient.accessToken) {\n await authenticate(kcAdminClient, provider, logger);\n } else {\n // returns null if token is not a JWT, string if payload is empty string, object if payload is a valid JSON\n const decodedToken = jwt.decode(kcAdminClient.accessToken);\n if (decodedToken && typeof decodedToken === 'object' && decodedToken.exp) {\n const tokenExpiry = decodedToken.exp * 1000; // Convert to milliseconds\n const now = Date.now();\n\n if (now > tokenExpiry - 30000) {\n refreshTokenPromise = authenticate(\n kcAdminClient,\n provider,\n logger,\n ).finally(() => {\n refreshTokenPromise = null;\n });\n }\n await refreshTokenPromise;\n }\n }\n}\n\nexport async function authenticate(\n kcAdminClient: KeycloakAdminClient,\n provider: KeycloakProviderConfig,\n logger: LoggerService,\n) {\n try {\n let credentials: Credentials;\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 await kcAdminClient.auth(credentials);\n } catch (error) {\n logger.error('Failed to authenticate', error.message);\n throw error;\n }\n}\n"],"names":["jwt","InputError"],"mappings":";;;;;;;;;AAsBA,IAAI,
|
|
1
|
+
{"version":3,"file":"authenticate.cjs.js","sources":["../../src/lib/authenticate.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 KeycloakAdminClient from '@keycloak/keycloak-admin-client';\nimport { KeycloakProviderConfig } from './config';\nimport { Credentials } from '@keycloak/keycloak-admin-client/lib/utils/auth';\nimport { InputError } from '@backstage/errors';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport jwt from 'jsonwebtoken';\n\nlet refreshTokenPromise: Promise<void> | null = null;\n\nexport async function ensureTokenValid(\n kcAdminClient: KeycloakAdminClient,\n provider: KeycloakProviderConfig,\n logger: LoggerService,\n) {\n if (!kcAdminClient.accessToken) {\n await authenticate(kcAdminClient, provider, logger);\n } else {\n // returns null if token is not a JWT, string if payload is empty string, object if payload is a valid JSON\n const decodedToken = jwt.decode(kcAdminClient.accessToken);\n if (decodedToken && typeof decodedToken === 'object' && decodedToken.exp) {\n const tokenExpiry = decodedToken.exp * 1000; // Convert to milliseconds\n const now = Date.now();\n\n if (now > tokenExpiry - 30000) {\n refreshTokenPromise = authenticate(\n kcAdminClient,\n provider,\n logger,\n ).finally(() => {\n refreshTokenPromise = null;\n });\n }\n await refreshTokenPromise;\n }\n }\n}\n\nexport async function authenticate(\n kcAdminClient: KeycloakAdminClient,\n provider: KeycloakProviderConfig,\n logger: LoggerService,\n) {\n try {\n let credentials: Credentials;\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 await kcAdminClient.auth(credentials);\n } catch (error) {\n logger.error('Failed to authenticate', error.message);\n throw error;\n }\n}\n"],"names":["jwt","InputError"],"mappings":";;;;;;;;;AAsBA,IAAI,mBAAA,GAA4C,IAAA;AAEhD,eAAsB,gBAAA,CACpB,aAAA,EACA,QAAA,EACA,MAAA,EACA;AACA,EAAA,IAAI,CAAC,cAAc,WAAA,EAAa;AAC9B,IAAA,MAAM,YAAA,CAAa,aAAA,EAAe,QAAA,EAAU,MAAM,CAAA;AAAA,EACpD,CAAA,MAAO;AAEL,IAAA,MAAM,YAAA,GAAeA,oBAAA,CAAI,MAAA,CAAO,aAAA,CAAc,WAAW,CAAA;AACzD,IAAA,IAAI,YAAA,IAAgB,OAAO,YAAA,KAAiB,QAAA,IAAY,aAAa,GAAA,EAAK;AACxE,MAAA,MAAM,WAAA,GAAc,aAAa,GAAA,GAAM,GAAA;AACvC,MAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAErB,MAAA,IAAI,GAAA,GAAM,cAAc,GAAA,EAAO;AAC7B,QAAA,mBAAA,GAAsB,YAAA;AAAA,UACpB,aAAA;AAAA,UACA,QAAA;AAAA,UACA;AAAA,SACF,CAAE,QAAQ,MAAM;AACd,UAAA,mBAAA,GAAsB,IAAA;AAAA,QACxB,CAAC,CAAA;AAAA,MACH;AACA,MAAA,MAAM,mBAAA;AAAA,IACR;AAAA,EACF;AACF;AAEA,eAAsB,YAAA,CACpB,aAAA,EACA,QAAA,EACA,MAAA,EACA;AACA,EAAA,IAAI;AACF,IAAA,IAAI,WAAA;AACJ,IAAA,IAAI,QAAA,CAAS,QAAA,IAAY,QAAA,CAAS,QAAA,EAAU;AAC1C,MAAA,WAAA,GAAc;AAAA,QACZ,SAAA,EAAW,UAAA;AAAA,QACX,QAAA,EAAU,SAAS,QAAA,IAAY,WAAA;AAAA,QAC/B,UAAU,QAAA,CAAS,QAAA;AAAA,QACnB,UAAU,QAAA,CAAS;AAAA,OACrB;AAAA,IACF,CAAA,MAAA,IAAW,QAAA,CAAS,QAAA,IAAY,QAAA,CAAS,YAAA,EAAc;AACrD,MAAA,WAAA,GAAc;AAAA,QACZ,SAAA,EAAW,oBAAA;AAAA,QACX,UAAU,QAAA,CAAS,QAAA;AAAA,QACnB,cAAc,QAAA,CAAS;AAAA,OACzB;AAAA,IACF,CAAA,MAAO;AACL,MAAA,MAAM,IAAIC,iBAAA;AAAA,QACR,CAAA,oEAAA;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,aAAA,CAAc,KAAK,WAAW,CAAA;AAAA,EACtC,SAAS,KAAA,EAAO;AACd,IAAA,MAAA,CAAO,KAAA,CAAM,wBAAA,EAA0B,KAAA,CAAM,OAAO,CAAA;AACpD,IAAA,MAAM,KAAA;AAAA,EACR;AACF;;;;;"}
|
package/dist/lib/config.cjs.js
CHANGED
|
@@ -35,7 +35,7 @@ const readProviderConfig = (id, providerConfigInstance) => {
|
|
|
35
35
|
}
|
|
36
36
|
const schedule = providerConfigInstance.has("schedule") ? backendPluginApi.readSchedulerServiceTaskScheduleDefinitionFromConfig(
|
|
37
37
|
providerConfigInstance.getConfig("schedule")
|
|
38
|
-
) :
|
|
38
|
+
) : void 0;
|
|
39
39
|
return {
|
|
40
40
|
id,
|
|
41
41
|
baseUrl,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.cjs.js","sources":["../../src/lib/config.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 { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api';\nimport type { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\n\n/**\n * The configuration parameters for a single Keycloak provider.\n *\n * @public\n */\nexport type KeycloakProviderConfig = {\n /**\n * Identifier of the provider which will be used i.e. at the location key for ingested entities.\n */\n id: string;\n\n /**\n * The Keycloak base URL\n */\n baseUrl: string;\n\n /**\n * The username to use for authenticating requests\n * If specified, password must also be specified\n */\n username?: string;\n\n /**\n * The password to use for authenticating requests\n * If specified, username must also be specified\n */\n password?: string;\n\n /**\n * The clientId to use for authenticating requests\n * If specified, clientSecret must also be specified\n */\n clientId?: string;\n\n /**\n * The clientSecret to use for authenticating requests\n * If specified, clientId must also be specified\n */\n clientSecret?: string;\n\n /**\n * name of the Keycloak realm\n */\n realm: string;\n\n /**\n * name of the Keycloak login realm\n */\n loginRealm?: string;\n\n /**\n * Schedule configuration for refresh tasks.\n */\n schedule?: SchedulerServiceTaskScheduleDefinition;\n\n /**\n * The number of users to query at a time.\n * @defaultValue 100\n * @remarks\n * This is a performance optimization to avoid querying too many users at once.\n * @see https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_users_resource\n */\n userQuerySize?: number;\n\n /**\n * The number of groups to query at a time.\n * @defaultValue 100\n * @remarks\n * This is a performance optimization to avoid querying too many groups at once.\n * @see https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_groups_resource\n */\n groupQuerySize?: number;\n\n /**\n * Maximum request concurrency to prevent DoS attacks on the Keycloak server.\n */\n maxConcurrency?: number;\n\n /**\n * Whether the API call will return a brief representation for groups and users or not. Defaults to true.\n * A complete representation will include additional attributes\n * @defaultValue true\n */\n briefRepresentation?: boolean;\n};\n\nconst readProviderConfig = (\n id: string,\n providerConfigInstance: Config,\n): KeycloakProviderConfig => {\n const baseUrl = providerConfigInstance.getString('baseUrl');\n const realm = providerConfigInstance.getOptionalString('realm') ?? 'master';\n const loginRealm =\n providerConfigInstance.getOptionalString('loginRealm') ?? 'master';\n const username = providerConfigInstance.getOptionalString('username');\n const password = providerConfigInstance.getOptionalString('password');\n const clientId = providerConfigInstance.getOptionalString('clientId');\n const clientSecret = providerConfigInstance.getOptionalString('clientSecret');\n const userQuerySize =\n providerConfigInstance.getOptionalNumber('userQuerySize');\n const groupQuerySize =\n providerConfigInstance.getOptionalNumber('groupQuerySize');\n const maxConcurrency =\n providerConfigInstance.getOptionalNumber('maxConcurrency');\n const briefRepresentation = providerConfigInstance.getOptionalBoolean(\n 'briefRepresentation',\n );\n\n if (clientId && !clientSecret) {\n throw new InputError(\n `clientSecret must be provided when clientId is defined.`,\n );\n }\n\n if (clientSecret && !clientId) {\n throw new InputError(\n `clientId must be provided when clientSecret is defined.`,\n );\n }\n\n if (username && !password) {\n throw new InputError(`password must be provided when username is defined.`);\n }\n\n if (password && !username) {\n throw new InputError(`username must be provided when password is defined.`);\n }\n\n const schedule = providerConfigInstance.has('schedule')\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n providerConfigInstance.getConfig('schedule'),\n )\n : undefined;\n\n return {\n id,\n baseUrl,\n loginRealm,\n realm,\n username,\n password,\n clientId,\n clientSecret,\n schedule,\n userQuerySize,\n groupQuerySize,\n maxConcurrency,\n briefRepresentation,\n };\n};\n\nexport const readProviderConfigs = (\n config: Config,\n): KeycloakProviderConfig[] => {\n const providersConfig = config.getOptionalConfig(\n 'catalog.providers.keycloakOrg',\n );\n if (!providersConfig) {\n return [];\n }\n return providersConfig.keys().map(id => {\n const providerConfigInstance = providersConfig.getConfig(id);\n return readProviderConfig(id, providerConfigInstance);\n });\n};\n"],"names":["InputError","readSchedulerServiceTaskScheduleDefinitionFromConfig"],"mappings":";;;;;AA2GA,MAAM,kBAAA,GAAqB,CACzB,EAAA,EACA,
|
|
1
|
+
{"version":3,"file":"config.cjs.js","sources":["../../src/lib/config.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 { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api';\nimport type { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api';\nimport type { Config } from '@backstage/config';\nimport { InputError } from '@backstage/errors';\n\n/**\n * The configuration parameters for a single Keycloak provider.\n *\n * @public\n */\nexport type KeycloakProviderConfig = {\n /**\n * Identifier of the provider which will be used i.e. at the location key for ingested entities.\n */\n id: string;\n\n /**\n * The Keycloak base URL\n */\n baseUrl: string;\n\n /**\n * The username to use for authenticating requests\n * If specified, password must also be specified\n */\n username?: string;\n\n /**\n * The password to use for authenticating requests\n * If specified, username must also be specified\n */\n password?: string;\n\n /**\n * The clientId to use for authenticating requests\n * If specified, clientSecret must also be specified\n */\n clientId?: string;\n\n /**\n * The clientSecret to use for authenticating requests\n * If specified, clientId must also be specified\n */\n clientSecret?: string;\n\n /**\n * name of the Keycloak realm\n */\n realm: string;\n\n /**\n * name of the Keycloak login realm\n */\n loginRealm?: string;\n\n /**\n * Schedule configuration for refresh tasks.\n */\n schedule?: SchedulerServiceTaskScheduleDefinition;\n\n /**\n * The number of users to query at a time.\n * @defaultValue 100\n * @remarks\n * This is a performance optimization to avoid querying too many users at once.\n * @see https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_users_resource\n */\n userQuerySize?: number;\n\n /**\n * The number of groups to query at a time.\n * @defaultValue 100\n * @remarks\n * This is a performance optimization to avoid querying too many groups at once.\n * @see https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_groups_resource\n */\n groupQuerySize?: number;\n\n /**\n * Maximum request concurrency to prevent DoS attacks on the Keycloak server.\n */\n maxConcurrency?: number;\n\n /**\n * Whether the API call will return a brief representation for groups and users or not. Defaults to true.\n * A complete representation will include additional attributes\n * @defaultValue true\n */\n briefRepresentation?: boolean;\n};\n\nconst readProviderConfig = (\n id: string,\n providerConfigInstance: Config,\n): KeycloakProviderConfig => {\n const baseUrl = providerConfigInstance.getString('baseUrl');\n const realm = providerConfigInstance.getOptionalString('realm') ?? 'master';\n const loginRealm =\n providerConfigInstance.getOptionalString('loginRealm') ?? 'master';\n const username = providerConfigInstance.getOptionalString('username');\n const password = providerConfigInstance.getOptionalString('password');\n const clientId = providerConfigInstance.getOptionalString('clientId');\n const clientSecret = providerConfigInstance.getOptionalString('clientSecret');\n const userQuerySize =\n providerConfigInstance.getOptionalNumber('userQuerySize');\n const groupQuerySize =\n providerConfigInstance.getOptionalNumber('groupQuerySize');\n const maxConcurrency =\n providerConfigInstance.getOptionalNumber('maxConcurrency');\n const briefRepresentation = providerConfigInstance.getOptionalBoolean(\n 'briefRepresentation',\n );\n\n if (clientId && !clientSecret) {\n throw new InputError(\n `clientSecret must be provided when clientId is defined.`,\n );\n }\n\n if (clientSecret && !clientId) {\n throw new InputError(\n `clientId must be provided when clientSecret is defined.`,\n );\n }\n\n if (username && !password) {\n throw new InputError(`password must be provided when username is defined.`);\n }\n\n if (password && !username) {\n throw new InputError(`username must be provided when password is defined.`);\n }\n\n const schedule = providerConfigInstance.has('schedule')\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n providerConfigInstance.getConfig('schedule'),\n )\n : undefined;\n\n return {\n id,\n baseUrl,\n loginRealm,\n realm,\n username,\n password,\n clientId,\n clientSecret,\n schedule,\n userQuerySize,\n groupQuerySize,\n maxConcurrency,\n briefRepresentation,\n };\n};\n\nexport const readProviderConfigs = (\n config: Config,\n): KeycloakProviderConfig[] => {\n const providersConfig = config.getOptionalConfig(\n 'catalog.providers.keycloakOrg',\n );\n if (!providersConfig) {\n return [];\n }\n return providersConfig.keys().map(id => {\n const providerConfigInstance = providersConfig.getConfig(id);\n return readProviderConfig(id, providerConfigInstance);\n });\n};\n"],"names":["InputError","readSchedulerServiceTaskScheduleDefinitionFromConfig"],"mappings":";;;;;AA2GA,MAAM,kBAAA,GAAqB,CACzB,EAAA,EACA,sBAAA,KAC2B;AAC3B,EAAA,MAAM,OAAA,GAAU,sBAAA,CAAuB,SAAA,CAAU,SAAS,CAAA;AAC1D,EAAA,MAAM,KAAA,GAAQ,sBAAA,CAAuB,iBAAA,CAAkB,OAAO,CAAA,IAAK,QAAA;AACnE,EAAA,MAAM,UAAA,GACJ,sBAAA,CAAuB,iBAAA,CAAkB,YAAY,CAAA,IAAK,QAAA;AAC5D,EAAA,MAAM,QAAA,GAAW,sBAAA,CAAuB,iBAAA,CAAkB,UAAU,CAAA;AACpE,EAAA,MAAM,QAAA,GAAW,sBAAA,CAAuB,iBAAA,CAAkB,UAAU,CAAA;AACpE,EAAA,MAAM,QAAA,GAAW,sBAAA,CAAuB,iBAAA,CAAkB,UAAU,CAAA;AACpE,EAAA,MAAM,YAAA,GAAe,sBAAA,CAAuB,iBAAA,CAAkB,cAAc,CAAA;AAC5E,EAAA,MAAM,aAAA,GACJ,sBAAA,CAAuB,iBAAA,CAAkB,eAAe,CAAA;AAC1D,EAAA,MAAM,cAAA,GACJ,sBAAA,CAAuB,iBAAA,CAAkB,gBAAgB,CAAA;AAC3D,EAAA,MAAM,cAAA,GACJ,sBAAA,CAAuB,iBAAA,CAAkB,gBAAgB,CAAA;AAC3D,EAAA,MAAM,sBAAsB,sBAAA,CAAuB,kBAAA;AAAA,IACjD;AAAA,GACF;AAEA,EAAA,IAAI,QAAA,IAAY,CAAC,YAAA,EAAc;AAC7B,IAAA,MAAM,IAAIA,iBAAA;AAAA,MACR,CAAA,uDAAA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,IAAI,YAAA,IAAgB,CAAC,QAAA,EAAU;AAC7B,IAAA,MAAM,IAAIA,iBAAA;AAAA,MACR,CAAA,uDAAA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,IAAI,QAAA,IAAY,CAAC,QAAA,EAAU;AACzB,IAAA,MAAM,IAAIA,kBAAW,CAAA,mDAAA,CAAqD,CAAA;AAAA,EAC5E;AAEA,EAAA,IAAI,QAAA,IAAY,CAAC,QAAA,EAAU;AACzB,IAAA,MAAM,IAAIA,kBAAW,CAAA,mDAAA,CAAqD,CAAA;AAAA,EAC5E;AAEA,EAAA,MAAM,QAAA,GAAW,sBAAA,CAAuB,GAAA,CAAI,UAAU,CAAA,GAClDC,qEAAA;AAAA,IACE,sBAAA,CAAuB,UAAU,UAAU;AAAA,GAC7C,GACA,MAAA;AAEJ,EAAA,OAAO;AAAA,IACL,EAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,YAAA;AAAA,IACA,QAAA;AAAA,IACA,aAAA;AAAA,IACA,cAAA;AAAA,IACA,cAAA;AAAA,IACA;AAAA,GACF;AACF,CAAA;AAEO,MAAM,mBAAA,GAAsB,CACjC,MAAA,KAC6B;AAC7B,EAAA,MAAM,kBAAkB,MAAA,CAAO,iBAAA;AAAA,IAC7B;AAAA,GACF;AACA,EAAA,IAAI,CAAC,eAAA,EAAiB;AACpB,IAAA,OAAO,EAAC;AAAA,EACV;AACA,EAAA,OAAO,eAAA,CAAgB,IAAA,EAAK,CAAE,GAAA,CAAI,CAAA,EAAA,KAAM;AACtC,IAAA,MAAM,sBAAA,GAAyB,eAAA,CAAgB,SAAA,CAAU,EAAE,CAAA;AAC3D,IAAA,OAAO,kBAAA,CAAmB,IAAI,sBAAsB,CAAA;AAAA,EACtD,CAAC,CAAA;AACH;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.cjs.js","sources":["../../src/lib/constants.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\nexport const KEYCLOAK_HOST_ANNOTATION = 'keycloak.org/host';\nexport const KEYCLOAK_ID_ANNOTATION = 'keycloak.org/id';\nexport const KEYCLOAK_REALM_ANNOTATION = 'keycloak.org/realm';\nexport const KEYCLOAK_ENTITY_QUERY_SIZE = 100;\nexport const KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT = true;\n"],"names":[],"mappings":";;AAiBO,MAAM,
|
|
1
|
+
{"version":3,"file":"constants.cjs.js","sources":["../../src/lib/constants.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\nexport const KEYCLOAK_HOST_ANNOTATION = 'keycloak.org/host';\nexport const KEYCLOAK_ID_ANNOTATION = 'keycloak.org/id';\nexport const KEYCLOAK_REALM_ANNOTATION = 'keycloak.org/realm';\nexport const KEYCLOAK_ENTITY_QUERY_SIZE = 100;\nexport const KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT = true;\n"],"names":[],"mappings":";;AAiBO,MAAM,sBAAA,GAAyB;AAC/B,MAAM,yBAAA,GAA4B;AAClC,MAAM,0BAAA,GAA6B;AACnC,MAAM,qCAAA,GAAwC;;;;;;;"}
|
package/dist/lib/read.cjs.js.map
CHANGED
|
@@ -1 +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';\nimport { LimitFunction } from 'p-limit';\n\nimport { KeycloakProviderConfig } from './config';\nimport {\n KEYCLOAK_ENTITY_QUERY_SIZE,\n KEYCLOAK_ID_ANNOTATION,\n KEYCLOAK_REALM_ANNOTATION,\n KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT,\n} from './constants';\nimport {\n sanitizeUserNameTransformer,\n sanitizeGroupNameTransformer,\n} from './transformers';\nimport {\n GroupRepresentationWithParent,\n GroupRepresentationWithParentAndEntity,\n GroupTransformer,\n UserRepresentationWithEntity,\n UserTransformer,\n} from './types';\nimport { ensureTokenValid } from './authenticate';\nimport { Attributes, Counter } from '@opentelemetry/api';\n\nexport const parseGroup = async (\n keycloakGroup: GroupRepresentationWithParent,\n realm: string,\n groupTransformer?: GroupTransformer,\n): Promise<GroupEntity | undefined> => {\n const transformer = groupTransformer ?? sanitizeGroupNameTransformer;\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 groupIndex: Map<string, string[]>,\n userTransformer?: UserTransformer,\n): Promise<UserEntity | undefined> => {\n const transformer = userTransformer ?? sanitizeUserNameTransformer;\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: groupIndex.get(user.username!) ?? [],\n },\n };\n\n return await transformer(entity, user, realm, keycloakGroups);\n};\n\nexport async function getEntities<T extends Users | Groups>(\n getEntitiesFn: () => Promise<T>,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n dataBatchFailureCounter: Counter<Attributes>,\n taskInstanceId: string,\n limit: LimitFunction,\n entityQuerySize: number = KEYCLOAK_ENTITY_QUERY_SIZE,\n): Promise<Awaited<ReturnType<T['find']>>> {\n const entitiesAPI = await getEntitiesFn();\n const rawEntityCount = await entitiesAPI.count({ realm: config.realm });\n const entityCount =\n typeof rawEntityCount === 'number' ? rawEntityCount : rawEntityCount.count;\n\n const pageCount = Math.ceil(entityCount / entityQuerySize);\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n // The next line acts like range in python\n const entityPromises = Array.from({ length: pageCount }, (_, i) =>\n limit(() =>\n getEntitiesFn().then(entities => {\n return entities\n .find({\n realm: config.realm,\n max: entityQuerySize,\n first: i * entityQuerySize,\n briefRepresentation: brief,\n })\n .then(ents => {\n logger.debug(\n `Importing keycloak entities batch with index ${i} from pages: ${pageCount}`,\n );\n return ents;\n })\n .catch(err => {\n dataBatchFailureCounter.add(1, { taskInstanceId: taskInstanceId });\n logger.warn(\n `Failed to retieve Keycloak entities for taskInstanceId: ${taskInstanceId}. Error: ${err}`,\n );\n return [];\n }) as ReturnType<T['find']>;\n }),\n ),\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 groupsAPI: () => Promise<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 groups = await groupsAPI();\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 kcAdminClient: KeycloakAdminClient,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n topLevelGroups: GroupRepresentationWithParent[],\n) {\n const allGroups: GroupRepresentationWithParent[] = [];\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n for (const group of topLevelGroups) {\n allGroups.push(group);\n\n if (group.subGroupCount! > 0) {\n await ensureTokenValid(kcAdminClient, config, logger);\n const subgroups = await kcAdminClient.groups.listSubGroups({\n parentId: group.id!,\n first: 0,\n max: group.subGroupCount,\n briefRepresentation: brief,\n realm: config.realm,\n });\n const subGroupResults = await processGroupsRecursively(\n kcAdminClient,\n config,\n logger,\n subgroups,\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 limit: LimitFunction,\n taskInstanceId: string,\n dataBatchFailureCounter: Counter<Attributes>,\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 async () => {\n await ensureTokenValid(client, config, logger);\n return client.users;\n },\n config,\n logger,\n dataBatchFailureCounter,\n taskInstanceId,\n limit,\n options?.userQuerySize,\n );\n logger.debug(`Fetched ${kUsers.length} users from Keycloak`);\n\n const topLevelKGroups = (await getEntities(\n async () => {\n await ensureTokenValid(client, config, logger);\n return client.groups;\n },\n config,\n logger,\n dataBatchFailureCounter,\n taskInstanceId,\n limit,\n options?.groupQuerySize,\n )) as GroupRepresentationWithParent[];\n logger.debug(`Fetched ${topLevelKGroups.length} groups from Keycloak`);\n\n let serverVersion: number;\n\n try {\n await ensureTokenValid(client, config, logger);\n const serverInfo = await client.serverInfo.find();\n\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 isVersion22orLower = serverVersion <= 22;\n\n let rawKGroups: GroupRepresentationWithParent[] = [];\n\n logger.debug(`Processing groups recursively`);\n if (isVersion22orLower) {\n rawKGroups = topLevelKGroups.reduce(\n (acc, g) => acc.concat(...traverseGroups(g)),\n [] as GroupRepresentationWithParent[],\n );\n } else {\n rawKGroups = await processGroupsRecursively(\n client,\n config,\n logger,\n topLevelKGroups,\n );\n }\n\n logger.debug(`Fetching group members for keycloak groups and list subgroups`);\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n const kGroups = await Promise.all(\n rawKGroups.map(g =>\n limit(async () => {\n g.members = await getAllGroupMembers(\n async () => {\n await ensureTokenValid(client, config, logger);\n return client.groups as Groups;\n },\n g.id!,\n config,\n options,\n );\n\n if (!isVersion22orLower) {\n if (g.subGroupCount! > 0) {\n await ensureTokenValid(client, config, logger);\n g.subGroups = await client.groups.listSubGroups({\n parentId: g.id!,\n first: 0,\n max: g.subGroupCount,\n briefRepresentation: brief,\n realm: config.realm,\n });\n }\n if (g.parentId) {\n await ensureTokenValid(client, config, logger);\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\n logger.debug(`Parsing groups`);\n const parsedGroups = await Promise.all(\n kGroups.map(async g => {\n // it is possible if fetch request failed\n if (!g) {\n return null;\n }\n const entity = await parseGroup(\n g,\n config.realm,\n options?.groupTransformer,\n );\n if (entity) {\n return { ...g, entity } as GroupRepresentationWithParentAndEntity;\n }\n return null;\n }),\n );\n const filteredParsedGroups = parsedGroups.filter(\n (group): group is GroupRepresentationWithParentAndEntity => group !== null,\n );\n\n const groupIndex = new Map<string, string[]>();\n filteredParsedGroups.forEach(group => {\n if (group.members) {\n group.members.forEach(member => {\n if (!groupIndex.has(member)) {\n groupIndex.set(member, []);\n }\n groupIndex.get(member)?.push(group.entity.metadata.name);\n });\n }\n });\n\n logger.debug('Parsing users');\n const parsedUsers = await Promise.all(\n kUsers.map(async u => {\n // it is possible if fetch request failed\n if (!u) {\n return null;\n }\n const entity = await parseUser(\n u,\n config.realm,\n filteredParsedGroups,\n groupIndex,\n options?.userTransformer,\n );\n if (entity) {\n return { ...u, entity } as UserRepresentationWithEntity;\n }\n return null;\n }),\n );\n const filteredParsedUsers = parsedUsers.filter(\n (user): user is UserRepresentationWithEntity => user !== null,\n );\n\n logger.debug(`Set up group members and children information`);\n\n const userMap = new Map(\n filteredParsedUsers.map(user => [user.username, user.entity.metadata.name]),\n );\n\n const groupMap = new Map(\n filteredParsedGroups.map(group => [group.name, group.entity.metadata.name]),\n );\n\n const groups = filteredParsedGroups.map(g => {\n const entity = g.entity;\n entity.spec.members =\n g.entity.spec.members?.flatMap(m => userMap.get(m) ?? []) ?? [];\n entity.spec.children =\n g.entity.spec.children?.flatMap(c => groupMap.get(c) ?? []) ?? [];\n entity.spec.parent = groupMap.get(entity.spec.parent);\n return entity;\n });\n\n logger.info(\n `Prepared to ingest ${parsedUsers.length} users and ${groups.length} groups into the catalog from Keycloak`,\n );\n\n return { users: filteredParsedUsers.map(u => u.entity), groups };\n};\n"],"names":["sanitizeGroupNameTransformer","KEYCLOAK_ID_ANNOTATION","KEYCLOAK_REALM_ANNOTATION","sanitizeUserNameTransformer","KEYCLOAK_ENTITY_QUERY_SIZE","KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT","ensureTokenValid"],"mappings":"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"}
|
|
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';\nimport { LimitFunction } from 'p-limit';\n\nimport { KeycloakProviderConfig } from './config';\nimport {\n KEYCLOAK_ENTITY_QUERY_SIZE,\n KEYCLOAK_ID_ANNOTATION,\n KEYCLOAK_REALM_ANNOTATION,\n KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT,\n} from './constants';\nimport {\n sanitizeUserNameTransformer,\n sanitizeGroupNameTransformer,\n} from './transformers';\nimport {\n GroupRepresentationWithParent,\n GroupRepresentationWithParentAndEntity,\n GroupTransformer,\n UserRepresentationWithEntity,\n UserTransformer,\n} from './types';\nimport { ensureTokenValid } from './authenticate';\nimport { Attributes, Counter } from '@opentelemetry/api';\n\nexport const parseGroup = async (\n keycloakGroup: GroupRepresentationWithParent,\n realm: string,\n groupTransformer?: GroupTransformer,\n): Promise<GroupEntity | undefined> => {\n const transformer = groupTransformer ?? sanitizeGroupNameTransformer;\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 groupIndex: Map<string, string[]>,\n userTransformer?: UserTransformer,\n): Promise<UserEntity | undefined> => {\n const transformer = userTransformer ?? sanitizeUserNameTransformer;\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: groupIndex.get(user.username!) ?? [],\n },\n };\n\n return await transformer(entity, user, realm, keycloakGroups);\n};\n\nexport async function getEntities<T extends Users | Groups>(\n getEntitiesFn: () => Promise<T>,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n dataBatchFailureCounter: Counter<Attributes>,\n taskInstanceId: string,\n limit: LimitFunction,\n entityQuerySize: number = KEYCLOAK_ENTITY_QUERY_SIZE,\n): Promise<Awaited<ReturnType<T['find']>>> {\n const entitiesAPI = await getEntitiesFn();\n const rawEntityCount = await entitiesAPI.count({ realm: config.realm });\n const entityCount =\n typeof rawEntityCount === 'number' ? rawEntityCount : rawEntityCount.count;\n\n const pageCount = Math.ceil(entityCount / entityQuerySize);\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n // The next line acts like range in python\n const entityPromises = Array.from({ length: pageCount }, (_, i) =>\n limit(() =>\n getEntitiesFn().then(entities => {\n return entities\n .find({\n realm: config.realm,\n max: entityQuerySize,\n first: i * entityQuerySize,\n briefRepresentation: brief,\n })\n .then(ents => {\n logger.debug(\n `Importing keycloak entities batch with index ${i} from pages: ${pageCount}`,\n );\n return ents;\n })\n .catch(err => {\n dataBatchFailureCounter.add(1, { taskInstanceId: taskInstanceId });\n logger.warn(\n `Failed to retieve Keycloak entities for taskInstanceId: ${taskInstanceId}. Error: ${err}`,\n );\n return [];\n }) as ReturnType<T['find']>;\n }),\n ),\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 groupsAPI: () => Promise<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 groups = await groupsAPI();\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 kcAdminClient: KeycloakAdminClient,\n config: KeycloakProviderConfig,\n logger: LoggerService,\n topLevelGroups: GroupRepresentationWithParent[],\n) {\n const allGroups: GroupRepresentationWithParent[] = [];\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n for (const group of topLevelGroups) {\n allGroups.push(group);\n\n if (group.subGroupCount! > 0) {\n await ensureTokenValid(kcAdminClient, config, logger);\n const subgroups = await kcAdminClient.groups.listSubGroups({\n parentId: group.id!,\n first: 0,\n max: group.subGroupCount,\n briefRepresentation: brief,\n realm: config.realm,\n });\n const subGroupResults = await processGroupsRecursively(\n kcAdminClient,\n config,\n logger,\n subgroups,\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 limit: LimitFunction,\n taskInstanceId: string,\n dataBatchFailureCounter: Counter<Attributes>,\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 async () => {\n await ensureTokenValid(client, config, logger);\n return client.users;\n },\n config,\n logger,\n dataBatchFailureCounter,\n taskInstanceId,\n limit,\n options?.userQuerySize,\n );\n logger.debug(`Fetched ${kUsers.length} users from Keycloak`);\n\n const topLevelKGroups = (await getEntities(\n async () => {\n await ensureTokenValid(client, config, logger);\n return client.groups;\n },\n config,\n logger,\n dataBatchFailureCounter,\n taskInstanceId,\n limit,\n options?.groupQuerySize,\n )) as GroupRepresentationWithParent[];\n logger.debug(`Fetched ${topLevelKGroups.length} groups from Keycloak`);\n\n let serverVersion: number;\n\n try {\n await ensureTokenValid(client, config, logger);\n const serverInfo = await client.serverInfo.find();\n\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 isVersion22orLower = serverVersion <= 22;\n\n let rawKGroups: GroupRepresentationWithParent[] = [];\n\n logger.debug(`Processing groups recursively`);\n if (isVersion22orLower) {\n rawKGroups = topLevelKGroups.reduce(\n (acc, g) => acc.concat(...traverseGroups(g)),\n [] as GroupRepresentationWithParent[],\n );\n } else {\n rawKGroups = await processGroupsRecursively(\n client,\n config,\n logger,\n topLevelKGroups,\n );\n }\n\n logger.debug(`Fetching group members for keycloak groups and list subgroups`);\n const brief =\n config.briefRepresentation ?? KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT;\n\n const kGroups = await Promise.all(\n rawKGroups.map(g =>\n limit(async () => {\n g.members = await getAllGroupMembers(\n async () => {\n await ensureTokenValid(client, config, logger);\n return client.groups as Groups;\n },\n g.id!,\n config,\n options,\n );\n\n if (!isVersion22orLower) {\n if (g.subGroupCount! > 0) {\n await ensureTokenValid(client, config, logger);\n g.subGroups = await client.groups.listSubGroups({\n parentId: g.id!,\n first: 0,\n max: g.subGroupCount,\n briefRepresentation: brief,\n realm: config.realm,\n });\n }\n if (g.parentId) {\n await ensureTokenValid(client, config, logger);\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\n logger.debug(`Parsing groups`);\n const parsedGroups = await Promise.all(\n kGroups.map(async g => {\n // it is possible if fetch request failed\n if (!g) {\n return null;\n }\n const entity = await parseGroup(\n g,\n config.realm,\n options?.groupTransformer,\n );\n if (entity) {\n return { ...g, entity } as GroupRepresentationWithParentAndEntity;\n }\n return null;\n }),\n );\n const filteredParsedGroups = parsedGroups.filter(\n (group): group is GroupRepresentationWithParentAndEntity => group !== null,\n );\n\n const groupIndex = new Map<string, string[]>();\n filteredParsedGroups.forEach(group => {\n if (group.members) {\n group.members.forEach(member => {\n if (!groupIndex.has(member)) {\n groupIndex.set(member, []);\n }\n groupIndex.get(member)?.push(group.entity.metadata.name);\n });\n }\n });\n\n logger.debug('Parsing users');\n const parsedUsers = await Promise.all(\n kUsers.map(async u => {\n // it is possible if fetch request failed\n if (!u) {\n return null;\n }\n const entity = await parseUser(\n u,\n config.realm,\n filteredParsedGroups,\n groupIndex,\n options?.userTransformer,\n );\n if (entity) {\n return { ...u, entity } as UserRepresentationWithEntity;\n }\n return null;\n }),\n );\n const filteredParsedUsers = parsedUsers.filter(\n (user): user is UserRepresentationWithEntity => user !== null,\n );\n\n logger.debug(`Set up group members and children information`);\n\n const userMap = new Map(\n filteredParsedUsers.map(user => [user.username, user.entity.metadata.name]),\n );\n\n const groupMap = new Map(\n filteredParsedGroups.map(group => [group.name, group.entity.metadata.name]),\n );\n\n const groups = filteredParsedGroups.map(g => {\n const entity = g.entity;\n entity.spec.members =\n g.entity.spec.members?.flatMap(m => userMap.get(m) ?? []) ?? [];\n entity.spec.children =\n g.entity.spec.children?.flatMap(c => groupMap.get(c) ?? []) ?? [];\n entity.spec.parent = groupMap.get(entity.spec.parent);\n return entity;\n });\n\n logger.info(\n `Prepared to ingest ${parsedUsers.length} users and ${groups.length} groups into the catalog from Keycloak`,\n );\n\n return { users: filteredParsedUsers.map(u => u.entity), groups };\n};\n"],"names":["sanitizeGroupNameTransformer","KEYCLOAK_ID_ANNOTATION","KEYCLOAK_REALM_ANNOTATION","sanitizeUserNameTransformer","KEYCLOAK_ENTITY_QUERY_SIZE","KEYCLOAK_BRIEF_REPRESENTATION_DEFAULT","ensureTokenValid"],"mappings":"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"}
|
|
@@ -1 +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\n/**\n * @public\n * Group transformer that does nothing.\n */\nexport const noopGroupTransformer: GroupTransformer = async (\n entity,\n _user,\n _realm,\n) => entity;\n\n/**\n * @public\n * User transformer that does nothing.\n */\nexport const noopUserTransformer: UserTransformer = async (\n entity,\n _user,\n _realm,\n _groups,\n) => entity;\n\n/**\n * @public\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\n/**\n * @public\n * User transformer that sanitizes .metadata.name from invalid Backstage object name to a valid name\n */\nexport const sanitizeUserNameTransformer: 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\n/**\n * @public\n * Group transformer that sanitizes .metadata.name from invalid Backstage object name to a valid name\n */\nexport const sanitizeGroupNameTransformer: GroupTransformer = async (\n entity,\n _user,\n _realm,\n) => {\n entity.metadata.name = entity.metadata.name.replace(/[^a-zA-Z0-9\\-_.]/g, '-');\n return entity;\n};\n"],"names":[],"mappings":";;AAqBO,MAAM,
|
|
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\n/**\n * @public\n * Group transformer that does nothing.\n */\nexport const noopGroupTransformer: GroupTransformer = async (\n entity,\n _user,\n _realm,\n) => entity;\n\n/**\n * @public\n * User transformer that does nothing.\n */\nexport const noopUserTransformer: UserTransformer = async (\n entity,\n _user,\n _realm,\n _groups,\n) => entity;\n\n/**\n * @public\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\n/**\n * @public\n * User transformer that sanitizes .metadata.name from invalid Backstage object name to a valid name\n */\nexport const sanitizeUserNameTransformer: 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\n/**\n * @public\n * Group transformer that sanitizes .metadata.name from invalid Backstage object name to a valid name\n */\nexport const sanitizeGroupNameTransformer: GroupTransformer = async (\n entity,\n _user,\n _realm,\n) => {\n entity.metadata.name = entity.metadata.name.replace(/[^a-zA-Z0-9\\-_.]/g, '-');\n return entity;\n};\n"],"names":[],"mappings":";;AAqBO,MAAM,oBAAA,GAAyC,OACpD,MAAA,EACA,KAAA,EACA,MAAA,KACG;AAME,MAAM,mBAAA,GAAuC,OAClD,MAAA,EACA,KAAA,EACA,QACA,OAAA,KACG;AAME,MAAM,wBAAA,GAA4C,OACvD,MAAA,EACA,KAAA,EACA,QACA,OAAA,KACG;AACH,EAAA,MAAA,CAAO,SAAS,IAAA,GAAO,MAAA,CAAO,SAAS,IAAA,CAAK,OAAA,CAAQ,iBAAiB,GAAG,CAAA;AACxE,EAAA,OAAO,MAAA;AACT;AAMO,MAAM,2BAAA,GAA+C,OAC1D,MAAA,EACA,KAAA,EACA,QACA,OAAA,KACG;AACH,EAAA,MAAA,CAAO,SAAS,IAAA,GAAO,MAAA,CAAO,SAAS,IAAA,CAAK,OAAA,CAAQ,qBAAqB,GAAG,CAAA;AAC5E,EAAA,OAAO,MAAA;AACT;AAMO,MAAM,4BAAA,GAAiD,OAC5D,MAAA,EACA,KAAA,EACA,MAAA,KACG;AACH,EAAA,MAAA,CAAO,SAAS,IAAA,GAAO,MAAA,CAAO,SAAS,IAAA,CAAK,OAAA,CAAQ,qBAAqB,GAAG,CAAA;AAC5E,EAAA,OAAO,MAAA;AACT;;;;;;;;"}
|
|
@@ -1 +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';\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 * @public\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,
|
|
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';\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 * @public\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,oCAAA,CAAoB;AAAA,EACrE,QAAA,EAAU,SAAA;AAAA,EACV,QAAA,EAAU,iCAAA;AAAA,EACV,SAAS,GAAA,EAAK;AACZ,IAAA,IAAI,eAAA;AACJ,IAAA,IAAI,gBAAA;AAEJ,IAAA,GAAA,CAAI,uBAAuBC,4CAAA,EAAmC;AAAA,MAC5D,mBAAmB,WAAA,EAAa;AAC9B,QAAA,IAAI,eAAA,EAAiB;AACnB,UAAA,MAAM,IAAIC,kBAAW,uCAAuC,CAAA;AAAA,QAC9D;AACA,QAAA,eAAA,GAAkB,WAAA;AAAA,MACpB,CAAA;AAAA,MACA,oBAAoB,WAAA,EAAa;AAC/B,QAAA,IAAI,gBAAA,EAAkB;AACpB,UAAA,MAAM,IAAIA,kBAAW,wCAAwC,CAAA;AAAA,QAC/D;AACA,QAAA,gBAAA,GAAmB,WAAA;AAAA,MACrB;AAAA,KACD,CAAA;AACD,IAAA,GAAA,CAAI,YAAA,CAAa;AAAA,MACf,IAAA,EAAM;AAAA,QACJ,OAAA,EAASC,iDAAA;AAAA,QACT,QAAQC,6BAAA,CAAa,UAAA;AAAA,QACrB,QAAQA,6BAAA,CAAa,MAAA;AAAA,QACrB,WAAWA,6BAAA,CAAa;AAAA,OAC1B;AAAA,MACA,MAAM,IAAA,CAAK,EAAE,SAAS,MAAA,EAAQ,MAAA,EAAQ,WAAU,EAAG;AACjD,QAAA,OAAA,CAAQ,iBAAA;AAAA,UACNC,mDAAA,CAA0B,UAAA;AAAA,YACxB,EAAE,QAAQ,MAAA,EAAO;AAAA,YACjB;AAAA,cACE,SAAA;AAAA,cACA,QAAA,EAAU,UAAU,yBAAA,CAA0B;AAAA,gBAC5C,SAAA,EAAW,EAAE,OAAA,EAAS,EAAA,EAAG;AAAA,gBACzB,OAAA,EAAS,EAAE,OAAA,EAAS,CAAA;AAAE,eACvB,CAAA;AAAA,cACD,eAAA;AAAA,cACA;AAAA;AACF;AACF,SACF;AAAA,MACF;AAAA,KACD,CAAA;AAAA,EACH;AACF,CAAC;;;;"}
|
|
@@ -1 +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 KeyCloakAdminClient from '@keycloak/keycloak-admin-client';\nimport { Attributes, Counter, Meter, metrics } from '@opentelemetry/api';\n// @ts-ignore\nimport { merge } from 'lodash';\nimport pLimit from 'p-limit';\nimport { v4 as uuidv4 } 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';\nimport { authenticate } from '../lib/authenticate';\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 meter: Meter;\n private counter: Counter<Attributes>;\n private scheduleFn?: () => Promise<void>;\n\n /**\n * Static builder method to create multiple KeycloakOrgEntityProvider instances from a single config.\n * @param deps - The dependencies required for the provider, including the configuration and logger.\n * @param options - Options for scheduling tasks and transforming users and groups.\n * @returns An array of KeycloakOrgEntityProvider instances.\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.meter = metrics.getMeter('default');\n this.counter = this.meter.createCounter(\n 'backend_keycloak.fetch.task.failure.count',\n {\n description:\n 'Counts the number of failed Keycloak data fetch tasks. Each increment indicates a complete failure of a fetch task, meaning no data was provided to the Catalog API. However, data may still be fetched in subsequent tasks, depending on the nature of the error.',\n },\n );\n this.schedule(options.taskRunner);\n }\n\n /**\n * Returns the name of this entity provider.\n */\n getProviderName(): string {\n return `KeycloakOrgEntityProvider:${this.options.id}`;\n }\n\n /**\n * Connect to Backstage catalog entity provider\n * @param connection - The connection to the catalog API ingestor, which allows the provision of new entities.\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; taskInstanceId: string }) {\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\n const kcAdminClient = new KeyCloakAdminClient({\n baseUrl: provider.baseUrl,\n realmName: provider.loginRealm,\n });\n await authenticate(kcAdminClient, provider, logger);\n\n const concurrency = provider.maxConcurrency ?? 20;\n const limit = pLimit(concurrency);\n\n const dataBatchFailureCounter = this.meter.createCounter(\n 'backend_keycloak.fetch.data.batch.failure.count',\n {\n description:\n 'Keycloak data batch fetch failure counter. Incremented for each batch fetch failure. Each failure means that a part of the data was not fetched due to an error, and thus the corresponding data batch was skipped during the current fetch task.',\n },\n );\n const { users, groups } = await readKeycloakRealm(\n kcAdminClient,\n provider,\n logger,\n limit,\n options.taskInstanceId,\n dataBatchFailureCounter,\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 /**\n * Periodically schedules a task to read Keycloak user and group information, parse it, and provision it to the Backstage catalog.\n * @param taskRunner - The task runner to use for scheduling tasks.\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 taskInstanceId = uuidv4();\n const logger = this.options.logger.child({\n class: KeycloakOrgEntityProvider.prototype.constructor.name,\n taskId: id,\n taskInstanceId: taskInstanceId,\n });\n\n try {\n await this.read({ logger, taskInstanceId });\n } catch (error) {\n this.counter.add(1, { taskInstanceId: taskInstanceId });\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","metrics","config","readProviderConfigs","InputError","NotFoundError","KeyCloakAdminClient","authenticate","pLimit","readKeycloakRealm","uuidv4","isError"],"mappings":"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"}
|
|
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 KeyCloakAdminClient from '@keycloak/keycloak-admin-client';\nimport { Attributes, Counter, Meter, metrics } from '@opentelemetry/api';\n// @ts-ignore\nimport { merge } from 'lodash';\nimport pLimit from 'p-limit';\nimport { v4 as uuidv4 } 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';\nimport { authenticate } from '../lib/authenticate';\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 meter: Meter;\n private counter: Counter<Attributes>;\n private scheduleFn?: () => Promise<void>;\n\n /**\n * Static builder method to create multiple KeycloakOrgEntityProvider instances from a single config.\n * @param deps - The dependencies required for the provider, including the configuration and logger.\n * @param options - Options for scheduling tasks and transforming users and groups.\n * @returns An array of KeycloakOrgEntityProvider instances.\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.meter = metrics.getMeter('default');\n this.counter = this.meter.createCounter(\n 'backend_keycloak.fetch.task.failure.count',\n {\n description:\n 'Counts the number of failed Keycloak data fetch tasks. Each increment indicates a complete failure of a fetch task, meaning no data was provided to the Catalog API. However, data may still be fetched in subsequent tasks, depending on the nature of the error.',\n },\n );\n this.schedule(options.taskRunner);\n }\n\n /**\n * Returns the name of this entity provider.\n */\n getProviderName(): string {\n return `KeycloakOrgEntityProvider:${this.options.id}`;\n }\n\n /**\n * Connect to Backstage catalog entity provider\n * @param connection - The connection to the catalog API ingestor, which allows the provision of new entities.\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; taskInstanceId: string }) {\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\n const kcAdminClient = new KeyCloakAdminClient({\n baseUrl: provider.baseUrl,\n realmName: provider.loginRealm,\n });\n await authenticate(kcAdminClient, provider, logger);\n\n const concurrency = provider.maxConcurrency ?? 20;\n const limit = pLimit(concurrency);\n\n const dataBatchFailureCounter = this.meter.createCounter(\n 'backend_keycloak.fetch.data.batch.failure.count',\n {\n description:\n 'Keycloak data batch fetch failure counter. Incremented for each batch fetch failure. Each failure means that a part of the data was not fetched due to an error, and thus the corresponding data batch was skipped during the current fetch task.',\n },\n );\n const { users, groups } = await readKeycloakRealm(\n kcAdminClient,\n provider,\n logger,\n limit,\n options.taskInstanceId,\n dataBatchFailureCounter,\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 /**\n * Periodically schedules a task to read Keycloak user and group information, parse it, and provision it to the Backstage catalog.\n * @param taskRunner - The task runner to use for scheduling tasks.\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 taskInstanceId = uuidv4();\n const logger = this.options.logger.child({\n class: KeycloakOrgEntityProvider.prototype.constructor.name,\n taskId: id,\n taskInstanceId: taskInstanceId,\n });\n\n try {\n await this.read({ logger, taskInstanceId });\n } catch (error) {\n this.counter.add(1, { taskInstanceId: taskInstanceId });\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","metrics","config","readProviderConfigs","InputError","NotFoundError","KeyCloakAdminClient","authenticate","pLimit","readKeycloakRealm","uuidv4","isError"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage-community/plugin-catalog-backend-module-keycloak",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.0",
|
|
4
4
|
"description": "A Backend backend plugin for Keycloak",
|
|
5
5
|
"main": "./dist/index.cjs.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"tsc": "tsc"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@backstage/backend-plugin-api": "^1.
|
|
50
|
-
"@backstage/catalog-model": "^1.
|
|
51
|
-
"@backstage/errors": "^1.
|
|
52
|
-
"@backstage/plugin-catalog-backend-module-logs": "^0.1.
|
|
53
|
-
"@backstage/plugin-catalog-node": "^2.1
|
|
49
|
+
"@backstage/backend-plugin-api": "^1.9.1",
|
|
50
|
+
"@backstage/catalog-model": "^1.9.0",
|
|
51
|
+
"@backstage/errors": "^1.3.1",
|
|
52
|
+
"@backstage/plugin-catalog-backend-module-logs": "^0.1.22",
|
|
53
|
+
"@backstage/plugin-catalog-node": "^2.2.1",
|
|
54
54
|
"@keycloak/keycloak-admin-client": "26.6.3",
|
|
55
55
|
"@opentelemetry/api": "^1.9.0",
|
|
56
56
|
"jsonwebtoken": "^9.0.0",
|
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
"uuid": "^13.0.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@backstage/backend-defaults": "^0.
|
|
63
|
-
"@backstage/backend-test-utils": "^1.11.
|
|
64
|
-
"@backstage/cli": "^0.36.
|
|
65
|
-
"@backstage/config": "^1.3.
|
|
66
|
-
"@backstage/plugin-catalog-backend": "^3.
|
|
62
|
+
"@backstage/backend-defaults": "^0.17.1",
|
|
63
|
+
"@backstage/backend-test-utils": "^1.11.3",
|
|
64
|
+
"@backstage/cli": "^0.36.2",
|
|
65
|
+
"@backstage/config": "^1.3.8",
|
|
66
|
+
"@backstage/plugin-catalog-backend": "^3.7.0",
|
|
67
67
|
"@backstage/types": "^1.2.2",
|
|
68
68
|
"@types/lodash": "4.17.24",
|
|
69
69
|
"deepmerge": "4.3.1",
|