@@ -185,7 +185,7 @@ export const sanitizeSlug = (slug: string) =>
185185 . replace ( / - / g, "_" )
186186 . replace ( / [ ^ a - z 0 - 9 _ ] / gi, "" ) ;
187187
188- class Yates {
188+ export class Yates {
189189 private databaseScope : string | null = null ;
190190
191191 constructor ( private prisma : PrismaClient ) { }
@@ -229,6 +229,8 @@ class Yates {
229229
230230 const currentDatabase = result [ 0 ] ?. current_database ;
231231
232+ debug ( "Current database for Yates:" , currentDatabase ) ;
233+
232234 if ( ! currentDatabase ) {
233235 throw new Error (
234236 "Failed to determine the current database for scoping Yates roles." ,
@@ -668,14 +670,7 @@ class Yates {
668670 } ) => {
669671 await this . ensureDatabaseScope ( ) ;
670672
671- // See https://github.com/prisma/prisma/discussions/14777
672- // We are reaching into the prisma internals to get the data model.
673- // This is a bit sketchy, but we can get the internal type definition from the runtime library
674- // and there is even a test case in prisma that checks that this value is exported
675- // See https://github.com/prisma/prisma/blob/5.1.0/packages/client/tests/functional/extensions/pdp.ts#L51
676- // This is a private API, so not much we can do about the cast
677- const runtimeDataModel = ( this . prisma as any )
678- . _runtimeDataModel as RuntimeDataModel ;
673+ const runtimeDataModel = this . inspectRunTimeDataModel ( ) ;
679674 const models = Object . keys ( runtimeDataModel . models ) . map (
680675 ( m ) => runtimeDataModel . models [ m ] . dbName || m ,
681676 ) as Models [ ] ;
@@ -903,6 +898,73 @@ class Yates {
903898 }
904899 }
905900 } ;
901+
902+ inspectDBRoles = async ( role : string ) => {
903+ await this . ensureDatabaseScope ( ) ;
904+ const hashedRoleName = this . createRoleName ( role ) ;
905+
906+ // Load all policies for the role
907+ const roles = await this . prisma . $queryRawUnsafe <
908+ {
909+ tablename : string ;
910+ policyname : string ;
911+ cmd : string ;
912+ policy_roles : string [ ] ;
913+ matched_role : string [ ] ;
914+ } [ ]
915+ > ( `
916+ WITH RECURSIVE role_tree AS(
917+ --Start from your role
918+ SELECT
919+ r.oid,
920+ r.rolname
921+ FROM pg_roles r
922+ WHERE r.rolname = '${ hashedRoleName } '
923+
924+ UNION
925+
926+ --Walk "upwards": all parent roles granted to it
927+ SELECT
928+ parent.oid,
929+ parent.rolname
930+ FROM pg_auth_members m
931+ JOIN role_tree rt
932+ ON m.member = rt.oid
933+ JOIN pg_roles parent
934+ ON parent.oid = m.roleid
935+ )
936+ SELECT
937+ p.tablename,
938+ p.policyname,
939+ p.cmd,
940+ p.roles:: text[] AS policy_roles,
941+ rt.rolname AS matched_role
942+ FROM pg_policies p
943+ JOIN role_tree rt
944+ ON(
945+ p.roles IS NULL
946+ OR array_length(p.roles, 1) = 0
947+ OR rt.rolname = ANY(p.roles:: text[])
948+ )
949+ WHERE p.schemaname = 'public'
950+ ORDER BY p.policyname, matched_role;
951+ ` ) ;
952+
953+ return roles ;
954+ } ;
955+
956+ inspectRunTimeDataModel = ( ) : RuntimeDataModel => {
957+ // See https://github.com/prisma/prisma/discussions/14777
958+ // We are reaching into the prisma internals to get the data model.
959+ // This is a bit sketchy, but we can get the internal type definition from the runtime library
960+ // and there is even a test case in prisma that checks that this value is exported
961+ // See https://github.com/prisma/prisma/blob/5.1.0/packages/client/tests/functional/extensions/pdp.ts#L51
962+ // This is a private API, so not much we can do about the cast
963+ const runtimeDataModel = ( this . prisma as any )
964+ . _runtimeDataModel as RuntimeDataModel ;
965+
966+ return runtimeDataModel ;
967+ } ;
906968}
907969
908970/**
0 commit comments