"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const kms = require("@aws-cdk/aws-kms");
const cdk = require("@aws-cdk/cdk");
const bucket_policy_1 = require("./bucket-policy");
const notifications_resource_1 = require("./notifications-resource");
const perms = require("./perms");
const s3_generated_1 = require("./s3.generated");
const util_1 = require("./util");
/**
 * Represents an S3 Bucket.
 *
 * Buckets can be either defined within this stack:
 *
 *     new Bucket(this, 'MyBucket', { props });
 *
 * Or imported from an existing bucket:
 *
 *     BucketRef.import(this, 'MyImportedBucket', { bucketArn: ... });
 *
 * You can also export a bucket and import it into another stack:
 *
 *     const ref = myBucket.export();
 *     BucketRef.import(this, 'MyImportedBucket', ref);
 *
 */
class BucketRef extends cdk.Construct {
    constructor() {
        super(...arguments);
        /**
         * Indicates if a bucket resource policy should automatically created upon
         * the first call to `addToResourcePolicy`.
         */
        this.autoCreatePolicy = false;
    }
    /**
     * Creates a Bucket construct that represents an external bucket.
     *
     * @param parent The parent creating construct (usually `this`).
     * @param name The construct's name.
     * @param ref A BucketRefProps object. Can be obtained from a call to
     * `bucket.export()`.
     */
    static import(parent, name, props) {
        return new ImportedBucketRef(parent, name, props);
    }
    /**
     * Exports this bucket from the stack.
     */
    export() {
        return {
            bucketArn: new cdk.Output(this, 'BucketArn', { value: this.bucketArn }).makeImportValue(),
            bucketName: new cdk.Output(this, 'BucketName', { value: this.bucketName }).makeImportValue(),
        };
    }
    /**
     * Adds a statement to the resource policy for a principal (i.e.
     * account/role/service) to perform actions on this bucket and/or it's
     * contents. Use `bucketArn` and `arnForObjects(keys)` to obtain ARNs for
     * this bucket or objects.
     */
    addToResourcePolicy(permission) {
        if (!this.policy && this.autoCreatePolicy) {
            this.policy = new bucket_policy_1.BucketPolicy(this, 'Policy', { bucket: this });
        }
        if (this.policy) {
            this.policy.document.addStatement(permission);
        }
    }
    /**
     * The https:// URL of this bucket.
     * @example https://s3.us-west-1.amazonaws.com/onlybucket
     * Similar to calling `urlForObject` with no object key.
     */
    get bucketUrl() {
        return this.urlForObject();
    }
    /**
     * The https URL of an S3 object. For example:
     * @example https://s3.us-west-1.amazonaws.com/onlybucket
     * @example https://s3.us-west-1.amazonaws.com/bucket/key
     * @example https://s3.cn-north-1.amazonaws.com.cn/china-bucket/mykey
     * @param key The S3 key of the object. If not specified, the URL of the
     *            bucket is returned.
     * @returns an ObjectS3Url token
     */
    urlForObject(key) {
        const components = ['https://', 's3.', new cdk.AwsRegion(), '.', new cdk.AwsURLSuffix(), '/', this.bucketName];
        if (key) {
            // trim prepending '/'
            if (typeof key === 'string' && key.startsWith('/')) {
                key = key.substr(1);
            }
            components.push('/');
            components.push(key);
        }
        return new cdk.FnConcat(...components);
    }
    /**
     * Returns an ARN that represents all objects within the bucket that match
     * the key pattern specified. To represent all keys, specify ``"*"``.
     *
     * If you specify multiple components for keyPattern, they will be concatenated::
     *
     *     arnForObjects('home/', team, '/', user, '/*')
     *
     */
    arnForObjects(...keyPattern) {
        return new cdk.FnConcat(this.bucketArn, '/', ...keyPattern);
    }
    /**
     * Temporary API for granting read permissions for this bucket and it's
     * contents to an IAM principal (Role/Group/User).
     *
     * If an encryption key is used, permission to ues the key to decrypt the
     * contents of the bucket will also be granted.
     */
    grantRead(identity, objectsKeyPattern = '*') {
        if (!identity) {
            return;
        }
        this.grant(identity, objectsKeyPattern, perms.BUCKET_READ_ACTIONS, perms.KEY_READ_ACTIONS);
    }
    /**
     * Grants read/write permissions for this bucket and it's contents to an IAM
     * principal (Role/Group/User).
     *
     * If an encryption key is used, permission to use the key for
     * encrypt/decrypt will also be granted.
     */
    grantReadWrite(identity, objectsKeyPattern = '*') {
        if (!identity) {
            return;
        }
        const bucketActions = perms.BUCKET_READ_ACTIONS.concat(perms.BUCKET_WRITE_ACTIONS);
        const keyActions = perms.KEY_READ_ACTIONS.concat(perms.KEY_WRITE_ACTIONS);
        this.grant(identity, objectsKeyPattern, bucketActions, keyActions);
    }
    grant(identity, objectsKeyPattern, bucketActions, keyActions) {
        const resources = [
            this.bucketArn,
            this.arnForObjects(objectsKeyPattern)
        ];
        identity.addToPolicy(new cdk.PolicyStatement()
            .addResources(...resources)
            .addActions(...bucketActions));
        // grant key permissions if there's an associated key.
        if (this.encryptionKey) {
            // KMS permissions need to be granted both directions
            identity.addToPolicy(new cdk.PolicyStatement()
                .addResource(this.encryptionKey.keyArn)
                .addActions(...keyActions));
            this.encryptionKey.addToResourcePolicy(new cdk.PolicyStatement()
                .addResource('*')
                .addPrincipal(identity.principal)
                .addActions(...keyActions));
        }
    }
}
exports.BucketRef = BucketRef;
/**
 * An S3 bucket with associated policy objects
 *
 * This bucket does not yet have all features that exposed by the underlying
 * BucketResource.
 */
