All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pl.edu.icm.unity.saml.idp.console.SAMLEditorGeneralTab Maven / Gradle / Ivy

There is a newer version: 4.0.5
Show newest version
/*
 * Copyright (c) 2021 Bixbit - Krzysztof Benedyczak. All rights reserved.
 * See LICENCE.txt file for licensing information.
 */

package pl.edu.icm.unity.saml.idp.console;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.accordion.AccordionPanel;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.binder.ValidationResult;
import io.imunity.console.utils.tprofile.OutputTranslationProfileFieldFactory;
import io.imunity.vaadin.elements.LocalizedTextFieldDetails;
import io.imunity.vaadin.elements.TooltipFactory;
import io.imunity.vaadin.elements.grid.EditableGrid;
import io.imunity.vaadin.endpoint.common.api.SubViewSwitcher;
import io.imunity.vaadin.auth.services.DefaultServiceDefinition;
import io.imunity.vaadin.auth.services.ServiceEditorBase;
import io.imunity.vaadin.auth.services.ServiceEditorComponent;
import io.imunity.vaadin.endpoint.common.file.FileField;
import pl.edu.icm.unity.base.exceptions.WrongArgumentException;
import pl.edu.icm.unity.base.i18n.I18nString;
import pl.edu.icm.unity.base.identity.IdentityType;
import pl.edu.icm.unity.base.message.MessageSource;
import pl.edu.icm.unity.engine.api.config.UnityServerConfiguration;
import pl.edu.icm.unity.engine.api.endpoint.EndpointPathValidator;
import pl.edu.icm.unity.saml.console.SAMLIdentityMapping;
import pl.edu.icm.unity.saml.idp.SAMLIdPConfiguration.AssertionSigningPolicy;
import pl.edu.icm.unity.saml.idp.SAMLIdPConfiguration.RequestAcceptancePolicy;
import pl.edu.icm.unity.saml.idp.SAMLIdPConfiguration.ResponseSigningPolicy;
import xmlbeans.org.oasis.saml2.metadata.EntityDescriptorDocument;

import java.io.ByteArrayInputStream;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static io.imunity.vaadin.elements.CSSVars.TEXT_FIELD_BIG;
import static io.imunity.vaadin.elements.CssClassNames.*;

/**
 * SAML service editor general tab
 * 
 * @author P.Piernik
 *
 */
public class SAMLEditorGeneralTab extends VerticalLayout implements ServiceEditorBase.EditorTab
{
	private final MessageSource msg;
	private Binder samlServiceBinder;
	private Binder configBinder;
	private final OutputTranslationProfileFieldFactory profileFieldFactory;
	private final UnityServerConfiguration serverConfig;
	private final SubViewSwitcher subViewSwitcher;
	private final Set credentials;
	private final Set truststores;
	private final List usedEndpointsPaths;
	private final Set serverContextPaths;
	private final String serverPrefix;
	private final Collection idTypes;
	private boolean editMode;
	private Checkbox signMetadata;
	private boolean initialValidation;
	private Button metaLinkButton;
	private HorizontalLayout metaLinkButtonWrapper;
	private Span metaOffInfo;
	
	public SAMLEditorGeneralTab(MessageSource msg, String serverPrefix, Set serverContextPaths, UnityServerConfiguration serverConfig,
			SubViewSwitcher subViewSwitcher, OutputTranslationProfileFieldFactory profileFieldFactory,
			List usedEndpointsPaths,
			Set credentials, Set truststores, Collection idTypes)
	{
		this.msg = msg;
		this.serverConfig = serverConfig;
		this.subViewSwitcher = subViewSwitcher;
		this.profileFieldFactory = profileFieldFactory;
		this.usedEndpointsPaths = usedEndpointsPaths;
		this.credentials = credentials;
		this.truststores = truststores;
		this.idTypes = idTypes;
		this.serverPrefix = serverPrefix;
		this.serverContextPaths = serverContextPaths;
		
	}

	public void initUI(Binder samlServiceBinder,
			Binder configBinder, boolean editMode)
	{
		this.samlServiceBinder = samlServiceBinder;
		this.configBinder = configBinder;
		this.editMode = editMode;

		setPadding(false);
		VerticalLayout main = new VerticalLayout();
		main.setPadding(false);
		main.add(buildHeaderSection());
		main.add(buildMetadataSection());
		main.add(buildAdvancedSection());
		main.add(buildIdenityTypeMappingSection());
		main.add(profileFieldFactory.getWrappedFieldInstance(subViewSwitcher, configBinder,
				"translationProfile"));
		add(main);
	}

	private Component buildHeaderSection()
	{
		HorizontalLayout main = new HorizontalLayout();

		FormLayout mainGeneralLayout = new FormLayout();
		mainGeneralLayout.addClassName(BIG_VAADIN_FORM_ITEM_LABEL.getName());
		mainGeneralLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));

		main.add(mainGeneralLayout);

		metaLinkButton = new Button();
		metaOffInfo = new Span();

		HorizontalLayout infoLayout = new HorizontalLayout();
		infoLayout.addClassName(IDP_INFO_LAYOUT.getName());

		VerticalLayout wrapper = new VerticalLayout();
		wrapper.setPadding(false);
		infoLayout.add(wrapper);
		wrapper.add(new Span(msg.getMessage("SAMLEditorGeneralTab.importantURLs")));

		FormLayout infoLayoutWrapper = new FormLayout();
		infoLayoutWrapper.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
		wrapper.add(infoLayoutWrapper);
		metaLinkButtonWrapper = new HorizontalLayout();
		metaLinkButtonWrapper.add(metaLinkButton);
		infoLayoutWrapper.addFormItem(metaLinkButtonWrapper, msg.getMessage("SAMLEditorGeneralTab.metadataLink"));
		infoLayoutWrapper.addFormItem(metaOffInfo, msg.getMessage("SAMLEditorGeneralTab.metadataOff")).setVisible(false);

		metaLinkButton.addClickListener(e -> UI.getCurrent().getPage().open(metaLinkButton.getText(), "_blank"));
		metaLinkButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
		main.add(infoLayout);
		refreshMetaButton(false);
				
		TextField name = new TextField();
		name.setReadOnly(editMode);
		samlServiceBinder.forField(name).asRequired()
				.bind(DefaultServiceDefinition::getName, DefaultServiceDefinition::setName);
		mainGeneralLayout.addFormItem(name, msg.getMessage("ServiceEditorBase.name"));

		TextField contextPath = new TextField();
		contextPath.setPlaceholder("/saml-idp");
		contextPath.setRequiredIndicatorVisible(true);
		contextPath.setReadOnly(editMode);
		samlServiceBinder.forField(contextPath).withValidator((v, c) -> {			
			ValidationResult r;
			if (editMode)
			{
				r = validatePathForEdit(v);
			}else
			{
				r = validatePathForAdd(v);
			}
			
			if (!r.isError())
			{
				metaLinkButton.setText(serverPrefix + v + "/metadata");
			}
			
			return r;
		}).bind(DefaultServiceDefinition::getAddress, DefaultServiceDefinition::setAddress);
		mainGeneralLayout.addFormItem(contextPath, msg.getMessage("SAMLEditorGeneralTab.contextPath"));

		LocalizedTextFieldDetails displayedName = new LocalizedTextFieldDetails(msg.getEnabledLocales()
				.values(), msg.getLocale());
		samlServiceBinder.forField(displayedName)
				.withConverter(I18nString::new, I18nString::getLocalizedMap)
				.bind(DefaultServiceDefinition::getDisplayedName, DefaultServiceDefinition::setDisplayedName);
		displayedName.setWidth(TEXT_FIELD_BIG.value());
		mainGeneralLayout.addFormItem(displayedName, msg.getMessage("ServiceEditorBase.displayedName"));

		TextField description = new TextField();
		description.setWidth(TEXT_FIELD_BIG.value());
		samlServiceBinder.forField(description)
				.bind(DefaultServiceDefinition::getDescription, DefaultServiceDefinition::setDescription);
		mainGeneralLayout.addFormItem(description, msg.getMessage("ServiceEditorBase.description"));

		TextField issuerURI = new TextField();
		issuerURI.setWidth(TEXT_FIELD_BIG.value());
		configBinder.forField(issuerURI).asRequired()
				.bind(SAMLServiceConfiguration::getIssuerURI, SAMLServiceConfiguration::setIssuerURI);
		mainGeneralLayout.addFormItem(issuerURI, msg.getMessage("SAMLEditorGeneralTab.issuerURI"));

		Select signAssertionPolicy = new Select<>();
		signAssertionPolicy.setItems(AssertionSigningPolicy.values());
		configBinder.forField(signAssertionPolicy).asRequired()
				.bind(SAMLServiceConfiguration::getSignAssertionPolicy, SAMLServiceConfiguration::setSignAssertionPolicy);
		mainGeneralLayout.addFormItem(signAssertionPolicy, msg.getMessage("SAMLEditorGeneralTab.signAssertionPolicy"));

		Select signResponcePolicy = new Select<>();
		signResponcePolicy.setItems(ResponseSigningPolicy.values());
		configBinder.forField(signResponcePolicy).asRequired()
				.bind(SAMLServiceConfiguration::getSignResponcePolicy, SAMLServiceConfiguration::setSignResponcePolicy);
		mainGeneralLayout.addFormItem(signResponcePolicy, msg.getMessage("SAMLEditorGeneralTab.signResponcePolicy"));

		ComboBox signResponseCredential = new ComboBox<>();
		signResponseCredential.setItems(credentials);
		configBinder.forField(signResponseCredential)
				.asRequired((v, c) -> ((v == null || v.isEmpty()))
						? ValidationResult.error(msg.getMessage("fieldRequired"))
						: ValidationResult.ok())
				.bind(SAMLServiceConfiguration::getSignResponseCredential, SAMLServiceConfiguration::setSignResponseCredential);
		mainGeneralLayout.addFormItem(signResponseCredential, msg.getMessage("SAMLEditorGeneralTab.signResponseCredential"));

		ComboBox additionallyAdvertisedCredential = new ComboBox<>();
		additionallyAdvertisedCredential.setItems(credentials);
		configBinder.forField(additionallyAdvertisedCredential)
				.bind(SAMLServiceConfiguration::getAdditionallyAdvertisedCredential, SAMLServiceConfiguration::setAdditionallyAdvertisedCredential);
		mainGeneralLayout.addFormItem(additionallyAdvertisedCredential, msg.getMessage("SAMLEditorGeneralTab.additionallyAdvertisedCredential"))
				.add(TooltipFactory.get(msg.getMessage("SAMLEditorGeneralTab.additionallyAdvertisedCredentialDesc")));
		
		ComboBox httpsTruststore = new ComboBox<>();
		httpsTruststore.setItems(truststores);
		configBinder.forField(httpsTruststore)
				.bind(SAMLServiceConfiguration::getHttpsTruststore, SAMLServiceConfiguration::setHttpsTruststore);
		mainGeneralLayout.addFormItem(httpsTruststore, msg.getMessage("SAMLEditorGeneralTab.httpsTruststore"));

		Checkbox skipConsentScreen = new Checkbox(msg.getMessage("SAMLEditorGeneralTab.skipConsentScreen"));
		configBinder.forField(skipConsentScreen)
				.bind(SAMLServiceConfiguration::isSkipConsentScreen, SAMLServiceConfiguration::setSkipConsentScreen);
		mainGeneralLayout.addFormItem(skipConsentScreen, "");

		Checkbox editableConsentScreen = new Checkbox(
				msg.getMessage("SAMLEditorGeneralTab.editableConsentScreen"));
		configBinder.forField(editableConsentScreen)
				.bind(SAMLServiceConfiguration::isEditableConsentScreen, SAMLServiceConfiguration::setEditableConsentScreen);
		mainGeneralLayout.addFormItem(editableConsentScreen, "");

		skipConsentScreen.addValueChangeListener(e -> editableConsentScreen.setEnabled(!e.getValue()));

		Select acceptPolicy = new Select<>();
		acceptPolicy.setItems(RequestAcceptancePolicy.values());
		configBinder.forField(acceptPolicy).asRequired()
				.bind(SAMLServiceConfiguration::getRequestAcceptancePolicy, SAMLServiceConfiguration::setRequestAcceptancePolicy);
		mainGeneralLayout.addFormItem(acceptPolicy, msg.getMessage("SAMLEditorGeneralTab.acceptPolicy"));

		Checkbox setNotBefore = new Checkbox(
				msg.getMessage("SAMLEditorGeneralTab.setNotBefore"));
		configBinder.forField(setNotBefore)
				.bind(SAMLServiceConfiguration::isSetNotBeforeConstraint, SAMLServiceConfiguration::setSetNotBeforeConstraint);
		mainGeneralLayout.addFormItem(setNotBefore, "");

		
		return main;
	}

	private AccordionPanel buildAdvancedSection()
	{
		FormLayout advancedLayout = new FormLayout();
		advancedLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
		advancedLayout.addClassName(BIG_VAADIN_FORM_ITEM_LABEL.getName());

		IntegerField authenticationTimeout = new IntegerField();
		authenticationTimeout.setStepButtonsVisible(true);
		configBinder.forField(authenticationTimeout).asRequired(msg.getMessage("notAPositiveNumber"))
				.bind(SAMLServiceConfiguration::getAuthenticationTimeout, SAMLServiceConfiguration::setAuthenticationTimeout);
		advancedLayout.addFormItem(authenticationTimeout, msg.getMessage("SAMLEditorGeneralTab.authenticationTimeout"));

		IntegerField requestValidity = new IntegerField();
		requestValidity.setStepButtonsVisible(true);
		configBinder.forField(requestValidity).asRequired(msg.getMessage("notAPositiveNumber"))
				.bind(SAMLServiceConfiguration::getRequestValidity, SAMLServiceConfiguration::setRequestValidity);
		advancedLayout.addFormItem(requestValidity, msg.getMessage("SAMLEditorGeneralTab.requestValidity"));

		IntegerField attrAssertionValidity = new IntegerField();
		attrAssertionValidity.setStepButtonsVisible(true);
		configBinder.forField(attrAssertionValidity).asRequired(msg.getMessage("notAPositiveNumber"))
				.bind(SAMLServiceConfiguration::getAttrAssertionValidity, SAMLServiceConfiguration::setAttrAssertionValidity);
		advancedLayout.addFormItem(attrAssertionValidity, msg.getMessage("SAMLEditorGeneralTab.attributeAssertionValidity"));

		Checkbox returnSingleAssertion = new Checkbox(
				msg.getMessage("SAMLEditorGeneralTab.returnSingleAssertion"));
		configBinder.forField(returnSingleAssertion)
				.bind(SAMLServiceConfiguration::isReturnSingleAssertion, SAMLServiceConfiguration::setReturnSingleAssertion);
		advancedLayout.addFormItem(returnSingleAssertion, "");

		AccordionPanel accordionPanel = new AccordionPanel(msg.getMessage("SAMLEditorGeneralTab.advanced"),
				advancedLayout);
		accordionPanel.setWidthFull();
		return accordionPanel;
	}

	private AccordionPanel buildMetadataSection()
	{
		FormLayout metadataPublishing = new FormLayout();
		metadataPublishing.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
		metadataPublishing.addClassName(BIG_VAADIN_FORM_ITEM_LABEL.getName());

		Checkbox publishMetadata = new Checkbox(msg.getMessage("SAMLEditorGeneralTab.publishMetadata"));
		configBinder.forField(publishMetadata).withValidator((v, c) -> {
			if (!initialValidation)
			{
				refreshMetaButton(v);
				initialValidation = true;
			}
			if (v)
			{
				metaLinkButtonWrapper.setVisible(true);
				metaOffInfo.getParent().get().setVisible(false);
			} else
			{
				metaLinkButtonWrapper.setVisible(false);
				metaOffInfo.getParent().get().setVisible(true);
			}

			return ValidationResult.ok();

		}).bind(SAMLServiceConfiguration::isPublishMetadata, SAMLServiceConfiguration::setPublishMetadata);
		metadataPublishing.addFormItem(publishMetadata, "");

		signMetadata = new Checkbox(msg.getMessage("SAMLEditorGeneralTab.signMetadata"));
		configBinder.forField(signMetadata)
				.bind(SAMLServiceConfiguration::isSignMetadata, SAMLServiceConfiguration::setSignMetadata);
		signMetadata.setEnabled(false);
		metadataPublishing.addFormItem(signMetadata, "");

		Checkbox autoGenerateMetadata = new Checkbox(
				msg.getMessage("SAMLEditorGeneralTab.autoGenerateMetadata"));
		configBinder.forField(autoGenerateMetadata)
				.bind(SAMLServiceConfiguration::isAutoGenerateMetadata, SAMLServiceConfiguration::setAutoGenerateMetadata);
		autoGenerateMetadata.setEnabled(false);
		metadataPublishing.addFormItem(autoGenerateMetadata, "");

		FileField metadataSource = new FileField(msg, "text/xml", "metadata.xml",
				serverConfig.getFileSizeLimit());
		metadataSource.configureBinding(configBinder, "metadataSource", Optional.of((value, context) -> {
			if (value != null && value.getLocal() != null)
			{
				try
				{
					EntityDescriptorDocument.Factory
							.parse(new ByteArrayInputStream(value.getLocal()));
				} catch (Exception e)
				{
					return ValidationResult.error(
							msg.getMessage("SAMLEditorGeneralTab.invalidMetadataFile"));
				}
			}

			boolean isEmpty = value == null || (value.getLocal() == null
					&& (value.getSrc() == null || value.getSrc().isEmpty()));

			if (publishMetadata.getValue() && (!autoGenerateMetadata.getValue() && isEmpty))
			{
				return ValidationResult.error(msg.getMessage("SAMLEditorGeneralTab.idpMetaEmpty"));
			}

			return ValidationResult.ok();

		}));
		metadataSource.setEnabled(false);
		metadataPublishing.addFormItem(metadataSource, msg.getMessage("SAMLEditorGeneralTab.metadataFile"));
		publishMetadata.addValueChangeListener(e -> {
			boolean v = e.getValue();
			signMetadata.setEnabled(v);
			autoGenerateMetadata.setEnabled(v);
			metadataSource.setEnabled(!autoGenerateMetadata.getValue() && v);
		});

		autoGenerateMetadata.addValueChangeListener(e -> {
			metadataSource.setEnabled(!e.getValue() && publishMetadata.getValue());
		});

		AccordionPanel accordionPanel = new AccordionPanel(msg.getMessage("SAMLEditorGeneralTab.metadata"),
				metadataPublishing);
		accordionPanel.setWidthFull();
		return accordionPanel;
	}
	
	private void refreshMetaButton(Boolean enabled)
	{
		metaLinkButton.setEnabled(enabled);
	}

	private AccordionPanel buildIdenityTypeMappingSection()
	{
		VerticalLayout idTypeMappingLayout = new VerticalLayout();
		idTypeMappingLayout.setMargin(false);

		EditableGrid idMappings = new EditableGrid<>(msg::getMessage, SAMLIdentityMapping::new);
		idTypeMappingLayout.add(idMappings);
		idMappings.addComboBoxColumn(SAMLIdentityMapping::getUnityId, SAMLIdentityMapping::setUnityId,
				idTypes.stream().map(IdentityType::getName).collect(Collectors.toList()))
				.setHeader(msg.getMessage("SAMLEditorGeneralTab.idMappings.unityId"))
				.setAutoWidth(true)
				.setFlexGrow(2);
		idMappings.addColumn(SAMLIdentityMapping::getSamlId, SAMLIdentityMapping::setSamlId, true)
				.setHeader(msg.getMessage("SAMLEditorGeneralTab.idMappings.samlId"))
				.setAutoWidth(true)
				.setFlexGrow(2);

		idMappings.setWidthFull();
		configBinder.forField(idMappings)
				.bind(SAMLServiceConfiguration::getIdentityMapping, SAMLServiceConfiguration::setIdentityMapping);

		AccordionPanel accordionPanel = new AccordionPanel(msg.getMessage("SAMLEditorGeneralTab.idenityTypeMapping"),
				idTypeMappingLayout);
		accordionPanel.setWidthFull();
		return accordionPanel;
	}
	
	private ValidationResult validatePathForAdd(String path)
	{
		if (path == null || path.isEmpty())
		{
			return ValidationResult.error(msg.getMessage("fieldRequired"));
		}

		if (usedEndpointsPaths.contains(path))
		{
			return ValidationResult.error(msg.getMessage("ServiceEditorBase.usedContextPath"));
		}

		try
		{
			EndpointPathValidator.validateEndpointPath(path, serverContextPaths);

		} catch (WrongArgumentException e)
		{
			return ValidationResult.error(msg.getMessage("ServiceEditorBase.invalidContextPath"));
		}

		return ValidationResult.ok();
	}
	
	private ValidationResult validatePathForEdit(String path)
	{
		try
		{
			EndpointPathValidator.validateEndpointPath(path);

		} catch (WrongArgumentException e)
		{
			return ValidationResult.error(msg.getMessage("ServiceEditorBase.invalidContextPath"));
		}

		return ValidationResult.ok();
	}

	@Override
	public VaadinIcon getIcon()
	{
		return VaadinIcon.COGS;
	}

	@Override
	public String getType()
	{
		return ServiceEditorComponent.ServiceEditorTab.GENERAL.toString();
	}

	@Override
	public Component getComponent()
	{
		return this;
	}

	@Override
	public String getCaption()
	{
		return msg.getMessage("ServiceEditorBase.general");
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy