diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index e4a3d3d6de..52780c6729 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -42,6 +42,7 @@ import { ServiceFactoryOrFunction } from '@backstage/backend-plugin-api'; import { TokenManagerService } from '@backstage/backend-plugin-api'; import { transport } from 'winston'; import { UrlReader } from '@backstage/backend-common'; +import { UserInfoService } from '@backstage/backend-plugin-api'; // @public (undocumented) export const authServiceFactory: () => ServiceFactory; @@ -333,6 +334,12 @@ export const tokenManagerServiceFactory: () => ServiceFactory< // @public (undocumented) export const urlReaderServiceFactory: () => ServiceFactory; +// @public (undocumented) +export const userInfoServiceFactory: () => ServiceFactory< + UserInfoService, + 'plugin' +>; + // @public export class WinstonLogger implements RootLoggerService { // (undocumented) diff --git a/packages/backend-app-api/src/services/implementations/index.ts b/packages/backend-app-api/src/services/implementations/index.ts index e038a13224..a1114ab3e3 100644 --- a/packages/backend-app-api/src/services/implementations/index.ts +++ b/packages/backend-app-api/src/services/implementations/index.ts @@ -31,3 +31,4 @@ export * from './rootLogger'; export * from './scheduler'; export * from './tokenManager'; export * from './urlReader'; +export * from './userInfo'; diff --git a/packages/backend-app-api/src/services/implementations/userInfo/index.ts b/packages/backend-app-api/src/services/implementations/userInfo/index.ts new file mode 100644 index 0000000000..13b42f053c --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/userInfo/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { userInfoServiceFactory } from './userInfoServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/userInfo/userInfoServiceFactory.ts b/packages/backend-app-api/src/services/implementations/userInfo/userInfoServiceFactory.ts new file mode 100644 index 0000000000..a177f2442b --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/userInfo/userInfoServiceFactory.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BackstageUserCredentials, + UserInfoService, + BackstageUserInfo, + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { toInternalBackstageCredentials } from '../auth/authServiceFactory'; +import { decodeJwt } from 'jose'; + +// TODO: The intention is for this to eventually be replaced by a call to the auth-backend +export class DefaultUserInfoService implements UserInfoService { + async getUserInfo( + credentials: BackstageUserCredentials, + ): Promise { + const internalCredentials = toInternalBackstageCredentials(credentials); + if (internalCredentials.type !== 'user') { + throw new Error('Only user credentials are supported'); + } + const { sub: userEntityRef, ent: ownershipEntityRefs = [] } = decodeJwt( + internalCredentials.token, + ); + + if (typeof userEntityRef !== 'string') { + throw new Error('User entity ref must be a string'); + } + if (!Array.isArray(ownershipEntityRefs)) { + throw new Error('Ownership entity refs must be an array'); + } + + return { userEntityRef, ownershipEntityRefs }; + } +} + +/** @public */ +export const userInfoServiceFactory = createServiceFactory({ + service: coreServices.userInfo, + deps: {}, + async factory() { + return new DefaultUserInfoService(); + }, +}); diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 68dff04e48..d49eefd8f2 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -121,6 +121,14 @@ export type BackstageUserCredentials = { userEntityRef: string; }; +// @public (undocumented) +export interface BackstageUserInfo { + // (undocumented) + ownershipEntityRefs: string[]; + // (undocumented) + userEntityRef: string; +} + // @public export interface CacheService { delete(key: string): Promise; @@ -146,6 +154,7 @@ export type CacheServiceSetOptions = { // @public export namespace coreServices { const auth: ServiceRef; + const userInfo: ServiceRef; const cache: ServiceRef; const rootConfig: ServiceRef; const database: ServiceRef; @@ -524,4 +533,12 @@ export interface UrlReaderService { readUrl(url: string, options?: ReadUrlOptions): Promise; search(url: string, options?: SearchOptions): Promise; } + +// @public (undocumented) +export interface UserInfoService { + // (undocumented) + getUserInfo( + credentials: BackstageUserCredentials, + ): Promise; +} ``` diff --git a/packages/backend-plugin-api/src/services/definitions/UserInfoService.ts b/packages/backend-plugin-api/src/services/definitions/UserInfoService.ts new file mode 100644 index 0000000000..b789116ffb --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/UserInfoService.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BackstageUserCredentials } from './AuthService'; + +/** @public */ +export interface BackstageUserInfo { + userEntityRef: string; + ownershipEntityRefs: string[]; +} + +/** @public */ +export interface UserInfoService { + getUserInfo( + credentials: BackstageUserCredentials, + ): Promise; +} diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index c9cadd84c8..a7c9d09445 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -31,6 +31,17 @@ export namespace coreServices { id: 'core.auth', }); + /** + * The service reference for the plugin scoped {@link UserInfoService}. + * + * @public + */ + export const userInfo = createServiceRef< + import('./UserInfoService').UserInfoService + >({ + id: 'core.userInfo', + }); + /** * The service reference for the plugin scoped {@link CacheService}. * diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index 843269c3ee..294bed1eb1 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -62,4 +62,5 @@ export type { SearchResponseFile, UrlReaderService, } from './UrlReaderService'; +export type { BackstageUserInfo, UserInfoService } from './UserInfoService'; export type { IdentityService } from './IdentityService';