class Bucket extends BucketRef {
    constructor(parent, name, props = {}) {
        super(parent, name);
        this.autoCreatePolicy = true;
        this.lifecycleRules = [];
        util_1.validateBucketName(props && props.bucketName);
        const { bucketEncryption, encryptionKey } = this.parseEncryption(props);
        const resource = new s3_generated_1.cloudformation.BucketResource(this, 'Resource', {
            bucketName: props && props.bucketName,
            bucketEncryption,
            versioningConfiguration: props.versioned ? { status: 'Enabled' } : undefined,
            lifecycleConfiguration: new cdk.Token(() => this.parseLifecycleConfiguration()),
        });
        cdk.applyRemovalPolicy(resource, props.removalPolicy);
        this.versioned = props.versioned;
        this.policy = props.policy;
        this.encryptionKey = encryptionKey;
        this.bucketArn = resource.bucketArn;
        this.bucketName = resource.ref;
        this.domainName = resource.bucketDomainName;
        this.dualstackDomainName = resource.bucketDualStackDomainName;
        // Add all lifecycle rules
        (props.lifecycleRules || []).forEach(this.addLifecycleRule.bind(this));
        // defines a BucketNotifications construct. Notice that an actual resource will only
        // be added if there are notifications added, so we don't need to condition this.
        this.notifications = new notifications_resource_1.BucketNotifications(this, 'Notifications', { bucket: this });
    }
    /**
     * Add a lifecycle rule to the bucket
     *
     * @param rule The rule to add
     */
    addLifecycleRule(rule) {
        if ((rule.noncurrentVersionExpirationInDays !== undefined
            || (rule.noncurrentVersionTransitions && rule.noncurrentVersionTransitions.length > 0))
            && !this.versioned) {
            throw new Error("Cannot use 'noncurrent' rules on a nonversioned bucket");
        }
        this.lifecycleRules.push(rule);
    }
    /**
     * Adds a bucket notification event destination.
     * @param event The event to trigger the notification
     * @param dest The notification destination (Lambda, SNS Topic or SQS Queue)
     *
     * @param filters S3 object key filter rules to determine which objects
     * trigger this event. Each filter must include a `prefix` and/or `suffix`
     * that will be matched against the s3 object key. Refer to the S3 Developer Guide
     * for details about allowed filter rules.
     *
     * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-filtering
     *
     * @example
     *
     *      bucket.onEvent(EventType.OnObjectCreated, myLambda, 'home/myusername/*')
     *
     * @see
     * https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html
     */
    onEvent(event, dest, ...filters) {
        this.notifications.addNotification(event, dest, ...filters);
    }
    /**
     * Subscribes a destination to receive notificatins when an object is
     * created in the bucket. This is identical to calling
     * `onEvent(EventType.ObjectCreated)`.
     *
     * @param dest The notification destination (see onEvent)
     * @param filters Filters (see onEvent)
     */
    onObjectCreated(dest, ...filters) {
        return this.onEvent(EventType.ObjectCreated, dest, ...filters);
    }
    /**
     * Subscribes a destination to receive notificatins when an object is
     * removed from the bucket. This is identical to calling
     * `onEvent(EventType.ObjectRemoved)`.
     *
     * @param dest The notification destination (see onEvent)
     * @param filters Filters (see onEvent)
     */
    onObjectRemoved(dest, ...filters) {
        return this.onEvent(EventType.ObjectRemoved, dest, ...filters);
    }
    /**
     * Set up key properties and return the Bucket encryption property from the
     * user's configuration.
     */
    parseEncryption(props) {
        // default to unencrypted.
        const encryptionType = props.encryption || BucketEncryption.Unencrypted;
        // if encryption key is set, encryption must be set to KMS.
        if (encryptionType !== BucketEncryption.Kms && props.encryptionKey) {
            throw new Error(`encryptionKey is specified, so 'encryption' must be set to KMS (value: ${encryptionType})`);
        }
        if (encryptionType === BucketEncryption.Unencrypted) {
            return { bucketEncryption: undefined, encryptionKey: undefined };
        }
        if (encryptionType === BucketEncryption.Kms) {
            const encryptionKey = props.encryptionKey || new kms.EncryptionKey(this, 'Key', {
                description: `Created by ${this.path}`
            });
            const bucketEncryption = {
                serverSideEncryptionConfiguration: [
                    {
                        serverSideEncryptionByDefault: {
                            sseAlgorithm: 'aws:kms',
                            kmsMasterKeyId: encryptionKey.keyArn
                        }
                    }
                ]
            };
            return { encryptionKey, bucketEncryption };
        }
        if (encryptionType === BucketEncryption.S3Managed) {
            const bucketEncryption = {
                serverSideEncryptionConfiguration: [
                    { serverSideEncryptionByDefault: { sseAlgorithm: 'AES256' } }
                ]
            };
            return { bucketEncryption };
        }
        if (encryptionType === BucketEncryption.KmsManaged) {
            const bucketEncryption = {
                serverSideEncryptionConfiguration: [
                    { serverSideEncryptionByDefault: { sseAlgorithm: 'aws:kms' } }
                ]
            };
            return { bucketEncryption };
        }
        throw new Error(`Unexpected 'encryptionType': ${encryptionType}`);
    }
    /**
     * Parse the lifecycle configuration out of the uucket props
     * @param props Par
     */
    parseLifecycleConfiguration() {
        if (!this.lifecycleRules || this.lifecycleRules.length === 0) {
            return undefined;
        }
        return { rules: this.lifecycleRules.map(parseLifecycleRule) };
        function parseLifecycleRule(rule) {
            const enabled = rule.enabled !== undefined ? rule.enabled : true;
            const x = {
                // tslint:disable-next-line:max-line-length
                abortIncompleteMultipartUpload: rule.abortIncompleteMultipartUploadAfterDays !== undefined ? { daysAfterInitiation: rule.abortIncompleteMultipartUploadAfterDays } : undefined,
                expirationDate: rule.expirationDate,
                expirationInDays: rule.expirationInDays,
                id: rule.id,
                noncurrentVersionExpirationInDays: rule.noncurrentVersionExpirationInDays,
                noncurrentVersionTransitions: rule.noncurrentVersionTransitions,
                prefix: rule.prefix,
                status: enabled ? 'Enabled' : 'Disabled',
                transitions: rule.transitions,
                tagFilters: parseTagFilters(rule.tagFilters)
            };
            return x;
        }
        function parseTagFilters(tagFilters) {
            if (!tagFilters || tagFilters.length === 0) {
                return undefined;
            }
            return Object.keys(tagFilters).map(tag => ({
                key: tag,
                value: tagFilters[tag]
            }));
        }
    }
}
exports.Bucket = Bucket;
/**
 * What kind of server-side encryption to apply to this bucket
 */
