Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

import org.apache.commons.lang3.StringUtils;
import java.util.List;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.WebMarkupContainer;
Expand All @@ -51,7 +52,9 @@
public class BaseUrlConnectorStepPanel extends AbstractFormWizardStepPanel<ConnectorDevelopmentDetailsModel> {

private static final String PANEL_TYPE = "cdw-base-url";
public static final ItemName PROPERTY_ITEM_NAME = ItemName.from("", "baseAddress");
public static final ItemName BASE_ADDRESS_ITEM_NAME = ItemName.from("", "baseAddress");
public static final ItemName DEVELOPMENT_MODE_ITEM_NAME = ItemName.from("", "developmentMode");
public static final ItemName SCIM_BASE_URL_ITEM_NAME = ItemName.from("", "scimBaseUrl");

private static final String ID_AI_ALERT = "aiAlert";

Expand Down Expand Up @@ -83,12 +86,14 @@ protected IModel<? extends PrismContainerWrapper> getContainerFormModel() {
protected void onInitialize() {
super.onInitialize();
try {
PrismPropertyValueWrapper<Object> suggestedValue = getDetailsModel().getObjectWrapper().findProperty(
ItemPath.create(ConnectorDevelopmentType.F_APPLICATION, ConnDevApplicationInfoType.F_BASE_API_ENDPOINT)).getValue();
if (StringUtils.isNotEmpty((String) suggestedValue.getRealValue())) {
PrismPropertyValueWrapper<String> configurationValue = (PrismPropertyValueWrapper<String>) getContainerFormModel().getObject().findProperty(PROPERTY_ITEM_NAME).getValue();
if (StringUtils.isEmpty(configurationValue.getRealValue())) {
configurationValue.setRealValue((String) suggestedValue.getRealValue());
String suggestedUrl = (String) getDetailsModel().getObjectWrapper().findProperty(
ItemPath.create(ConnectorDevelopmentType.F_APPLICATION, ConnDevApplicationInfoType.F_BASE_API_ENDPOINT))
.getValue().getRealValue();
if (StringUtils.isNotEmpty(suggestedUrl)) {
ItemName urlField = isScim() ? SCIM_BASE_URL_ITEM_NAME : BASE_ADDRESS_ITEM_NAME;
PrismPropertyValueWrapper<String> fieldValue = (PrismPropertyValueWrapper<String>) getContainerFormModel().getObject().findProperty(urlField).getValue();
if (StringUtils.isEmpty(fieldValue.getRealValue())) {
fieldValue.setRealValue(suggestedUrl);
}
}
} catch (SchemaException e) {
Expand Down Expand Up @@ -186,22 +191,48 @@ protected IModel<?> getSubTextModel() {
}

protected boolean checkMandatory(ItemWrapper wrapper) {
if (QNameUtil.match(wrapper.getItemName(), PROPERTY_ITEM_NAME)) {
return true;
if (isScim()) {
if (QNameUtil.match(wrapper.getItemName(), SCIM_BASE_URL_ITEM_NAME)) {
return true;
}
} else {
if (QNameUtil.match(wrapper.getItemName(), BASE_ADDRESS_ITEM_NAME)) {
return true;
}
}
return wrapper.isMandatory();
}

@Override
protected ItemVisibilityHandler getVisibilityHandler() {
return wrapper -> {
if (QNameUtil.match(wrapper.getItemName(), PROPERTY_ITEM_NAME)) {
return ItemVisibility.AUTO;
if (isScim()) {
if (scimItemNames().stream().anyMatch(name -> QNameUtil.match(wrapper.getItemName(), name))) {
return ItemVisibility.AUTO;
}
} else {
if (QNameUtil.match(wrapper.getItemName(), BASE_ADDRESS_ITEM_NAME)) {
return ItemVisibility.AUTO;
}
}
return ItemVisibility.HIDDEN;
};
}

private List<ItemName> scimItemNames() {
return List.of(SCIM_BASE_URL_ITEM_NAME, DEVELOPMENT_MODE_ITEM_NAME);
}

private boolean isScim() {
try {
PrismPropertyWrapper<ConnDevIntegrationType> integrationType = getDetailsModel().getObjectWrapper().findProperty(
ItemPath.create(ConnectorDevelopmentType.F_CONNECTOR, ConnDevConnectorType.F_INTEGRATION_TYPE));
return ConnDevIntegrationType.SCIM.equals(integrationType.getValue().getRealValue());
} catch (SchemaException e) {
return false;
}
}

@Override
public String getStepId() {
return PANEL_TYPE;
Expand Down Expand Up @@ -236,7 +267,8 @@ public boolean onNextPerformed(AjaxRequestTarget target) {

@Override
public boolean isCompleted() {
ItemName fieldToCheck = isScim() ? SCIM_BASE_URL_ITEM_NAME : BASE_ADDRESS_ITEM_NAME;
return ConnectorDevelopmentWizardUtil.existTestingResourcePropertyValue(
getDetailsModel(), getPanelType(), PROPERTY_ITEM_NAME);
getDetailsModel(), getPanelType(), fieldToCheck);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ protected StringValuesWidgetDetailsDto load() {
values.put(
createStringResource("ConnectorDevelopmentWizardSummaryPanel.baseUrl"),
defineValueModel((String) ConnectorDevelopmentWizardUtil.getTestingResourcePropertyValue(
detailsModel, null, BaseUrlConnectorStepPanel.PROPERTY_ITEM_NAME)));
detailsModel, null, BaseUrlConnectorStepPanel.BASE_ADDRESS_ITEM_NAME)));
values.put(
createStringResource("ConnectorDevelopmentWizardSummaryPanel.testEndpoint"),
defineValueModel((String) ConnectorDevelopmentWizardUtil.getTestingResourcePropertyValue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class ScimRestConfigurationProperties {
public static final ItemName REST_TOKEN_NAME = new ItemName("restTokenName");
public static final ItemName REST_TOKEN_VALUE = new ItemName("restTokenValue");
public static final ItemName BASE_ADDRESS = new ItemName("baseAddress");
public static final ItemName DEVELOPMENT_MODE = new ItemName("developmentMode");
public static final ItemName TRUST_ALL_CERTIFICATES = new ItemName("trustAllCertificates");
public static final ItemName SCIM_BEARER_TOKEN = new ItemName("scimBearerToken");
public static final ItemName SCIM_BASE_URL = new ItemName("scimBaseUrl");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public static ConnectorDevelopmentBackend backendFor(String connectorDevelopment
private static ConnectorDevelopmentBackend backendFor(ConnDevIntegrationType integrationType, ConnectorDevelopmentType connDev, ConnDevBeans beans, Task task, OperationResult result) {
return switch (integrationType) {
case REST -> new RestBackend(beans, connDev, task, result);
case SCIM -> new JsonHalBackend(beans, connDev, task, result);
case SCIM -> new ScimBackend(beans, connDev, task, result);
case DUMMY -> new OfflineBackend(beans, connDev, task, result);
};

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
public class RestBackend extends ConnectorDevelopmentBackend {

private static final long SLEEP_TIME = 5 * 1000L;
private static final JsonNodeFactory JSON_FACTORY = JsonNodeFactory.instance;
protected static final JsonNodeFactory JSON_FACTORY = JsonNodeFactory.instance;


private static final Trace LOGGER = TraceManager.getTrace(ConnectorDevelopmentBackend.class);
Expand Down Expand Up @@ -249,7 +249,7 @@ private String sessionId() {
return developmentObject().getOid();
}

private ServiceClient client() {
protected ServiceClient client() {
return beans.client(sessionId(), this::restoreSession, this::synchronizeSession, result);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package com.evolveum.midpoint.smart.impl.conndev;

import com.evolveum.midpoint.model.api.util.ResourceUtils;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.smart.api.conndev.ScimRestConfigurationProperties;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.smart.impl.conndev.activity.ConnDevBeans;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnDevDocumentationSourceType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorDevelopmentType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ProcessedDocumentationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;

import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Stream;

public class ScimBackend extends RestBackend {

private static final Trace LOGGER = TraceManager.getTrace(ScimBackend.class);

private static final List<String> SCIM_OBJECT_CLASSES = List.of("conndev_ScimSchema", "conndev_ScimResource");

public ScimBackend(ConnDevBeans beans, ConnectorDevelopmentType connDev, Task task, OperationResult result) {
super(beans, connDev, task, result);
}

@Override
public List<ConnDevDocumentationSourceType> discoverDocumentation() {
return super.discoverDocumentation();
}

@Override
public void ensureDocumentationIsProcessed() throws SchemaException, ExpressionEvaluationException, CommunicationException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, PolicyViolationException, ObjectAlreadyExistsException {
super.ensureDocumentationIsProcessed();

refreshScimDocumentation();
}

private void refreshScimDocumentation() throws SchemaException, ExpressionEvaluationException, CommunicationException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, PolicyViolationException, ObjectAlreadyExistsException {
var testingResourceOid = getTestingResourceOid();

if (!isScimDiscoveryConfigured(testingResourceOid)) {
LOGGER.info("Testing resource {} is not configured for SCIM discovery (missing scimBaseUrl or developmentMode), skipping", testingResourceOid);
return;
}

LOGGER.info("Starting SCIM schema discovery from shadows on testing resource {}", testingResourceOid);
ResourceUtils.deleteSchema(testingResourceOid, beans.modelService, task, result);
beans.provisioningService.testResource(testingResourceOid, task, result);

var newScimDocs = SCIM_OBJECT_CLASSES
.stream()
.flatMap(objectClass -> loadShadowsAsDocumentation(testingResourceOid, objectClass).stream())
.map(ProcessedDocumentation::toBean)
.toList();

var mergedDocs = new ArrayList<>(
developmentObject()
.getProcessedDocumentation()
.stream()
.filter(d -> SCIM_OBJECT_CLASSES.stream().noneMatch(c -> d.getUri().startsWith(c)))
.map(ProcessedDocumentationType::clone)
.toList()
);
mergedDocs.addAll(newScimDocs);

var delta = PrismContext.get().deltaFor(ConnectorDevelopmentType.class)
.item(ConnectorDevelopmentType.F_PROCESSED_DOCUMENTATION)
.replaceRealValues(mergedDocs)
.<ConnectorDevelopmentType>asObjectDelta(developmentObject().getOid());
beans.modelService.executeChanges(List.of(delta), null, task, result);
reload();
ensureDocumentationIsUploaded(client().synchronizationClient());
}

private List<ProcessedDocumentation> loadShadowsAsDocumentation(String resourceOid, String objectClassLocalName) {
try {
var objectClass = new QName(SchemaConstants.NS_RI, objectClassLocalName);
var query = PrismContext.get().queryFor(ShadowType.class)
.item(ShadowType.F_RESOURCE_REF).ref(resourceOid)
.and().item(ShadowType.F_OBJECT_CLASS).eq(objectClass)
.build();

var shadows = beans.provisioningService.searchObjects(ShadowType.class, query, null, task, result);
if (shadows.isEmpty()) {
LOGGER.warn("No shadows found for object class {} on resource {}", objectClassLocalName, resourceOid);
return List.of();
}

var serializer = PrismContext.get().jsonSerializer();
var mapper = new ObjectMapper();
var docs = new ArrayList<ProcessedDocumentation>();
for (var shadow : shadows) {
var attrs = shadow.findContainer(ShadowType.F_ATTRIBUTES);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

MidPilot service expects one schema per file / documentation.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

So, implementation should not merge it into one JSON but let it as separated documentation, correct?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

updated

if (attrs != null && !attrs.isEmpty()) {
var json = serializer.serialize(attrs.getValue());
var parsedAttrs = mapper.readTree(json).get("attributes");
var idNode = parsedAttrs.get("id");
var name = idNode != null
? idNode.asText()
: shadow.getOid();
var doc = new ProcessedDocumentation(UUID.randomUUID().toString(), objectClassLocalName + "_" + name);
doc.write(parsedAttrs.toString());
docs.add(doc);
}
}
return docs;
} catch (Exception e) {
throw new SystemException("Could not load shadow documentation for " + objectClassLocalName, e);
}
}

private String getTestingResourceOid() {
var testing = developmentObject().getTesting();
if (testing == null || testing.getTestingResource() == null) {
throw new SystemException("No testing resource configured");
}
return testing.getTestingResource().getOid();
}

private boolean isScimDiscoveryConfigured(String testingResourceOid) {
try {
var resource = beans.modelService.getObject(ResourceType.class, testingResourceOid, null, task, result).asObjectable();
var connectorConfig = ItemPath.create(ResourceType.F_CONNECTOR_CONFIGURATION, SchemaConstants.ICF_CONFIGURATION_PROPERTIES_LOCAL_NAME);

var scimBaseUrl = resource.asPrismObject().findProperty(ItemPath.create(connectorConfig, ScimRestConfigurationProperties.SCIM_BASE_URL));
var developmentMode = resource.asPrismObject().findProperty(ItemPath.create(connectorConfig, ScimRestConfigurationProperties.DEVELOPMENT_MODE));

var hasScimBaseUrl = scimBaseUrl != null && scimBaseUrl.getRealValue() != null
&& !((String) scimBaseUrl.getRealValue()).isBlank();
var hasDevelopmentMode = developmentMode != null && Boolean.TRUE.equals(developmentMode.getRealValue());

return hasScimBaseUrl && hasDevelopmentMode;
} catch (Exception e) {
LOGGER.warn("Could not check configuration on testing resource {}: {}", testingResourceOid, e.getMessage());
return false;
}
}
}
Loading