From 5d2245e478ce0092ef7d5c0c343a71e69c3c75e0 Mon Sep 17 00:00:00 2001 From: gkc Date: Mon, 9 Dec 2024 15:49:43 +0000 Subject: [PATCH 1/7] feat: new at_policy package --- .github/workflows/at_libraries.yaml | 1 + packages/at_policy/.gitignore | 7 + packages/at_policy/CHANGELOG.md | 3 + packages/at_policy/LICENSE | 29 +++ packages/at_policy/README.md | 25 +++ packages/at_policy/analysis_options.yaml | 19 ++ packages/at_policy/example/.gitignore | 3 + packages/at_policy/example/CHANGELOG.md | 3 + packages/at_policy/example/README.md | 23 +++ .../at_policy/example/analysis_options.yaml | 19 ++ packages/at_policy/example/bin/client.dart | 59 ++++++ packages/at_policy/example/bin/common.dart | 7 + packages/at_policy/example/bin/policy.dart | 58 ++++++ packages/at_policy/example/bin/service.dart | 97 +++++++++ packages/at_policy/example/pubspec.yaml | 18 ++ packages/at_policy/lib/at_policy.dart | 5 + packages/at_policy/lib/src/policy/impl.dart | 168 ++++++++++++++++ .../at_policy/lib/src/policy/interfaces.dart | 65 ++++++ packages/at_policy/lib/src/policy/models.dart | 190 ++++++++++++++++++ .../at_policy/lib/src/policy/models.g.dart | 128 ++++++++++++ packages/at_policy/lib/src/version.dart | 2 + packages/at_policy/pubspec.yaml | 31 +++ packages/at_policy/test/at_policy_test.dart | 153 ++++++++++++++ 23 files changed, 1113 insertions(+) create mode 100644 packages/at_policy/.gitignore create mode 100644 packages/at_policy/CHANGELOG.md create mode 100644 packages/at_policy/LICENSE create mode 100644 packages/at_policy/README.md create mode 100644 packages/at_policy/analysis_options.yaml create mode 100644 packages/at_policy/example/.gitignore create mode 100644 packages/at_policy/example/CHANGELOG.md create mode 100644 packages/at_policy/example/README.md create mode 100644 packages/at_policy/example/analysis_options.yaml create mode 100644 packages/at_policy/example/bin/client.dart create mode 100644 packages/at_policy/example/bin/common.dart create mode 100644 packages/at_policy/example/bin/policy.dart create mode 100644 packages/at_policy/example/bin/service.dart create mode 100644 packages/at_policy/example/pubspec.yaml create mode 100644 packages/at_policy/lib/at_policy.dart create mode 100644 packages/at_policy/lib/src/policy/impl.dart create mode 100644 packages/at_policy/lib/src/policy/interfaces.dart create mode 100644 packages/at_policy/lib/src/policy/models.dart create mode 100644 packages/at_policy/lib/src/policy/models.g.dart create mode 100644 packages/at_policy/lib/src/version.dart create mode 100644 packages/at_policy/pubspec.yaml create mode 100644 packages/at_policy/test/at_policy_test.dart diff --git a/.github/workflows/at_libraries.yaml b/.github/workflows/at_libraries.yaml index ed174d1c..095aef86 100644 --- a/.github/workflows/at_libraries.yaml +++ b/.github/workflows/at_libraries.yaml @@ -66,6 +66,7 @@ jobs: - at_commons - at_utils - at_cli_commons + - at_policy steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/packages/at_policy/.gitignore b/packages/at_policy/.gitignore new file mode 100644 index 00000000..3cceda55 --- /dev/null +++ b/packages/at_policy/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/at_policy/CHANGELOG.md b/packages/at_policy/CHANGELOG.md new file mode 100644 index 00000000..effe43c8 --- /dev/null +++ b/packages/at_policy/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/at_policy/LICENSE b/packages/at_policy/LICENSE new file mode 100644 index 00000000..caf63d72 --- /dev/null +++ b/packages/at_policy/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2020, The @ Foundation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/at_policy/README.md b/packages/at_policy/README.md new file mode 100644 index 00000000..e65bd423 --- /dev/null +++ b/packages/at_policy/README.md @@ -0,0 +1,25 @@ +The Atsign FoundationThe Atsign Foundation + +[![pub package](https://img.shields.io/pub/v/at_policy)](https://pub.dev/packages/at_policy) +[![pub points](https://img.shields.io/pub/points/at_policy?logo=dart)](https://pub.dev/packages/at_onboarding_cli/score) +[![gitHub license](https://img.shields.io/badge/license-BSD3-blue.svg)](./LICENSE) + +# at_policy + +## Introduction +The at_policy libraries provide generic scaffolding for building policy +management services which policy enforcement endpoints communicate with via +atProtocol and therefore get all the benefits of using atProtocol - outbound +communication only, end-to-end encryption, using atSigns rather than IP +addresses + +## Usage +See full worked examples in the [example](example/README.md) directory. + +## Open source usage and contributions + +This is freely licensed open source code, so feel free to use it as is, suggest +changes or enhancements or create your own version. +See [CONTRIBUTING.md](../../CONTRIBUTING.md) for +detailed guidance on how to set up tools, tests and make a pull request.on. +See CONTRIBUTING.md for detailed guidance on how to set up tools, tests and make a pull request. \ No newline at end of file diff --git a/packages/at_policy/analysis_options.yaml b/packages/at_policy/analysis_options.yaml new file mode 100644 index 00000000..30e7e470 --- /dev/null +++ b/packages/at_policy/analysis_options.yaml @@ -0,0 +1,19 @@ +# Defines a default set of lint rules enforced for +# projects at Google. For details and rationale, +# see https://pub.dev/packages/lints. +include: package:lints/recommended.yaml + +# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. +# Uncomment to specify additional rules. +linter: + rules: + annotate_overrides: true + prefer_final_fields: true + implicit_call_tearoffs: true + camel_case_types : true + unnecessary_string_interpolations : true + unnecessary_brace_in_string_interps: true + await_only_futures : true + unawaited_futures: true + depend_on_referenced_packages : false + avoid_function_literals_in_foreach_calls: true diff --git a/packages/at_policy/example/.gitignore b/packages/at_policy/example/.gitignore new file mode 100644 index 00000000..3a857904 --- /dev/null +++ b/packages/at_policy/example/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/packages/at_policy/example/CHANGELOG.md b/packages/at_policy/example/CHANGELOG.md new file mode 100644 index 00000000..effe43c8 --- /dev/null +++ b/packages/at_policy/example/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/at_policy/example/README.md b/packages/at_policy/example/README.md new file mode 100644 index 00000000..a03c2197 --- /dev/null +++ b/packages/at_policy/example/README.md @@ -0,0 +1,23 @@ +# at_policy examples + +## Overview +- Imagine a client which is making a request to some service; it can make + three types of request + - `getPublicInfo` + - `getProtectedInfo` + - `getConfidentialInfo` +- The service needs to make a policy decision for each request - should it + respond with the info, or respond with a "not permitted" error? It + asks `policy` for information about how it should respond + +## Programmes +- `client.dart` + - sends requests to `service` +- `service.dart` + - listens for requests from `client` and determines client intent + - sends message to `policy` requesting info about what policy decision it + should make regarding the client's intent +- `policy.dart` + - listens for requests from `service` with policy intents + - responds with the info that `service` requires in order to be able to + make a policy decision diff --git a/packages/at_policy/example/analysis_options.yaml b/packages/at_policy/example/analysis_options.yaml new file mode 100644 index 00000000..30e7e470 --- /dev/null +++ b/packages/at_policy/example/analysis_options.yaml @@ -0,0 +1,19 @@ +# Defines a default set of lint rules enforced for +# projects at Google. For details and rationale, +# see https://pub.dev/packages/lints. +include: package:lints/recommended.yaml + +# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. +# Uncomment to specify additional rules. +linter: + rules: + annotate_overrides: true + prefer_final_fields: true + implicit_call_tearoffs: true + camel_case_types : true + unnecessary_string_interpolations : true + unnecessary_brace_in_string_interps: true + await_only_futures : true + unawaited_futures: true + depend_on_referenced_packages : false + avoid_function_literals_in_foreach_calls: true diff --git a/packages/at_policy/example/bin/client.dart b/packages/at_policy/example/bin/client.dart new file mode 100644 index 00000000..30816a6f --- /dev/null +++ b/packages/at_policy/example/bin/client.dart @@ -0,0 +1,59 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:at_cli_commons/at_cli_commons.dart'; +import 'package:at_client/at_client.dart'; +import 'package:chalkdart/chalk.dart'; +import 'common.dart'; + +void main(List args) async { + try { + var argsParser = CLIBase.argsParser + ..addOption('service-atsign', + mandatory: true, + help: "the atSign of the service this client is using"); + + var serviceAtsign = argsParser.parse(args)['service-atsign']; + + var atClient = (await CLIBase.fromCommandLineArgs(args)).atClient; + + var rpc = AtRpcClient( + atClient: atClient, + baseNameSpace: atClient.getPreferences()!.namespace!, + domainNameSpace: policyRequestNamespace, + serverAtsign: serviceAtsign); + + stdout.writeln('Can make three types of request.'); + for (final rt in RequestType.values) { + stdout.writeln(' ${rt.index + 1}. ${rt.name}'); + } + stdout.writeln(); + while (true) { + stdout.write(chalk.brightBlue('Enter request type number: ')); + int reqType = int.parse(stdin.readLineSync()!.trim()); + if (reqType < 1 || reqType > RequestType.values.length) { + stdout.writeln(chalk.red('Invalid request type')); + continue; + } + + try { + + + var response = await rpc + .call({'reqType': RequestType.values[reqType - 1].name}) + .timeout(Duration(seconds: 15)); + + + stdout.writeln(chalk.green(response)); + } on TimeoutException { + stderr.writeln(chalk.brightRed('timed out waiting for response')); + } catch (e) { + stderr.writeln(chalk.brightRed(e)); + } + } + + } catch (e) { + print(e); + print(CLIBase.argsParser.usage); + } +} diff --git a/packages/at_policy/example/bin/common.dart b/packages/at_policy/example/bin/common.dart new file mode 100644 index 00000000..4d663507 --- /dev/null +++ b/packages/at_policy/example/bin/common.dart @@ -0,0 +1,7 @@ +const String policyRequestNamespace = 'at_policy_example'; + +enum RequestType { + getPublicInfo, + getProtectedInfo, + getConfidentialInfo, +} diff --git a/packages/at_policy/example/bin/policy.dart b/packages/at_policy/example/bin/policy.dart new file mode 100644 index 00000000..c9333972 --- /dev/null +++ b/packages/at_policy/example/bin/policy.dart @@ -0,0 +1,58 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:at_cli_commons/at_cli_commons.dart'; +import 'package:at_policy/at_policy.dart'; +import 'package:chalkdart/chalk.dart'; + +void main(List args) async { + try { + var atClient = (await CLIBase.fromCommandLineArgs(args)).atClient; + + PolicyService ps = PolicyService( + baseNamespace: atClient.getPreferences()!.namespace!, + loggingAtsign: atClient.getCurrentAtSign()!, + allowList: {}, + allowAll: true, + atClient: atClient, + handler: DemoPolicyRequestHandler(), + ); + + await ps.run(); + } catch (e) { + print(e); + print(CLIBase.argsParser.usage); + } +} + +class DemoPolicyRequestHandler implements PolicyRequestHandler { + @override + Future getPolicyDetails( + PolicyRequest req) async { + + stdout.writeln(chalk.blue('Received request $req')); + + stdout.write('(A)pprove or (D)eny? : '); + String decision = ''; + while (decision.isEmpty) { + decision = stdin.readLineSync()!; + } + final bool approved = decision.toLowerCase().startsWith('a'); + + if (approved) { + List details = []; + for (var i in req.intents) { + details.add(PolicyDetail( + intent: i.intent, + info: {'authorized': true}, + )); + } + return PolicyResponse( + message: 'manually approved', + policyDetails: details, + ); + } else { + return PolicyResponse(message: 'nope', policyDetails: []); + } + } +} diff --git a/packages/at_policy/example/bin/service.dart b/packages/at_policy/example/bin/service.dart new file mode 100644 index 00000000..50fc3453 --- /dev/null +++ b/packages/at_policy/example/bin/service.dart @@ -0,0 +1,97 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:at_cli_commons/at_cli_commons.dart'; +import 'package:at_client/at_client.dart'; +import 'package:at_policy/at_policy.dart'; +import 'package:chalkdart/chalk.dart'; +import 'common.dart'; + +void main(List args) async { + try { + var argsParser = CLIBase.argsParser + ..addOption('policy-atsign', + mandatory: true, help: "the atSign of the policy service"); + + var policyAtsign = argsParser.parse(args)['policy-atsign']; + + var atClient = (await CLIBase.fromCommandLineArgs(args)).atClient; + + var policyRpcClient = AtRpcClient( + atClient: atClient, + baseNameSpace: atClient.getPreferences()!.namespace!, + domainNameSpace: policyRequestNamespace, + serverAtsign: policyAtsign, + ); + + var thisRpcServer = AtRpc( + atClient: atClient, + baseNameSpace: atClient.getPreferences()!.namespace!, + domainNameSpace: policyRequestNamespace, + callbacks: DemoRpcServer( + myAtsign: atClient.getCurrentAtSign()!, + policyAtsign: policyAtsign, + policyRpcClient: policyRpcClient, + ), + allowList: {}, + allowAll: true, + ); + + thisRpcServer.start(); + } catch (e) { + print(e); + print(CLIBase.argsParser.usage); + } +} + +class DemoRpcServer implements AtRpcCallbacks { + String myAtsign; + String policyAtsign; + AtRpcClient policyRpcClient; + + DemoRpcServer({ + required this.myAtsign, + required this.policyAtsign, + required this.policyRpcClient, + }); + + @override + Future handleRequest(AtRpcReq request, String fromAtSign) async { + stdout.writeln(chalk.blue('Received request ${request.toJson()}')); + RequestType rt; + + try { + rt = RequestType.values.byName(request.payload['reqType']); + } catch (e) { + return AtRpcResp( + reqId: request.reqId, + respType: AtRpcRespType.error, + payload: {}, + message: 'Invalid request ${request.payload}'); + } + + PolicyRequest polReq = PolicyRequest( + serviceAtsign: myAtsign, + serviceName: 'example_service_001', + serviceGroupName: 'example_service', + clientAtsign: fromAtSign, + intents: [PolicyIntent(intent: rt.name, params: {})]); + + stderr.writeln('Sending policy check request : $polReq'); + Map policyResponse = + await policyRpcClient.call(polReq.toJson()); + stderr.writeln('Received policy info : $policyResponse'); + return AtRpcResp( + reqId: request.reqId, + respType: AtRpcRespType.success, + payload: {'some': 'payload'}, + message: 'Not yet implemented', // TODO + ); + } + + @override + Future handleResponse(AtRpcResp response) async { + // Not expecting to receive responses + throw UnimplementedError(); + } +} diff --git a/packages/at_policy/example/pubspec.yaml b/packages/at_policy/example/pubspec.yaml new file mode 100644 index 00000000..9ef9e365 --- /dev/null +++ b/packages/at_policy/example/pubspec.yaml @@ -0,0 +1,18 @@ +name: example +description: Simple at_policy usage examples +version: 1.0.0 +publish_to: none + +environment: + sdk: ^3.0.6 + +# Add regular dependencies here. +dependencies: + at_policy: + path: .. + at_cli_commons: ^1.2.1 + at_client: ^3.3.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.9 diff --git a/packages/at_policy/lib/at_policy.dart b/packages/at_policy/lib/at_policy.dart new file mode 100644 index 00000000..fbf7cf0b --- /dev/null +++ b/packages/at_policy/lib/at_policy.dart @@ -0,0 +1,5 @@ +/// Policy management via atProtocol +library; + +export 'src/policy/interfaces.dart'; +export 'src/policy/models.dart'; diff --git a/packages/at_policy/lib/src/policy/impl.dart b/packages/at_policy/lib/src/policy/impl.dart new file mode 100644 index 00000000..c4aa804e --- /dev/null +++ b/packages/at_policy/lib/src/policy/impl.dart @@ -0,0 +1,168 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:at_client/at_client.dart' hide StringBuffer; +import 'package:at_client/at_client_mixins.dart'; +import 'package:at_policy/at_policy.dart'; +import 'package:at_utils/at_logger.dart'; + +/// +class PolicyServiceImpl with AtClientBindings implements PolicyService { + @override + final AtSignLogger logger = AtSignLogger(' PolicyServiceImpl '); + + @override + late AtClient atClient; + + @override + final PolicyRequestHandler handler; + + @override + final String baseNamespace; + + @override + final String policyRequestNamespace; + + @override + String get policyAtsign => atClient.getCurrentAtSign()!; + + @override + final String loggingAtsign; + + @override + final Set allowList; + + @override + final bool allowAll; + + @override + RpcTransformer? requestTransformer; + + @override + RpcTransformer? responseTransformer; + + late final AtRpc rpc; + + static const JsonEncoder jsonPrettyPrinter = JsonEncoder.withIndent(' '); + + PolicyServiceImpl({ + required this.atClient, + required this.handler, + required this.baseNamespace, + required this.policyRequestNamespace, + required this.loggingAtsign, + required this.allowList, + required this.allowAll, + this.requestTransformer, + this.responseTransformer, + }) { + rpc = AtRpc( + atClient: atClient, + baseNameSpace: baseNamespace, + domainNameSpace: policyRequestNamespace, + callbacks: this, + allowList: allowList, + allowAll: allowAll, + ); + } + + @override + Future run() async { + rpc.start(); + + logger.info('Listening for requests at ' + '${rpc.domainNameSpace}.${rpc.rpcsNameSpace}.${rpc.baseNameSpace}'); + } + + @override + Future handleRequest( + AtRpcReq rpcRequest, String fromAtSign) async { + logger.info('Received request from $fromAtSign: ' + '${jsonPrettyPrinter.convert(rpcRequest.toJson())}'); + + Map requestPayload = rpcRequest.payload; + if (requestTransformer != null) { + requestPayload = await requestTransformer!(requestPayload); + } + PolicyRequest policyRequest; + + try { + policyRequest = PolicyRequest.fromJson(requestPayload); + } catch (e) { + final msg = 'Failed PolicyRequest.fromJson: ${e.toString()}'; + logger.severe(msg); + return AtRpcResp.nack(request: rpcRequest, message: msg); + } + + try { + // We will send a 'log' notification to the loggingAtsign + var logKey = AtKey() + ..key = '${DateTime.now().millisecondsSinceEpoch}.logs.policy' + ..sharedBy = policyAtsign + ..sharedWith = loggingAtsign + ..namespace = baseNamespace + ..metadata = (Metadata() + ..isPublic = false + ..isEncrypted = true + ..namespaceAware = true); + + final event = PolicyLogEvent( + timestamp: DateTime.now().millisecondsSinceEpoch, + deviceAtsign: fromAtSign, + policyAtsign: atClient.getCurrentAtSign(), + devicename: policyRequest.serviceName, + deviceGroupName: policyRequest.serviceGroupName, + clientAtsign: policyRequest.clientAtsign, + eventType: PolicyLogEventType.requestFromDevice, + eventDetails: {'intents': policyRequest.intents}, + message: '', + ); + await notify( + logKey, + jsonEncode(event), + checkForFinalDeliveryStatus: false, + waitForFinalDeliveryStatus: false, + ttln: Duration(hours: 1), + ); + } catch (e, st) { + logger.severe('Failed to send PolicyLogEvent with exception $e' + '\nStackTrace:' + '\n$st'); + } + + PolicyResponse policyResponse; + AtRpcResp rpcResponse; + try { + policyResponse = await handler.getPolicyDetails(policyRequest); + Map responsePayload = policyResponse.toJson(); + if (responseTransformer != null) { + responsePayload = await responseTransformer!(responsePayload); + } + rpcResponse = AtRpcResp( + reqId: rpcRequest.reqId, + respType: AtRpcRespType.success, + payload: responsePayload); + } catch (e, st) { + logger.severe('Exception: $e'); + policyResponse = PolicyResponse( + message: 'Exception: $e', + policyDetails: [], + ); + Map responsePayload = policyResponse.toJson(); + if (responseTransformer != null) { + responsePayload = await responseTransformer!(responsePayload); + } + rpcResponse = AtRpcResp( + reqId: rpcRequest.reqId, + respType: AtRpcRespType.error, + payload: responsePayload); + } + + return rpcResponse; + } + + @override + Future handleResponse(AtRpcResp response) { + throw UnimplementedError(); + } +} diff --git a/packages/at_policy/lib/src/policy/interfaces.dart b/packages/at_policy/lib/src/policy/interfaces.dart new file mode 100644 index 00000000..088957a0 --- /dev/null +++ b/packages/at_policy/lib/src/policy/interfaces.dart @@ -0,0 +1,65 @@ +import 'dart:async'; + +import 'package:at_client/at_client.dart' hide StringBuffer; +import 'package:at_utils/at_logger.dart'; +import 'package:at_policy/src/policy/impl.dart'; +import 'package:at_policy/src/policy/models.dart'; + +abstract class PolicyRequestHandler { + Future getPolicyDetails(PolicyRequest req); +} + +typedef RpcTransformer = Future> Function(Map); + +/// - Listens for requests for policy info from services +/// - Returns info for each of the policy intents in the request. +abstract class PolicyService implements AtRpcCallbacks { + abstract final AtSignLogger logger; + + /// The [AtClient] used to communicate with things using this PolicyService + AtClient get atClient; + + PolicyRequestHandler get handler; + + /// Starts the service + Future run(); + + String get baseNamespace; + + String get policyRequestNamespace; + + String get policyAtsign; + + String get loggingAtsign; + + Set get allowList; + + bool get allowAll; + + /// For handling requests where the request payload json is not a + /// [PolicyRequest], but it can be transformed into one. e.g. legacy requests + RpcTransformer? requestTransformer; + + /// Transform PolicyResponses into some other (e.g. legacy) format + RpcTransformer? responseTransformer; + + factory PolicyService ({ + required AtClient atClient, + required String baseNamespace, + required PolicyRequestHandler handler, + String policyRequestNamespace = 'requests.policy', + String? loggingAtsign, + Set? allowList, + bool allowAll = true, + }) { + return PolicyServiceImpl( + atClient: atClient, + handler: handler, + baseNamespace: baseNamespace, + policyRequestNamespace: policyRequestNamespace, + loggingAtsign: loggingAtsign ?? atClient.getCurrentAtSign()!, + allowList: allowList ?? {}, + allowAll: allowAll, + ); + } +} diff --git a/packages/at_policy/lib/src/policy/models.dart b/packages/at_policy/lib/src/policy/models.dart new file mode 100644 index 00000000..1b057c74 --- /dev/null +++ b/packages/at_policy/lib/src/policy/models.dart @@ -0,0 +1,190 @@ +import 'dart:convert'; + +import 'package:at_client/at_client.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'models.g.dart'; + +const JsonEncoder jsonPrettyPrinter = JsonEncoder.withIndent(' '); + +@JsonSerializable(explicitToJson: true) +class PolicyIntent { + final String intent; + final Map? params; + + PolicyIntent({ + required this.intent, + this.params, + }); + + Map toJson() => _$PolicyIntentToJson(this); + + static PolicyIntent fromJson(Map json) => + _$PolicyIntentFromJson(json); + + @override + String toString() => jsonPrettyPrinter.convert(toJson()); +} + +@JsonSerializable(explicitToJson: true) +class PolicyDetail { + final String intent; + Map info; + + PolicyDetail({ + required this.intent, + required this.info, + }); + + Map toJson() => _$PolicyDetailToJson(this); + + static PolicyDetail fromJson(Map json) => + _$PolicyDetailFromJson(json); + + @override + String toString() => jsonPrettyPrinter.convert(toJson()); +} + +@JsonSerializable(explicitToJson: true) +class PolicyRequest { + final String serviceAtsign; + final String serviceName; + final String serviceGroupName; + final String clientAtsign; + final List intents; + + PolicyRequest({ + required this.serviceAtsign, + required this.serviceName, + required this.serviceGroupName, + required this.clientAtsign, + required this.intents, + }); + + Map toJson() => _$PolicyRequestToJson(this); + + static PolicyRequest fromJson(Map json) => + _$PolicyRequestFromJson(json); + + @override + String toString() => jsonPrettyPrinter.convert(toJson()); +} + +@JsonSerializable(explicitToJson: true) +class PolicyResponse { + final String? message; + final List policyDetails; + + PolicyResponse({ + required this.message, + required this.policyDetails, + }) { + Set intents = {}; + for (final i in policyDetails) { + if (intents.contains(i.intent)) { + throw IllegalArgumentException('More than one PolicyDetail provided for intent: ${i.intent}'); + } else { + intents.add(i.intent); + } + } + } + + PolicyDetail? infoForIntent(String intent) { + for (final i in policyDetails) { + if (i.intent == intent) { + return i; + } + } + return null; + } + + Map toJson() => _$PolicyResponseToJson(this); + + static PolicyResponse fromJson(Map json) => + _$PolicyResponseFromJson(json); + + @override + String toString() => jsonPrettyPrinter.convert(toJson()); +} + +abstract class CoreDeviceInfo { + final int timestamp; + final String deviceAtsign; + final String? policyAtsign; + final String devicename; + final String deviceGroupName; + + CoreDeviceInfo({ + required this.timestamp, + required this.deviceAtsign, + required this.policyAtsign, + required this.devicename, + required this.deviceGroupName, + }); +} + +@JsonSerializable(explicitToJson: true) +class DeviceInfo extends CoreDeviceInfo { + final List managerAtsigns; + final String version; + final String corePackageVersion; + final Map supportedFeatures; + final List allowedServices; // aka permitOpens + String? status; + + DeviceInfo({ + required super.timestamp, + required super.deviceAtsign, + required super.policyAtsign, + required super.devicename, + required super.deviceGroupName, + required this.managerAtsigns, + required this.version, + required this.corePackageVersion, + required this.supportedFeatures, + required this.allowedServices, + this.status, + }); + + Map toJson() => _$DeviceInfoToJson(this); + + static DeviceInfo fromJson(Map json) => + _$DeviceInfoFromJson(json); + + @override + String toString() => jsonPrettyPrinter.convert(toJson()); +} + +enum PolicyLogEventType { + requestFromDevice, + responseToDevice, + deviceDecision, +} + +@JsonSerializable(explicitToJson: true) +class PolicyLogEvent extends CoreDeviceInfo { + final String clientAtsign; + final PolicyLogEventType eventType; + final String? message; + final Map eventDetails; + + PolicyLogEvent({ + required super.timestamp, + required super.deviceAtsign, + required super.policyAtsign, + required super.devicename, + required super.deviceGroupName, + required this.clientAtsign, + required this.eventType, + required this.eventDetails, + required this.message, + }); + + Map toJson() => _$PolicyLogEventToJson(this); + + static PolicyLogEvent fromJson(Map json) => + _$PolicyLogEventFromJson(json); + + @override + String toString() => jsonPrettyPrinter.convert(toJson()); +} diff --git a/packages/at_policy/lib/src/policy/models.g.dart b/packages/at_policy/lib/src/policy/models.g.dart new file mode 100644 index 00000000..eee33e5e --- /dev/null +++ b/packages/at_policy/lib/src/policy/models.g.dart @@ -0,0 +1,128 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PolicyIntent _$PolicyIntentFromJson(Map json) => PolicyIntent( + intent: json['intent'] as String, + params: json['params'] as Map?, + ); + +Map _$PolicyIntentToJson(PolicyIntent instance) => + { + 'intent': instance.intent, + 'params': instance.params, + }; + +PolicyDetail _$PolicyDetailFromJson(Map json) => PolicyDetail( + intent: json['intent'] as String, + info: json['info'] as Map, + ); + +Map _$PolicyDetailToJson(PolicyDetail instance) => + { + 'intent': instance.intent, + 'info': instance.info, + }; + +PolicyRequest _$PolicyRequestFromJson(Map json) => + PolicyRequest( + serviceAtsign: json['serviceAtsign'] as String, + serviceName: json['serviceName'] as String, + serviceGroupName: json['serviceGroupName'] as String, + clientAtsign: json['clientAtsign'] as String, + intents: (json['intents'] as List) + .map((e) => PolicyIntent.fromJson(e as Map)) + .toList(), + ); + +Map _$PolicyRequestToJson(PolicyRequest instance) => + { + 'serviceAtsign': instance.serviceAtsign, + 'serviceName': instance.serviceName, + 'serviceGroupName': instance.serviceGroupName, + 'clientAtsign': instance.clientAtsign, + 'intents': instance.intents.map((e) => e.toJson()).toList(), + }; + +PolicyResponse _$PolicyResponseFromJson(Map json) => + PolicyResponse( + message: json['message'] as String?, + policyDetails: (json['policyDetails'] as List) + .map((e) => PolicyDetail.fromJson(e as Map)) + .toList(), + ); + +Map _$PolicyResponseToJson(PolicyResponse instance) => + { + 'message': instance.message, + 'policyDetails': instance.policyDetails.map((e) => e.toJson()).toList(), + }; + +DeviceInfo _$DeviceInfoFromJson(Map json) => DeviceInfo( + timestamp: (json['timestamp'] as num).toInt(), + deviceAtsign: json['deviceAtsign'] as String, + policyAtsign: json['policyAtsign'] as String?, + devicename: json['devicename'] as String, + deviceGroupName: json['deviceGroupName'] as String, + managerAtsigns: (json['managerAtsigns'] as List) + .map((e) => e as String) + .toList(), + version: json['version'] as String, + corePackageVersion: json['corePackageVersion'] as String, + supportedFeatures: json['supportedFeatures'] as Map, + allowedServices: (json['allowedServices'] as List) + .map((e) => e as String) + .toList(), + status: json['status'] as String?, + ); + +Map _$DeviceInfoToJson(DeviceInfo instance) => + { + 'timestamp': instance.timestamp, + 'deviceAtsign': instance.deviceAtsign, + 'policyAtsign': instance.policyAtsign, + 'devicename': instance.devicename, + 'deviceGroupName': instance.deviceGroupName, + 'managerAtsigns': instance.managerAtsigns, + 'version': instance.version, + 'corePackageVersion': instance.corePackageVersion, + 'supportedFeatures': instance.supportedFeatures, + 'allowedServices': instance.allowedServices, + 'status': instance.status, + }; + +PolicyLogEvent _$PolicyLogEventFromJson(Map json) => + PolicyLogEvent( + timestamp: (json['timestamp'] as num).toInt(), + deviceAtsign: json['deviceAtsign'] as String, + policyAtsign: json['policyAtsign'] as String?, + devicename: json['devicename'] as String, + deviceGroupName: json['deviceGroupName'] as String, + clientAtsign: json['clientAtsign'] as String, + eventType: $enumDecode(_$PolicyLogEventTypeEnumMap, json['eventType']), + eventDetails: json['eventDetails'] as Map, + message: json['message'] as String?, + ); + +Map _$PolicyLogEventToJson(PolicyLogEvent instance) => + { + 'timestamp': instance.timestamp, + 'deviceAtsign': instance.deviceAtsign, + 'policyAtsign': instance.policyAtsign, + 'devicename': instance.devicename, + 'deviceGroupName': instance.deviceGroupName, + 'clientAtsign': instance.clientAtsign, + 'eventType': _$PolicyLogEventTypeEnumMap[instance.eventType]!, + 'message': instance.message, + 'eventDetails': instance.eventDetails, + }; + +const _$PolicyLogEventTypeEnumMap = { + PolicyLogEventType.requestFromDevice: 'requestFromDevice', + PolicyLogEventType.responseToDevice: 'responseToDevice', + PolicyLogEventType.deviceDecision: 'deviceDecision', +}; diff --git a/packages/at_policy/lib/src/version.dart b/packages/at_policy/lib/src/version.dart new file mode 100644 index 00000000..526cd53d --- /dev/null +++ b/packages/at_policy/lib/src/version.dart @@ -0,0 +1,2 @@ +// Generated code. Do not modify. +const packageVersion = '1.0.0'; diff --git a/packages/at_policy/pubspec.yaml b/packages/at_policy/pubspec.yaml new file mode 100644 index 00000000..8ebd7c07 --- /dev/null +++ b/packages/at_policy/pubspec.yaml @@ -0,0 +1,31 @@ +name: at_policy +description: Policy management via atProtocol +version: 1.0.0 +repository: https://github.com/atsign-foundation/noports + +environment: + sdk: ^3.5.0 + +dependencies: + at_client: ^3.3.0 + at_cli_commons: ^1.2.0 + at_utils: ^3.0.19 + json_annotation: ^4.9.0 + +dependency_overrides: + dartssh2: + git: + url: https://github.com/atsign-foundation/dartssh2 + ref: trunk + args: + git: + ref: gkc/show-aliases-in-usage + url: https://github.com/gkc/args + +dev_dependencies: + build_runner: ^2.4.12 + json_serializable: ^6.8.0 + build_version: ^2.1.1 + lints: ^4.0.0 + mocktail: ^1.0.1 + test: ^1.24.4 diff --git a/packages/at_policy/test/at_policy_test.dart b/packages/at_policy/test/at_policy_test.dart new file mode 100644 index 00000000..4fc20251 --- /dev/null +++ b/packages/at_policy/test/at_policy_test.dart @@ -0,0 +1,153 @@ +import 'package:at_client/at_client.dart'; +import 'package:at_policy/at_policy.dart'; +import 'package:at_policy/src/policy/impl.dart'; +import 'package:at_utils/at_logger.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +class MockAtClient extends Mock implements AtClient {} + +class MockNotificationService extends Mock implements NotificationService {} + +void main() { + AtSignLogger.root_level = 'shout'; + AtClient atClient = MockAtClient(); + NotificationService notificationService = MockNotificationService(); + setUpAll(() { + registerFallbackValue(NotificationParams()); + when(() => atClient.getCurrentAtSign()).thenReturn('@policy'); + when(() => atClient.notificationService).thenReturn(notificationService); + when(() => notificationService.notify(any(), + checkForFinalDeliveryStatus: any(named: 'checkForFinalDeliveryStatus'), + waitForFinalDeliveryStatus: any(named: 'waitForFinalDeliveryStatus'), + onSuccess: any(named: 'onSuccess'), + onError: any(named: 'onError'), + onSentToSecondary: + any(named: 'onSentToSecondary'))).thenAnswer((_) async { + return NotificationResult(); + }); + }); + late PolicyService ps; + setUp(() { + ps = PolicyServiceImpl( + atClient: atClient, + handler: TestHandler(), + baseNamespace: 'test', + policyRequestNamespace: 'policy', + loggingAtsign: '@alice', + allowList: {}, + allowAll: true, + ); + }); + + AtRpcReq _createRpcPolicyRequest(List intents) { + PolicyRequest req = PolicyRequest( + serviceAtsign: '@service', + serviceName: 'service_1', + serviceGroupName: 'test_services', + clientAtsign: '@alice', + intents: intents, + ); + return AtRpcReq( + reqId: DateTime.now().microsecondsSinceEpoch, + payload: req.toJson(), + ); + } + + test('Test happy path', () async { + AtRpcResp rpcResp = await ps.handleRequest(_createRpcPolicyRequest([ + PolicyIntent(intent: 'emote', params: {'emotion': 'happiness'}) + ]), '@service'); + PolicyResponse pr = PolicyResponse.fromJson(rpcResp.payload); + expect(pr.policyDetails.length, 1); + expect(pr.policyDetails[0].intent, 'emote'); + expect(pr.policyDetails[0].info, isNotNull); + expect(pr.policyDetails[0].info, isNotEmpty); + expect(pr.policyDetails[0].info['allowed'], true); + expect(pr.policyDetails[0].info['message'], 'Laughter is encouraged'); + }); + + test('Test negative response', () async { + AtRpcResp rpcResp = await ps.handleRequest(_createRpcPolicyRequest([ + PolicyIntent(intent: 'emote', params: {'emotion': 'anger'}) + ]), '@service'); + PolicyResponse pr = PolicyResponse.fromJson(rpcResp.payload); + expect(pr.policyDetails.length, 1); + expect(pr.policyDetails[0].intent, 'emote'); + expect(pr.policyDetails[0].info, isNotNull); + expect(pr.policyDetails[0].info, isNotEmpty); + expect(pr.policyDetails[0].info['allowed'], false); + expect(pr.policyDetails[0].info['message'], 'Anger is discouraged'); + }); + + test('Test null request', () async { + AtRpcResp rpcResp = await ps.handleRequest(AtRpcReq( + reqId: DateTime.now().microsecondsSinceEpoch, + payload: {}, + ), '@service'); + expect(rpcResp.respType, AtRpcRespType.nack); + expect(rpcResp.message, contains('Failed PolicyRequest.fromJson')); + }); + + test('Test malformed request', () async { + AtRpcResp rpcResp = await ps.handleRequest(AtRpcReq( + reqId: DateTime.now().microsecondsSinceEpoch, + payload: {'foo':12345,'bar':false}, + ), '@service'); + expect(rpcResp.respType, AtRpcRespType.nack); + expect(rpcResp.message, contains('Failed PolicyRequest.fromJson')); + }); + + test('Test unknown intent', () async { + AtRpcResp rpcResp = await ps.handleRequest(_createRpcPolicyRequest([ + PolicyIntent(intent: 'dance', params: {}) + ]), '@service'); + expect(rpcResp.respType, AtRpcRespType.error); + expect(rpcResp.payload['message'], contains('Unknown intent: dance')); + }); + +} + +class TestHandler implements PolicyRequestHandler { + @override + Future getPolicyDetails(PolicyRequest req) async { + if (req.intents.isEmpty) { + throw ArgumentError('No intents were supplied in the PolicyRequest'); + } + PolicyResponse resp = PolicyResponse(message: '', policyDetails: []); + for (final intent in req.intents) { + if (intent.params == null) { + throw ArgumentError( + 'No parameters supplied with intent ${intent.intent}'); + } + switch (intent.intent) { + case 'emote': + String? emotion = intent.params!['emotion']; + if (emotion == null) { + throw ArgumentError('the "emotion" parameter is required' + ' for the ${intent.intent} intent'); + } + switch (emotion) { + case 'happiness': + resp.policyDetails.add(PolicyDetail( + intent: intent.intent, + info: {'allowed': true, 'message': 'Laughter is encouraged'}, + )); + case 'anger': + resp.policyDetails.add(PolicyDetail( + intent: intent.intent, + info: {'allowed': false, 'message': "Anger is discouraged"}, + )); + default: + resp.policyDetails.add(PolicyDetail( + intent: intent.intent, + info: {'allowed': false, 'message': 'Expressing $emotion is not allowed'}, + )); + } + default: + throw ArgumentError('Unknown intent: ${intent.intent}'); + } + } + return resp; + } +} From d02c53f124739ee8b8f022ab35db9d301f9bb894 Mon Sep 17 00:00:00 2001 From: gkc Date: Mon, 9 Dec 2024 15:51:26 +0000 Subject: [PATCH 2/7] chore: run dart format --- packages/at_policy/example/bin/client.dart | 8 +-- packages/at_policy/example/bin/policy.dart | 4 +- packages/at_policy/lib/src/policy/impl.dart | 4 +- .../at_policy/lib/src/policy/interfaces.dart | 5 +- packages/at_policy/lib/src/policy/models.dart | 3 +- packages/at_policy/test/at_policy_test.dart | 49 ++++++++++++------- 6 files changed, 41 insertions(+), 32 deletions(-) diff --git a/packages/at_policy/example/bin/client.dart b/packages/at_policy/example/bin/client.dart index 30816a6f..e82d67e5 100644 --- a/packages/at_policy/example/bin/client.dart +++ b/packages/at_policy/example/bin/client.dart @@ -37,12 +37,9 @@ void main(List args) async { } try { - - var response = await rpc - .call({'reqType': RequestType.values[reqType - 1].name}) - .timeout(Duration(seconds: 15)); - + .call({'reqType': RequestType.values[reqType - 1].name}).timeout( + Duration(seconds: 15)); stdout.writeln(chalk.green(response)); } on TimeoutException { @@ -51,7 +48,6 @@ void main(List args) async { stderr.writeln(chalk.brightRed(e)); } } - } catch (e) { print(e); print(CLIBase.argsParser.usage); diff --git a/packages/at_policy/example/bin/policy.dart b/packages/at_policy/example/bin/policy.dart index c9333972..9c17cb0e 100644 --- a/packages/at_policy/example/bin/policy.dart +++ b/packages/at_policy/example/bin/policy.dart @@ -27,9 +27,7 @@ void main(List args) async { class DemoPolicyRequestHandler implements PolicyRequestHandler { @override - Future getPolicyDetails( - PolicyRequest req) async { - + Future getPolicyDetails(PolicyRequest req) async { stdout.writeln(chalk.blue('Received request $req')); stdout.write('(A)pprove or (D)eny? : '); diff --git a/packages/at_policy/lib/src/policy/impl.dart b/packages/at_policy/lib/src/policy/impl.dart index c4aa804e..a9d63e00 100644 --- a/packages/at_policy/lib/src/policy/impl.dart +++ b/packages/at_policy/lib/src/policy/impl.dart @@ -80,7 +80,7 @@ class PolicyServiceImpl with AtClientBindings implements PolicyService { logger.info('Received request from $fromAtSign: ' '${jsonPrettyPrinter.convert(rpcRequest.toJson())}'); - Map requestPayload = rpcRequest.payload; + Map requestPayload = rpcRequest.payload; if (requestTransformer != null) { requestPayload = await requestTransformer!(requestPayload); } @@ -142,7 +142,7 @@ class PolicyServiceImpl with AtClientBindings implements PolicyService { reqId: rpcRequest.reqId, respType: AtRpcRespType.success, payload: responsePayload); - } catch (e, st) { + } catch (e) { logger.severe('Exception: $e'); policyResponse = PolicyResponse( message: 'Exception: $e', diff --git a/packages/at_policy/lib/src/policy/interfaces.dart b/packages/at_policy/lib/src/policy/interfaces.dart index 088957a0..74c13b17 100644 --- a/packages/at_policy/lib/src/policy/interfaces.dart +++ b/packages/at_policy/lib/src/policy/interfaces.dart @@ -9,7 +9,8 @@ abstract class PolicyRequestHandler { Future getPolicyDetails(PolicyRequest req); } -typedef RpcTransformer = Future> Function(Map); +typedef RpcTransformer = Future> Function( + Map); /// - Listens for requests for policy info from services /// - Returns info for each of the policy intents in the request. @@ -43,7 +44,7 @@ abstract class PolicyService implements AtRpcCallbacks { /// Transform PolicyResponses into some other (e.g. legacy) format RpcTransformer? responseTransformer; - factory PolicyService ({ + factory PolicyService({ required AtClient atClient, required String baseNamespace, required PolicyRequestHandler handler, diff --git a/packages/at_policy/lib/src/policy/models.dart b/packages/at_policy/lib/src/policy/models.dart index 1b057c74..1942d3d7 100644 --- a/packages/at_policy/lib/src/policy/models.dart +++ b/packages/at_policy/lib/src/policy/models.dart @@ -82,7 +82,8 @@ class PolicyResponse { Set intents = {}; for (final i in policyDetails) { if (intents.contains(i.intent)) { - throw IllegalArgumentException('More than one PolicyDetail provided for intent: ${i.intent}'); + throw IllegalArgumentException( + 'More than one PolicyDetail provided for intent: ${i.intent}'); } else { intents.add(i.intent); } diff --git a/packages/at_policy/test/at_policy_test.dart b/packages/at_policy/test/at_policy_test.dart index 4fc20251..ffe120ec 100644 --- a/packages/at_policy/test/at_policy_test.dart +++ b/packages/at_policy/test/at_policy_test.dart @@ -13,6 +13,7 @@ void main() { AtSignLogger.root_level = 'shout'; AtClient atClient = MockAtClient(); NotificationService notificationService = MockNotificationService(); + setUpAll(() { registerFallbackValue(NotificationParams()); when(() => atClient.getCurrentAtSign()).thenReturn('@policy'); @@ -27,7 +28,9 @@ void main() { return NotificationResult(); }); }); + late PolicyService ps; + setUp(() { ps = PolicyServiceImpl( atClient: atClient, @@ -40,7 +43,7 @@ void main() { ); }); - AtRpcReq _createRpcPolicyRequest(List intents) { + AtRpcReq createRpcPolicyRequest(List intents) { PolicyRequest req = PolicyRequest( serviceAtsign: '@service', serviceName: 'service_1', @@ -55,9 +58,11 @@ void main() { } test('Test happy path', () async { - AtRpcResp rpcResp = await ps.handleRequest(_createRpcPolicyRequest([ + AtRpcResp rpcResp = await ps.handleRequest( + createRpcPolicyRequest([ PolicyIntent(intent: 'emote', params: {'emotion': 'happiness'}) - ]), '@service'); + ]), + '@service'); PolicyResponse pr = PolicyResponse.fromJson(rpcResp.payload); expect(pr.policyDetails.length, 1); expect(pr.policyDetails[0].intent, 'emote'); @@ -68,9 +73,11 @@ void main() { }); test('Test negative response', () async { - AtRpcResp rpcResp = await ps.handleRequest(_createRpcPolicyRequest([ + AtRpcResp rpcResp = await ps.handleRequest( + createRpcPolicyRequest([ PolicyIntent(intent: 'emote', params: {'emotion': 'anger'}) - ]), '@service'); + ]), + '@service'); PolicyResponse pr = PolicyResponse.fromJson(rpcResp.payload); expect(pr.policyDetails.length, 1); expect(pr.policyDetails[0].intent, 'emote'); @@ -81,31 +88,34 @@ void main() { }); test('Test null request', () async { - AtRpcResp rpcResp = await ps.handleRequest(AtRpcReq( - reqId: DateTime.now().microsecondsSinceEpoch, - payload: {}, - ), '@service'); + AtRpcResp rpcResp = await ps.handleRequest( + AtRpcReq( + reqId: DateTime.now().microsecondsSinceEpoch, + payload: {}, + ), + '@service'); expect(rpcResp.respType, AtRpcRespType.nack); expect(rpcResp.message, contains('Failed PolicyRequest.fromJson')); }); test('Test malformed request', () async { - AtRpcResp rpcResp = await ps.handleRequest(AtRpcReq( - reqId: DateTime.now().microsecondsSinceEpoch, - payload: {'foo':12345,'bar':false}, - ), '@service'); + AtRpcResp rpcResp = await ps.handleRequest( + AtRpcReq( + reqId: DateTime.now().microsecondsSinceEpoch, + payload: {'foo': 12345, 'bar': false}, + ), + '@service'); expect(rpcResp.respType, AtRpcRespType.nack); expect(rpcResp.message, contains('Failed PolicyRequest.fromJson')); }); test('Test unknown intent', () async { - AtRpcResp rpcResp = await ps.handleRequest(_createRpcPolicyRequest([ - PolicyIntent(intent: 'dance', params: {}) - ]), '@service'); + AtRpcResp rpcResp = await ps.handleRequest( + createRpcPolicyRequest([PolicyIntent(intent: 'dance', params: {})]), + '@service'); expect(rpcResp.respType, AtRpcRespType.error); expect(rpcResp.payload['message'], contains('Unknown intent: dance')); }); - } class TestHandler implements PolicyRequestHandler { @@ -141,7 +151,10 @@ class TestHandler implements PolicyRequestHandler { default: resp.policyDetails.add(PolicyDetail( intent: intent.intent, - info: {'allowed': false, 'message': 'Expressing $emotion is not allowed'}, + info: { + 'allowed': false, + 'message': 'Expressing $emotion is not allowed' + }, )); } default: From 51f10932c75bf1f4f0b3594e1c6d737071f2686f Mon Sep 17 00:00:00 2001 From: gkc Date: Mon, 9 Dec 2024 15:58:34 +0000 Subject: [PATCH 3/7] docs: update README build: update the example package name in at_policy to `at_policy_example` --- packages/at_policy/README.md | 2 +- packages/at_policy/example/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/at_policy/README.md b/packages/at_policy/README.md index e65bd423..b1e6b219 100644 --- a/packages/at_policy/README.md +++ b/packages/at_policy/README.md @@ -7,7 +7,7 @@ # at_policy ## Introduction -The at_policy libraries provide generic scaffolding for building policy +The at_policy library provides generic scaffolding for building policy management services which policy enforcement endpoints communicate with via atProtocol and therefore get all the benefits of using atProtocol - outbound communication only, end-to-end encryption, using atSigns rather than IP diff --git a/packages/at_policy/example/pubspec.yaml b/packages/at_policy/example/pubspec.yaml index 9ef9e365..62e0dc0c 100644 --- a/packages/at_policy/example/pubspec.yaml +++ b/packages/at_policy/example/pubspec.yaml @@ -1,4 +1,4 @@ -name: example +name: at_policy_example description: Simple at_policy usage examples version: 1.0.0 publish_to: none From a2409fa2e0c01c69d07a05c37517150e74d113c8 Mon Sep 17 00:00:00 2001 From: gkc Date: Mon, 9 Dec 2024 16:29:56 +0000 Subject: [PATCH 4/7] fix: change the default rpc "domainNamespace" to 'policy' test: use PolicyService factory constructor rather than PolicyServiceImpl --- packages/at_policy/lib/src/policy/interfaces.dart | 2 +- packages/at_policy/test/at_policy_test.dart | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/at_policy/lib/src/policy/interfaces.dart b/packages/at_policy/lib/src/policy/interfaces.dart index 74c13b17..d9df40bc 100644 --- a/packages/at_policy/lib/src/policy/interfaces.dart +++ b/packages/at_policy/lib/src/policy/interfaces.dart @@ -48,7 +48,7 @@ abstract class PolicyService implements AtRpcCallbacks { required AtClient atClient, required String baseNamespace, required PolicyRequestHandler handler, - String policyRequestNamespace = 'requests.policy', + String policyRequestNamespace = 'policy', String? loggingAtsign, Set? allowList, bool allowAll = true, diff --git a/packages/at_policy/test/at_policy_test.dart b/packages/at_policy/test/at_policy_test.dart index ffe120ec..35683e0a 100644 --- a/packages/at_policy/test/at_policy_test.dart +++ b/packages/at_policy/test/at_policy_test.dart @@ -1,6 +1,5 @@ import 'package:at_client/at_client.dart'; import 'package:at_policy/at_policy.dart'; -import 'package:at_policy/src/policy/impl.dart'; import 'package:at_utils/at_logger.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; @@ -32,7 +31,7 @@ void main() { late PolicyService ps; setUp(() { - ps = PolicyServiceImpl( + ps = PolicyService( atClient: atClient, handler: TestHandler(), baseNamespace: 'test', From 77c395505f0ab324c83e475a7dc76a3c9eef7842 Mon Sep 17 00:00:00 2001 From: gkc Date: Mon, 9 Dec 2024 21:35:26 +0000 Subject: [PATCH 5/7] build: remove unused dependency dartssh2 --- packages/at_policy/pubspec.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/at_policy/pubspec.yaml b/packages/at_policy/pubspec.yaml index 8ebd7c07..3b9e97bc 100644 --- a/packages/at_policy/pubspec.yaml +++ b/packages/at_policy/pubspec.yaml @@ -13,10 +13,6 @@ dependencies: json_annotation: ^4.9.0 dependency_overrides: - dartssh2: - git: - url: https://github.com/atsign-foundation/dartssh2 - ref: trunk args: git: ref: gkc/show-aliases-in-usage From 7fbc5e8e80812261b41f5b9bcffc0795feacdf36 Mon Sep 17 00:00:00 2001 From: gkc Date: Mon, 9 Dec 2024 21:35:41 +0000 Subject: [PATCH 6/7] docs: fixed typo in README --- packages/at_policy/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/at_policy/README.md b/packages/at_policy/README.md index b1e6b219..61358f02 100644 --- a/packages/at_policy/README.md +++ b/packages/at_policy/README.md @@ -20,6 +20,5 @@ See full worked examples in the [example](example/README.md) directory. This is freely licensed open source code, so feel free to use it as is, suggest changes or enhancements or create your own version. -See [CONTRIBUTING.md](../../CONTRIBUTING.md) for -detailed guidance on how to set up tools, tests and make a pull request.on. -See CONTRIBUTING.md for detailed guidance on how to set up tools, tests and make a pull request. \ No newline at end of file +See [CONTRIBUTING.md](../../CONTRIBUTING.md) for detailed guidance on how to +set up tools, tests and make a pull request. \ No newline at end of file From b1e736e46079676af759662c6be39454fdd9ab42 Mon Sep 17 00:00:00 2001 From: gkc Date: Mon, 9 Dec 2024 21:37:15 +0000 Subject: [PATCH 7/7] refactor: removed noports-specific `DeviceInfo` class; renamed this package's `CoreDeviceInfo` class to `CoreServiceInfo` and renamed some instance variables also (device -> service) --- packages/at_policy/lib/src/policy/impl.dart | 8 +-- packages/at_policy/lib/src/policy/models.dart | 62 +++++-------------- .../at_policy/lib/src/policy/models.g.dart | 51 +++------------ 3 files changed, 28 insertions(+), 93 deletions(-) diff --git a/packages/at_policy/lib/src/policy/impl.dart b/packages/at_policy/lib/src/policy/impl.dart index a9d63e00..2dfc79c7 100644 --- a/packages/at_policy/lib/src/policy/impl.dart +++ b/packages/at_policy/lib/src/policy/impl.dart @@ -108,12 +108,12 @@ class PolicyServiceImpl with AtClientBindings implements PolicyService { final event = PolicyLogEvent( timestamp: DateTime.now().millisecondsSinceEpoch, - deviceAtsign: fromAtSign, + serviceAtsign: fromAtSign, policyAtsign: atClient.getCurrentAtSign(), - devicename: policyRequest.serviceName, - deviceGroupName: policyRequest.serviceGroupName, + serviceName: policyRequest.serviceName, + serviceGroupName: policyRequest.serviceGroupName, clientAtsign: policyRequest.clientAtsign, - eventType: PolicyLogEventType.requestFromDevice, + eventType: PolicyLogEventType.request, eventDetails: {'intents': policyRequest.intents}, message: '', ); diff --git a/packages/at_policy/lib/src/policy/models.dart b/packages/at_policy/lib/src/policy/models.dart index 1942d3d7..caff4135 100644 --- a/packages/at_policy/lib/src/policy/models.dart +++ b/packages/at_policy/lib/src/policy/models.dart @@ -108,62 +108,30 @@ class PolicyResponse { String toString() => jsonPrettyPrinter.convert(toJson()); } -abstract class CoreDeviceInfo { +abstract class CoreServiceInfo { final int timestamp; - final String deviceAtsign; + final String serviceAtsign; final String? policyAtsign; - final String devicename; - final String deviceGroupName; + final String serviceName; + final String serviceGroupName; - CoreDeviceInfo({ + CoreServiceInfo({ required this.timestamp, - required this.deviceAtsign, + required this.serviceAtsign, required this.policyAtsign, - required this.devicename, - required this.deviceGroupName, - }); -} - -@JsonSerializable(explicitToJson: true) -class DeviceInfo extends CoreDeviceInfo { - final List managerAtsigns; - final String version; - final String corePackageVersion; - final Map supportedFeatures; - final List allowedServices; // aka permitOpens - String? status; - - DeviceInfo({ - required super.timestamp, - required super.deviceAtsign, - required super.policyAtsign, - required super.devicename, - required super.deviceGroupName, - required this.managerAtsigns, - required this.version, - required this.corePackageVersion, - required this.supportedFeatures, - required this.allowedServices, - this.status, + required this.serviceName, + required this.serviceGroupName, }); - - Map toJson() => _$DeviceInfoToJson(this); - - static DeviceInfo fromJson(Map json) => - _$DeviceInfoFromJson(json); - - @override - String toString() => jsonPrettyPrinter.convert(toJson()); } enum PolicyLogEventType { - requestFromDevice, - responseToDevice, - deviceDecision, + request, + response, + decision, } @JsonSerializable(explicitToJson: true) -class PolicyLogEvent extends CoreDeviceInfo { +class PolicyLogEvent extends CoreServiceInfo { final String clientAtsign; final PolicyLogEventType eventType; final String? message; @@ -171,10 +139,10 @@ class PolicyLogEvent extends CoreDeviceInfo { PolicyLogEvent({ required super.timestamp, - required super.deviceAtsign, + required super.serviceAtsign, required super.policyAtsign, - required super.devicename, - required super.deviceGroupName, + required super.serviceName, + required super.serviceGroupName, required this.clientAtsign, required this.eventType, required this.eventDetails, diff --git a/packages/at_policy/lib/src/policy/models.g.dart b/packages/at_policy/lib/src/policy/models.g.dart index eee33e5e..42577f77 100644 --- a/packages/at_policy/lib/src/policy/models.g.dart +++ b/packages/at_policy/lib/src/policy/models.g.dart @@ -62,46 +62,13 @@ Map _$PolicyResponseToJson(PolicyResponse instance) => 'policyDetails': instance.policyDetails.map((e) => e.toJson()).toList(), }; -DeviceInfo _$DeviceInfoFromJson(Map json) => DeviceInfo( - timestamp: (json['timestamp'] as num).toInt(), - deviceAtsign: json['deviceAtsign'] as String, - policyAtsign: json['policyAtsign'] as String?, - devicename: json['devicename'] as String, - deviceGroupName: json['deviceGroupName'] as String, - managerAtsigns: (json['managerAtsigns'] as List) - .map((e) => e as String) - .toList(), - version: json['version'] as String, - corePackageVersion: json['corePackageVersion'] as String, - supportedFeatures: json['supportedFeatures'] as Map, - allowedServices: (json['allowedServices'] as List) - .map((e) => e as String) - .toList(), - status: json['status'] as String?, - ); - -Map _$DeviceInfoToJson(DeviceInfo instance) => - { - 'timestamp': instance.timestamp, - 'deviceAtsign': instance.deviceAtsign, - 'policyAtsign': instance.policyAtsign, - 'devicename': instance.devicename, - 'deviceGroupName': instance.deviceGroupName, - 'managerAtsigns': instance.managerAtsigns, - 'version': instance.version, - 'corePackageVersion': instance.corePackageVersion, - 'supportedFeatures': instance.supportedFeatures, - 'allowedServices': instance.allowedServices, - 'status': instance.status, - }; - PolicyLogEvent _$PolicyLogEventFromJson(Map json) => PolicyLogEvent( timestamp: (json['timestamp'] as num).toInt(), - deviceAtsign: json['deviceAtsign'] as String, + serviceAtsign: json['serviceAtsign'] as String, policyAtsign: json['policyAtsign'] as String?, - devicename: json['devicename'] as String, - deviceGroupName: json['deviceGroupName'] as String, + serviceName: json['serviceName'] as String, + serviceGroupName: json['serviceGroupName'] as String, clientAtsign: json['clientAtsign'] as String, eventType: $enumDecode(_$PolicyLogEventTypeEnumMap, json['eventType']), eventDetails: json['eventDetails'] as Map, @@ -111,10 +78,10 @@ PolicyLogEvent _$PolicyLogEventFromJson(Map json) => Map _$PolicyLogEventToJson(PolicyLogEvent instance) => { 'timestamp': instance.timestamp, - 'deviceAtsign': instance.deviceAtsign, + 'serviceAtsign': instance.serviceAtsign, 'policyAtsign': instance.policyAtsign, - 'devicename': instance.devicename, - 'deviceGroupName': instance.deviceGroupName, + 'serviceName': instance.serviceName, + 'serviceGroupName': instance.serviceGroupName, 'clientAtsign': instance.clientAtsign, 'eventType': _$PolicyLogEventTypeEnumMap[instance.eventType]!, 'message': instance.message, @@ -122,7 +89,7 @@ Map _$PolicyLogEventToJson(PolicyLogEvent instance) => }; const _$PolicyLogEventTypeEnumMap = { - PolicyLogEventType.requestFromDevice: 'requestFromDevice', - PolicyLogEventType.responseToDevice: 'responseToDevice', - PolicyLogEventType.deviceDecision: 'deviceDecision', + PolicyLogEventType.request: 'request', + PolicyLogEventType.response: 'response', + PolicyLogEventType.decision: 'decision', };