var BucketEncryption;
(function (BucketEncryption) {
    /**
     * Objects in the bucket are not encrypted.
     */
    BucketEncryption["Unencrypted"] = "NONE";
    /**
     * Server-side KMS encryption with a master key managed by KMS.
     */
    BucketEncryption["KmsManaged"] = "MANAGED";
    /**
     * Server-side encryption with a master key managed by S3.
     */
    BucketEncryption["S3Managed"] = "S3MANAGED";
    /**
     * Server-side encryption with a KMS key managed by the user.
     * If `encryptionKey` is specified, this key will be used, otherwise, one will be defined.
     */
    BucketEncryption["Kms"] = "KMS";
})(BucketEncryption = exports.BucketEncryption || (exports.BucketEncryption = {}));
/**
 * The name of the bucket.
 */
class BucketName extends cdk.Token {
}
exports.BucketName = BucketName;
/**
 * A key to an S3 object.
 */
class ObjectKey extends cdk.Token {
}
exports.ObjectKey = ObjectKey;
/**
 * The web URL (https://s3.us-west-1.amazonaws.com/bucket/key) of an S3 object.
 */
class S3Url extends cdk.Token {
}
exports.S3Url = S3Url;
/**
 * Notification event types.
 */
var EventType;
(function (EventType) {
    /**
     * Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using
     * these event types, you can enable notification when an object is created
     * using a specific API, or you can use the s3:ObjectCreated:* event type to
     * request notification regardless of the API that was used to create an
     * object.
     */
    EventType["ObjectCreated"] = "s3:ObjectCreated:*";
    /**
     * Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using
     * these event types, you can enable notification when an object is created
     * using a specific API, or you can use the s3:ObjectCreated:* event type to
     * request notification regardless of the API that was used to create an
     * object.
     */
    EventType["ObjectCreatedPut"] = "s3:ObjectCreated:Put";
    /**
     * Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using
     * these event types, you can enable notification when an object is created
     * using a specific API, or you can use the s3:ObjectCreated:* event type to
     * request notification regardless of the API that was used to create an
     * object.
     */
    EventType["ObjectCreatedPost"] = "s3:ObjectCreated:Post";
    /**
     * Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using
     * these event types, you can enable notification when an object is created
     * using a specific API, or you can use the s3:ObjectCreated:* event type to
     * request notification regardless of the API that was used to create an
     * object.
     */
    EventType["ObjectCreatedCopy"] = "s3:ObjectCreated:Copy";
    /**
     * Amazon S3 APIs such as PUT, POST, and COPY can create an object. Using
     * these event types, you can enable notification when an object is created
     * using a specific API, or you can use the s3:ObjectCreated:* event type to
     * request notification regardless of the API that was used to create an
     * object.
     */
    EventType["ObjectCreatedCompleteMultipartUpload"] = "s3:ObjectCreated:CompleteMultipartUpload";
    /**
     * By using the ObjectRemoved event types, you can enable notification when
     * an object or a batch of objects is removed from a bucket.
     *
     * You can request notification when an object is deleted or a versioned
     * object is permanently deleted by using the s3:ObjectRemoved:Delete event
     * type. Or you can request notification when a delete marker is created for
     * a versioned object by using s3:ObjectRemoved:DeleteMarkerCreated. For
     * information about deleting versioned objects, see Deleting Object
     * Versions. You can also use a wildcard s3:ObjectRemoved:* to request
     * notification anytime an object is deleted.
     *
     * You will not receive event notifications from automatic deletes from
     * lifecycle policies or from failed operations.
     */
    EventType["ObjectRemoved"] = "s3:ObjectRemoved:*";
    /**
     * By using the ObjectRemoved event types, you can enable notification when
     * an object or a batch of objects is removed from a bucket.
     *
     * You can request notification when an object is deleted or a versioned
     * object is permanently deleted by using the s3:ObjectRemoved:Delete event
     * type. Or you can request notification when a delete marker is created for
     * a versioned object by using s3:ObjectRemoved:DeleteMarkerCreated. For
     * information about deleting versioned objects, see Deleting Object
     * Versions. You can also use a wildcard s3:ObjectRemoved:* to request
     * notification anytime an object is deleted.
     *
     * You will not receive event notifications from automatic deletes from
     * lifecycle policies or from failed operations.
     */
    EventType["ObjectRemovedDelete"] = "s3:ObjectRemoved:Delete";
    /**
     * By using the ObjectRemoved event types, you can enable notification when
     * an object or a batch of objects is removed from a bucket.
     *
     * You can request notification when an object is deleted or a versioned
     * object is permanently deleted by using the s3:ObjectRemoved:Delete event
     * type. Or you can request notification when a delete marker is created for
     * a versioned object by using s3:ObjectRemoved:DeleteMarkerCreated. For
     * information about deleting versioned objects, see Deleting Object
     * Versions. You can also use a wildcard s3:ObjectRemoved:* to request
     * notification anytime an object is deleted.
     *
     * You will not receive event notifications from automatic deletes from
     * lifecycle policies or from failed operations.
     */
    EventType["ObjectRemovedDeleteMarkerCreated"] = "s3:ObjectRemoved:DeleteMarkerCreated";
    /**
     * You can use this event type to request Amazon S3 to send a notification
     * message when Amazon S3 detects that an object of the RRS storage class is
     * lost.
     */
    EventType["ReducedRedundancyLostObject"] = "s3:ReducedRedundancyLostObject";
})(EventType = exports.EventType || (exports.EventType = {}));
class ImportedBucketRef extends BucketRef {
    constructor(parent, name, props) {
        super(parent, name);
        this.bucketArn = util_1.parseBucketArn(props);
        this.bucketName = util_1.parseBucketName(props);
        this.autoCreatePolicy = false;
        this.policy = undefined;
    }
}
//# sourceMappingURL=data:application/json;base64, 