Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions packages/aws-cdk-lib/aws-dynamodb/test/dynamodb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,59 @@ describe('L1 static factory methods', () => {
});
});

describe('L1 grant method', () => {
test('grant can be used to add permissions to a principal', () => {
// GIVEN
const stack = new Stack();
const table = new CfnTable(stack, 'MyTable', {
keySchema: [{ attributeName: 'ID', keyType: 'HASH' }],
});
const user = new iam.User(stack, 'user');

// WHEN
const grant = table.grant(
user,
['dynamodb:GetRecords', 'dynamodb:GetShardIterator'],
{
'StringEqualsIfExists': {
'dynamodb:Select': 'SPECIFIC_ATTRIBUTES',
},
},
);

// THEN
grant.assertSuccess();
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
'PolicyDocument': {
'Statement': [
{
'Action': ['dynamodb:GetRecords', 'dynamodb:GetShardIterator'],
'Effect': 'Allow',
'Resource': {
'Fn::GetAtt': [
'MyTable',
'Arn',
],
},
'Condition': {
'StringEqualsIfExists': {
'dynamodb:Select': 'SPECIFIC_ATTRIBUTES',
},
},
},
],
'Version': '2012-10-17',
},
'PolicyName': 'userDefaultPolicy083DF682',
'Users': [
{
'Ref': 'user2C2B57AE',
},
],
});
});
});

testDeprecated('when specifying every property', () => {
const stack = new Stack();
const stream = new kinesis.Stream(stack, 'MyStream');
Expand Down
6 changes: 6 additions & 0 deletions tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,14 @@ export class CdkCloudWatch extends ExternalModule {
public readonly MetricOptions = Type.fromName(this, 'MetricOptions');
}

export class CdkIam extends ExternalModule {
public readonly Grant = $T(Type.fromName(this, 'Grant'));
public readonly IGrantable = $T(Type.fromName(this, 'IGrantable'));
}

export const CDK_CORE = new CdkCore('aws-cdk-lib');
export const CDK_CLOUDWATCH = new CdkCloudWatch('aws-cdk-lib/aws-cloudwatch');
export const CDK_IAM = new CdkIam('aws-cdk-lib/aws-iam');
export const CONSTRUCTS = new Constructs();

function makeCallableExpr(scope: IScope, name: string) {
Expand Down
65 changes: 59 additions & 6 deletions tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import {
Block,
ClassType,
code,
DocsSpec,
DummyScope,
expr, Expression,
expr,
Expression,
Initializer,
InterfaceType,
IScope,
Expand All @@ -16,26 +18,29 @@ import {
MemberVisibility,
Module,
ObjectLiteral,
Stability, Statement,
Stability,
Statement,
stmt,
StructType,
SuperInitializer,
ThingSymbol,
TruthyOr,
Type,
TypeDeclarationStatement,
DocsSpec,
} from '@cdklabs/typewriter';
import { CDK_CORE, CONSTRUCTS } from './cdk';
import { CDK_CORE, CDK_IAM, CONSTRUCTS } from './cdk';
import { CloudFormationMapping } from './cloudformation-mapping';
import { ResourceDecider, shouldBuildReferenceInterface } from './resource-decider';
import { TypeConverter } from './type-converter';
import {
attributePropertyName,
cfnParserNameFromType,
cfnProducerNameFromType,
classNameFromResource,
cloudFormationDocLink, propertyNameFromCloudFormation,
propStructNameFromResource, referencePropertyName,
cloudFormationDocLink,
propertyNameFromCloudFormation,
propStructNameFromResource,
referencePropertyName,
staticRequiredTransform,
staticResourceTypeName,
} from '../naming';
Expand Down Expand Up @@ -152,6 +157,7 @@ export class ResourceClass extends ClassType {
this.makeFromCloudFormationFactory();
this.makeFromArnFactory();
this.makeFromNameFactory();
this.makeGrant();

if (this.resource.cloudFormationTransform) {
this.addProperty({
Expand Down Expand Up @@ -467,6 +473,53 @@ export class ResourceClass extends ClassType {
);
}

private makeGrant() {
const cfnArnProperty = this.decider.findArnProperty();
if (cfnArnProperty == null) {
return;
}

CDK_IAM.import(this.module, 'iam');

const grant = this.addMethod({
name: 'grant',
docs: {
summary: 'Grant the given IGrantable permissions to perform the actions specified in actions on this resource.',
stability: Stability.External,
},
returnType: CDK_IAM.Grant,
});

grant.addParameter({
name: 'grantee',
type: CDK_IAM.IGrantable,
documentation: 'The principal to grant permissions to.',
});

grant.addParameter({
name: 'actions',
type: Type.arrayOf(Type.STRING),
documentation: 'The actions to grant permissions for.',
});

grant.addParameter({
name: 'conditions',
type: Type.mapOf(Type.mapOf(Type.ambient('unknown'))),
documentation: 'The conditions under which the actions should be allowed.',
optional: true,
});

grant.addBody(
stmt.ret(CDK_IAM.Grant.addToPrincipal(expr.object({
grantee: expr.ident('grantee'),
actions: expr.ident('actions'),
resourceArns: expr.list([$this[attributePropertyName(cfnArnProperty)]]),
scope: $this,
conditions: expr.ident('conditions'),
}))),
);
}

private makeConstructor() {
// Ctor
const init = this.addInitializer({
Expand Down
Loading