|
5 | 5 | from typing import Literal, TypedDict |
6 | 6 |
|
7 | 7 | from localstack.aws.api.sns import ( |
| 8 | + Endpoint, |
8 | 9 | MessageAttributeMap, |
| 10 | + PhoneNumber, |
| 11 | + PlatformApplication, |
9 | 12 | PublishBatchRequestEntry, |
| 13 | + TopicAttributesMap, |
10 | 14 | subscriptionARN, |
11 | 15 | topicARN, |
12 | 16 | ) |
13 | | -from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute |
14 | | -from localstack.utils.aws.arns import parse_arn |
| 17 | +from localstack.services.stores import ( |
| 18 | + AccountRegionBundle, |
| 19 | + BaseStore, |
| 20 | + CrossRegionAttribute, |
| 21 | + LocalAttribute, |
| 22 | +) |
15 | 23 | from localstack.utils.objects import singleton_factory |
16 | 24 | from localstack.utils.strings import long_uid |
| 25 | +from localstack.utils.tagging import TaggingService |
| 26 | + |
| 27 | + |
| 28 | +class Topic(TypedDict, total=True): |
| 29 | + arn: str |
| 30 | + name: str |
| 31 | + attributes: TopicAttributesMap |
| 32 | + data_protection_policy: str |
| 33 | + subscriptions: list[str] |
| 34 | + |
17 | 35 |
|
18 | 36 | SnsProtocols = Literal[ |
19 | 37 | "http", "https", "email", "email-json", "sms", "sqs", "application", "lambda", "firehose" |
|
23 | 41 | "APNS", "APNS_SANDBOX", "ADM", "FCM", "Baidu", "GCM", "MPNS", "WNS" |
24 | 42 | ] |
25 | 43 |
|
| 44 | + |
| 45 | +class EndpointAttributeNames(StrEnum): |
| 46 | + CUSTOM_USER_DATA = "CustomUserData" |
| 47 | + Token = "Token" |
| 48 | + ENABLED = "Enabled" |
| 49 | + |
| 50 | + |
| 51 | +SMS_ATTRIBUTE_NAMES = [ |
| 52 | + "DeliveryStatusIAMRole", |
| 53 | + "DeliveryStatusSuccessSamplingRate", |
| 54 | + "DefaultSenderID", |
| 55 | + "DefaultSMSType", |
| 56 | + "UsageReportS3Bucket", |
| 57 | +] |
| 58 | +SMS_TYPES = ["Promotional", "Transactional"] |
| 59 | +SMS_DEFAULT_SENDER_REGEX = r"^(?=[A-Za-z0-9]{1,11}$)(?=.*[A-Za-z])[A-Za-z0-9]+$" |
26 | 60 | SnsMessageProtocols = Literal[SnsProtocols, SnsApplicationPlatforms] |
27 | 61 |
|
28 | 62 |
|
29 | | -def create_default_sns_topic_policy(topic_arn: str) -> dict: |
| 63 | +class SnsSubscription(TypedDict, total=False): |
30 | 64 | """ |
31 | | - Creates the default SNS topic policy for the given topic ARN. |
32 | | -
|
33 | | - :param topic_arn: The topic arn |
34 | | - :return: A policy document |
| 65 | + In SNS, Subscription can be represented with only TopicArn, Endpoint, Protocol, SubscriptionArn and Owner, for |
| 66 | + example in ListSubscriptions. However, when getting a subscription with GetSubscriptionAttributes, it will return |
| 67 | + the Subscription object merged with its own attributes. |
| 68 | + This represents this merged object, for internal use and in GetSubscriptionAttributes |
| 69 | + https://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html |
35 | 70 | """ |
36 | | - return { |
37 | | - "Version": "2008-10-17", |
38 | | - "Id": "__default_policy_ID", |
39 | | - "Statement": [ |
40 | | - { |
41 | | - "Sid": "__default_statement_ID", |
42 | | - "Effect": "Allow", |
43 | | - "Principal": {"AWS": "*"}, |
44 | | - "Action": [ |
45 | | - "SNS:GetTopicAttributes", |
46 | | - "SNS:SetTopicAttributes", |
47 | | - "SNS:AddPermission", |
48 | | - "SNS:RemovePermission", |
49 | | - "SNS:DeleteTopic", |
50 | | - "SNS:Subscribe", |
51 | | - "SNS:ListSubscriptionsByTopic", |
52 | | - "SNS:Publish", |
53 | | - ], |
54 | | - "Resource": topic_arn, |
55 | | - "Condition": {"StringEquals": {"AWS:SourceOwner": parse_arn(topic_arn)["account"]}}, |
56 | | - } |
57 | | - ], |
58 | | - } |
| 71 | + |
| 72 | + TopicArn: topicARN |
| 73 | + Endpoint: str |
| 74 | + Protocol: SnsProtocols |
| 75 | + SubscriptionArn: subscriptionARN |
| 76 | + PendingConfirmation: Literal["true", "false"] |
| 77 | + Owner: str | None |
| 78 | + SubscriptionPrincipal: str | None |
| 79 | + FilterPolicy: str | None |
| 80 | + FilterPolicyScope: Literal["MessageAttributes", "MessageBody"] |
| 81 | + RawMessageDelivery: Literal["true", "false"] |
| 82 | + ConfirmationWasAuthenticated: Literal["true", "false"] |
| 83 | + SubscriptionRoleArn: str | None |
| 84 | + DeliveryPolicy: str | None |
59 | 85 |
|
60 | 86 |
|
61 | 87 | @singleton_factory |
@@ -126,60 +152,50 @@ def from_batch_entry(cls, entry: PublishBatchRequestEntry, is_fifo=False) -> "Sn |
126 | 152 | ) |
127 | 153 |
|
128 | 154 |
|
129 | | -class SnsSubscription(TypedDict, total=False): |
130 | | - """ |
131 | | - In SNS, Subscription can be represented with only TopicArn, Endpoint, Protocol, SubscriptionArn and Owner, for |
132 | | - example in ListSubscriptions. However, when getting a subscription with GetSubscriptionAttributes, it will return |
133 | | - the Subscription object merged with its own attributes. |
134 | | - This represents this merged object, for internal use and in GetSubscriptionAttributes |
135 | | - https://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html |
136 | | - """ |
| 155 | +@dataclass |
| 156 | +class PlatformEndpoint: |
| 157 | + platform_application_arn: str |
| 158 | + platform_endpoint: Endpoint |
137 | 159 |
|
138 | | - TopicArn: topicARN |
139 | | - Endpoint: str |
140 | | - Protocol: SnsProtocols |
141 | | - SubscriptionArn: subscriptionARN |
142 | | - PendingConfirmation: Literal["true", "false"] |
143 | | - Owner: str | None |
144 | | - SubscriptionPrincipal: str | None |
145 | | - FilterPolicy: str | None |
146 | | - FilterPolicyScope: Literal["MessageAttributes", "MessageBody"] |
147 | | - RawMessageDelivery: Literal["true", "false"] |
148 | | - ConfirmationWasAuthenticated: Literal["true", "false"] |
149 | | - SubscriptionRoleArn: str | None |
150 | | - DeliveryPolicy: str | None |
| 160 | + |
| 161 | +@dataclass |
| 162 | +class PlatformApplicationDetails: |
| 163 | + platform_application: PlatformApplication |
| 164 | + # maps all Endpoints of the PlatformApplication, from their Token to their ARN |
| 165 | + platform_endpoints: dict[str, str] |
151 | 166 |
|
152 | 167 |
|
153 | 168 | class SnsStore(BaseStore): |
154 | | - # maps topic ARN to subscriptions ARN |
155 | | - topic_subscriptions: dict[str, list[str]] = LocalAttribute(default=dict) |
| 169 | + # maps topic ARN to Topic |
| 170 | + topics: dict[str, Topic] = LocalAttribute(default=dict) |
156 | 171 |
|
157 | 172 | # maps subscription ARN to SnsSubscription |
158 | 173 | subscriptions: dict[str, SnsSubscription] = LocalAttribute(default=dict) |
159 | 174 |
|
| 175 | + # filter policy are stored as JSON string in subscriptions, store the decoded result Dict |
| 176 | + subscription_filter_policy: dict[subscriptionARN, dict] = LocalAttribute(default=dict) |
| 177 | + |
160 | 178 | # maps confirmation token to subscription ARN |
161 | 179 | subscription_tokens: dict[str, str] = LocalAttribute(default=dict) |
162 | 180 |
|
163 | | - # maps topic ARN to list of tags |
164 | | - sns_tags: dict[str, list[dict]] = LocalAttribute(default=dict) |
| 181 | + # maps platform application arns to platform applications |
| 182 | + platform_applications: dict[str, PlatformApplicationDetails] = LocalAttribute(default=dict) |
| 183 | + |
| 184 | + # maps endpoint arns to platform endpoints |
| 185 | + platform_endpoints: dict[str, PlatformEndpoint] = LocalAttribute(default=dict) |
165 | 186 |
|
166 | 187 | # cache of topic ARN to platform endpoint messages (used primarily for testing) |
167 | 188 | platform_endpoint_messages: dict[str, list[dict]] = LocalAttribute(default=dict) |
168 | 189 |
|
| 190 | + # topic/subscription independent default values for sending sms messages |
| 191 | + sms_attributes: dict[str, str] = LocalAttribute(default=dict) |
| 192 | + |
169 | 193 | # list of sent SMS messages |
170 | 194 | sms_messages: list[dict] = LocalAttribute(default=list) |
171 | 195 |
|
172 | | - # filter policy are stored as JSON string in subscriptions, store the decoded result Dict |
173 | | - subscription_filter_policy: dict[subscriptionARN, dict] = LocalAttribute(default=dict) |
| 196 | + TAGS: TaggingService = CrossRegionAttribute(default=TaggingService) |
174 | 197 |
|
175 | | - def get_topic_subscriptions(self, topic_arn: str) -> list[SnsSubscription]: |
176 | | - topic_subscriptions = self.topic_subscriptions.get(topic_arn, []) |
177 | | - subscriptions = [ |
178 | | - subscription |
179 | | - for subscription_arn in topic_subscriptions |
180 | | - if (subscription := self.subscriptions.get(subscription_arn)) |
181 | | - ] |
182 | | - return subscriptions |
| 198 | + PHONE_NUMBERS_OPTED_OUT: set[PhoneNumber] = CrossRegionAttribute(default=set) |
183 | 199 |
|
184 | 200 |
|
185 | 201 | sns_stores = AccountRegionBundle("sns", SnsStore) |
0 commit comments