org.finra.herd.service.impl.StorageUnitServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.finra.herd.service.impl.StorageUnitServiceImpl.java

Source

/*
* Copyright 2015 herd contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.finra.herd.service.impl;

import java.util.UUID;

import com.amazonaws.auth.policy.actions.S3Actions;
import com.amazonaws.services.securitytoken.model.Credentials;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import org.finra.herd.core.HerdDateUtils;
import org.finra.herd.core.helper.ConfigurationHelper;
import org.finra.herd.dao.BusinessObjectDataDao;
import org.finra.herd.dao.StsDao;
import org.finra.herd.dao.config.DaoSpringModuleConfig;
import org.finra.herd.dao.helper.AwsHelper;
import org.finra.herd.model.AlreadyExistsException;
import org.finra.herd.model.annotation.NamespacePermission;
import org.finra.herd.model.api.xml.AwsCredential;
import org.finra.herd.model.api.xml.BusinessObjectDataKey;
import org.finra.herd.model.api.xml.BusinessObjectFormatKey;
import org.finra.herd.model.api.xml.NamespacePermissionEnum;
import org.finra.herd.model.api.xml.S3KeyPrefixInformation;
import org.finra.herd.model.api.xml.StorageUnitDownloadCredential;
import org.finra.herd.model.api.xml.StorageUnitUploadCredential;
import org.finra.herd.model.dto.ConfigurationValue;
import org.finra.herd.model.jpa.BusinessObjectDataEntity;
import org.finra.herd.model.jpa.BusinessObjectFormatEntity;
import org.finra.herd.model.jpa.StorageEntity;
import org.finra.herd.service.StorageUnitService;
import org.finra.herd.service.helper.AwsPolicyBuilder;
import org.finra.herd.service.helper.BusinessObjectDataHelper;
import org.finra.herd.service.helper.BusinessObjectFormatDaoHelper;
import org.finra.herd.service.helper.KmsActions;
import org.finra.herd.service.helper.S3KeyPrefixHelper;
import org.finra.herd.service.helper.StorageDaoHelper;
import org.finra.herd.service.helper.StorageHelper;

@Service
@Transactional(value = DaoSpringModuleConfig.HERD_TRANSACTION_MANAGER_BEAN_NAME)
public class StorageUnitServiceImpl implements StorageUnitService {
    @Autowired
    private AwsHelper awsHelper;

    @Autowired
    private BusinessObjectDataDao businessObjectDataDao;

    @Autowired
    private BusinessObjectDataHelper businessObjectDataHelper;

    @Autowired
    private BusinessObjectFormatDaoHelper businessObjectFormatDaoHelper;

    @Autowired
    private ConfigurationHelper configurationHelper;

    @Autowired
    private S3KeyPrefixHelper s3KeyPrefixHelper;

    @Autowired
    private StorageDaoHelper storageDaoHelper;

    @Autowired
    private StorageHelper storageHelper;

    @Autowired
    private StsDao stsDao;

    @NamespacePermission(fields = "#businessObjectDataKey.namespace", permissions = NamespacePermissionEnum.READ)
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public S3KeyPrefixInformation getS3KeyPrefix(BusinessObjectDataKey businessObjectDataKey,
            String businessObjectFormatPartitionKey, String storageName, Boolean createNewVersion) {
        return getS3KeyPrefixImpl(businessObjectDataKey, businessObjectFormatPartitionKey, storageName,
                createNewVersion);
    }

    @NamespacePermission(fields = "#businessObjectDataKey?.namespace", permissions = NamespacePermissionEnum.WRITE)
    @Override
    public StorageUnitUploadCredential getStorageUnitUploadCredential(BusinessObjectDataKey businessObjectDataKey,
            Boolean createNewVersion, String storageName) {
        StorageUnitUploadCredential businessObjectDataUploadCredential = new StorageUnitUploadCredential();
        businessObjectDataUploadCredential.setAwsCredential(
                getBusinessObjectDataS3Credential(businessObjectDataKey, createNewVersion, storageName, true));
        businessObjectDataUploadCredential
                .setAwsKmsKeyId(getStorageKmsKeyId(storageDaoHelper.getStorageEntity(storageName.trim())));
        return businessObjectDataUploadCredential;
    }

    @NamespacePermission(fields = "#businessObjectDataKey?.namespace", permissions = NamespacePermissionEnum.READ)
    @Override
    public StorageUnitDownloadCredential getStorageUnitDownloadCredential(
            BusinessObjectDataKey businessObjectDataKey, String storageName) {
        StorageUnitDownloadCredential businessObjectDataDownloadCredential = new StorageUnitDownloadCredential();
        businessObjectDataDownloadCredential.setAwsCredential(
                getBusinessObjectDataS3Credential(businessObjectDataKey, null, storageName, false));
        return businessObjectDataDownloadCredential;
    }

    /**
     * Gets the S3 key prefix.
     *
     * @param businessObjectDataKey the business object data key
     * @param businessObjectFormatPartitionKey the business object format partition key
     * @param storageName the storage name
     * @param createNewVersion specifies if it is OK to return an S3 key prefix for a new business object data version that is not an initial version. This
     * parameter is ignored, when the business object data version is specified.
     *
     * @return the S3 key prefix
     */
    protected S3KeyPrefixInformation getS3KeyPrefixImpl(BusinessObjectDataKey businessObjectDataKey,
            String businessObjectFormatPartitionKey, String storageName, Boolean createNewVersion) {
        // Validate and trim the business object data key.
        businessObjectDataHelper.validateBusinessObjectDataKey(businessObjectDataKey, true, false);

        // If specified, trim the partition key parameter.
        String businessObjectFormatPartitionKeyLocal = businessObjectFormatPartitionKey;
        if (businessObjectFormatPartitionKeyLocal != null) {
            businessObjectFormatPartitionKeyLocal = businessObjectFormatPartitionKeyLocal.trim();
        }

        // If specified, trim the storage name. Otherwise, default to the configuration option.
        String storageNameLocal = storageName;
        if (StringUtils.isNotBlank(storageNameLocal)) {
            storageNameLocal = storageNameLocal.trim();
        } else {
            storageNameLocal = configurationHelper.getProperty(ConfigurationValue.S3_STORAGE_NAME_DEFAULT);
        }

        // Get the business object format for the specified parameters and make sure it exists.
        BusinessObjectFormatEntity businessObjectFormatEntity = businessObjectFormatDaoHelper
                .getBusinessObjectFormatEntity(new BusinessObjectFormatKey(businessObjectDataKey.getNamespace(),
                        businessObjectDataKey.getBusinessObjectDefinitionName(),
                        businessObjectDataKey.getBusinessObjectFormatUsage(),
                        businessObjectDataKey.getBusinessObjectFormatFileType(),
                        businessObjectDataKey.getBusinessObjectFormatVersion()));

        // If specified, ensure that partition key matches what's configured within the business object format.
        if (StringUtils.isNotBlank(businessObjectFormatPartitionKeyLocal)) {
            Assert.isTrue(
                    businessObjectFormatEntity.getPartitionKey()
                            .equalsIgnoreCase(businessObjectFormatPartitionKeyLocal),
                    "Partition key \"" + businessObjectFormatPartitionKeyLocal
                            + "\" doesn't match configured business object format partition key \""
                            + businessObjectFormatEntity.getPartitionKey() + "\".");
        }

        // Get and validate the storage along with the relative attributes.
        StorageEntity storageEntity = storageDaoHelper.getStorageEntity(storageNameLocal);

        // If the business object data version is not specified, get the next business object data version value.
        if (businessObjectDataKey.getBusinessObjectDataVersion() == null) {
            // Get the latest data version for this business object data, if it exists.
            BusinessObjectDataEntity latestVersionBusinessObjectDataEntity = businessObjectDataDao
                    .getBusinessObjectDataByAltKey(new BusinessObjectDataKey(businessObjectDataKey.getNamespace(),
                            businessObjectDataKey.getBusinessObjectDefinitionName(),
                            businessObjectDataKey.getBusinessObjectFormatUsage(),
                            businessObjectDataKey.getBusinessObjectFormatFileType(),
                            businessObjectDataKey.getBusinessObjectFormatVersion(),
                            businessObjectDataKey.getPartitionValue(),
                            businessObjectDataKey.getSubPartitionValues(), null));

            // Throw an error if this business object data already exists and createNewVersion flag is not set.
            if (latestVersionBusinessObjectDataEntity != null && !createNewVersion) {
                throw new AlreadyExistsException("Initial version of the business object data already exists.");
            }

            businessObjectDataKey.setBusinessObjectDataVersion(latestVersionBusinessObjectDataEntity == null
                    ? BusinessObjectDataEntity.BUSINESS_OBJECT_DATA_INITIAL_VERSION
                    : latestVersionBusinessObjectDataEntity.getVersion() + 1);
        }

        // Build the S3 key prefix string.
        String s3KeyPrefix = s3KeyPrefixHelper.buildS3KeyPrefix(storageEntity, businessObjectFormatEntity,
                businessObjectDataKey);

        // Create and return the S3 key prefix.
        S3KeyPrefixInformation s3KeyPrefixInformation = new S3KeyPrefixInformation();
        s3KeyPrefixInformation.setS3KeyPrefix(s3KeyPrefix);
        return s3KeyPrefixInformation;
    }

    /**
     * Creates and returns a set of AWS credentials which can be used to access the S3 object indicated by the given business object data and storage.
     *
     * @param businessObjectDataKey Business object data key
     * @param createNewVersion true to create credentials for the next version up from the latest business object data, otherwise, uses specified data version
     * in data key.
     * @param storageName Name of storage to access
     * @param isUpload true if this credential is to upload, false to download
     *
     * @return Credentials which has the permissions to perform the specified actions at the specified storage.
     */
    private AwsCredential getBusinessObjectDataS3Credential(BusinessObjectDataKey businessObjectDataKey,
            Boolean createNewVersion, String storageName, boolean isUpload) {
        Assert.isTrue(StringUtils.isNotBlank(storageName), "storageName must be specified");
        Assert.isTrue(businessObjectDataKey.getBusinessObjectDataVersion() != null || createNewVersion != null,
                "One of businessObjectDataVersion or createNewVersion must be specified.");
        Assert.isTrue(
                businessObjectDataKey.getBusinessObjectDataVersion() == null
                        || !Boolean.TRUE.equals(createNewVersion),
                "createNewVersion must be false or unspecified when businessObjectDataVersion is specified.");

        /*
         * Choose configurations based on whether this is an upload or download operation.
         */
        ConfigurationValue roleArnConfigurationValue;
        ConfigurationValue defaultSessionDurationConfigurationValue;
        ConfigurationValue sessionDurationConfigurationValue;
        S3Actions[] s3Actions;
        KmsActions[] kmsActions;

        if (isUpload) {
            roleArnConfigurationValue = ConfigurationValue.S3_ATTRIBUTE_NAME_UPLOAD_ROLE_ARN;
            defaultSessionDurationConfigurationValue = ConfigurationValue.AWS_S3_DEFAULT_UPLOAD_SESSION_DURATION_SECS;
            sessionDurationConfigurationValue = ConfigurationValue.S3_ATTRIBUTE_NAME_UPLOAD_SESSION_DURATION_SECS;
            s3Actions = new S3Actions[] { S3Actions.PutObject, S3Actions.DeleteObject };
            kmsActions = new KmsActions[] { KmsActions.GENERATE_DATA_KEY, KmsActions.DECRYPT };
        } else {
            roleArnConfigurationValue = ConfigurationValue.S3_ATTRIBUTE_NAME_DOWNLOAD_ROLE_ARN;
            defaultSessionDurationConfigurationValue = ConfigurationValue.AWS_S3_DEFAULT_DOWNLOAD_SESSION_DURATION_SECS;
            sessionDurationConfigurationValue = ConfigurationValue.S3_ATTRIBUTE_NAME_DOWNLOAD_SESSION_DURATION_SECS;
            s3Actions = new S3Actions[] { S3Actions.GetObject };
            kmsActions = new KmsActions[] { KmsActions.DECRYPT };
        }

        StorageEntity storageEntity = storageDaoHelper.getStorageEntity(storageName.trim());
        String roleArn = storageHelper.getStorageAttributeValueByName(
                configurationHelper.getProperty(roleArnConfigurationValue), storageEntity, true);
        Integer durationSeconds = storageHelper.getStorageAttributeIntegerValueByName(
                configurationHelper.getProperty(sessionDurationConfigurationValue), storageEntity,
                configurationHelper.getProperty(defaultSessionDurationConfigurationValue, Integer.class));
        String bucketName = storageHelper.getStorageAttributeValueByName(
                configurationHelper.getProperty(ConfigurationValue.S3_ATTRIBUTE_NAME_BUCKET_NAME), storageEntity,
                true);

        S3KeyPrefixInformation s3KeyPrefixInformation = getS3KeyPrefixImpl(businessObjectDataKey, null, storageName,
                createNewVersion);
        /*
         * Policy is different based on whether this is meant for downloading or uploading.
         * However, both uploader and downloader requires a ListBucket at the bucket level.
         */
        AwsPolicyBuilder awsPolicyBuilder = new AwsPolicyBuilder()
                .withS3Prefix(bucketName, s3KeyPrefixInformation.getS3KeyPrefix(), s3Actions)
                .withS3(bucketName, null, S3Actions.ListObjects);

        /*
         * Only add KMS policies if the storage specifies a KMS ID
         */
        String kmsKeyId = getStorageKmsKeyId(storageEntity);
        if (kmsKeyId != null) {
            awsPolicyBuilder.withKms(kmsKeyId.trim(), kmsActions);
        }

        Credentials credentials = stsDao.getTemporarySecurityCredentials(awsHelper.getAwsParamsDto(),
                UUID.randomUUID().toString(), roleArn, durationSeconds, awsPolicyBuilder.build());

        AwsCredential awsCredential = new AwsCredential();
        awsCredential.setAwsAccessKey(credentials.getAccessKeyId());
        awsCredential.setAwsSecretKey(credentials.getSecretAccessKey());
        awsCredential.setAwsSessionToken(credentials.getSessionToken());
        awsCredential.setAwsSessionExpirationTime(
                HerdDateUtils.getXMLGregorianCalendarValue(credentials.getExpiration()));
        return awsCredential;
    }

    private String getStorageKmsKeyId(StorageEntity storageEntity) {
        return storageHelper.getStorageAttributeValueByName(
                configurationHelper.getProperty(ConfigurationValue.S3_ATTRIBUTE_NAME_KMS_KEY_ID), storageEntity,
                false, true);
    }
}