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

io.imunity.fido.web.FidoEditorComponent Maven / Gradle / Ivy

/*
 * Copyright (c) 2020 Bixbit - Krzysztof Benedyczak All rights reserved.
 * See LICENCE.txt file for licensing information.
 */
package io.imunity.fido.web;

import static io.imunity.tooltip.TooltipExtension.tooltip;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import com.vaadin.server.VaadinService;
import com.vaadin.ui.Button;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;

import io.imunity.fido.FidoRegistration;
import io.imunity.fido.component.FidoComponent;
import io.imunity.fido.credential.FidoCredential;
import io.imunity.fido.credential.FidoCredentialInfo;
import pl.edu.icm.unity.MessageSource;
import pl.edu.icm.unity.exceptions.EngineException;
import pl.edu.icm.unity.exceptions.IllegalCredentialException;
import pl.edu.icm.unity.webui.common.Styles;
import pl.edu.icm.unity.webui.common.credentials.CredentialEditorContext;
import pl.edu.icm.unity.webui.common.credentials.MissingCredentialException;
import pl.edu.icm.unity.webui.common.safehtml.HtmlTag;

/**
 * Editor integrating FidoComponent and displays current Fido keys with status.
 *
 * @author R. Ledzinski
 */
class FidoEditorComponent extends CustomComponent
{
	private MessageSource msg;
	private final List credentials = new ArrayList<>();
	private final FidoComponent fidoComponent;
	private final VerticalLayout credentialsLayout;
	private Button addButton;
	private TextField username;
	private Button advancedOptionsButton;
	private boolean loginLessSupported;
	private VerticalLayout buttons;
	private VerticalLayout advancedOptions;
	private CheckBox loginLessAllowed;

	public FidoEditorComponent(final FidoRegistration fidoRegistration, final CredentialEditorContext context, 
			final MessageSource msg)
	{
		this.msg = msg;

		fidoComponent = FidoComponent.builder(msg)
				.fidoRegistration(fidoRegistration)
				.showSuccessNotification(false)
				.entityId(context.getEntityId())
				.credentialName(context.getCredentialName())
				.credentialConfiguration(context.getCredentialConfiguration())
				.newCredentialListener(this::addNewCredential)
				.allowAuthenticatorReUsage(isInDevelopmentMode())
				.build();
		fidoComponent.setHeight(0, Unit.PIXELS);

		Optional credential = Optional.ofNullable(context.getCredentialConfiguration())
				.map(s -> FidoCredential.deserialize(context.getCredentialConfiguration()));
		loginLessSupported = credential.map(FidoCredential::isLoginLessAllowed).orElse(false);

		username = new TextField(msg.getMessage("Fido.username"));
		username.setValue(credential.map(c -> c.getHostName() + " " + msg.getMessage("Fido.defaultUser")).orElse(msg.getMessage("Fido.defaultUser")));
		username.setWidth(100, Unit.PERCENTAGE);

		credentialsLayout = new VerticalLayout();
		credentialsLayout.setMargin(false);
		credentialsLayout.setSpacing(false);

		addButton = new Button();
		addButton.setDescription(msg.getMessage("Fido.newRegistration"));
		addButton.setCaption(msg.getMessage("Fido.register"));
		addButton.setWidth("100%");
		addButton.addClickListener(e -> fidoComponent.invokeRegistration(username.getValue(), loginLessAllowed.getValue()));

		advancedOptionsButton = new Button(msg.getMessage("Fido.advancedOptions"));
		advancedOptionsButton.addStyleName(Styles.vButtonLink.toString());
		advancedOptionsButton.addStyleName("u-highlightedLink");
		advancedOptionsButton.addClickListener(e -> {
			advancedOptions.setVisible(!advancedOptions.isVisible());
			reloadAdvancedOptions();
		});

		loginLessAllowed = new CheckBox(msg.getMessage("Fido.credEditor.loginLess"));
		tooltip(loginLessAllowed, msg.getMessage("Fido.credEditor.loginLess.tip"));
		loginLessAllowed.setValue(loginLessSupported);

		buttons = new VerticalLayout(addButton, advancedOptionsButton);
		buttons.setSpacing(false);
		buttons.setMargin(false);

		advancedOptions = new VerticalLayout(username, loginLessAllowed);
		advancedOptions.setMargin(false);
		advancedOptions.setVisible(false);

		VerticalLayout root = new VerticalLayout();
		root.setMargin(false);
		root.setSpacing(true);
		root.addStyleName("u-fidoEditorLayout");
		
		root.addComponents(fidoComponent, credentialsLayout, buttons, advancedOptions);

		setCompositionRoot(root);

		initUI(context.getExtraInformation());
	}

