diff --git a/packages/cedar_ffi/build.dart b/packages/cedar_ffi/build.dart index 4b451348..6addb1c3 100644 --- a/packages/cedar_ffi/build.dart +++ b/packages/cedar_ffi/build.dart @@ -10,7 +10,8 @@ final IOSink buildLogs = () { Platform.script.resolve('.dart_tool/build.log'), ); logsFile.createSync(recursive: true); - return logsFile.openWrite(mode: FileMode.write); + return logsFile.openWrite(mode: FileMode.write) + ..writeln('Starting build: ${DateTime.now()}'); }(); void main(List args) async { @@ -18,7 +19,10 @@ void main(List args) async { await build(args, (config, output) async { buildLogs.writeln(config.toString()); - output.addDependency(config.packageRoot.resolve('src/')); + output.addDependencies([ + config.packageRoot.resolve('build.dart'), + config.packageRoot.resolve('src/'), + ]); // Build the Rust code in `src/` to `target/`. // @@ -39,9 +43,27 @@ void main(List args) async { if (!File.fromUri(binaryOut).existsSync()) { throw Exception('$binaryOut does not exist'); } + if (config.targetOS == OS.windows) { + // Workaround for https://github.com/dart-lang/sdk/issues/55207 + // + // Bundle a second asset which can resolve symbols from a previously + // loaded DLL. This allows having a fallback mechanism, since you + // cannot add duplicate assets. + // + // This only matters in release mode, but since `config.buildMode` is + // always set to `release` in Dart, it's no good. + final loadedAsset = NativeCodeAsset( + package: packageName, + name: 'src/ffi/cedar_bindings.loaded.ffi.dart', + linkMode: LookupInProcess(), + os: config.targetOS, + architecture: config.targetArchitecture, + ); + output.addAsset(loadedAsset); + } final nativeAsset = NativeCodeAsset( package: packageName, - name: 'src/ffi/cedar_bindings.g.dart', + name: 'src/ffi/cedar_bindings.bundled.ffi.dart', linkMode: DynamicLoadingBundled(), os: config.targetOS, architecture: config.targetArchitecture, diff --git a/packages/cedar_ffi/ffigen.bundled.yaml b/packages/cedar_ffi/ffigen.bundled.yaml new file mode 100644 index 00000000..e73a0ba8 --- /dev/null +++ b/packages/cedar_ffi/ffigen.bundled.yaml @@ -0,0 +1,28 @@ +name: CedarFfiBundled +description: C bindings to the Cedar policy engine (bundled native asset) +ffi-native: +language: c +headers: + entry-points: + - "src/include/bindings.h" +compiler-opts: + # Suppress nullability warnings on macOS + - "-Wno-nullability-completeness" + # Ignore warnings about availability macro + - "-Wno-availability" +output: + bindings: "lib/src/ffi/cedar_bindings.bundled.ffi.dart" +comments: + style: any + length: full +exclude-all-by-default: true +import: + symbol-files: + - 'package:cedar_ffi/src/ffi/symbols.yaml' +functions: + include: + - "cedar_.*" + leaf: + # All C APIs are leaf functions (e.g. they do not call into Dart) + include: + - ".*" diff --git a/packages/cedar_ffi/ffigen.loaded.yaml b/packages/cedar_ffi/ffigen.loaded.yaml new file mode 100644 index 00000000..399e089b --- /dev/null +++ b/packages/cedar_ffi/ffigen.loaded.yaml @@ -0,0 +1,31 @@ +name: CedarFfiLoaded +description: C bindings to the Cedar policy engine (process native asset) +ffi-native: +language: c +headers: + entry-points: + - "src/include/bindings.h" +compiler-opts: + # Suppress nullability warnings on macOS + - "-Wno-nullability-completeness" + # Ignore warnings about availability macro + - "-Wno-availability" +output: + bindings: "lib/src/ffi/cedar_bindings.loaded.ffi.dart" +comments: + style: any + length: full +exclude-all-by-default: true +import: + symbol-files: + - 'package:cedar_ffi/src/ffi/symbols.yaml' +functions: + include: + - "cedar_.*" + expose-typedefs: + include: + - "cedar_.*" + leaf: + # All C APIs are leaf functions (e.g. they do not call into Dart) + include: + - ".*" diff --git a/packages/cedar_ffi/ffigen.symbols.yaml b/packages/cedar_ffi/ffigen.symbols.yaml new file mode 100644 index 00000000..323739b1 --- /dev/null +++ b/packages/cedar_ffi/ffigen.symbols.yaml @@ -0,0 +1,27 @@ +name: CedarFfiSymbols +description: C bindings to the Cedar policy engine (symbols-only) +ffi-native: +language: c +headers: + entry-points: + - "src/include/bindings.h" +compiler-opts: + # Suppress nullability warnings on macOS + - "-Wno-nullability-completeness" + # Ignore warnings about availability macro + - "-Wno-availability" +output: + bindings: "lib/src/ffi/cedar_bindings.symbols.ffi.dart" + symbol-file: + output: 'package:cedar_ffi/src/ffi/symbols.yaml' + import-path: 'package:cedar_ffi/src/ffi/cedar_bindings.symbols.ffi.dart' +comments: + style: any + length: full +exclude-all-by-default: true +structs: + include: + - "CCedar.*" + - "CedarStore" + - "CInitResult" + - "CAuthorizationDecision" \ No newline at end of file diff --git a/packages/cedar_ffi/lib/src/cedar_policy_set_ffi.dart b/packages/cedar_ffi/lib/src/cedar_policy_set_ffi.dart index 89f73eba..f3345134 100644 --- a/packages/cedar_ffi/lib/src/cedar_policy_set_ffi.dart +++ b/packages/cedar_ffi/lib/src/cedar_policy_set_ffi.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'dart:ffi'; import 'package:cedar/cedar.dart'; -import 'package:cedar_ffi/src/ffi/cedar_bindings.g.dart' as bindings; +import 'package:cedar_ffi/src/ffi/cedar_bindings.dart'; import 'package:ffi/ffi.dart'; /// An FFI extension of [CedarPolicySet]. @@ -21,7 +21,7 @@ Map> parsePolicies(String policiesIdl) { policiesIdl.toNativeUtf8(allocator: arena).cast(), ); switch (cPolicies) { - case bindings.CCedarPolicySetResult(:final errors, :final errors_len) + case CCedarPolicySetResult(:final errors, :final errors_len) when errors_len > 0: final errorStrings = []; for (var i = 0; i < errors_len; i++) { @@ -32,7 +32,7 @@ Map> parsePolicies(String policiesIdl) { '${errorStrings.join(', ')}', policiesIdl, ); - case bindings.CCedarPolicySetResult( + case CCedarPolicySetResult( :final policies, :final policy_ids, :final policies_len, diff --git a/packages/cedar_ffi/lib/src/engine/cedar_engine.dart b/packages/cedar_ffi/lib/src/engine/cedar_engine.dart index 94a56ee0..593d2212 100644 --- a/packages/cedar_ffi/lib/src/engine/cedar_engine.dart +++ b/packages/cedar_ffi/lib/src/engine/cedar_engine.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'dart:ffi'; import 'package:cedar/cedar.dart'; -import 'package:cedar_ffi/src/ffi/cedar_bindings.g.dart' as bindings; +import 'package:cedar_ffi/src/ffi/cedar_bindings.dart'; import 'package:ffi/ffi.dart'; import 'package:meta/meta.dart'; @@ -24,7 +24,7 @@ final class CedarEngine implements CedarAuthorizer, Finalizable { @visibleForTesting bool validate = true, }) { final storeRef = using((arena) { - final config = arena(); + final config = arena(); config.ref ..schema_json = jsonEncode(schema.toJson()).toNativeUtf8(allocator: arena).cast() @@ -61,16 +61,16 @@ final class CedarEngine implements CedarAuthorizer, Finalizable { } CedarEngine._({ - required Pointer ref, + required Pointer ref, }) : _ref = ref; - static final Finalizer> _finalizer = Finalizer( + static final Finalizer> _finalizer = Finalizer( bindings.cedar_deinit, ); var _closed = false; - final Pointer _ref; + final Pointer _ref; @override CedarAuthorizationResponse isAuthorized( @@ -82,7 +82,7 @@ final class CedarEngine implements CedarAuthorizer, Finalizable { throw StateError('Cedar engine is closed'); } return using((arena) { - final query = arena(); + final query = arena(); query.ref ..principal_str = switch (request.principal) { final principal? => principal.normalized @@ -122,13 +122,13 @@ final class CedarEngine implements CedarAuthorizer, Finalizable { }; final cDecision = bindings.cedar_is_authorized(_ref, query); return switch (cDecision) { - bindings.CAuthorizationDecision(:final completion_error) + CAuthorizationDecision(:final completion_error) when completion_error != nullptr => throw Exception( 'Error performing authorization: ' '${completion_error.cast().toDartString()}', ), - bindings.CAuthorizationDecision( + CAuthorizationDecision( :final is_authorized, :final reasons, :final reasons_len, diff --git a/packages/cedar_ffi/lib/src/ffi/cedar_bindings.bundled.ffi.dart b/packages/cedar_ffi/lib/src/ffi/cedar_bindings.bundled.ffi.dart new file mode 100644 index 00000000..1b0da877 --- /dev/null +++ b/packages/cedar_ffi/lib/src/ffi/cedar_bindings.bundled.ffi.dart @@ -0,0 +1,55 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; +import 'package:cedar_ffi/src/ffi/cedar_bindings.symbols.ffi.dart' as imp1; + +/// Parses a policy set from a Cedar policy string into JSON. +@ffi.Native)>( + symbol: 'cedar_parse_policy_set', isLeaf: true) +external imp1.CCedarPolicySetResult cedar_parse_policy_set( + ffi.Pointer policies, +); + +/// Links a policy template to a set of entities. +/// +/// Returns the linked policy template in JSON format. +@ffi.Native< + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer)>( + symbol: 'cedar_link_policy_template', isLeaf: true) +external ffi.Pointer cedar_link_policy_template( + ffi.Pointer policy_template_json, + ffi.Pointer entities_json, +); + +/// Initializes the Cedar policy engine with the given configuration. +/// +/// This must be called exactly once before any other Cedar functions are called. +@ffi.Native)>( + symbol: 'cedar_init', isLeaf: true) +external imp1.CInitResult cedar_init( + ffi.Pointer config, +); + +/// De-initializes the Cedar policy engine. +/// +/// This must be called exactly once when the Cedar policy engine is no longer needed. +@ffi.Native)>( + symbol: 'cedar_deinit', isLeaf: true) +external void cedar_deinit( + ffi.Pointer store, +); + +/// Performs a Cedar authorization check. +/// +/// This must be called after [cedar_init] has been called. +@ffi.Native< + imp1.CAuthorizationDecision Function( + ffi.Pointer, ffi.Pointer)>( + symbol: 'cedar_is_authorized', isLeaf: true) +external imp1.CAuthorizationDecision cedar_is_authorized( + ffi.Pointer store, + ffi.Pointer query, +); diff --git a/packages/cedar_ffi/lib/src/ffi/cedar_bindings.dart b/packages/cedar_ffi/lib/src/ffi/cedar_bindings.dart new file mode 100644 index 00000000..352ef30d --- /dev/null +++ b/packages/cedar_ffi/lib/src/ffi/cedar_bindings.dart @@ -0,0 +1,47 @@ +// ignore_for_file: non_constant_identifier_names + +import 'dart:ffi'; + +import 'package:cedar_ffi/src/ffi/cedar_bindings.bundled.ffi.dart' as bundled; +import 'package:cedar_ffi/src/ffi/cedar_bindings.loaded.ffi.dart' as loaded; + +export 'package:cedar_ffi/src/ffi/cedar_bindings.symbols.ffi.dart'; + +final bindings = CedarBindings._(); + +enum _LinkMode { bundled, loaded } + +final class CedarBindings { + CedarBindings._() { + try { + Native.addressOf>( + loaded.cedar_init); + _linkMode = _LinkMode.loaded; + } on Object { + Native.addressOf>( + bundled.cedar_init); + _linkMode = _LinkMode.bundled; + } + } + + late final _LinkMode _linkMode; + + late final cedar_init = + _linkMode == _LinkMode.loaded ? loaded.cedar_init : bundled.cedar_init; + + late final cedar_deinit = _linkMode == _LinkMode.loaded + ? loaded.cedar_deinit + : bundled.cedar_deinit; + + late final cedar_is_authorized = _linkMode == _LinkMode.loaded + ? loaded.cedar_is_authorized + : bundled.cedar_is_authorized; + + late final cedar_link_policy_template = _linkMode == _LinkMode.loaded + ? loaded.cedar_link_policy_template + : bundled.cedar_link_policy_template; + + late final cedar_parse_policy_set = _linkMode == _LinkMode.loaded + ? loaded.cedar_parse_policy_set + : bundled.cedar_parse_policy_set; +} diff --git a/packages/cedar_ffi/lib/src/ffi/cedar_bindings.loaded.ffi.dart b/packages/cedar_ffi/lib/src/ffi/cedar_bindings.loaded.ffi.dart new file mode 100644 index 00000000..0badf333 --- /dev/null +++ b/packages/cedar_ffi/lib/src/ffi/cedar_bindings.loaded.ffi.dart @@ -0,0 +1,71 @@ +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'package:cedar_ffi/src/ffi/cedar_bindings.symbols.ffi.dart' as imp1; +import 'dart:ffi' as ffi; + +/// Parses a policy set from a Cedar policy string into JSON. +@ffi.Native( + symbol: 'cedar_parse_policy_set', isLeaf: true) +external imp1.CCedarPolicySetResult cedar_parse_policy_set( + ffi.Pointer policies, +); + +/// Links a policy template to a set of entities. +/// +/// Returns the linked policy template in JSON format. +@ffi.Native( + symbol: 'cedar_link_policy_template', isLeaf: true) +external ffi.Pointer cedar_link_policy_template( + ffi.Pointer policy_template_json, + ffi.Pointer entities_json, +); + +/// Initializes the Cedar policy engine with the given configuration. +/// +/// This must be called exactly once before any other Cedar functions are called. +@ffi.Native(symbol: 'cedar_init', isLeaf: true) +external imp1.CInitResult cedar_init( + ffi.Pointer config, +); + +/// De-initializes the Cedar policy engine. +/// +/// This must be called exactly once when the Cedar policy engine is no longer needed. +@ffi.Native(symbol: 'cedar_deinit', isLeaf: true) +external void cedar_deinit( + ffi.Pointer store, +); + +/// Performs a Cedar authorization check. +/// +/// This must be called after [cedar_init] has been called. +@ffi.Native( + symbol: 'cedar_is_authorized', isLeaf: true) +external imp1.CAuthorizationDecision cedar_is_authorized( + ffi.Pointer store, + ffi.Pointer query, +); + +typedef NativeCedar_parse_policy_set = imp1.CCedarPolicySetResult Function( + ffi.Pointer policies); +typedef DartCedar_parse_policy_set = imp1.CCedarPolicySetResult Function( + ffi.Pointer policies); +typedef NativeCedar_link_policy_template = ffi.Pointer Function( + ffi.Pointer policy_template_json, + ffi.Pointer entities_json); +typedef DartCedar_link_policy_template = ffi.Pointer Function( + ffi.Pointer policy_template_json, + ffi.Pointer entities_json); +typedef NativeCedar_init = imp1.CInitResult Function( + ffi.Pointer config); +typedef DartCedar_init = imp1.CInitResult Function( + ffi.Pointer config); +typedef NativeCedar_deinit = ffi.Void Function( + ffi.Pointer store); +typedef DartCedar_deinit = void Function(ffi.Pointer store); +typedef NativeCedar_is_authorized = imp1.CAuthorizationDecision Function( + ffi.Pointer store, ffi.Pointer query); +typedef DartCedar_is_authorized = imp1.CAuthorizationDecision Function( + ffi.Pointer store, ffi.Pointer query); diff --git a/packages/cedar_ffi/lib/src/ffi/cedar_bindings.g.dart b/packages/cedar_ffi/lib/src/ffi/cedar_bindings.symbols.ffi.dart similarity index 73% rename from packages/cedar_ffi/lib/src/ffi/cedar_bindings.g.dart rename to packages/cedar_ffi/lib/src/ffi/cedar_bindings.symbols.ffi.dart index 332c51c5..6c95c202 100644 --- a/packages/cedar_ffi/lib/src/ffi/cedar_bindings.g.dart +++ b/packages/cedar_ffi/lib/src/ffi/cedar_bindings.symbols.ffi.dart @@ -4,51 +4,7 @@ // ignore_for_file: type=lint import 'dart:ffi' as ffi; -/// Parses a policy set from a Cedar policy string into JSON. -@ffi.Native)>( - symbol: 'cedar_parse_policy_set', isLeaf: true) -external CCedarPolicySetResult cedar_parse_policy_set( - ffi.Pointer policies, -); - -/// Links a policy template to a set of entities -@ffi.Native< - ffi.Pointer Function( - ffi.Pointer, ffi.Pointer)>( - symbol: 'cedar_link_policy_template', isLeaf: true) -external ffi.Pointer cedar_link_policy_template( - ffi.Pointer policy_template_json, - ffi.Pointer entities_json, -); - -/// Initializes the Cedar policy engine with the given configuration. -/// -/// This must be called exactly once before any other Cedar functions are called. -@ffi.Native)>( - symbol: 'cedar_init', isLeaf: true) -external CInitResult cedar_init( - ffi.Pointer config, -); - -/// De-initializes the Cedar policy engine. -/// -/// This must be called exactly once when the Cedar policy engine is no longer needed. -@ffi.Native)>( - symbol: 'cedar_deinit', isLeaf: true) -external void cedar_deinit( - ffi.Pointer store, -); - -/// Performs a Cedar authorization check. -/// -/// This must be called after [cedar_init] has been called. -@ffi.Native< - CAuthorizationDecision Function(ffi.Pointer, - ffi.Pointer)>(symbol: 'cedar_is_authorized', isLeaf: true) -external CAuthorizationDecision cedar_is_authorized( - ffi.Pointer store, - ffi.Pointer query, -); +final class CedarStore extends ffi.Opaque {} /// The result of parsing policies from a Cedar policy string into JSON /// via [cedar_parse_policies]. @@ -85,6 +41,17 @@ final class CCedarPolicySetResult extends ffi.Struct { external ffi.Pointer> errors; } +/// The result of initializing the Cedar policy engine via [cedar_init]. +final class CInitResult extends ffi.Struct { + /// Whether the operation succeeded. + external ffi.Pointer store; + + /// The error message, if any. + /// + /// Can be `null` to indicate no error. + external ffi.Pointer error; +} + final class CCedarConfig extends ffi.Struct { /// The Cedar schema, in JSON format. /// @@ -113,51 +80,6 @@ final class CCedarConfig extends ffi.Struct { external ffi.Pointer log_level; } -final class CCedarQuery extends ffi.Struct { - /// The principal to check authorization for, in entity UID format. - /// - /// Can be `null` to indicate an anonymous principal. - external ffi.Pointer principal_str; - - /// The resource to check authorization for, in entity UID format. - /// - /// Can be `null` to indicate an anonymous resource. - external ffi.Pointer resource_str; - - /// The action to check authorization for, in entity UID format. - /// - /// Can be `null` to indicate an anonymous action. - external ffi.Pointer action_str; - - /// The check's context, if any, in JSON format. - /// - /// Can be `null` to indicate no context. - external ffi.Pointer context_json; - - /// The Cedar entities, in JSON format. - /// - /// Can be `null` to use the existing entities. - external ffi.Pointer entities_json; - - /// The Cedar policies, in JSON format. - /// - /// Can be `null` to use the existing policies. - external ffi.Pointer policies_json; -} - -/// The result of initializing the Cedar policy engine via [cedar_init]. -final class CInitResult extends ffi.Struct { - /// Whether the operation succeeded. - external ffi.Pointer store; - - /// The error message, if any. - /// - /// Can be `null` to indicate no error. - external ffi.Pointer error; -} - -final class CedarStore extends ffi.Opaque {} - final class CAuthorizationDecision extends ffi.Struct { /// Whether the request is authorized. @ffi.Bool() @@ -191,3 +113,35 @@ final class CAuthorizationDecision extends ffi.Struct { @ffi.UintPtr() external int errors_len; } + +final class CCedarQuery extends ffi.Struct { + /// The principal to check authorization for, in entity UID format. + /// + /// Can be `null` to indicate an anonymous principal. + external ffi.Pointer principal_str; + + /// The resource to check authorization for, in entity UID format. + /// + /// Can be `null` to indicate an anonymous resource. + external ffi.Pointer resource_str; + + /// The action to check authorization for, in entity UID format. + /// + /// Can be `null` to indicate an anonymous action. + external ffi.Pointer action_str; + + /// The check's context, if any, in JSON format. + /// + /// Can be `null` to indicate no context. + external ffi.Pointer context_json; + + /// The Cedar entities, in JSON format. + /// + /// Can be `null` to use the existing entities. + external ffi.Pointer entities_json; + + /// The Cedar policies, in JSON format. + /// + /// Can be `null` to use the existing policies. + external ffi.Pointer policies_json; +} diff --git a/packages/cedar_ffi/lib/src/ffi/symbols.yaml b/packages/cedar_ffi/lib/src/ffi/symbols.yaml new file mode 100644 index 00000000..4c5ef743 --- /dev/null +++ b/packages/cedar_ffi/lib/src/ffi/symbols.yaml @@ -0,0 +1,18 @@ +format_version: 1.0.0 +files: + package:cedar_ffi/src/ffi/cedar_bindings.symbols.ffi.dart: + used-config: + ffi-native: false + symbols: + c:@S@CAuthorizationDecision: + name: CAuthorizationDecision + c:@S@CCedarConfig: + name: CCedarConfig + c:@S@CCedarPolicySetResult: + name: CCedarPolicySetResult + c:@S@CCedarQuery: + name: CCedarQuery + c:@S@CInitResult: + name: CInitResult + c:@S@CedarStore: + name: CedarStore diff --git a/packages/cedar_ffi/pubspec.yaml b/packages/cedar_ffi/pubspec.yaml index bd175532..51e68f2a 100644 --- a/packages/cedar_ffi/pubspec.yaml +++ b/packages/cedar_ffi/pubspec.yaml @@ -16,8 +16,8 @@ dependencies: json_annotation: ^4.8.1 logging: ^1.2.0 meta: ^1.12.0 - native_assets_cli: ">=0.4.2 <0.6.0" - native_toolchain_c: ">=0.3.4+1 <0.5.0" + native_assets_cli: ^0.5.0 + native_toolchain_c: ^0.4.0 dev_dependencies: build_runner: ^2.4.8 @@ -27,33 +27,3 @@ dev_dependencies: lints: ^3.0.0 path: ^1.8.3 test: ^1.21.0 - -ffigen: - name: CedarFfi - description: C bindings to the Cedar policy engine - ffi-native: - language: c - headers: - entry-points: - - "src/include/bindings.h" - compiler-opts: - # Suppress nullability warnings on macOS - - "-Wno-nullability-completeness" - # Ignore warnings about availability macro - - "-Wno-availability" - output: - bindings: "lib/src/ffi/cedar_bindings.g.dart" - comments: - style: any - length: full - exclude-all-by-default: true - functions: - include: - - "cedar_.*" - leaf: - # All C APIs are leaf functions (e.g. they do not call into Dart) - include: - - ".*" - structs: - include: - - "CCedar.*"