fix: review comments and naming

Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi>
This commit is contained in:
Heikki Hellgren
2024-04-02 09:12:29 +03:00
parent b0ae9ccac4
commit 776ec144ed
13 changed files with 115 additions and 97 deletions
+1 -1
View File
@@ -3,4 +3,4 @@
'@backstage/plugin-auth-node': patch
---
Allow overriding default ownership resolving
Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint`
+3 -6
View File
@@ -194,12 +194,6 @@ 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;
@@ -223,6 +217,9 @@ 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;
+25 -30
View File
@@ -19,39 +19,14 @@ import {
createBackendPlugin,
} from '@backstage/backend-plugin-api';
import {
authOwnershipResolutionExtensionPoint,
AuthOwnershipResolver,
AuthProviderFactory,
AuthProviderRegistrationOptions,
AuthProvidersExtensionPoint,
authProvidersExtensionPoint,
} from '@backstage/plugin-auth-node';
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
import { createRouter } from './service/router';
class AuthProvidersExtensionPointImpl implements AuthProvidersExtensionPoint {
#providers = new Map<string, AuthProviderFactory>();
#ownershipResolver?: AuthOwnershipResolver;
registerProvider(options: AuthProviderRegistrationOptions): void {
const { providerId, factory } = options;
if (this.#providers.has(providerId)) {
throw new Error(`Auth provider '${providerId}' was already registered`);
}
this.#providers.set(providerId, factory);
}
setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void {
this.#ownershipResolver = ownershipResolver;
}
get providers() {
return this.#providers;
}
get ownershipResolver() {
return this.#ownershipResolver;
}
}
/**
* Auth plugin
*
@@ -60,8 +35,28 @@ class AuthProvidersExtensionPointImpl implements AuthProvidersExtensionPoint {
export const authPlugin = createBackendPlugin({
pluginId: 'auth',
register(reg) {
const extensionPoint = new AuthProvidersExtensionPointImpl();
reg.registerExtensionPoint(authProvidersExtensionPoint, extensionPoint);
const providers = new Map<string, AuthProviderFactory>();
let ownershipResolver: AuthOwnershipResolver | undefined = undefined;
reg.registerExtensionPoint(authProvidersExtensionPoint, {
registerProvider({ providerId, factory }) {
if (providers.has(providerId)) {
throw new Error(
`Auth provider '${providerId}' was already registered`,
);
}
providers.set(providerId, factory);
},
});
reg.registerExtensionPoint(authOwnershipResolutionExtensionPoint, {
setAuthOwnershipResolver(resolver) {
if (ownershipResolver) {
throw new Error('Auth ownership resolver is already set');
}
ownershipResolver = resolver;
},
});
reg.registerInit({
deps: {
@@ -95,9 +90,9 @@ export const authPlugin = createBackendPlugin({
auth,
httpAuth,
catalogApi,
providerFactories: Object.fromEntries(extensionPoint.providers),
providerFactories: Object.fromEntries(providers),
disableDefaultProviderFactories: true,
ownershipResolver: extensionPoint.ownershipResolver,
ownershipResolver,
});
httpRouter.addAuthPolicy({
path: '/',
+1 -1
View File
@@ -34,4 +34,4 @@ export * from './lib/oauth';
export * from './lib/catalog';
export { DefaultAuthOwnershipResolver } from './lib/resolvers';
export { getDefaultOwnershipEntityRefs } from './lib/resolvers';
@@ -20,6 +20,7 @@ import {
DEFAULT_NAMESPACE,
Entity,
parseEntityRef,
RELATION_MEMBER_OF,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { ConflictError, InputError, NotFoundError } from '@backstage/errors';
@@ -38,6 +39,25 @@ import {
} from '@backstage/plugin-auth-node';
import { CatalogIdentityClient } from '../catalog';
/**
* 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]));
}
/**
* @internal
*/
@@ -50,7 +70,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext {
discovery: DiscoveryService;
auth: AuthService;
httpAuth: HttpAuthService;
ownershipResolver: AuthOwnershipResolver;
ownershipResolver?: AuthOwnershipResolver;
}): CatalogAuthResolverContext {
const catalogIdentityClient = new CatalogIdentityClient({
catalogApi: options.catalogApi,
@@ -76,7 +96,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext {
public readonly catalogIdentityClient: CatalogIdentityClient,
private readonly catalogApi: CatalogApi,
private readonly auth: AuthService,
private readonly ownershipResolver: AuthOwnershipResolver,
private readonly ownershipResolver?: AuthOwnershipResolver,
) {}
async issueToken(params: TokenParams) {
@@ -144,14 +164,19 @@ export class CatalogAuthResolverContext implements AuthResolverContext {
async signInWithCatalogUser(query: AuthResolverCatalogUserQuery) {
const { entity } = await this.findCatalogUser(query);
const ownershipRefs = await this.ownershipResolver.getOwnershipEntityRefs(
entity,
);
let ent: string[];
if (this.ownershipResolver) {
const { ownershipEntityRefs } =
await this.ownershipResolver.resolveOwnershipEntityRefs(entity);
ent = ownershipEntityRefs;
} else {
ent = getDefaultOwnershipEntityRefs(entity);
}
const token = await this.tokenIssuer.issueToken({
claims: {
sub: stringifyEntityRef(entity),
ent: ownershipRefs,
ent,
},
});
return { token };
@@ -1,43 +0,0 @@
/*
* 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,5 +14,7 @@
* limitations under the License.
*/
export { CatalogAuthResolverContext } from './CatalogAuthResolverContext';
export { DefaultAuthOwnershipResolver } from './DefaultAuthOwnershipResolver';
export {
CatalogAuthResolverContext,
getDefaultOwnershipEntityRefs,
} from './CatalogAuthResolverContext';
+1 -2
View File
@@ -35,7 +35,6 @@ 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 };
@@ -69,7 +68,7 @@ export function bindProviderRouters(
tokenManager,
tokenIssuer,
catalogApi,
ownershipResolver = new DefaultAuthOwnershipResolver(),
ownershipResolver,
} = options;
const providersConfig = config.getOptionalConfig('auth.providers');
+12 -3
View File
@@ -21,10 +21,21 @@ import { Strategy } from 'passport';
import { ZodSchema } from 'zod';
import { ZodTypeDef } from 'zod';
// @public (undocumented)
export interface AuthOwnershipResolutionExtensionPoint {
// (undocumented)
setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void;
}
// @public (undocumented)
export const authOwnershipResolutionExtensionPoint: ExtensionPoint<AuthOwnershipResolutionExtensionPoint>;
// @public
export interface AuthOwnershipResolver {
// (undocumented)
getOwnershipEntityRefs(entity: Entity): Promise<string[]>;
resolveOwnershipEntityRefs(entity: Entity): Promise<{
ownershipEntityRefs: string[];
}>;
}
// @public @deprecated (undocumented)
@@ -68,8 +79,6 @@ export interface AuthProviderRouteHandlers {
export interface AuthProvidersExtensionPoint {
// (undocumented)
registerProvider(options: AuthProviderRegistrationOptions): void;
// (undocumented)
setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void;
}
// @public (undocumented)
@@ -0,0 +1,28 @@
/*
* Copyright 2023 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 '../types';
import { createExtensionPoint } from '@backstage/backend-plugin-api';
/** @public */
export interface AuthOwnershipResolutionExtensionPoint {
setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void;
}
/** @public */
export const authOwnershipResolutionExtensionPoint =
createExtensionPoint<AuthOwnershipResolutionExtensionPoint>({
id: 'auth.ownershipResolution',
});
@@ -15,7 +15,7 @@
*/
import { createExtensionPoint } from '@backstage/backend-plugin-api';
import { AuthOwnershipResolver, AuthProviderFactory } from '../types';
import { AuthProviderFactory } from '../types';
/** @public */
export interface AuthProviderRegistrationOptions {
@@ -26,7 +26,6 @@ export interface AuthProviderRegistrationOptions {
/** @public */
export interface AuthProvidersExtensionPoint {
registerProvider(options: AuthProviderRegistrationOptions): void;
setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void;
}
/** @public */
@@ -19,3 +19,8 @@ export {
type AuthProviderRegistrationOptions,
type AuthProvidersExtensionPoint,
} from './AuthProvidersExtensionPoint';
export {
authOwnershipResolutionExtensionPoint,
type AuthOwnershipResolutionExtensionPoint,
} from './AuthOwnershipResolutionExtensionPoint';
+3 -1
View File
@@ -169,7 +169,9 @@ export type AuthResolverContext = {
* @public
*/
export interface AuthOwnershipResolver {
getOwnershipEntityRefs(entity: Entity): Promise<string[]>;
resolveOwnershipEntityRefs(
entity: Entity,
): Promise<{ ownershipEntityRefs: string[] }>;
}
/**