diff --git a/.changeset/eleven-pens-collect.md b/.changeset/eleven-pens-collect.md new file mode 100644 index 0000000000..0df1bb0400 --- /dev/null +++ b/.changeset/eleven-pens-collect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Corrected replacements for depreciated FilteredEntityLayout items diff --git a/.changeset/empty-pens-invent.md b/.changeset/empty-pens-invent.md new file mode 100644 index 0000000000..d6937afc84 --- /dev/null +++ b/.changeset/empty-pens-invent.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend': minor +--- + +**BREAKING**: The `authorization` property is no longer returned on search results when queried. Note: this will only result in a breaking change if you have custom code in your frontend that relies on the `authorization.resourceRef` property on documents. diff --git a/.changeset/fair-lamps-leave.md b/.changeset/fair-lamps-leave.md new file mode 100644 index 0000000000..9e902b51f2 --- /dev/null +++ b/.changeset/fair-lamps-leave.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-user-settings': patch +--- + +Wired up the OneLogin provider to be visible in the Settings UI when configured correctly. + +Previously it wasn't visible at all. diff --git a/.changeset/fast-cheetahs-grow.md b/.changeset/fast-cheetahs-grow.md new file mode 100644 index 0000000000..5567436b5e --- /dev/null +++ b/.changeset/fast-cheetahs-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-node': minor +--- + +**BREAKING**: Removed the deprecated `id` and `entity` fields from `BackstageSignInResult`. diff --git a/.changeset/happy-mugs-camp.md b/.changeset/happy-mugs-camp.md new file mode 100644 index 0000000000..22d13d80d1 --- /dev/null +++ b/.changeset/happy-mugs-camp.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-search-common': patch +--- + +- Introduce `SearchDocument` type. This type contains the subset of `IndexableDocument` properties relevant to the frontend, and is intended to be used for documents returned to the frontend from the search API. +- `SearchResultSet` is now a wrapper for documents of type `SearchDocument`, and is intended to be used in the frontend. This isn't a breaking change, since `IndexableDocument`s are valid `SearchDocument`s, so the old and new types are compatible. +- Introduce `IndexableResultSet` type, which wraps `IndexableDocument` instances in the same way as `SearchResultSet`. diff --git a/.changeset/khaki-pears-march.md b/.changeset/khaki-pears-march.md new file mode 100644 index 0000000000..5626bf3ea5 --- /dev/null +++ b/.changeset/khaki-pears-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +**BREAKING**: All sign-in resolvers must now return a `token` in their sign-in result. Returning an `id` is no longer supported. diff --git a/.changeset/light-drinks-rule.md b/.changeset/light-drinks-rule.md new file mode 100644 index 0000000000..2b99f5988c --- /dev/null +++ b/.changeset/light-drinks-rule.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-search-backend': patch +'@backstage/plugin-search-backend-node': patch +'@backstage/plugin-search-backend-module-elasticsearch': patch +'@backstage/plugin-search-backend-module-pg': patch +--- + +Use new `IndexableResultSet` type as return type of query method in `SearchEngine` implementation. diff --git a/.changeset/ninety-fishes-vanish.md b/.changeset/ninety-fishes-vanish.md new file mode 100644 index 0000000000..b0d3ac370f --- /dev/null +++ b/.changeset/ninety-fishes-vanish.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': patch +--- + +Switch to `SearchDocument` type in `DefaultResultListItem` props diff --git a/.changeset/purple-boats-punch.md b/.changeset/purple-boats-punch.md new file mode 100644 index 0000000000..817f0a79bc --- /dev/null +++ b/.changeset/purple-boats-punch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +**DEPRECATION**: The `tokenIssuer` option for `OAuthAdapter` is no longer needed and has been deprecated. diff --git a/.changeset/red-snakes-float.md b/.changeset/red-snakes-float.md new file mode 100644 index 0000000000..f1bd16d84c --- /dev/null +++ b/.changeset/red-snakes-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Add support for string refs to the `EntityRefLinks` component diff --git a/.changeset/silver-pots-call.md b/.changeset/silver-pots-call.md new file mode 100644 index 0000000000..42b3016545 --- /dev/null +++ b/.changeset/silver-pots-call.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +fix support config ref to use backstage/backstage diff --git a/.changeset/wicked-beds-return.md b/.changeset/wicked-beds-return.md new file mode 100644 index 0000000000..add952ba0a --- /dev/null +++ b/.changeset/wicked-beds-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +Fix division by zero in currentToDeclaredResourceToPerc when pod resources weren't set diff --git a/.changeset/wicked-feet-clap.md b/.changeset/wicked-feet-clap.md new file mode 100644 index 0000000000..e27f641820 --- /dev/null +++ b/.changeset/wicked-feet-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Updated the visibility of database connection fields in config to be secret diff --git a/ADOPTERS.md b/ADOPTERS.md index e00031589d..10661f9792 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -106,3 +106,4 @@ _If you're using Backstage in your organization, please try to add your company | [Alliander](https://www.alliander.com/) | [@leon-vg](https://github.com/leon-vg), [@gieljl](https://github.com/gieljl), [@niekteg](https://github.com/niekteg) | Developer portal - software catalog, technical documentation, software templates, tech radar and exploration of used tools/services | | [VIA](https://www.via.com.br) | [@vagnerguedes](https://github.com/vagnerguedes) | Centralized Developer Experience portal - Software catalog and documentation platform, software templates, techdocs, scaffolding, self-service infrastructure | | [Surevine](https://www.surevine.com/) | [@DJDANNY123](https://github.com/djdanny123) | Developer portal for software catalog, discovery and a view of the technologies we are using across the organisation, we are looking to explore how we can enrich our entities in Backstage by integrating a software bill of materials. | +| [Bonial International GmbH](https://www.bonial.com/) | [@pjungermann](https://github.com/pjungermann) | Centralized developer portal with software catalog, tech docs, templates, and more. | diff --git a/contrib/chart/backstage/templates/ingress.yaml b/contrib/chart/backstage/templates/ingress.yaml index cac9b61553..7e008c5d9d 100644 --- a/contrib/chart/backstage/templates/ingress.yaml +++ b/contrib/chart/backstage/templates/ingress.yaml @@ -74,7 +74,7 @@ spec: backend: {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} service: - name: {{ include "frontend.serviceName" . }} + name: {{ include "backend.serviceName" . }} port: number: 80 {{- else -}} diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 9a1cfdf736..0163877a8b 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -364,7 +364,7 @@ spec: secretsKey: USER_OAUTH_TOKEN additionalScopes: github: - - workflow:write + - workflow allowedHosts: - github.com ... diff --git a/microsite/blog/2022-03-17-backstage-1.0.md b/microsite/blog/2022-03-17-backstage-1.0.md index 43034664a3..02ead0390c 100644 --- a/microsite/blog/2022-03-17-backstage-1.0.md +++ b/microsite/blog/2022-03-17-backstage-1.0.md @@ -45,7 +45,7 @@ In terms of maturity and stability, the road ahead for Backstage is bright and b You can read more about Backstage’s roadmap [here](https://backstage.io/docs/overview/roadmap), but in terms of maturity and stability we would like to highlight the following: - CNCF incubation: The Backstage project [recently moved from Sandbox to Incubation](https://www.cncf.io/blog/2022/03/15/backstage-project-joins-the-cncf-incubator/) phase within the [CNCF maturity levels](https://www.cncf.io/projects/#:~:text=Maturity%20levels,should%20be%20adopting%20different%20projects.). -- Security auditing and strategy: With the CNCF’s support,, the project maintainers are working on a security audit initiative to make the platform more secure and will share more details soon. The audit is part of an overall security strategy being developed this quarter. +- Security auditing and strategy: With the CNCF’s support, the project maintainers are working on a security audit initiative to make the platform more secure and will share more details soon. The audit is part of an overall security strategy being developed this quarter. - Paid plugins for advanced and enterprise use cases: Last year, [Spotify announced its work on paid plugins](https://backstage.spotify.com/blog/paid-plugins-announcement/) built on top of the Backstage open source platform. This is another example of the platform’s maturity and Spotify’s continued commitment to the project, with a focus on customers and use cases requiring enterprise-focused support and plugins developed and proven by Spotify. # Some links to peruse diff --git a/packages/app/package.json b/packages/app/package.json index f152df41f9..29e6541df7 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -60,10 +60,10 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "@octokit/rest": "^18.5.3", - "@roadiehq/backstage-plugin-buildkite": "^1.4.0", - "@roadiehq/backstage-plugin-github-insights": "^1.6.0", - "@roadiehq/backstage-plugin-github-pull-requests": "^1.5.0", - "@roadiehq/backstage-plugin-travis-ci": "^1.4.0", + "@roadiehq/backstage-plugin-buildkite": "^2.0.0", + "@roadiehq/backstage-plugin-github-insights": "^2.0.0", + "@roadiehq/backstage-plugin-github-pull-requests": "^2.0.0", + "@roadiehq/backstage-plugin-travis-ci": "^2.0.0", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^17.0.2", diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 0a06c4fd5d..26068c6c8f 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -180,7 +180,7 @@ export const cicdContent = ( - boolean}> + @@ -192,7 +192,7 @@ export const cicdContent = ( - boolean}> + @@ -327,9 +327,7 @@ const overviewContent = ( {cicdCard} - boolean} - > + @@ -349,9 +347,7 @@ const overviewContent = ( - boolean} - > + diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 058805aa62..6c473af3c0 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -73,7 +73,7 @@ export interface Config { client: 'better-sqlite3' | 'sqlite3' | 'pg'; /** * Base database connection string or Knex object - * @secret + * @visibility secret */ connection: string | object; /** Database name prefix override */ @@ -109,7 +109,7 @@ export interface Config { client?: 'better-sqlite3' | 'sqlite3' | 'pg'; /** * Database connection string or Knex object override - * @secret + * @visibility secret */ connection?: string | object; /** @@ -138,7 +138,7 @@ export interface Config { store: 'redis'; /** * A redis connection string in the form `redis://user:pass@host:port`. - * @secret + * @visibility secret */ connection: string; } @@ -146,7 +146,7 @@ export interface Config { store: 'memcache'; /** * A memcache connection string in the form `user:pass@host:port`. - * @secret + * @visibility secret */ connection: string; }; diff --git a/packages/core-components/src/hooks/useSupportConfig.ts b/packages/core-components/src/hooks/useSupportConfig.ts index 546d779e01..e80eb457a1 100644 --- a/packages/core-components/src/hooks/useSupportConfig.ts +++ b/packages/core-components/src/hooks/useSupportConfig.ts @@ -42,7 +42,7 @@ const DEFAULT_SUPPORT_CONFIG: SupportConfig = { { // TODO: Update to dedicated support page on backstage.io/docs title: 'Add `app.support` config key', - url: 'https://github.com/andrewthauer/backstage/blob/master/app-config.yaml', + url: 'https://github.com/backstage/backstage/blob/master/app-config.yaml', }, ], }, diff --git a/packages/integration-react/src/api/ScmAuth.test.ts b/packages/integration-react/src/api/ScmAuth.test.ts index 1ad391d8d2..7e174c4cbf 100644 --- a/packages/integration-react/src/api/ScmAuth.test.ts +++ b/packages/integration-react/src/api/ScmAuth.test.ts @@ -153,11 +153,11 @@ describe('ScmAuth', () => { githubAuth.getCredentials({ url: 'http://example.com', additionalScope: { - customScopes: { github: ['org:read', 'workflow:write'] }, + customScopes: { github: ['org:read', 'workflow'] }, }, }), ).resolves.toMatchObject({ - token: 'repo read:org read:user org:read workflow:write', + token: 'repo read:org read:user org:read workflow', }); const gitlabAuth = ScmAuth.forGitlab(mockAuthApi); diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts index 07c8196dd2..87e6b96745 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts @@ -50,7 +50,6 @@ describe('oauth helpers', () => { email: 'foo@bar.com', }, backstageIdentity: { - id: 'a', token: 'a.b.c', identity: { type: 'user', @@ -110,7 +109,6 @@ describe('oauth helpers', () => { email: 'foo@bar.com', }, backstageIdentity: { - id: 'a', token: 'a.b.c', identity: { type: 'user', @@ -157,7 +155,6 @@ describe('oauth helpers', () => { displayName: "Adam l'Hôpital", }, backstageIdentity: { - id: 'a', token: 'a.b.c', identity: { type: 'user', diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index c1130b4270..0ce9dff66c 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -17,7 +17,7 @@ import express from 'express'; import { THOUSAND_DAYS_MS, TEN_MINUTES_MS, OAuthAdapter } from './OAuthAdapter'; import { encodeState } from './helpers'; -import { OAuthHandlers, OAuthResponse, OAuthState } from './types'; +import { OAuthHandlers, OAuthState } from './types'; const mockResponseData = { providerInfo: { @@ -30,18 +30,11 @@ const mockResponseData = { email: 'foo@bar.com', }, backstageIdentity: { - id: 'foo', token: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', }, }; -function mkTokenBody(payload: unknown): string { - return Buffer.from(JSON.stringify(payload), 'utf8') - .toString('base64') - .replace(/=/g, ''); -} - describe('OAuthAdapter', () => { class MyAuthProvider implements OAuthHandlers { async start() { @@ -324,13 +317,11 @@ describe('OAuthAdapter', () => { expect(mockResponse.json).toHaveBeenCalledWith({ ...mockResponseData, backstageIdentity: { - id: mockResponseData.backstageIdentity.id, token: mockResponseData.backstageIdentity.token, - idToken: mockResponseData.backstageIdentity.token, identity: { - ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', userEntityRef: 'user:default/jimmymarkum', + ownershipEntityRefs: ['user:default/jimmymarkum'], }, }, }); @@ -356,95 +347,6 @@ describe('OAuthAdapter', () => { ); }); - it('correctly populates incomplete identities', async () => { - const mockRefresh = jest.fn< - Promise<{ response: OAuthResponse }>, - [express.Request] - >(); - - const oauthProvider = new OAuthAdapter( - { - refresh: mockRefresh, - start: jest.fn(), - handler: jest.fn(), - } as OAuthHandlers, - { - ...oAuthProviderOptions, - tokenIssuer: { - issueToken: async ({ claims }) => `a.${mkTokenBody(claims)}.a`, - listPublicKeys: async () => ({ keys: [] }), - }, - disableRefresh: false, - isOriginAllowed: () => false, - }, - ); - - const mockRequest = { - header: () => 'XMLHttpRequest', - cookies: { - 'test-provider-refresh-token': 'token', - }, - query: {}, - } as unknown as express.Request; - - const mockResponse = { - json: jest.fn().mockReturnThis(), - status: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - // Without a token - mockRefresh.mockResolvedValueOnce({ - response: { - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: '', - }, - }, - }); - await oauthProvider.refresh(mockRequest, mockResponse); - expect(mockResponse.json).toHaveBeenCalledTimes(1); - expect(mockResponse.json).toHaveBeenLastCalledWith({ - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: `a.${mkTokenBody({ sub: 'user:default/foo' })}.a`, - idToken: `a.${mkTokenBody({ sub: 'user:default/foo' })}.a`, - identity: { - type: 'user', - userEntityRef: 'user:default/foo', - ownershipEntityRefs: [], - }, - }, - }); - - // With a token - mockRefresh.mockResolvedValueOnce({ - response: { - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, - }, - }, - }); - await oauthProvider.refresh(mockRequest, mockResponse); - expect(mockResponse.json).toHaveBeenCalledTimes(2); - expect(mockResponse.json).toHaveBeenLastCalledWith({ - ...mockResponseData, - backstageIdentity: { - id: 'foo', - token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, - idToken: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`, - identity: { - type: 'user', - userEntityRef: 'user:my-ns/foo', - ownershipEntityRefs: [], - }, - }, - }); - }); - it('sets the correct cookie configuration using a callbackUrl', async () => { const config = { baseUrl: 'http://domain.org/auth', diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 07611ddb9c..637dc1cff2 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -17,11 +17,6 @@ import express, { CookieOptions } from 'express'; import crypto from 'crypto'; import { URL } from 'url'; -import { - DEFAULT_NAMESPACE, - parseEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; import { BackstageIdentityResponse, BackstageSignInResult, @@ -58,7 +53,8 @@ export type Options = { cookieDomain: string; cookiePath: string; appOrigin: string; - tokenIssuer: TokenIssuer; + /** @deprecated This option is no longer needed */ + tokenIssuer?: TokenIssuer; isOriginAllowed: (origin: string) => boolean; callbackUrl: string; }; @@ -263,22 +259,11 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { if (!identity) { return undefined; } - - if (identity.token) { - return prepareBackstageIdentityResponse(identity); + if (!identity.token) { + throw new InputError(`Identity response must return a token`); } - const userEntityRef = stringifyEntityRef( - parseEntityRef(identity.id, { - defaultKind: 'user', - defaultNamespace: DEFAULT_NAMESPACE, - }), - ); - const token = await this.options.tokenIssuer.issueToken({ - claims: { sub: userEntityRef }, - }); - - return prepareBackstageIdentityResponse({ ...identity, token }); + return prepareBackstageIdentityResponse(identity); } private setNonceCookie = (res: express.Response, nonce: string) => { diff --git a/plugins/auth-backend/src/providers/aws-alb/provider.test.ts b/plugins/auth-backend/src/providers/aws-alb/provider.test.ts index 37b5bc1f68..eb801fdd46 100644 --- a/plugins/auth-backend/src/providers/aws-alb/provider.test.ts +++ b/plugins/auth-backend/src/providers/aws-alb/provider.test.ts @@ -123,9 +123,8 @@ describe('AwsAlbAuthProvider', () => { }), signInResolver: async () => { return { - id: 'user.name', token: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', }; }, }); @@ -136,11 +135,8 @@ describe('AwsAlbAuthProvider', () => { expect(mockResponse.json).toHaveBeenCalledWith({ backstageIdentity: { - id: 'user.name', token: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', - idToken: - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob', + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', identity: { ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', diff --git a/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts b/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts index aab0103140..eb2f5c478d 100644 --- a/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts +++ b/plugins/auth-backend/src/providers/gcp-iap/provider.test.ts @@ -45,7 +45,7 @@ describe('GcpIapProvider', () => { const iapToken = { sub: 's', email: 'e@mail.com' }; authHandler.mockResolvedValueOnce({ email: 'e@mail.com' }); - signInResolver.mockResolvedValueOnce({ id: 'i', token: backstageToken }); + signInResolver.mockResolvedValueOnce({ token: backstageToken }); tokenValidator.mockResolvedValueOnce(iapToken); const app = express(); @@ -61,8 +61,6 @@ describe('GcpIapProvider', () => { ); expect(response.body).toEqual({ backstageIdentity: { - id: 'i', - idToken: backstageToken, token: backstageToken, identity: { type: 'user', diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts b/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts index 048a3b9344..61f08bc535 100644 --- a/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts +++ b/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts @@ -43,7 +43,7 @@ import { describe('Oauth2ProxyAuthProvider', () => { const mockToken = - 'eyblob.eyJzdWIiOiJqaW1teW1hcmt1bSIsImVudCI6WyJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iXX0=.eyblob'; + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob'; let provider: Oauth2ProxyAuthProvider; let logger: jest.Mocked; @@ -122,7 +122,6 @@ describe('Oauth2ProxyAuthProvider', () => { profile: {}, }); signInResolver.mockResolvedValue({ - id: 'some-id', token: mockToken, }); @@ -142,7 +141,6 @@ describe('Oauth2ProxyAuthProvider', () => { const profile = { displayName: 'some value' }; mockRequest.header.mockReturnValue(`Bearer token`); signInResolver.mockResolvedValue({ - id: 'some-id', token: mockToken, }); authHandler.mockResolvedValue({ profile: profile }); @@ -162,12 +160,10 @@ describe('Oauth2ProxyAuthProvider', () => { ); expect(mockResponse.json).toHaveBeenCalledWith({ backstageIdentity: { - id: 'some-id', - idToken: mockToken, identity: { - ownershipEntityRefs: ['user:default/jimmymarkum'], type: 'user', userEntityRef: 'user:default/jimmymarkum', + ownershipEntityRefs: ['user:default/jimmymarkum'], }, token: mockToken, }, @@ -186,7 +182,6 @@ describe('Oauth2ProxyAuthProvider', () => { profile: {}, }); signInResolver.mockResolvedValue({ - id: 'some-id', token: mockToken, }); }); diff --git a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts index 24442379c4..dac06f964e 100644 --- a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts +++ b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.test.ts @@ -27,13 +27,10 @@ describe('prepareBackstageIdentityResponse', () => { const token = mkToken({ sub: 'k:ns/n', ent: ['k:ns/o'] }); expect( prepareBackstageIdentityResponse({ - id: 'x', token, }), ).toEqual({ - id: 'x', token, - idToken: token, identity: { type: 'user', userEntityRef: 'k:ns/n', @@ -41,67 +38,4 @@ describe('prepareBackstageIdentityResponse', () => { }, }); }); - - it('populates incomplete identities', () => { - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'n' }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'user:default/n', - ownershipEntityRefs: [], - }, - }); - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'k:n' }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'k:default/n', - ownershipEntityRefs: [], - }, - }); - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'ns/n' }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'user:ns/n', - ownershipEntityRefs: [], - }, - }); - expect( - prepareBackstageIdentityResponse({ - id: 'x', - token: mkToken({ sub: 'n', ent: ['k:ns/o'] }), - }), - ).toEqual({ - id: 'x', - token: expect.any(String), - idToken: expect.any(String), - identity: { - type: 'user', - userEntityRef: 'user:default/n', - ownershipEntityRefs: ['k:ns/o'], - }, - }); - }); }); diff --git a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts index 5a4e0895d9..761618c225 100644 --- a/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts +++ b/plugins/auth-backend/src/providers/prepareBackstageIdentityResponse.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -import { - DEFAULT_NAMESPACE, - parseEntityRef, - stringifyEntityRef, -} from '@backstage/catalog-model'; import { BackstageIdentityResponse, BackstageSignInResult, @@ -41,21 +36,11 @@ export function prepareBackstageIdentityResponse( ): BackstageIdentityResponse { const { sub, ent } = parseJwtPayload(result.token); - const userEntityRef = stringifyEntityRef( - parseEntityRef(sub, { - defaultKind: 'user', - defaultNamespace: DEFAULT_NAMESPACE, - }), - ); return { - ...{ - // TODO: idToken is for backwards compatibility and can be removed in the future - idToken: result.token, - ...result, - }, + ...result, identity: { type: 'user', - userEntityRef, + userEntityRef: sub, ownershipEntityRefs: ent ?? [], }, }; diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index 7840fc7d74..f7fdd6369b 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -3,7 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { Entity } from '@backstage/catalog-model'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; // @public @@ -13,10 +12,6 @@ export interface BackstageIdentityResponse extends BackstageSignInResult { // @public export interface BackstageSignInResult { - // @deprecated - entity?: Entity; - // @deprecated - id: string; token: string; } diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index fc595a13ba..cb430f2de1 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -24,7 +24,6 @@ }, "dependencies": { "@backstage/backend-common": "^0.13.1", - "@backstage/catalog-model": "^1.0.0", "@backstage/config": "^1.0.0", "@backstage/errors": "^1.0.0", "jose": "^1.27.1", diff --git a/plugins/auth-node/src/IdentityClient.test.ts b/plugins/auth-node/src/IdentityClient.test.ts index 72ef7f2a57..88d4021dc3 100644 --- a/plugins/auth-node/src/IdentityClient.test.ts +++ b/plugins/auth-node/src/IdentityClient.test.ts @@ -135,12 +135,11 @@ describe('IdentityClient', () => { const token = await factory.issueToken({ claims: { sub: 'foo' } }); const response = await client.authenticate(token); expect(response).toEqual({ - id: 'foo', token: token, identity: { - ownershipEntityRefs: [], type: 'user', userEntityRef: 'foo', + ownershipEntityRefs: [], }, }); }); @@ -202,12 +201,11 @@ describe('IdentityClient', () => { const token = await factory.issueToken({ claims: { sub: 'foo' } }); const response = await client.authenticate(token); expect(response).toEqual({ - id: 'foo', token: token, identity: { - ownershipEntityRefs: [], type: 'user', userEntityRef: 'foo', + ownershipEntityRefs: [], }, }); }); diff --git a/plugins/auth-node/src/IdentityClient.ts b/plugins/auth-node/src/IdentityClient.ts index d8e841bf75..54c2670ae8 100644 --- a/plugins/auth-node/src/IdentityClient.ts +++ b/plugins/auth-node/src/IdentityClient.ts @@ -87,7 +87,6 @@ export class IdentityClient { } const user: BackstageIdentityResponse = { - id: decoded.sub, token, identity: { type: 'user', diff --git a/plugins/auth-node/src/types.ts b/plugins/auth-node/src/types.ts index b574c2204d..0d3e015beb 100644 --- a/plugins/auth-node/src/types.ts +++ b/plugins/auth-node/src/types.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; - /** * A representation of a successful Backstage sign-in. * @@ -25,25 +23,6 @@ import { Entity } from '@backstage/catalog-model'; * @public */ export interface BackstageSignInResult { - /** - * An opaque ID that uniquely identifies the user within Backstage. - * - * This is typically the same as the user entity `metadata.name`. - * - * @deprecated Use the `identity` field instead - */ - id: string; - - /** - * The entity that the user is represented by within Backstage. - * - * This entity may or may not exist within the Catalog, and it can be used - * to read and store additional metadata about the user. - * - * @deprecated Use the `identity` field instead. - */ - entity?: Entity; - /** * The token used to authenticate the user within Backstage. */ diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index a1541b555e..b90072beba 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -261,15 +261,11 @@ export type EntityRefLinkProps = { } & Omit; // @public -export const EntityRefLinks: ({ - entityRefs, - defaultKind, - ...linkProps -}: EntityRefLinksProps) => JSX.Element; +export function EntityRefLinks(props: EntityRefLinksProps): JSX.Element; // @public export type EntityRefLinksProps = { - entityRefs: (Entity | CompoundEntityRef)[]; + entityRefs: (string | Entity | CompoundEntityRef)[]; defaultKind?: string; } & Omit; diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index d92cd2ec87..30dda011d4 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -25,7 +25,7 @@ import { LinkProps } from '@backstage/core-components'; * @public */ export type EntityRefLinksProps = { - entityRefs: (Entity | CompoundEntityRef)[]; + entityRefs: (string | Entity | CompoundEntityRef)[]; defaultKind?: string; } & Omit; @@ -34,17 +34,20 @@ export type EntityRefLinksProps = { * * @public */ -export const EntityRefLinks = ({ - entityRefs, - defaultKind, - ...linkProps -}: EntityRefLinksProps) => ( - <> - {entityRefs.map((r, i) => ( - - {i > 0 && ', '} - - - ))} - -); +export function EntityRefLinks(props: EntityRefLinksProps) { + const { entityRefs, defaultKind, ...linkProps } = props; + return ( + <> + {entityRefs.map((r, i) => ( + + {i > 0 && ', '} + + + ))} + + ); +} diff --git a/plugins/catalog/src/components/FilteredEntityLayout/index.ts b/plugins/catalog/src/components/FilteredEntityLayout/index.ts index 892aa34147..a9277a3298 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/index.ts +++ b/plugins/catalog/src/components/FilteredEntityLayout/index.ts @@ -18,7 +18,7 @@ import { CatalogFilterLayout } from '@backstage/plugin-catalog-react'; /** * @public - * @deprecated Use `FilteredCatalogLayout` from `@backstage/plugin-catalog-react` instead. + * @deprecated Use `CatalogFilterLayout` from `@backstage/plugin-catalog-react` instead. */ export const FilteredEntityLayout = CatalogFilterLayout as (props: { children: React.ReactNode; @@ -26,12 +26,12 @@ export const FilteredEntityLayout = CatalogFilterLayout as (props: { /** * @public - * @deprecated Use `FilteredCatalogLayout.Filters` from `@backstage/plugin-catalog-react` instead. + * @deprecated Use `CatalogFilterLayout.Filters` from `@backstage/plugin-catalog-react` instead. */ export const FilterContainer = CatalogFilterLayout.Filters; /** * @public - * @deprecated Use `FilteredCatalogLayout.Content` from `@backstage/plugin-catalog-react` instead. + * @deprecated Use `CatalogFilterLayout.Content` from `@backstage/plugin-catalog-react` instead. */ export const EntityListContainer = CatalogFilterLayout.Content; diff --git a/plugins/kubernetes/src/utils/pod.tsx b/plugins/kubernetes/src/utils/pod.tsx index 50cc983b9d..7bed160a5f 100644 --- a/plugins/kubernetes/src/utils/pod.tsx +++ b/plugins/kubernetes/src/utils/pod.tsx @@ -118,6 +118,8 @@ export const currentToDeclaredResourceToPerc = ( current: number | string, resource: number | string, ): string => { + if (Number(resource) === 0) return `0%`; + if (typeof current === 'number' && typeof resource === 'number') { return `${Math.round((current / resource) * 100)}%`; } diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index 22043d5761..7fac9f7840 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -138,7 +138,7 @@ describe('RepoUrlPicker', () => { 'ui:options': { requestUserCredentials: { secretsKey: 'testKey', - additionalScopes: { github: ['workflow:write'] }, + additionalScopes: { github: ['workflow'] }, }, }, }} @@ -164,7 +164,7 @@ describe('RepoUrlPicker', () => { additionalScope: { repoWrite: true, customScopes: { - github: ['workflow:write'], + github: ['workflow'], }, }, }); diff --git a/plugins/search-backend-module-elasticsearch/api-report.md b/plugins/search-backend-module-elasticsearch/api-report.md index 265fdda686..10231f3a83 100644 --- a/plugins/search-backend-module-elasticsearch/api-report.md +++ b/plugins/search-backend-module-elasticsearch/api-report.md @@ -10,10 +10,10 @@ import { Client } from '@elastic/elasticsearch'; import { Config } from '@backstage/config'; import type { ConnectionOptions } from 'tls'; import { IndexableDocument } from '@backstage/plugin-search-common'; +import { IndexableResultSet } from '@backstage/plugin-search-common'; import { Logger } from 'winston'; import { SearchEngine } from '@backstage/plugin-search-common'; import { SearchQuery } from '@backstage/plugin-search-common'; -import { SearchResultSet } from '@backstage/plugin-search-common'; // Warning: (ae-missing-release-tag) "ElasticSearchClientOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-search-backend-module-elasticsearch" does not have an export "ElasticSearchEngine" @@ -119,7 +119,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { getIndexer(type: string): Promise; newClient(create: (options: ElasticSearchClientOptions) => T): T; // (undocumented) - query(query: SearchQuery): Promise; + query(query: SearchQuery): Promise; // Warning: (ae-forgotten-export) The symbol "ElasticSearchQueryTranslator" needs to be exported by the entry point index.d.ts // // (undocumented) diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index f2ed5ed2b0..aec225a64b 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -21,9 +21,9 @@ import { import { Config } from '@backstage/config'; import { IndexableDocument, + IndexableResultSet, SearchEngine, SearchQuery, - SearchResultSet, } from '@backstage/plugin-search-common'; import { Client } from '@elastic/elasticsearch'; import esb from 'elastic-builder'; @@ -192,7 +192,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { return indexer; } - async query(query: SearchQuery): Promise { + async query(query: SearchQuery): Promise { const { elasticSearchQuery, documentTypes, pageSize } = this.translator(query); const queryIndices = documentTypes diff --git a/plugins/search-backend-module-pg/api-report.md b/plugins/search-backend-module-pg/api-report.md index 39847a4935..71ceac996e 100644 --- a/plugins/search-backend-module-pg/api-report.md +++ b/plugins/search-backend-module-pg/api-report.md @@ -5,11 +5,11 @@ ```ts import { BatchSearchEngineIndexer } from '@backstage/plugin-search-backend-node'; import { IndexableDocument } from '@backstage/plugin-search-common'; +import { IndexableResultSet } from '@backstage/plugin-search-common'; import { Knex } from 'knex'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { SearchEngine } from '@backstage/plugin-search-backend-node'; import { SearchQuery } from '@backstage/plugin-search-common'; -import { SearchResultSet } from '@backstage/plugin-search-common'; // Warning: (ae-missing-release-tag) "ConcretePgSearchQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -88,7 +88,7 @@ export class PgSearchEngine implements SearchEngine { // (undocumented) getIndexer(type: string): Promise; // (undocumented) - query(query: SearchQuery): Promise; + query(query: SearchQuery): Promise; // (undocumented) setTranslator( translator: (query: SearchQuery) => ConcretePgSearchQuery, diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts index 8f75112837..ea363d8ea1 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts @@ -15,7 +15,10 @@ */ import { PluginDatabaseManager } from '@backstage/backend-common'; import { SearchEngine } from '@backstage/plugin-search-backend-node'; -import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common'; +import { + SearchQuery, + IndexableResultSet, +} from '@backstage/plugin-search-common'; import { PgSearchEngineIndexer } from './PgSearchEngineIndexer'; import { DatabaseDocumentStore, @@ -81,7 +84,7 @@ export class PgSearchEngine implements SearchEngine { }); } - async query(query: SearchQuery): Promise { + async query(query: SearchQuery): Promise { const { pgQuery, pageSize } = this.translator(query); const rows = await this.databaseStore.transaction(async tx => diff --git a/plugins/search-backend-node/api-report.md b/plugins/search-backend-node/api-report.md index e4359eafac..ebc49df280 100644 --- a/plugins/search-backend-node/api-report.md +++ b/plugins/search-backend-node/api-report.md @@ -9,13 +9,13 @@ import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { DocumentDecoratorFactory } from '@backstage/plugin-search-common'; import { DocumentTypeInfo } from '@backstage/plugin-search-common'; import { IndexableDocument } from '@backstage/plugin-search-common'; +import { IndexableResultSet } from '@backstage/plugin-search-common'; import { Logger } from 'winston'; import { default as lunr_2 } from 'lunr'; import { QueryTranslator } from '@backstage/plugin-search-common'; import { Readable } from 'stream'; import { SearchEngine } from '@backstage/plugin-search-common'; import { SearchQuery } from '@backstage/plugin-search-common'; -import { SearchResultSet } from '@backstage/plugin-search-common'; import { Transform } from 'stream'; import { Writable } from 'stream'; @@ -87,7 +87,7 @@ export class LunrSearchEngine implements SearchEngine { // (undocumented) protected lunrIndices: Record; // (undocumented) - query(query: SearchQuery): Promise; + query(query: SearchQuery): Promise; // (undocumented) setTranslator(translator: LunrQueryTranslator): void; // (undocumented) diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index b1d695ad1d..09e8729ba8 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -16,8 +16,8 @@ import { IndexableDocument, + IndexableResultSet, SearchQuery, - SearchResultSet, QueryTranslator, SearchEngine, } from '@backstage/plugin-search-common'; @@ -147,7 +147,7 @@ export class LunrSearchEngine implements SearchEngine { return indexer; } - async query(query: SearchQuery): Promise { + async query(query: SearchQuery): Promise { const { lunrQueryBuilder, documentTypes, pageSize } = this.translator( query, ) as ConcreteLunrQuery; @@ -196,8 +196,8 @@ export class LunrSearchEngine implements SearchEngine { ? encodePageCursor({ page: page - 1 }) : undefined; - // Translate results into SearchResultSet - const realResultSet: SearchResultSet = { + // Translate results into IndexableResultSet + const realResultSet: IndexableResultSet = { results: results.slice(offset, offset + pageSize).map(d => { return { type: d.type, document: this.docStore[d.result.ref] }; }), diff --git a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts index 2e65e18a84..2a5fc471f5 100644 --- a/plugins/search-backend/src/service/AuthorizedSearchEngine.ts +++ b/plugins/search-backend/src/service/AuthorizedSearchEngine.ts @@ -25,12 +25,12 @@ import { } from '@backstage/plugin-permission-common'; import { DocumentTypeInfo, + IndexableResult, + IndexableResultSet, QueryRequestOptions, QueryTranslator, SearchEngine, SearchQuery, - SearchResult, - SearchResultSet, } from '@backstage/plugin-search-common'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; @@ -85,7 +85,7 @@ export class AuthorizedSearchEngine implements SearchEngine { async query( query: SearchQuery, options: QueryRequestOptions, - ): Promise { + ): Promise { const queryStartTime = Date.now(); const authorizer = new DataLoader( @@ -144,7 +144,7 @@ export class AuthorizedSearchEngine implements SearchEngine { const { page } = decodePageCursor(query.pageCursor); const targetResults = (page + 1) * this.pageSize; - let filteredResults: SearchResult[] = []; + let filteredResults: IndexableResult[] = []; let nextPageCursor: string | undefined; let latencyBudgetExhausted = false; @@ -183,7 +183,7 @@ export class AuthorizedSearchEngine implements SearchEngine { } private async filterResults( - results: SearchResult[], + results: IndexableResult[], typeDecisions: Record, authorizer: DataLoader, ) { diff --git a/plugins/search-backend/src/service/router.test.ts b/plugins/search-backend/src/service/router.test.ts index bdf46a240b..2efab91256 100644 --- a/plugins/search-backend/src/service/router.test.ts +++ b/plugins/search-backend/src/service/router.test.ts @@ -19,7 +19,6 @@ import { ConfigReader } from '@backstage/config'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { IndexBuilder, - LunrSearchEngine, SearchEngine, } from '@backstage/plugin-search-backend-node'; import express from 'express'; @@ -39,8 +38,19 @@ describe('createRouter', () => { beforeAll(async () => { const logger = getVoidLogger(); - const searchEngine = new LunrSearchEngine({ logger }); - const indexBuilder = new IndexBuilder({ logger, searchEngine }); + mockSearchEngine = { + getIndexer: jest.fn(), + setTranslator: jest.fn(), + query: jest.fn().mockResolvedValue({ + results: [], + nextPageCursor: '', + previousPageCursor: '', + }), + }; + const indexBuilder = new IndexBuilder({ + logger, + searchEngine: mockSearchEngine, + }); const router = await createRouter({ engine: indexBuilder.getSearchEngine(), @@ -56,7 +66,7 @@ describe('createRouter', () => { }); beforeEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); }); describe('GET /query', () => { @@ -101,6 +111,42 @@ describe('createRouter', () => { }); }); + it('removes backend-only properties from search documents', async () => { + mockSearchEngine.query.mockResolvedValue({ + results: [ + { + type: 'software-catalog', + document: { + text: 'foo', + title: 'bar baz', + location: '/catalog/default/component/example', + authorization: { + resourceRef: 'component:default/example', + }, + }, + }, + ], + nextPageCursor: '', + previousPageCursor: '', + }); + + const response = await request(app).get('/query'); + + expect(response.status).toEqual(200); + expect(response.body).toMatchObject({ + results: [ + { + type: 'software-catalog', + document: { + text: 'foo', + title: 'bar baz', + location: '/catalog/default/component/example', + }, + }, + ], + }); + }); + describe('search result filtering', () => { beforeAll(async () => { const logger = getVoidLogger(); diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index 92e4e5526f..ff91465cf4 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -26,6 +26,7 @@ import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-no import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { DocumentTypeInfo, + IndexableResultSet, SearchResultSet, } from '@backstage/plugin-search-common'; import { SearchEngine } from '@backstage/plugin-search-backend-node'; @@ -89,6 +90,17 @@ export async function createRouter( }), }); + const toSearchResults = (resultSet: IndexableResultSet): SearchResultSet => ({ + ...resultSet, + results: resultSet.results.map(result => ({ + ...result, + document: { + ...result.document, + authorization: undefined, + }, + })), + }); + const router = Router(); router.get( '/query', @@ -116,7 +128,7 @@ export async function createRouter( try { const resultSet = await engine?.query(query, { token }); - res.send(filterResultSet(resultSet)); + res.send(filterResultSet(toSearchResults(resultSet))); } catch (err) { throw new Error( `There was a problem performing the search query. ${err}`, diff --git a/plugins/search-common/api-report.md b/plugins/search-common/api-report.md index 8ab25d5e42..bd5a56c5cc 100644 --- a/plugins/search-common/api-report.md +++ b/plugins/search-common/api-report.md @@ -30,14 +30,17 @@ export type DocumentTypeInfo = { }; // @beta -export interface IndexableDocument { +export type IndexableDocument = SearchDocument & { authorization?: { resourceRef: string; }; - location: string; - text: string; - title: string; -} +}; + +// @beta (undocumented) +export type IndexableResult = Result; + +// @beta (undocumented) +export type IndexableResultSet = ResultSet; // @beta export type QueryRequestOptions = { @@ -47,13 +50,38 @@ export type QueryRequestOptions = { // @beta export type QueryTranslator = (query: SearchQuery) => unknown; +// @beta (undocumented) +export interface Result { + // (undocumented) + document: TDocument; + // (undocumented) + type: string; +} + +// @beta (undocumented) +export interface ResultSet { + // (undocumented) + nextPageCursor?: string; + // (undocumented) + previousPageCursor?: string; + // (undocumented) + results: Result[]; +} + +// @beta +export interface SearchDocument { + location: string; + text: string; + title: string; +} + // @beta export interface SearchEngine { getIndexer(type: string): Promise; query( query: SearchQuery, options?: QueryRequestOptions, - ): Promise; + ): Promise; setTranslator(translator: QueryTranslator): void; } @@ -70,20 +98,8 @@ export interface SearchQuery { } // @beta (undocumented) -export interface SearchResult { - // (undocumented) - document: IndexableDocument; - // (undocumented) - type: string; -} +export type SearchResult = Result; // @beta (undocumented) -export interface SearchResultSet { - // (undocumented) - nextPageCursor?: string; - // (undocumented) - previousPageCursor?: string; - // (undocumented) - results: SearchResult[]; -} +export type SearchResultSet = ResultSet; ``` diff --git a/plugins/search-common/src/types.ts b/plugins/search-common/src/types.ts index 51ce45617b..4eedabeb71 100644 --- a/plugins/search-common/src/types.ts +++ b/plugins/search-common/src/types.ts @@ -31,26 +31,45 @@ export interface SearchQuery { /** * @beta */ -export interface SearchResult { +export interface Result { type: string; - document: IndexableDocument; + document: TDocument; } /** * @beta */ -export interface SearchResultSet { - results: SearchResult[]; +export interface ResultSet { + results: Result[]; nextPageCursor?: string; previousPageCursor?: string; } /** - * Base properties that all indexed documents must include, as well as some - * common properties that documents are encouraged to use where appropriate. * @beta */ -export interface IndexableDocument { +export type SearchResult = Result; + +/** + * @beta + */ +export type SearchResultSet = ResultSet; + +/** + * @beta + */ +export type IndexableResult = Result; + +/** + * @beta + */ +export type IndexableResultSet = ResultSet; + +/** + * Base properties that all search documents must include. + * @beta + */ +export interface SearchDocument { /** * The primary name of the document (e.g. name, title, identifier, etc). */ @@ -66,7 +85,16 @@ export interface IndexableDocument { * is clicked). */ location: string; +} +/** + * Properties related to indexing of documents. This type is only useful for + * backends working directly with documents being inserted or retrieved from + * search indexes. When dealing with documents in the frontend, use + * {@link SearchDocument}. + * @beta + */ +export type IndexableDocument = SearchDocument & { /** * Optional authorization information to be used when determining whether this * search result should be visible to a given user. @@ -77,7 +105,7 @@ export interface IndexableDocument { */ resourceRef: string; }; -} +}; /** * Information about a specific document type. Intended to be used in the @@ -178,5 +206,5 @@ export interface SearchEngine { query( query: SearchQuery, options?: QueryRequestOptions, - ): Promise; + ): Promise; } diff --git a/plugins/search/api-report.md b/plugins/search/api-report.md index 0a277dab85..1bdd918802 100644 --- a/plugins/search/api-report.md +++ b/plugins/search/api-report.md @@ -9,13 +9,13 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { AsyncState } from 'react-use/lib/useAsync'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; -import { IndexableDocument } from '@backstage/plugin-search-common'; import { InputBaseProps } from '@material-ui/core'; import { JsonObject } from '@backstage/types'; import { default as React_2 } from 'react'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; +import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchQuery } from '@backstage/plugin-search-common'; import { SearchResult as SearchResult_2 } from '@backstage/plugin-search-common'; import { SearchResultSet } from '@backstage/plugin-search-common'; @@ -31,7 +31,7 @@ export const DefaultResultListItem: ({ }: { icon?: ReactNode; secondaryAction?: ReactNode; - result: IndexableDocument; + result: SearchDocument; lineClamp?: number | undefined; }) => JSX.Element; diff --git a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx index d9fbfe315e..46045f91ee 100644 --- a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx +++ b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx @@ -15,7 +15,7 @@ */ import React, { ReactNode } from 'react'; -import { IndexableDocument } from '@backstage/plugin-search-common'; +import { SearchDocument } from '@backstage/plugin-search-common'; import { ListItem, ListItemIcon, @@ -29,7 +29,7 @@ import TextTruncate from 'react-text-truncate'; type Props = { icon?: ReactNode; secondaryAction?: ReactNode; - result: IndexableDocument; + result: SearchDocument; lineClamp?: number; }; diff --git a/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx b/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx index 7025167658..019967cf55 100644 --- a/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx +++ b/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx @@ -24,6 +24,7 @@ import { microsoftAuthApiRef, bitbucketAuthApiRef, atlassianAuthApiRef, + oneloginAuthApiRef, } from '@backstage/core-plugin-api'; type Props = { @@ -80,6 +81,14 @@ export const DefaultProviderSettings = ({ configuredProviders }: Props) => ( icon={Star} /> )} + {configuredProviders.includes('onelogin') && ( + + )} {configuredProviders.includes('atlassian') && (