diff --git a/engine/pom.xml b/engine/pom.xml index 7d306e5ed..8d014d343 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -163,6 +163,5 @@ slf4j-api 2.0.9 - diff --git a/engine/src/main/java/com/arcadedb/GlobalConfiguration.java b/engine/src/main/java/com/arcadedb/GlobalConfiguration.java index f10572e69..000b81075 100644 --- a/engine/src/main/java/com/arcadedb/GlobalConfiguration.java +++ b/engine/src/main/java/com/arcadedb/GlobalConfiguration.java @@ -179,6 +179,7 @@ public Object call(final Object value) { OIDC_AUTH("arcadedb.oidcAuth", SCOPE.SERVER, "Use OIDC Auth instead of basic", Boolean.class, true), KEYCLOAK_ROOT_URL("arcadedb.keycloakRootUrl", SCOPE.SERVER, "Keycloak root URL", String.class, "http://df-keycloak:8080"), + DF_CLASSIFICATION_URL("arcadedb.classificationRootUrl", SCOPE.SERVER, "DF-classification root URL", String.class, "http://localhost:8000"), KEYCLOAK_ADMIN_USERNAME("arcadedb.keycloakAdminUsername", SCOPE.SERVER, "Keycloak admin username", String.class, "admin"), KEYCLOAK_ADMIN_PASSWORD("arcadedb.keycloakAdminPassword", SCOPE.SERVER, "Keycloak admin password", String.class, ""), KEYCLOAK_CLIENT_ID("arcadedb.keycloakClientId", SCOPE.SERVER, "Keycloak client ID", String.class, "df-backend"), diff --git a/engine/src/main/java/com/arcadedb/database/DocumentValidator.java b/engine/src/main/java/com/arcadedb/database/DocumentValidator.java index fd0b62547..dfc4a6d0f 100644 --- a/engine/src/main/java/com/arcadedb/database/DocumentValidator.java +++ b/engine/src/main/java/com/arcadedb/database/DocumentValidator.java @@ -25,6 +25,7 @@ import com.arcadedb.schema.Property; import com.arcadedb.schema.Type; import com.arcadedb.security.AuthorizationUtils; +import com.arcadedb.security.DataFabricClassificationClient; import com.arcadedb.security.SecurityDatabaseUser; import com.arcadedb.serializer.json.JSONObject; @@ -52,6 +53,7 @@ public static void verifyDocumentClassificationValidForDeployment(String toCheck if (!classificationOptions.containsKey(toCheck)) throw new ValidationException("Classification must be one of " + classificationOptions); + // TODO: Do not default to S. App should not come up if no deployment classification is set. var deploymentClassification = System.getProperty("deploymentClassification", "S"); if (classificationOptions.get(deploymentClassification) < classificationOptions.get(toCheck)) throw new ValidationException("Classification " + toCheck + " is not allowed in this deployment"); @@ -61,78 +63,59 @@ public static void verifyDocumentClassificationValidForDeployment(String toCheck } public static void validateClassificationMarkings(final MutableDocument document, - SecurityDatabaseUser securityDatabaseUser, RecordAction action) { + SecurityDatabaseUser securityDatabaseUser, RecordAction action) throws ValidationException { if (document == null) { - throw new ValidationException("Document is null"); + throw new ValidationException("Document is null!"); } + String message = "Validating classification markings on " + document.toJSON(true) + + ". CRUD op '" + action + "'..."; + LogManager.instance().log(DocumentValidator.class, Level.INFO, message); + if (document instanceof MutableEmbeddedDocument) { + LogManager.instance().log(DocumentValidator.class, Level.INFO, + "Document is a MutableEmbeddedDocument. Skipping validation."); return; } if (document.getRecordType() == EmbeddedDocument.RECORD_TYPE) { + LogManager.instance().log(DocumentValidator.class, Level.INFO, + "Document is a RECORD_TYPE. Skipping validation."); return; } // Skip validation checks if classification validation is disabled for the database if (!document.getDatabase().getSchema().getEmbedded().isClassificationValidationEnabled()) { + LogManager.instance().log(DocumentValidator.class, Level.INFO, + "Classification validation is disabled on database. Skipping validation."); return; } - boolean validSources = false; - // validate sources, if present - if (document.has(MutableDocument.SOURCES_ARRAY_ATTRIBUTE) && !document.toJSON().getJSONArray(MutableDocument.SOURCES_ARRAY_ATTRIBUTE).isEmpty()) { - validateSources(document, securityDatabaseUser, action); - validSources = true; - } - - if (document.has(MutableDocument.CLASSIFICATION_PROPERTY) - && document.toJSON().getJSONObject(MutableDocument.CLASSIFICATION_PROPERTY).has("components") && document.toJSON().getJSONObject(MutableDocument.CLASSIFICATION_PROPERTY).getJSONObject("components").has(MutableDocument.CLASSIFICATION_PROPERTY)) { - - var classificationMarkings = document.toJSON().getJSONObject(MutableDocument.CLASSIFICATION_PROPERTY).getJSONObject("components") - .getString(MutableDocument.CLASSIFICATION_GENERAL_PROPERTY); - - if (classificationMarkings.trim().isEmpty()) { - throw new ValidationException("Classification " + classificationMarkings + " is not valid"); - } - - // TODO handle SBU, LES, etc. - String classification = classificationMarkings; - if (classificationMarkings.contains("//")) { - classification = classificationMarkings.substring(0, classificationMarkings.indexOf("//")); - } + // TODO: Add sources validation back in. - // Validate the user can set the classification of the document. Can't create higher than what you can access. - if (!AuthorizationUtils.checkPermissionsOnDocument(document, securityDatabaseUser, action)) { - throw new ValidationException("User cannot set classification markings on documents higher than or outside their current access."); - } - - try { - var databaseClassification = document.getDatabase().getSchema().getEmbedded().getClassification(); - verifyDocumentClassificationValidForDeployment(classification, databaseClassification); - } catch (IllegalArgumentException e) { - throw new ValidationException("Invalid classification: " + classification); - } - - var classificationObj = new JSONObject(document.get(MutableDocument.CLASSIFICATION_PROPERTY).toString()); - - var nonSystemPropsCount = document.getPropertyNames().stream() - .filter(prop -> !prop.startsWith("@")) - .filter(prop -> !MutableDocument.CUSTOM_SYSTEM_PROPERTIES.contains(prop)) - .count(); + if (!document.has(MutableDocument.CLASSIFICATION_PROPERTY)) { + throw new ValidationException("Document has no classification property!"); + } - // TODO this check only matters if there are non system attributes on the object. + if (!document.toJSON().getJSONObject(MutableDocument.CLASSIFICATION_PROPERTY).has("components")) { + throw new ValidationException("Document has no classification.components property!"); + } - // TODO reenable attribute classification checks.s - // if (nonSystemPropsCount > 0 && !classificationObj.has(MutableDocument.CLASSIFICATION_ATTRIBUTES_PROPERTY)) { - // throw new ValidationException("Missing classification attributes on document"); - // } + var component = document.toJSON().getJSONObject(MutableDocument.CLASSIFICATION_PROPERTY).getJSONObject("components").toString(); + boolean valid = DataFabricClassificationClient.validateDocumentClassification(component); + if (!valid) { + throw new ValidationException("Document has no valid classification defined!"); + } + // TODO handle SBU, LES, etc. - validateAttributeClassificationTagging(document, classificationObj.getJSONObject(MutableDocument.CLASSIFICATION_ATTRIBUTES_PROPERTY), securityDatabaseUser, action); - } else if (!validSources){ - throw new ValidationException("Missing overall classification data on document"); + // Validate the user can set the classification of the document. Can't create higher than what you can access. + if (!AuthorizationUtils.checkPermissionsOnDocument(document, securityDatabaseUser, action)) { + throw new ValidationException("User cannot set classification markings on documents higher than or outside their current access."); } + + var classificationObj = new JSONObject(document.get(MutableDocument.CLASSIFICATION_PROPERTY).toString()); + validateAttributeClassificationTagging(document, classificationObj.getJSONObject(MutableDocument.CLASSIFICATION_ATTRIBUTES_PROPERTY), securityDatabaseUser, action); } private static void validateAttributeClassificationTagging(final MutableDocument document, final JSONObject attributes, SecurityDatabaseUser securityDatabaseUser, RecordAction action) { @@ -160,7 +143,7 @@ private static void validateAttributeClassificationTagging(final MutableDocument var attributeClassification = value.toString(); if (attributeClassification != null && !attributeClassification.trim().isEmpty()) { - // This call might not be necessary after integrating with df-classification. + // TODO: This call might not be necessary after integrating with df-classification. verifyDocumentClassificationValidForDeployment( attributeClassification, document.getDatabase().getSchema().getEmbedded().getClassification()); @@ -168,7 +151,21 @@ private static void validateAttributeClassificationTagging(final MutableDocument // Taking the attribute's classification value and fetching its numerical representation. var inputIndex = AuthorizationUtils.classificationOptions.get(attributeClassification); var userClearance = securityDatabaseUser.getClearanceForCountryOrTetragraphCode("USA"); - var userClearanceIndex = AuthorizationUtils.classificationOptions.get(userClearance); + + // TODO temp fix. refactor this to depend on the same deployment config specifying classification/clearance options. + int userClearanceIndex = 0; + + switch(userClearance.toUpperCase()) { + case "UNCLASSIFIED": userClearanceIndex = 0; + case "CUI": userClearanceIndex = 1; + case "CONFIDENTIAL": userClearanceIndex = 2; + case "SECRET": userClearanceIndex = 3; + case "TOP SECRET": userClearanceIndex = 4; + } + + // var userClearanceIndex = AuthorizationUtils.classificationOptions.get(userClearance); + + LogManager.instance().log(DocumentValidator.class, Level.INFO, inputIndex + "_" + userClearanceIndex); if (inputIndex > userClearanceIndex) { throw new ValidationException( @@ -192,6 +189,8 @@ private static void validateAttributeClassificationTagging(final MutableDocument */ private static void validateSources(final MutableDocument document, SecurityDatabaseUser securityDatabaseUser, RecordAction action) { var sources = document.toJSON().getJSONArray(MutableDocument.SOURCES_ARRAY_ATTRIBUTE); + + LogManager.instance().log(DocumentValidator.class, Level.INFO, "Validating source classifications..."); sources.forEach(obj -> { var jo = (JSONObject) obj; diff --git a/engine/src/main/java/com/arcadedb/database/MutableDocument.java b/engine/src/main/java/com/arcadedb/database/MutableDocument.java index cdadfa026..e32556178 100644 --- a/engine/src/main/java/com/arcadedb/database/MutableDocument.java +++ b/engine/src/main/java/com/arcadedb/database/MutableDocument.java @@ -24,7 +24,6 @@ import com.arcadedb.schema.Property; import com.arcadedb.schema.Type; import com.arcadedb.security.SecurityDatabaseUser; -import com.arcadedb.security.ACCM.AccmProperty; import com.arcadedb.serializer.json.JSONObject; import java.lang.reflect.*; @@ -76,8 +75,6 @@ public synchronized boolean isDirty() { public synchronized void setBuffer(final Binary buffer) { super.setBuffer(buffer); dirty = false; - // map = null; // AVOID RESETTING HERE FOR INDEXES THAT CAN LOOKUP UP FOR FIELDS - // CAUSING AN UNMARSHALLING } @Override @@ -137,39 +134,6 @@ public synchronized JSONObject toJSON(final boolean includeMetadata) { return result; } - /** - * Convert externally configurable required proeprty defintiions into arcade internal required properties. - * Not used at the moment, leaving short term for possible use. - * @return - */ - public List getAccmProperties() { - - List accmProperties = new ArrayList<>(); - List properties = new ArrayList<>(); - - for (AccmProperty accmProperty : accmProperties) { - DocumentType parentType = database.getSchema().getType(accmProperty.parentType()); - - if (parentType == null) { - continue; - } - - Property p = new Property(parentType, - accmProperty.name(), accmProperty.dataType()); - p.setMandatory(accmProperty.required()); - p.setNotNull(accmProperty.notNull()); - - if (!accmProperty.options().isEmpty()) { - p.setRegexp(String.join("|", accmProperty.options())); - } else if (accmProperty.validationRegex() != null) { - p.setRegexp(accmProperty.validationRegex()); - } - properties.add(p); - } - - return properties; - } - /** * Triggers the native required property valiation of arcade, as well as the one time ACCM validation. * ACCM validation follows a different recursive type checking pattern than arcade, so it is done separately. @@ -227,29 +191,6 @@ public void validate() throws ValidationException { DocumentValidator.validate(this); } - private MutableEmbeddedDocument getEmbeddedDocumentToValidate(MutableDocument mutableDocument, final String name) { - // form of embeddedDoc.property - // could be embeddedDoc1.embeddedDoc2.property - - if (name == null || name == "" || !name.contains(".")) { - return (MutableEmbeddedDocument) mutableDocument; - } - - String embeddedDocName = name.substring(0, name.indexOf(".")); - String remainingPropertyName = name.substring(name.indexOf(".") + 1); - final Object fieldValue = mutableDocument.get(embeddedDocName); - - if (fieldValue != null && fieldValue instanceof MutableEmbeddedDocument) { - if (remainingPropertyName.split(".").length > 1) { - return getEmbeddedDocumentToValidate((MutableDocument) fieldValue, remainingPropertyName); - } else { - return (MutableEmbeddedDocument) fieldValue; - } - } else { - throw new ValidationException("Document classification incomplete for property: " + name); - } - } - @Override public synchronized boolean has(final String propertyName) { checkForLazyLoadingProperties(); diff --git a/engine/src/main/java/com/arcadedb/security/ACCM/Argument.java b/engine/src/main/java/com/arcadedb/security/ACCM/Argument.java index 6cae3833d..8cf855e46 100644 --- a/engine/src/main/java/com/arcadedb/security/ACCM/Argument.java +++ b/engine/src/main/java/com/arcadedb/security/ACCM/Argument.java @@ -181,6 +181,7 @@ private boolean evaluateInternal(JSONObject json) { // check if this.value is a list if (this.value instanceof List) { for (Object val : (List) this.value) { + LogManager.instance().log(this, Level.INFO, "val type: " + val.getClass().getName()); if (val.equals(docFieldValue)) { return true; } diff --git a/engine/src/main/java/com/arcadedb/security/AuthorizationUtils.java b/engine/src/main/java/com/arcadedb/security/AuthorizationUtils.java index 797ce4018..5aa99850f 100644 --- a/engine/src/main/java/com/arcadedb/security/AuthorizationUtils.java +++ b/engine/src/main/java/com/arcadedb/security/AuthorizationUtils.java @@ -1,8 +1,6 @@ package com.arcadedb.security; -import java.util.List; import java.util.Map; -import java.util.Set; import java.util.logging.Level; import com.arcadedb.database.Document; @@ -10,13 +8,7 @@ import com.arcadedb.database.EmbeddedDatabase.RecordAction; import com.arcadedb.exception.ValidationException; import com.arcadedb.log.LogManager; -import com.arcadedb.serializer.json.JSONArray; import com.arcadedb.serializer.json.JSONObject; -import com.arcadedb.security.ACCM.Argument; -import com.arcadedb.security.ACCM.ArgumentOperator; -import com.arcadedb.security.ACCM.Expression; -import com.arcadedb.security.ACCM.ExpressionOperator; -import com.arcadedb.security.ACCM.GraphType; import com.arcadedb.security.ACCM.TypeRestriction; public class AuthorizationUtils { @@ -29,20 +21,12 @@ public class AuthorizationUtils { // todo move to opa since it needs to be configurable public static final Map classificationOptions = Map.of("U", 0, "CUI", 1, "C", 2, "S", 3, "TS", 4); - // private static TypeRestriction getTypeRestriction() { - // Argument classificationArg = new Argument("classificationTest", ArgumentOperator.ANY_OF, new String[]{"U", "S"}); - // Argument releasableToArg = new Argument("releaseableTo", ArgumentOperator.ANY_IN, new String[]{"USA"}); - - // Expression expression = new Expression(ExpressionOperator.AND, classificationArg, releasableToArg); - // TypeRestriction typeRestriction = new TypeRestriction("beta", GraphType.VERTEX, List.of(expression), List.of(expression), List.of(expression), List.of(expression)); - // return typeRestriction; - // } - /** * Checks if the provided classification is permitted given the deployment classification. * @param classification * @return */ + @Deprecated // Possibly remove. Handled by df-classification. public static boolean isClassificationValidForDeployment(final String classification) { if (classification == null || classification.isEmpty()) return false; @@ -60,135 +44,6 @@ public static boolean isClassificationValidForDeployment(final String classifica return classificationOptions.get(deploymentClassification) >= classificationOptions.get(c); } - /** - * Checks if the user is authorized to view the resource, taking into account the user's clearance, attributes, the full resouce ACCM markings. - * @param userClearance - * @param nationality - * @param resourceClassification - * @return - */ - // public static boolean isUserAuthorizedForResourceMarking(final String userClearance, final String nationality, final String tetragraphs, - // final String resourceClassification) { - - // // TODO are tetrapgrahs that group countries auto applicable to users of those countires, or do users need explicit authoirzation for their data? - // var processedResourceClassification = resourceClassification.replace("FVEY", "USA,AUS,CAN,GBR,NZL"); - // if (!isClearanceAuthorized(userClearance, processedResourceClassification)) { - // // System.out.println("blocked by clearance"); - // return false; - // } - - // // NO FORN takes precedence over REL TO? - // if (isBlockedByNoForn(nationality, processedResourceClassification)) { - // // System.out.println("blocked by noforn"); - // return false; - // } - - // if (isBlockedByReleaseableTo(nationality, tetragraphs, processedResourceClassification)) { - // // System.out.println("blocked by noforn"); - // return false; - // } - - // return true; - // } - - /** - * Checks if the user has sufficient clearance to view the resource, outside of any other ACCM restrictions. - * @param userClearance - * @param resourceClassificationMarking - * @return - */ - // public static boolean isClearanceAuthorized(final String userClearance, final String resourceClassificationMarking) { - // if (userClearance == null || userClearance.isEmpty()) - // return false; - - // String processedUserClearance = userClearance.toUpperCase(); - // processedUserClearance = processedUserClearance.trim(); - - // String processedResourceClearance = getClassificationFromResourceMarkings(resourceClassificationMarking); - // processedResourceClearance = processedResourceClearance.trim(); - - // if (!classificationOptions.containsKey(processedUserClearance)) - // throw new IllegalArgumentException("Clearance must be one of " + classificationOptions); - - // if (resourceClassificationMarking == null || resourceClassificationMarking.isEmpty()) - // return false; - - // if (!classificationOptions.containsKey(processedResourceClearance)) - // throw new IllegalArgumentException("Invalid resource classification " + processedResourceClearance); - - // return classificationOptions.get(processedUserClearance) >= classificationOptions.get(processedResourceClearance); - // } - - /** - * Checks if the classification markings contains a releaseable to block, and if so, checks if the user - * belongs to an allowable nationality. - * @param nationality - * @return - */ - // private static boolean isBlockedByReleaseableTo(final String nationality, final String tetragraphs, - // final String resourceClassificationMarkings) { - // // TODO add support for banner barking AUTHORIZED FOR RELEASE TO - // if (resourceClassificationMarkings.contains("REL TO")) { - // if (nationality == null || nationality.isEmpty()) { - // return true; - // } - - // var releaseableTo = resourceClassificationMarkings.substring(resourceClassificationMarkings.indexOf("REL TO")); - // if (releaseableTo.contains("//")) { - // releaseableTo.substring(0, releaseableTo.indexOf("//")); - // } - - // releaseableTo = releaseableTo.substring("REL TO".length() + 1); - // releaseableTo = releaseableTo.replaceAll(" ", ""); - - // if (releaseableTo.contains(",")) { - // return !Set.of(releaseableTo.split(",")).stream().map(r -> r.toString()).anyMatch(r -> { - // if (r.trim().isEmpty()) { - // return false; - // } else if (r.length() == 3) { - // return r.equals(nationality); - // } else if (tetragraphs != null && !tetragraphs.isEmpty() && r.length() == 4) { - // return tetragraphs.contains(r); - // } - // return false; - // }); - // } else { - // return !releaseableTo.equals(nationality); - // } - // } - - // return false; - // } - - /** - * Checks if the user is unable to see the resource due to a NOFORN marking. - * @param nationality - * @param resourceClassification - * @return - */ - // private static boolean isBlockedByNoForn(final String nationality, final String resourceClassification) { - // return (containsBlockText("NOFORN", resourceClassification) || containsBlockText("NF", resourceClassification)) - // && (nationality == null || !nationality.equals("USA")); - // } - - /** - * Checks if the resource classification markings contains the text between single forward slash blocks. - * @param text - * @param resourceClassification - * @return - */ - private static boolean containsBlockText(final String text, final String resourceClassification) { - if (resourceClassification.contains("/")) { - return Set.of(resourceClassification.split("/")).contains(text); - } else if (resourceClassification.equals(text)) { - return true; - } else { - // if the string ends with the text, or starts with the text with a space after it, or contains the text with a space before and after it - return resourceClassification.endsWith(" " + text) || resourceClassification.endsWith("-" + text) - || resourceClassification.startsWith(text + " ") || resourceClassification.contains(" " + text + " "); - } - } - /** * Remove the parenthesis and any spaces from the classification string that may be a part of a portion marking. * @param resourceClassification @@ -221,10 +76,6 @@ private static String getClassificationFromResourceMarkings(final String resourc return classification; } - public static boolean checkPermissionsOnDocumentToCreate(final Document document, final SecurityDatabaseUser currentUser) { - return checkPermissionsOnDocument(document, currentUser, RecordAction.CREATE); - } - public static boolean checkPermissionsOnDocumentToRead(final Document document, final SecurityDatabaseUser currentUser) { // log duration in ns long startTime = System.nanoTime(); @@ -237,39 +88,33 @@ public static boolean checkPermissionsOnDocumentToRead(final Document document, return result; } - public static boolean checkPermissionsOnDocumentToUpdate(final Document document, final SecurityDatabaseUser currentUser) { - return checkPermissionsOnDocument(document, currentUser, RecordAction.UPDATE); - } - - public static boolean checkPermissionsOnDocumentToDelete(final Document document, final SecurityDatabaseUser currentUser) { - return checkPermissionsOnDocument(document, currentUser, RecordAction.DELETE); - } - // split out crud actions public static boolean checkPermissionsOnDocument(final Document document, final SecurityDatabaseUser currentUser, final RecordAction action) { + String message = "Checking permissions on document " + document.toJSON(true) + + " against user '" + currentUser + "' and CRUD op '" + action + "'..."; + LogManager.instance().log(AuthorizationUtils.class, Level.INFO, message); + // Allow root user to access all documents for HA syncing between nodes if (currentUser.getName().equals("root")) { return true; } - LogManager.instance().log(AuthorizationUtils.class, Level.WARNING, "check perms on doc: " + action); - - // TODO short term - check classification, attribution on document - - // TODO long term - replace with filtering by low classification of related/linked document. - // Expensive to do at read time. Include linkages and classification at write time? - // Needs performance testing and COA analysis. - // TODO prevent data stewards from seeing data outside their access if (currentUser.isServiceAccount() || currentUser.isDataSteward(document.getTypeName())) { return true; } - // If classification is not enabled on database it does not make sense to keep going. is not enabled. + // If classification is not enabled on database it does not make sense to keep going. if (!document.getDatabase().getSchema().getEmbedded().isClassificationValidationEnabled()) { return true; } + // TODO short term - check classification, attribution on document + + // TODO long term - replace with filtering by low classification of related/linked document. + // Expensive to do at read time. Include linkages and classification at write time? + // Needs performance testing and COA analysis. + // Prevent users from accessing documents that have not been marked, unless we're evaluating a user's permission to a doc that hasn't been created yet. // The action where we do not want to raise exception is on create and update. The update is included because on edge creation there is technically // an update in place where we actually link two vertices. @@ -305,29 +150,16 @@ public static boolean checkPermissionsOnDocument(final Document document, final throw new ValidationException("Missing type restrictions for user"); } - // if (document.toJSON().has("sources")) { - // // Combining all results - // boolean results = true; - // JSONArray sources = document.toJSON().getJSONArray("sources"); - // for (int i = 0; i < sources.length(); i++) { - // results &= evalutateAccm(typeRestriction, sources.getJSONObject(i), action); - // } - // return results; - // } else if (document.toJSON().has("classification")) { - // return evalutateAccm(typeRestriction, document.toJSON().getJSONObject("classification"), action); - // } else { - // throw new ValidationException("Misformated classification payload"); - // } - if (document.has("classification")) { var map = document.getMap("classification"); // map to json JSONObject classification = new JSONObject(map); + LogManager.instance().log(AuthorizationUtils.class, Level.INFO, + "Authorizing classifications " + classification.toString() + " against type restrictions: " + typeRestriction); return evalutateAccm(typeRestriction, classification, action); } - //) // TODO add sources back in return true; diff --git a/engine/src/main/java/com/arcadedb/security/DataFabricClassificationClient.java b/engine/src/main/java/com/arcadedb/security/DataFabricClassificationClient.java new file mode 100644 index 000000000..2bea9061a --- /dev/null +++ b/engine/src/main/java/com/arcadedb/security/DataFabricClassificationClient.java @@ -0,0 +1,58 @@ +package com.arcadedb.security; + +import com.arcadedb.GlobalConfiguration; +import com.arcadedb.log.LogManager; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.logging.Level; + +public class DataFabricClassificationClient { + + /** + * Returns true if the given classification document is a valid classification structure for this data fabric + * deployment. If the given classification is greater than the global configuration, then is it still + * considered invalid. + * + * @return true if the classification structure is valid and meets classification level of the data fabric deployment + */ + public static boolean validateDocumentClassification(String classification) { + String urlString = String.format("%s/api/v1/classification/validate", GlobalConfiguration.DF_CLASSIFICATION_URL.getValueAsString()); + + URL url = null; + try { + url = new URL(urlString); + LogManager.instance().log(DataFabricClassificationClient.class, Level.INFO, "Validating " + classification); + LogManager.instance().log(DataFabricClassificationClient.class, Level.INFO, "Validation URL " + urlString); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + try { + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setDoOutput(true); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + + OutputStream os = conn.getOutputStream(); + os.write(classification.getBytes()); + os.flush(); + + if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { + LogManager.instance().log( + DataFabricClassificationClient.class, + Level.WARNING, + "Validation failed!"); + return false; + } + + conn.disconnect(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return true; + } +} diff --git a/server/src/main/java/com/arcadedb/server/DataFabricRestClient.java b/server/src/main/java/com/arcadedb/server/DataFabricRestClient.java index f1c9aea99..96d0f1f15 100644 --- a/server/src/main/java/com/arcadedb/server/DataFabricRestClient.java +++ b/server/src/main/java/com/arcadedb/server/DataFabricRestClient.java @@ -8,7 +8,6 @@ import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -63,23 +62,6 @@ protected static String loginAndGetEncodedAccessString() { return tokenJO.getString("access_token"); } - // todo this can go away - public static String getAccessTokenJsonFromResponse(String token) { - if (token != null) { - JSONObject tokenJO = new JSONObject(token); - String accessTokenString = tokenJO.getString("access_token"); - String encodedString = accessTokenString.substring(accessTokenString.indexOf(".") + 1, - accessTokenString.lastIndexOf(".")); - byte[] decodedBytes = Base64.getDecoder().decode(encodedString); - String decodedString = new String(decodedBytes); - log.debug("getAccessTokenFromResponse {}", decodedString); - - return decodedString; - } - - return null; - } - protected static String postUnauthenticatedAndGetResponse(String url, Map formData) { HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) @@ -104,20 +86,6 @@ public static String postAuthenticatedAndGetResponse(String url, String jsonPayl return sendAndGetResponse(request); } - // todo this can be removed - protected static String putAuthenticatedAndGetResponse(String url, String jsonPayload) { - String accessTokenString = loginAndGetEncodedAccessString(); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .PUT(HttpRequest.BodyPublishers.ofString(jsonPayload)) - .header("Content-Type", "application/json") - .header("Authorization", "Bearer " + accessTokenString) - .build(); - - return sendAndGetResponse(request); - } - protected static String sendAndGetResponse(HttpRequest request) { HttpClient client = HttpClient.newHttpClient(); try { diff --git a/server/src/main/java/com/arcadedb/server/security/ServerSecurity.java b/server/src/main/java/com/arcadedb/server/security/ServerSecurity.java index d56e94302..1240e0287 100644 --- a/server/src/main/java/com/arcadedb/server/security/ServerSecurity.java +++ b/server/src/main/java/com/arcadedb/server/security/ServerSecurity.java @@ -439,7 +439,7 @@ private ServerSecurityUser getOrCreateUser(final String username) { // 6. get user attribtues for ACCM Map attributes = result.getAttributes(); - log.debug("OPA policy attributes are " + attributes); + log.info("OPA policy attributes are {}", attributes); ServerSecurityUser serverSecurityUser = new ServerSecurityUser(server, userJson, arcadeRoles, attributes, System.currentTimeMillis(), result.getPolicy()); @@ -466,7 +466,7 @@ private ServerSecurityUser getOrCreateUser(final String username) { // matches and database schema last updated is older than expiration time. public ServerSecurityUser authenticate(final String userName, final String databaseName) { - + LogManager.instance().log(this, Level.INFO, "Authenticating '%s' to database '%s'", userName, databaseName); final ServerSecurityUser su = getOrCreateUser(userName); if (su == null) { throw new ServerSecurityException("User not valid"); diff --git a/server/src/main/java/com/arcadedb/server/security/oidc/OpaClient.java b/server/src/main/java/com/arcadedb/server/security/oidc/OpaClient.java index ffd246588..8d02f72e3 100644 --- a/server/src/main/java/com/arcadedb/server/security/oidc/OpaClient.java +++ b/server/src/main/java/com/arcadedb/server/security/oidc/OpaClient.java @@ -66,6 +66,29 @@ public static OpaResponse getPolicy(String username, Set databaseNames) } var hasAccessToNoForn = opaPolicyJson.has(NOFORN) && opaPolicyJson.get(NOFORN).asBoolean(); + List completeClassifications = new ArrayList<>(); + // Add the full name of classifications to the list. Prevents documents with BlackCape classifications from being blocked. + for (String classification : authorizedClassificationsList) { + switch (classification) { + case "U": + completeClassifications.add("UNCLASSIFIED"); + break; + case "C": + completeClassifications.add("CONFIDENTIAL"); + break; + case "S": + completeClassifications.add("SECRET"); + break; + case "TS": + completeClassifications.add("TOP SECRET"); + break; + default: + LogManager.instance().log(OpaClient.class, Level.INFO, "No valid short-hand classifications found."); + break; + } + } + + authorizedClassificationsList.addAll(completeClassifications); // relto List relTo = new ArrayList<>(); // TODO Arrays.asList(responseJson.get("releasable_to").asText().split(","));