	private boolean isInDevelopmentMode()
	{
		VaadinService vaadinService = VaadinService.getCurrent();
		if (vaadinService == null)
			return false;
		return !vaadinService.getDeploymentConfiguration().isProductionMode();
	}
	
	void initUI(final String extraInformation)
	{
		initCredentials(extraInformation);
		reload();
	}

	private void initCredentials(String extraInformation)
	{
		credentials.clear();

		if (isNull(extraInformation) || extraInformation.isEmpty())
		{
			return;
		}

		credentials.addAll(FidoCredentialInfo.deserializeList(extraInformation).stream()
				.map(info -> new FidoCredentialInfoWrapper(FidoCredentialInfoWrapper.CredentialState.STORED, info))
				.collect(Collectors.toList()));
	}

	private void reload()
	{
		credentialsLayout.removeAllComponents();

		credentials.stream()
				.filter(info -> info.getState() != FidoCredentialInfoWrapper.CredentialState.DELETED)
				.map(info -> (Component) new FidoPreviewComponent(msg, info, this::reload))
				.forEach(comp ->
				{
					credentialsLayout.addComponent(HtmlTag.horizontalLine());
					credentialsLayout.addComponent(comp);
				});

		if (credentialsLayout.getComponentCount() > 0)
		{
			credentialsLayout.setVisible(true);
			credentialsLayout.addComponent(HtmlTag.horizontalLine());
		} else
		{
			credentialsLayout.setVisible(false);
		}

		addButton.setVisible(nonNull(fidoComponent.getEntityId()) || credentialsLayout.getComponentCount() == 0);
		reloadAdvancedOptions();
	}

	private void reloadAdvancedOptions()
	{
		username.setVisible(isNull(fidoComponent.getEntityId()));
		loginLessAllowed.setVisible(loginLessSupported);

		if (!username.isVisible() && !loginLessAllowed.isVisible()) {
			advancedOptions.setVisible(false);
			advancedOptionsButton.setVisible(false);
			return;
		}

		advancedOptionsButton.setVisible(nonNull(fidoComponent.getEntityId()) || credentialsLayout.getComponentCount() == 0);
		if (!advancedOptionsButton.isVisible()) {
			advancedOptions.setVisible(false);
		}
		advancedOptionsButton.setCaption(advancedOptions.isVisible() ?  msg.getMessage("Fido.advancedOptions.hide") : msg.getMessage("Fido.advancedOptions"));
	}

	private void addNewCredential(final FidoCredentialInfo credential)
	{
		FidoCredentialInfo newCredential = credential.copyBuilder()
				.description(msg.getMessage("FidoExc.defaultKeyDesc", credentials.size() + 1))
				.build();
		credentials.add(new FidoCredentialInfoWrapper(FidoCredentialInfoWrapper.CredentialState.NEW, newCredential));
		reload();
	}

	public String getValue() throws IllegalCredentialException
	{
		if (credentials.stream().noneMatch(c -> c.getState() != FidoCredentialInfoWrapper.CredentialState.DELETED))
			throw new MissingCredentialException(msg.getMessage("FidoExc.noKeysToStore"));

		return FidoCredentialInfo.serializeList(credentials.stream()
				.filter(info -> info.getState() != FidoCredentialInfoWrapper.CredentialState.DELETED)
				.map(FidoCredentialInfoWrapper::getCredential)
				.collect(Collectors.toList()));
	}

	long getNonDeletedKeysNumber()
	{
		return credentials.stream().filter(c -> c.getState() != FidoCredentialInfoWrapper.CredentialState.DELETED).count();
	}

	public void setCredentialError(EngineException error)
	{
		if (nonNull(error))
			fidoComponent.showError("Error", error.getLocalizedMessage());
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy