org.eclipse.winery.accountability.AccountabilityManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.winery.accountability.AccountabilityManagerImpl.java

Source

/*******************************************************************************
 * Copyright (c) 2018 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
 *******************************************************************************/

package org.eclipse.winery.accountability;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import org.eclipse.winery.accountability.blockchain.BlockchainAccess;
import org.eclipse.winery.accountability.model.FileProvenanceElement;
import org.eclipse.winery.accountability.model.ModelProvenanceElement;
import org.eclipse.winery.accountability.model.ProvenanceVerification;
import org.eclipse.winery.accountability.model.authorization.AuthorizationInfo;
import org.eclipse.winery.accountability.storage.ImmutableStorageProvider;
import org.eclipse.winery.common.HashingUtil;
import org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFile;
import org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFileAttributes;
import org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFileParser;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.SerializationException;
import org.eclipse.virgo.util.parser.manifest.ManifestContents;
import org.eclipse.virgo.util.parser.manifest.RecoveringManifestParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.eclipse.winery.accountability.model.ProvenanceVerification.ID_NOT_FOUND;
import static org.eclipse.winery.accountability.model.ProvenanceVerification.INVALID;
import static org.eclipse.winery.accountability.model.ProvenanceVerification.NO_AUTHORIZATION_DATA_AVAILABLE;
import static org.eclipse.winery.accountability.model.ProvenanceVerification.NO_HASH_AVAILABLE;
import static org.eclipse.winery.accountability.model.ProvenanceVerification.NO_MANIFEST_FILE_IN_PROVENANCE_LAYER;
import static org.eclipse.winery.accountability.model.ProvenanceVerification.UNAUTHORIZED;
import static org.eclipse.winery.accountability.model.ProvenanceVerification.VERIFIED;

public class AccountabilityManagerImpl implements AccountabilityManager {

    private static final Logger LOGGER = LoggerFactory.getLogger(AccountabilityManagerImpl.class);
    private static final String NO_AUTHORIZATION_DATA = "No authorization data stored in the blockchain";
    private BlockchainAccess blockchain;
    private ImmutableStorageProvider storageProvider;

    AccountabilityManagerImpl(BlockchainAccess blockchainAccess, ImmutableStorageProvider storageProvider) {
        this.blockchain = blockchainAccess;
        this.storageProvider = storageProvider;
    }

    @Override
    public CompletableFuture<Map<String, ProvenanceVerification>> verify(String processIdentifier,
            String manifestId, Map<String, File> files) {
        Objects.requireNonNull(processIdentifier);
        Objects.requireNonNull(manifestId);
        Objects.requireNonNull(files);

        LOGGER.info("Verifying process id: " + processIdentifier);

        CompletableFuture<AuthorizationInfo> authorizationTree = this.blockchain
                .getAuthorizationTree(processIdentifier);

        return this.blockchain.getProvenance(processIdentifier).thenCombine(authorizationTree,
                (provenanceElements, authorizationInfo) -> {
                    // TODO: extract into one method and write test cases
                    Map<String, ProvenanceVerification> map = null;
                    try {
                        if (authorizationInfo == null) {
                            LOGGER.info(NO_AUTHORIZATION_DATA);
                            map = new HashMap<>();
                            map.put(manifestId, NO_AUTHORIZATION_DATA_AVAILABLE);
                        } else {
                            map = this.validateBlockchainInput(provenanceElements, manifestId, files,
                                    authorizationInfo);
                        }
                    } catch (NoSuchAlgorithmException e) {
                        LOGGER.error("Could not find the hashing algorithm.", e);
                    } catch (IOException e) {
                        LOGGER.error("Could not transform file to byte array.", e);
                    } catch (SerializationException e) {
                        LOGGER.error("Could not deserialize stored manifest file.", e);
                    }

                    return map;
                });
    }

    @Override
    public CompletableFuture<String> storeFingerprint(String processIdentifier, String fingerprint) {
        Objects.requireNonNull(processIdentifier);
        Objects.requireNonNull(fingerprint);

        return this.blockchain.saveFingerprint(processIdentifier, fingerprint);
    }

    @Override
    public CompletableFuture<Map<String, String>> storeState(Map<String, InputStream> files) {
        Objects.requireNonNull(files);
        Map<String, CompletableFuture<String>> allFuturesMap = new HashMap<>();

        for (Map.Entry<String, InputStream> entry : files.entrySet()) {
            allFuturesMap.put(entry.getKey(), storageProvider.store(entry.getValue()));
        }

        return CompletableFuture
                // execute all futures on parallel
                .allOf(allFuturesMap.values().toArray(new CompletableFuture[allFuturesMap.size()]))
                // when all done, collect the results
                .thenApply((Void) -> {
                    Map<String, String> result = new HashMap<>();

                    for (Map.Entry<String, CompletableFuture<String>> entry : allFuturesMap.entrySet()) {
                        result.put(entry.getKey(), entry.getValue().join());
                    }

                    return result;
                });
    }

    @Override
    public CompletableFuture<String> storeState(InputStream data) {
        Objects.requireNonNull(data);

        return storageProvider.store(data);
    }

    @Override
    public CompletableFuture<InputStream> retrieveState(String addressInImmutableStorage) {
        return storageProvider.retrieve(addressInImmutableStorage);
    }

    @Override
    public CompletableFuture<List<ModelProvenanceElement>> getHistory(String processIdentifier) {
        CompletableFuture<AuthorizationInfo> authorizationTree = this.blockchain
                .getAuthorizationTree(processIdentifier);

        return this.blockchain.getProvenance(processIdentifier).thenCombine(authorizationTree,
                this::enhanceHistoryElements);
    }

    @Override
    public CompletableFuture<List<FileProvenanceElement>> getHistory(String processIdentifier, String fileId) {
        CompletableFuture<AuthorizationInfo> authorizationTree = this.blockchain
                .getAuthorizationTree(processIdentifier);

        return this.blockchain.getProvenance(processIdentifier).thenCombine(authorizationTree,
                (provenanceElements, authorizationInfo) -> {
                    List<FileProvenanceElement> result;

                    if (authorizationInfo != null) {
                        result = getHistoryOfSingleFile(provenanceElements, fileId, authorizationInfo);
                    } else {
                        LOGGER.info(NO_AUTHORIZATION_DATA);
                        FileProvenanceElement historyElement = new FileProvenanceElement("", 0,
                                NO_AUTHORIZATION_DATA);
                        historyElement.setAuthorized(false);
                        historyElement.setFileName(fileId);
                        result = Collections.singletonList(historyElement);
                    }

                    return result;
                });
    }

    @Override
    public CompletableFuture<String> authorize(String processIdentifier, String authorizedEthereumAddress,
            String authorizedIdentity) {
        LOGGER.info("Authorizing \"" + authorizedEthereumAddress + "\" for " + processIdentifier);
        return this.blockchain.authorize(processIdentifier, authorizedEthereumAddress, authorizedIdentity);
    }

    @Override
    public CompletableFuture<AuthorizationInfo> getAuthorization(String processIdentifier) {
        LOGGER.info("Retrieving authorization info for process " + processIdentifier);
        return this.blockchain.getAuthorizationTree(processIdentifier);
    }

    @Override
    public void close() {
        this.blockchain.close();
        this.storageProvider.close();
    }

    private List<ModelProvenanceElement> enhanceHistoryElements(List<ModelProvenanceElement> historyElements,
            AuthorizationInfo authorizationInfo) {
        historyElements.forEach(element -> {
            element.setAuthorizedFlag(authorizationInfo);
            element.setAuthorName(authorizationInfo != null
                    ? authorizationInfo.getRealWorldIdentity(element.getAuthorAddress()).orElseGet(String::new)
                    : "");
            this.fillFilesOfModel(element);
        });
        return historyElements;
    }

    protected List<FileProvenanceElement> getHistoryOfSingleFile(List<ModelProvenanceElement> historyElements,
            String fileId, AuthorizationInfo authorizationInfo) {
        List<FileProvenanceElement> history = new ArrayList<>();

        if (Objects.nonNull(historyElements) && historyElements.size() > 0) {
            for (ModelProvenanceElement modelProvenanceElement : historyElements) {
                if (Objects.nonNull(modelProvenanceElement.getFingerprint())) {
                    ManifestContents manifestContents = new RecoveringManifestParser()
                            .parse(modelProvenanceElement.getFingerprint());

                    for (String name : manifestContents.getSectionNames()) {
                        if (name.equals(fileId)) {
                            modelProvenanceElement.setAuthorizedFlag(authorizationInfo);
                            modelProvenanceElement.setAuthorName(authorizationInfo
                                    .getRealWorldIdentity(modelProvenanceElement.getAuthorAddress())
                                    .orElseGet(String::new));

                            FileProvenanceElement currentFile = new FileProvenanceElement(modelProvenanceElement);
                            currentFile.setAddressInImmutableStorage(manifestContents.getAttributesForSection(name)
                                    .get(TOSCAMetaFileAttributes.IMMUTABLE_ADDRESS));
                            currentFile.setFileHash(TOSCAMetaFileAttributes.HASH + "-" + manifestContents
                                    .getAttributesForSection(name).get(TOSCAMetaFileAttributes.HASH));
                            currentFile.setFileName(fileId);
                            history.add(currentFile);

                            break;
                        }
                    }
                }
            }
        }

        if (history.size() > 0)
            return history;

        return null;
    }

