Skip to content

Commit

Permalink
add api for listing rules
Browse files Browse the repository at this point in the history
  • Loading branch information
artsiomkorzun committed Mar 14, 2024
1 parent f2a4e17 commit c468b49
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class ControllerSelector {
private static final Pattern INVITATIONS = Pattern.compile("^/v1/invitations$");
private static final Pattern INVITATION = Pattern.compile("^/v1/invitations/([a-zA-Z0-9]+)$");
private static final Pattern PUBLICATIONS = Pattern.compile("^/v1/ops/publications/(list|get|create|delete|approve|reject)$");
private static final Pattern PUBLICATION_RULES = Pattern.compile("^/v1/ops/publications/rules/list$");

private static final Pattern RESOURCE_OPERATIONS = Pattern.compile("^/v1/ops/resources/(move)$");

Expand Down Expand Up @@ -261,11 +262,17 @@ private static Controller selectPost(Proxy proxy, ProxyContext context, String p
case "create" -> controller::createPublication;
case "delete" -> controller::deletePublication;
case "approve" -> controller::approvePublication;
case "reject" -> controller:: rejectPublication;
case "reject" -> controller::rejectPublication;
default -> null;
};
}

match = match(PUBLICATION_RULES, path);
if (match != null) {
PublicationController controller = new PublicationController(proxy, context);
return controller::listRules;
}

match = match(RESOURCE_OPERATIONS, path);
if (match != null) {
ResourceOperationController controller = new ResourceOperationController(proxy, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.epam.aidial.core.data.Publications;
import com.epam.aidial.core.data.ResourceLink;
import com.epam.aidial.core.data.ResourceType;
import com.epam.aidial.core.data.Rules;
import com.epam.aidial.core.security.AccessService;
import com.epam.aidial.core.security.EncryptionService;
import com.epam.aidial.core.service.LockService;
Expand Down Expand Up @@ -133,6 +134,21 @@ public Future<?> rejectPublication() {
return Future.succeededFuture();
}

public Future<?> listRules() {
context.getRequest()
.body()
.compose(body -> {
String url = ProxyUtil.convertToObject(body, ResourceLink.class).url();
ResourceDescription rule = decodeRule(url);
checkRuleAccess(rule);
return vertx.executeBlocking(() -> publicationService.listRules(rule));
})
.onSuccess(rules -> context.respond(HttpStatus.OK, new Rules(rules)))
.onFailure(error -> respondError("Can't list rules", error));

return Future.succeededFuture();
}

private void respondError(String message, Throwable error) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
String body = null;
Expand Down Expand Up @@ -172,6 +188,25 @@ private ResourceDescription decodePublication(String path, boolean allowPublic)
return resource;
}

private ResourceDescription decodeRule(String path) {
try {
if (!path.startsWith(BlobStorageUtil.PUBLIC_LOCATION)) {
throw new IllegalArgumentException();
}

String folder = path.substring(BlobStorageUtil.PUBLIC_LOCATION.length());
ResourceDescription resource = ResourceDescription.fromEncoded(ResourceType.RULES, BlobStorageUtil.PUBLIC_BUCKET, BlobStorageUtil.PUBLIC_LOCATION, folder);

if (!resource.isFolder()) {
throw new IllegalArgumentException();
}

return resource;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid resource: " + path, e);
}
}

private void checkAccess(ResourceDescription resource, boolean allowUser) {
boolean hasAccess = accessService.hasAdminAccess(context);

Expand All @@ -184,4 +219,10 @@ private void checkAccess(ResourceDescription resource, boolean allowUser) {
throw new HttpException(HttpStatus.FORBIDDEN, "Forbidden resource: " + resource.getUrl());
}
}

private void checkRuleAccess(ResourceDescription rule) {
if (!accessService.hasReadAccess(rule, context)) {
throw new HttpException(HttpStatus.FORBIDDEN, "Forbidden resource: " + rule.getUrl());
}
}
}
7 changes: 7 additions & 0 deletions src/main/java/com/epam/aidial/core/data/Rules.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.epam.aidial.core.data;

import java.util.List;
import java.util.Map;

public record Rules(Map<String, List<Rule>> rules) {
}
37 changes: 26 additions & 11 deletions src/main/java/com/epam/aidial/core/service/PublicationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
Expand Down Expand Up @@ -56,6 +57,9 @@ public class PublicationService {

private static final Set<ResourceType> ALLOWED_RESOURCES = Set.of(ResourceType.FILE, ResourceType.CONVERSATION, ResourceType.PROMPT);

/**
* Key is updated time from the metadata. Value is decoded map (folder path, list of rules).
*/
private final AtomicReference<Pair<Long, Map<String, List<Rule>>>> cachedRules = new AtomicReference<>();
private final EncryptionService encryption;
private final ResourceService resources;
Expand Down Expand Up @@ -105,6 +109,27 @@ public void filterForbidden(ProxyContext context, ResourceDescription folder, Re
metadata.setItems(filtered);
}

public Map<String, List<Rule>> listRules(ResourceDescription resource) {
if (!resource.isFolder() || !resource.isPublic()) {
throw new IllegalArgumentException("Bad rule url: " + resource.getUrl());
}

Map<String, List<Rule>> rules = getCachedRules();
Map<String, List<Rule>> result = new TreeMap<>();

while (resource != null) {
String url = ruleUrl(resource);;
List<Rule> list = rules.get(url);
resource = resource.getParent();

if (list != null) {
result.put(url, list);
}
}

return result;
}

public Collection<Publication> listPublications(ResourceDescription resource) {
if (resource.getType() != ResourceType.PUBLICATION || !resource.isRootFolder()) {
throw new IllegalArgumentException("Bad publication url: " + resource.getUrl());
Expand Down Expand Up @@ -564,17 +589,7 @@ private static String encodePublications(Map<String, Publication> publications)

private static Map<String, List<Rule>> decodeRules(String json) {
Map<String, List<Rule>> rules = ProxyUtil.convertToObject(json, RULES_TYPE);

if (rules == null) {
Rule rule = new Rule();
rule.setSource("roles");
rule.setFunction(Rule.Function.TRUE);
rule.setTargets(List.of());
rules = new LinkedHashMap<>();
rules.put(PUBLIC_LOCATION, List.of(rule));
}

return rules;
return (rules == null) ? new LinkedHashMap<>() : rules;
}

private static String encodeRules(Map<String, List<Rule>> rules) {
Expand Down
85 changes: 85 additions & 0 deletions src/test/java/com/epam/aidial/core/PublicationApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -518,4 +518,89 @@ void testPublicationList() {
}
""");
}

@Test
void listRules() {
Response response = operationRequest("/v1/ops/publications/rules/list", """
{"url": ""}
""");
verify(response, 400);

response = operationRequest("/v1/ops/publications/rules/list", """
{"url": "public"}
""");
verify(response, 400);

response = operationRequest("/v1/ops/publications/rules/list", """
{"url": "public/"}
""");
verifyJson(response, 200, """
{
"rules" : { }
}
""");

response = operationRequest("/v1/ops/publications/rules/list", """
{"url": "public/"}
""", "authorization", "user");
verifyJson(response, 200, """
{
"rules" : { }
}
""");

response = operationRequest("/v1/ops/publications/rules/list", """
{"url": "public/"}
""", "authorization", "admin");
verifyJson(response, 200, """
{
"rules" : { }
}
""");

response = resourceRequest(HttpMethod.PUT, "/my/folder/conversation", CONVERSATION_BODY_1);
verify(response, 200);

response = operationRequest("/v1/ops/publications/create", PUBLICATION_REQUEST.formatted(bucket, bucket));
verify(response, 200);

response = operationRequest("/v1/ops/publications/approve", PUBLICATION_URL, "authorization", "admin");
verify(response, 200);


response = operationRequest("/v1/ops/publications/rules/list", """
{"url": "public/folder/"}
""", "authorization", "user");
verifyJson(response, 200, """
{
"rules" : {
"public/folder/" : [ {
"function" : "EQUAL",
"source" : "roles",
"targets" : [ "user" ]
} ]
}
}
""");

response = operationRequest("/v1/ops/publications/rules/list", """
{"url": "public/folder/"}
""", "authorization", "admin");
verifyJson(response, 200, """
{
"rules" : {
"public/folder/" : [ {
"function" : "EQUAL",
"source" : "roles",
"targets" : [ "user" ]
} ]
}
}
""");

response = operationRequest("/v1/ops/publications/rules/list", """
{"url": "public/folder/"}
""");
verify(response, 403);
}
}

0 comments on commit c468b49

Please sign in to comment.