Skip to content
This repository has been archived by the owner on Feb 13, 2024. It is now read-only.

Feature/simple rules #470

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/business-partner-agent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@
</goals>
<configuration>
<!-- See https://nodejs.org/en/download/ -->
<nodeVersion>v14.17.0</nodeVersion>
<nodeVersion>v14.17.1</nodeVersion>
<npmVersion>6.14.13</npmVersion>
</configuration>
</execution>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.hyperledger.aries.webhook.EventHandler;

import javax.inject.Inject;
import javax.inject.Named;

/**
* Handles incoming aca-py webhook events
Expand All @@ -46,7 +47,12 @@ public class AriesWebhookController {
public static final String WEBHOOK_CONTROLLER_PATH = "/log/topic";

@Inject
EventHandler handler;
@Named("aries")
EventHandler ariesEventHandler;

@Inject
@Named("rules")
EventHandler rulesEventHandler;

@Post(WEBHOOK_CONTROLLER_PATH + "/{eventType}")
public void logEvent(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename method? It does not solely log.
accept event? consume?

Expand All @@ -55,6 +61,7 @@ public void logEvent(

log.info("Webhook received, type: {}", eventType);

handler.handleEvent(eventType, eventBody);
ariesEventHandler.handleEvent(eventType, eventBody);
rulesEventHandler.handleEvent(eventType, eventBody); // rules always run after
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2020-2021 - for information on the respective copyright owner
* see the NOTICE file and/or the repository at
* https://github.com/hyperledger-labs/business-partner-agent
*
* 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.
*/
package org.hyperledger.bpa.controller;

import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.*;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.hyperledger.bpa.controller.api.rules.RuleRequest;
import org.hyperledger.bpa.impl.rules.RulesData;
import org.hyperledger.bpa.impl.rules.RulesService;

import javax.inject.Inject;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Controller("/api/rules")
@Tag(name = "Rules")
@Secured(SecurityRule.IS_AUTHENTICATED)
@ExecuteOn(TaskExecutors.IO)
public class RulesController {

@Inject
RulesService rules;

/**
* Add new rule with trigger and action
*
* @param ruleRequest {@link RuleRequest}
* @return {@link RulesData}
*/
@Post
public HttpResponse<RulesData> addRule(@Body RuleRequest ruleRequest) {
return HttpResponse.ok(rules.add(ruleRequest.getTrigger(), ruleRequest.getAction()));
}

/**
* Update rule
*
* @param id {@link UUID} rule id
* @param ruleRequest {@link RuleRequest}
* @return {@link RulesData}
*/
@Put("/{id}")
public HttpResponse<RulesData> updateRule(@PathVariable String id, @Body RuleRequest ruleRequest) {
Optional<RulesData> updated = rules.update(
UUID.fromString(id), ruleRequest.getTrigger(), ruleRequest.getAction());
if (updated.isPresent()) {
return HttpResponse.ok(updated.get());
}
return HttpResponse.notFound();
}

/**
* Dele rule by id
*
* @param id {@link UUID} rule id
* @return {@link HttpResponse}
*/
@Delete("/{id}")
public HttpResponse<Void> deleteRule(@PathVariable String id) {
rules.delete(UUID.fromString(id));
return HttpResponse.ok();
}

/**
* List configured rules
*
* @return list of {@link RulesData}
*/
@Get
public HttpResponse<List<RulesData>> listRules() {
return HttpResponse.ok(rules.getAll());
}

/**
* Get configured rule by id
*
* @param id {@link UUID} rule id
* @return {@link RulesData}
*/
@Get("/{id}")
public HttpResponse<RulesData> getById(@PathVariable String id) {
Optional<RulesData> rule = rules.get(UUID.fromString(id));
if (rule.isPresent()) {
return HttpResponse.ok(rule.get());
}
return HttpResponse.notFound();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2020-2021 - for information on the respective copyright owner
* see the NOTICE file and/or the repository at
* https://github.com/hyperledger-labs/business-partner-agent
*
* 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.
*/
package org.hyperledger.bpa.controller.api.rules;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hyperledger.bpa.impl.rules.RulesData;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class RuleRequest {
private RulesData.Trigger trigger;
private RulesData.Action action;
}
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,12 @@ private void resolveAndSend(ConnectionRecord record, Partner p) {
// only incoming connections in state request
if (ConnectionState.REQUEST.equals(record.getState())) {
didResolver.lookupIncoming(p);
sendConnectionEvent(record, conv.toAPIObject(p));
}
}

private void sendConnectionEvent(@NonNull ConnectionRecord record, @NonNull PartnerAPI p) {
// TODO both or either?
messageService.sendMessage(WebSocketMessageBody.partnerReceived(p));
if (isConnectionRequest(record)) {
messageService.sendMessage(WebSocketMessageBody.partnerConnectionRequest(p));
// TODO both or either?
PartnerAPI pApi = conv.toAPIObject(p);
messageService.sendMessage(WebSocketMessageBody.partnerReceived(pApi));
if (isConnectionRequest(record)) {
messageService.sendMessage(WebSocketMessageBody.partnerConnectionRequest(pApi));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2020-2021 - for information on the respective copyright owner
* see the NOTICE file and/or the repository at
* https://github.com/hyperledger-labs/business-partner-agent
*
* 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.
*/
package org.hyperledger.bpa.impl.rules;

import io.micronaut.context.ApplicationContext;
import lombok.Builder;
import lombok.Data;
import org.hyperledger.aries.api.connection.ConnectionRecord;
import org.hyperledger.aries.api.present_proof.PresentationExchangeRecord;
import org.hyperledger.bpa.model.Partner;

@Data
@Builder
public class EventContext {
private Partner partner;
private PresentationExchangeRecord presEx;
private ConnectionRecord connRec;
private ApplicationContext ctx;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright (c) 2020-2021 - for information on the respective copyright owner
* see the NOTICE file and/or the repository at
* https://github.com/hyperledger-labs/business-partner-agent
*
* 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.
*/
package org.hyperledger.bpa.impl.rules;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.hyperledger.aries.api.connection.ConnectionRecord;
import org.hyperledger.aries.api.connection.ConnectionState;
import org.hyperledger.aries.api.connection.ConnectionTheirRole;
import org.hyperledger.bpa.model.ActiveRules;

import java.util.UUID;

@Slf4j
@Data
@Builder
public class RulesData {

private UUID ruleId;
private Trigger trigger;
private Action action;

@JsonTypeInfo(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) to store the type information. Is there a decision, which one to use?

use = JsonTypeInfo.Id.NAME,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Trigger.ConnectionTrigger.class, name = Trigger.CONNECTION_TRIGGER_NAME),
@JsonSubTypes.Type(value = Trigger.ProofReceivedTrigger.class, name = Trigger.PROOF_RECEIVED_TRIGGER_NAME)
})
@NoArgsConstructor
public abstract static class Trigger {

public static final String CONNECTION_TRIGGER_NAME = "connection";
public static final String PROOF_RECEIVED_TRIGGER_NAME = "proof_received";

abstract boolean apply(EventContext ctx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apply is a very generic name.
shallApplyAction
applicableRule


@SuppressWarnings("unused")
private String type;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this field? It's not used via accessors and builders. The database stores Triggers as jsonb.

How about pulling this field into the subclasses, if there is a purpose for it. And then change Trigger to an interface, which is the also a functional one. Or does that create serialization issues?


@JsonTypeName(Trigger.CONNECTION_TRIGGER_NAME)
@Builder
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public static class ConnectionTrigger extends Trigger {
private ConnectionTheirRole role;
private String tag;
private String goalCode;

@Override
public boolean apply(EventContext ctx) {
ConnectionRecord connRec = ctx.getConnRec();
boolean apply = connRec != null && ConnectionState.REQUEST.equals(connRec.getState());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boolean isRequest = connRec != null && ConnectionState.REQUEST.equals(connRec.getState());
boolean roleMatches = role==null || connRec != null && role.equals(connRec.getTheirRole());
return isRequest && roleMatches;

if (role != null) {
apply = apply && role.equals(connRec.getTheirRole());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connRec could be null?

}
return apply;
}
}

@JsonTypeName(Trigger.PROOF_RECEIVED_TRIGGER_NAME)
@Builder
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public static class ProofReceivedTrigger extends Trigger {
private String tag;
private UUID proofTemplateId;

@Override
public boolean apply(EventContext ctx) {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a TODO, that this can be implemented as soon as proof templates are available?

}
}
}

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Action.TagConnection.class, name = Action.TAG_CONNECTION_ACTION_NAME),
@JsonSubTypes.Type(value = Action.SendProofRequest.class, name = Action.SEND_PROOF_REQUEST_ACTION_NAME)
})
@NoArgsConstructor
public abstract static class Action {

public static final String TAG_CONNECTION_ACTION_NAME = "tag_connection";
public static final String SEND_PROOF_REQUEST_ACTION_NAME = "send_proof_request";

abstract void run(EventContext ctx);

@SuppressWarnings("unused")
private String type;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as Trigger.type?


@JsonTypeName(Action.TAG_CONNECTION_ACTION_NAME)
@Builder
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public static class TagConnection extends Action {
private String connectionId;
private String tag;

@Override
void run(EventContext ctx) {
log.debug("tag: {}", tag);
}
}

@JsonTypeName(Action.SEND_PROOF_REQUEST_ACTION_NAME)
@Builder
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public static class SendProofRequest extends Action {
private String connectionId;
private UUID proofTemplateId;

@Override
void run(EventContext ctx) {

}
}
}

public static RulesData fromActive(@NonNull ActiveRules ar) {
return RulesData.builder()
.ruleId(ar.getId())
.trigger(ar.getTrigger())
.action(ar.getAction())
.build();
}
}
Loading