    protected Map<String, ProvenanceVerification> validateBlockchainInput(
            List<ModelProvenanceElement> historyElements, String manifestId, Map<String, File> files,
            AuthorizationInfo authorizationInfo) throws IOException, NoSuchAlgorithmException {
        LOGGER.info("Start validating...");
        Map<String, ProvenanceVerification> verificationMap = new HashMap<>();
        ModelProvenanceElement validHistoryElement = verifyManifest(historyElements, manifestId, files,
                verificationMap, authorizationInfo);

        if (Objects.nonNull(validHistoryElement) && verificationMap.get(manifestId) == VERIFIED) {
            verifyFiles(validHistoryElement, files, verificationMap);
        }

        LOGGER.info("Completed validation.");

        return verificationMap;
    }

    protected ModelProvenanceElement verifyManifest(List<ModelProvenanceElement> historyElements, String manifestId,
            Map<String, File> files, Map<String, ProvenanceVerification> verificationMap,
            AuthorizationInfo authorizationInfo) throws NoSuchAlgorithmException, IOException {
        LOGGER.info("Start validating manifest file...");
        ModelProvenanceElement validHistoryElement = null;
        ProvenanceVerification manifestVerification = INVALID;

        if (Objects.nonNull(historyElements) && historyElements.size() > 0) {
            File manifestFile = files.remove(manifestId);
            String checksum = HashingUtil.getChecksum(manifestFile, TOSCAMetaFileAttributes.HASH);
            for (ModelProvenanceElement element : historyElements) {
                String retrievedChecksum = HashingUtil.getChecksum(
                        IOUtils.toInputStream(element.getFingerprint(), StandardCharsets.UTF_8),
                        TOSCAMetaFileAttributes.HASH);
                if (retrievedChecksum.compareTo(checksum) == 0) {
                    validHistoryElement = element;
                    manifestVerification = authorizationInfo.isAuthorized(validHistoryElement.getAuthorAddress())
                            ? VERIFIED
                            : UNAUTHORIZED;
                    break;
                }
            }
        } else {
            manifestVerification = NO_MANIFEST_FILE_IN_PROVENANCE_LAYER;
        }

        LOGGER.info("Manifest verification resulted in " + manifestVerification);
        verificationMap.put(manifestId, manifestVerification);

        return validHistoryElement;
    }

    protected void verifyFiles(ModelProvenanceElement validHistoryElement, Map<String, File> files,
            Map<String, ProvenanceVerification> verificationMap)
            throws SerializationException, IOException, NoSuchAlgorithmException {
        LOGGER.info("Start validating files...");
        for (Map.Entry<String, File> entry : files.entrySet()) {
            String fileId = entry.getKey();
            LOGGER.info("Validating \"" + fileId + "\"");
            String checksum = HashingUtil.getChecksum(entry.getValue(), TOSCAMetaFileAttributes.HASH);
            ProvenanceVerification verified = verifyFileInManifest(validHistoryElement, fileId, checksum);

            verificationMap.put(entry.getKey(), verified);
            LOGGER.info("\"" + entry.getKey() + "\" " + verified);
        }

        LOGGER.info("Completed files validation.");
    }

    protected ProvenanceVerification verifyFileInManifest(ModelProvenanceElement element, String fileId,
            String checksum) throws SerializationException {
        // 1. parse element.state as ManifestContent
        ManifestContents manifestContents = new RecoveringManifestParser().parse(element.getFingerprint());

        if (manifestContents.getSectionNames().contains(fileId)) {
            String storedHash = manifestContents.getAttributesForSection(fileId).get(TOSCAMetaFileAttributes.HASH);
            if (Objects.isNull(storedHash)) {
                return NO_HASH_AVAILABLE;
            } else if (storedHash.equals(checksum)) {
                // 2.1 if section == fileId and hash == checksum -> file verified
                return VERIFIED;
            } else {
                return INVALID;
            }
        }

        return ID_NOT_FOUND;
    }

    private void fillFilesOfModel(ModelProvenanceElement model) {
        // 1. parse element.state as ManifestContent
        RecoveringManifestParser genericParser = new RecoveringManifestParser();
        ManifestContents manifestContents = genericParser.parse(model.getFingerprint());
        // 2. parse the ManifestContent as a TOSCAMetaFile
        TOSCAMetaFileParser parser = new TOSCAMetaFileParser();
        TOSCAMetaFile toscaMetaFile = parser.parse(manifestContents, genericParser.getProblems().size());
        //3. retrieve files from meta file
        Objects.requireNonNull(toscaMetaFile);

        List<FileProvenanceElement> result = toscaMetaFile.getFileBlocks().stream().map(fileSection -> {
            FileProvenanceElement fileElement = new FileProvenanceElement(model);
            fileElement.setFileHash(fileSection.get(TOSCAMetaFileAttributes.HASH));
            fileElement.setAddressInImmutableStorage(fileSection.get(TOSCAMetaFileAttributes.IMMUTABLE_ADDRESS));
            fileElement.setFileName(fileSection.get(TOSCAMetaFileAttributes.NAME));

            return fileElement;
        }).sorted(Comparator.comparing(FileProvenanceElement::getFileName)).collect(Collectors.toList());

        model.setFiles(result);
    }
}