feat: allow overriding default ownership resolving
This allows to modify the ownership resolving in the auth resolve context. For example if user wants to include parent groups also to the ownershipEntityRefs, it's not possible unless the built-in auth providers are forked and rewritten. Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
'@backstage/plugin-auth-node': patch
|
||||
---
|
||||
|
||||
Allow overriding default ownership resolving
|
||||
@@ -3,6 +3,7 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { AuthOwnershipResolver } from '@backstage/plugin-auth-node';
|
||||
import { AuthProviderConfig as AuthProviderConfig_2 } from '@backstage/plugin-auth-node';
|
||||
import { AuthProviderFactory as AuthProviderFactory_2 } from '@backstage/plugin-auth-node';
|
||||
import { AuthProviderRouteHandlers as AuthProviderRouteHandlers_2 } from '@backstage/plugin-auth-node';
|
||||
@@ -193,6 +194,12 @@ export function createOriginFilter(config: Config): (origin: string) => boolean;
|
||||
// @public (undocumented)
|
||||
export function createRouter(options: RouterOptions): Promise<express.Router>;
|
||||
|
||||
// @public
|
||||
export class DefaultAuthOwnershipResolver implements AuthOwnershipResolver {
|
||||
// (undocumented)
|
||||
getOwnershipEntityRefs(entity: Entity): Promise<string[]>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const defaultAuthProviderFactories: {
|
||||
[providerId: string]: AuthProviderFactory_2;
|
||||
@@ -216,9 +223,6 @@ export type GcpIapResult = GcpIapResult_2;
|
||||
// @public @deprecated
|
||||
export type GcpIapTokenInfo = GcpIapTokenInfo_2;
|
||||
|
||||
// @public
|
||||
export function getDefaultOwnershipEntityRefs(entity: Entity): string[];
|
||||
|
||||
// @public (undocumented)
|
||||
export type GithubOAuthResult = {
|
||||
fullProfile: Profile;
|
||||
@@ -668,6 +672,8 @@ export interface RouterOptions {
|
||||
// (undocumented)
|
||||
logger: LoggerService;
|
||||
// (undocumented)
|
||||
ownershipResolver?: AuthOwnershipResolver;
|
||||
// (undocumented)
|
||||
providerFactories?: ProviderFactories;
|
||||
// (undocumented)
|
||||
tokenFactoryAlgorithm?: string;
|
||||
|
||||
@@ -34,4 +34,4 @@ export * from './lib/oauth';
|
||||
|
||||
export * from './lib/catalog';
|
||||
|
||||
export { getDefaultOwnershipEntityRefs } from './lib/resolvers';
|
||||
export { DefaultAuthOwnershipResolver } from './lib/resolvers';
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
DEFAULT_NAMESPACE,
|
||||
Entity,
|
||||
parseEntityRef,
|
||||
RELATION_MEMBER_OF,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ConflictError, InputError, NotFoundError } from '@backstage/errors';
|
||||
@@ -31,31 +30,13 @@ import {
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { TokenIssuer } from '../../identity/types';
|
||||
import { CatalogIdentityClient } from '../catalog';
|
||||
import {
|
||||
AuthOwnershipResolver,
|
||||
AuthResolverCatalogUserQuery,
|
||||
AuthResolverContext,
|
||||
TokenParams,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
|
||||
/**
|
||||
* Uses the default ownership resolution logic to return an array
|
||||
* of entity refs that the provided entity claims ownership through.
|
||||
*
|
||||
* A reference to the entity itself will also be included in the returned array.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function getDefaultOwnershipEntityRefs(entity: Entity) {
|
||||
const membershipRefs =
|
||||
entity.relations
|
||||
?.filter(
|
||||
r => r.type === RELATION_MEMBER_OF && r.targetRef.startsWith('group:'),
|
||||
)
|
||||
.map(r => r.targetRef) ?? [];
|
||||
|
||||
return Array.from(new Set([stringifyEntityRef(entity), ...membershipRefs]));
|
||||
}
|
||||
import { CatalogIdentityClient } from '../catalog';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -69,6 +50,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext {
|
||||
discovery: DiscoveryService;
|
||||
auth: AuthService;
|
||||
httpAuth: HttpAuthService;
|
||||
ownershipResolver: AuthOwnershipResolver;
|
||||
}): CatalogAuthResolverContext {
|
||||
const catalogIdentityClient = new CatalogIdentityClient({
|
||||
catalogApi: options.catalogApi,
|
||||
@@ -84,6 +66,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext {
|
||||
catalogIdentityClient,
|
||||
options.catalogApi,
|
||||
options.auth,
|
||||
options.ownershipResolver,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,6 +76,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext {
|
||||
public readonly catalogIdentityClient: CatalogIdentityClient,
|
||||
private readonly catalogApi: CatalogApi,
|
||||
private readonly auth: AuthService,
|
||||
private readonly ownershipResolver: AuthOwnershipResolver,
|
||||
) {}
|
||||
|
||||
async issueToken(params: TokenParams) {
|
||||
@@ -160,7 +144,9 @@ export class CatalogAuthResolverContext implements AuthResolverContext {
|
||||
|
||||
async signInWithCatalogUser(query: AuthResolverCatalogUserQuery) {
|
||||
const { entity } = await this.findCatalogUser(query);
|
||||
const ownershipRefs = getDefaultOwnershipEntityRefs(entity);
|
||||
const ownershipRefs = await this.ownershipResolver.getOwnershipEntityRefs(
|
||||
entity,
|
||||
);
|
||||
|
||||
const token = await this.tokenIssuer.issueToken({
|
||||
claims: {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 { AuthOwnershipResolver } from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
Entity,
|
||||
RELATION_MEMBER_OF,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
|
||||
/**
|
||||
* Uses the default ownership resolution logic to return an array
|
||||
* of entity refs that the provided entity claims ownership through.
|
||||
*
|
||||
* A reference to the entity itself will also be included in the returned array.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class DefaultAuthOwnershipResolver implements AuthOwnershipResolver {
|
||||
async getOwnershipEntityRefs(entity: Entity): Promise<string[]> {
|
||||
const membershipRefs =
|
||||
entity.relations
|
||||
?.filter(
|
||||
r =>
|
||||
r.type === RELATION_MEMBER_OF && r.targetRef.startsWith('group:'),
|
||||
)
|
||||
.map(r => r.targetRef) ?? [];
|
||||
|
||||
return Array.from(new Set([stringifyEntityRef(entity), ...membershipRefs]));
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
CatalogAuthResolverContext,
|
||||
getDefaultOwnershipEntityRefs,
|
||||
} from './CatalogAuthResolverContext';
|
||||
export { CatalogAuthResolverContext } from './CatalogAuthResolverContext';
|
||||
export { DefaultAuthOwnershipResolver } from './DefaultAuthOwnershipResolver';
|
||||
|
||||
@@ -25,13 +25,17 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
|
||||
import { Config } from '@backstage/config';
|
||||
import { NotFoundError, assertError } from '@backstage/errors';
|
||||
import { AuthProviderFactory } from '@backstage/plugin-auth-node';
|
||||
import { assertError, NotFoundError } from '@backstage/errors';
|
||||
import {
|
||||
AuthOwnershipResolver,
|
||||
AuthProviderFactory,
|
||||
} from '@backstage/plugin-auth-node';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Minimatch } from 'minimatch';
|
||||
import { CatalogAuthResolverContext } from '../lib/resolvers/CatalogAuthResolverContext';
|
||||
import { TokenIssuer } from '../identity/types';
|
||||
import { DefaultAuthOwnershipResolver } from '../lib/resolvers';
|
||||
|
||||
/** @public */
|
||||
export type ProviderFactories = { [s: string]: AuthProviderFactory };
|
||||
@@ -49,6 +53,7 @@ export function bindProviderRouters(
|
||||
httpAuth: HttpAuthService;
|
||||
tokenManager: TokenManager;
|
||||
tokenIssuer: TokenIssuer;
|
||||
ownershipResolver?: AuthOwnershipResolver;
|
||||
catalogApi?: CatalogApi;
|
||||
},
|
||||
) {
|
||||
@@ -64,6 +69,7 @@ export function bindProviderRouters(
|
||||
tokenManager,
|
||||
tokenIssuer,
|
||||
catalogApi,
|
||||
ownershipResolver = new DefaultAuthOwnershipResolver(),
|
||||
} = options;
|
||||
|
||||
const providersConfig = config.getOptionalConfig('auth.providers');
|
||||
@@ -95,6 +101,7 @@ export function bindProviderRouters(
|
||||
discovery,
|
||||
auth,
|
||||
httpAuth,
|
||||
ownershipResolver,
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -23,15 +23,16 @@ import {
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { defaultAuthProviderFactories } from '../providers';
|
||||
import { AuthOwnershipResolver } from '@backstage/plugin-auth-node';
|
||||
import {
|
||||
createLegacyAuthAdapters,
|
||||
PluginDatabaseManager,
|
||||
PluginEndpointDiscovery,
|
||||
TokenManager,
|
||||
createLegacyAuthAdapters,
|
||||
} from '@backstage/backend-common';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { bindOidcRouter, TokenFactory, KeyStores } from '../identity';
|
||||
import { bindOidcRouter, KeyStores, TokenFactory } from '../identity';
|
||||
import session from 'express-session';
|
||||
import connectSessionKnex from 'connect-session-knex';
|
||||
import passport from 'passport';
|
||||
@@ -41,7 +42,7 @@ import { TokenIssuer } from '../identity/types';
|
||||
import { StaticTokenIssuer } from '../identity/StaticTokenIssuer';
|
||||
import { StaticKeyStore } from '../identity/StaticKeyStore';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ProviderFactories, bindProviderRouters } from '../providers/router';
|
||||
import { bindProviderRouters, ProviderFactories } from '../providers/router';
|
||||
|
||||
/** @public */
|
||||
export interface RouterOptions {
|
||||
@@ -56,6 +57,7 @@ export interface RouterOptions {
|
||||
providerFactories?: ProviderFactories;
|
||||
disableDefaultProviderFactories?: boolean;
|
||||
catalogApi?: CatalogApi;
|
||||
ownershipResolver?: AuthOwnershipResolver;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
||||
@@ -21,6 +21,12 @@ import { Strategy } from 'passport';
|
||||
import { ZodSchema } from 'zod';
|
||||
import { ZodTypeDef } from 'zod';
|
||||
|
||||
// @public
|
||||
export interface AuthOwnershipResolver {
|
||||
// (undocumented)
|
||||
getOwnershipEntityRefs(entity: Entity): Promise<string[]>;
|
||||
}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type AuthProviderConfig = {
|
||||
baseUrl: string;
|
||||
|
||||
@@ -43,5 +43,6 @@ export type {
|
||||
SignInInfo,
|
||||
SignInResolver,
|
||||
TokenParams,
|
||||
AuthOwnershipResolver,
|
||||
} from './types';
|
||||
export { tokenTypes } from './types';
|
||||
|
||||
@@ -163,6 +163,15 @@ export type AuthResolverContext = {
|
||||
): Promise<BackstageSignInResult>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolver interface for resolving the ownership entity references for entity
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface AuthOwnershipResolver {
|
||||
getOwnershipEntityRefs(entity: Entity): Promise<string[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any Auth provider needs to implement this interface which handles the routes in the
|
||||
* auth backend. Any auth API requests from the frontend reaches these methods.
|
||||
|
||||
Reference in New Issue
Block a user