|
| 1 | +from typing import Dict, Optional |
| 2 | + |
| 3 | +from aws_lambda_powertools.utilities.data_classes.common import DictWrapper, get_header_value |
| 4 | + |
| 5 | + |
| 6 | +class S3ObjectContext(DictWrapper): |
| 7 | + """The input and output details for connections to Amazon S3 and S3 Object Lambda.""" |
| 8 | + |
| 9 | + @property |
| 10 | + def input_s3_url(self) -> str: |
| 11 | + """A pre-signed URL that can be used to fetch the original object from Amazon S3. |
| 12 | +
|
| 13 | + The URL is signed using the original caller’s identity, and their permissions |
| 14 | + will apply when the URL is used. If there are signed headers in the URL, the |
| 15 | + Lambda function must include these in the call to Amazon S3, except for the Host.""" |
| 16 | + return self["inputS3Url"] |
| 17 | + |
| 18 | + @property |
| 19 | + def output_route(self) -> str: |
| 20 | + """A routing token that is added to the S3 Object Lambda URL when the Lambda function |
| 21 | + calls `WriteGetObjectResponse`.""" |
| 22 | + return self["outputRoute"] |
| 23 | + |
| 24 | + @property |
| 25 | + def output_token(self) -> str: |
| 26 | + """An opaque token used by S3 Object Lambda to match the WriteGetObjectResponse call |
| 27 | + with the original caller.""" |
| 28 | + return self["outputToken"] |
| 29 | + |
| 30 | + |
| 31 | +class S3ObjectConfiguration(DictWrapper): |
| 32 | + """Configuration information about the S3 Object Lambda access point.""" |
| 33 | + |
| 34 | + @property |
| 35 | + def access_point_arn(self) -> str: |
| 36 | + """The Amazon Resource Name (ARN) of the S3 Object Lambda access point that received |
| 37 | + this request.""" |
| 38 | + return self["accessPointArn"] |
| 39 | + |
| 40 | + @property |
| 41 | + def supporting_access_point_arn(self) -> str: |
| 42 | + """The ARN of the supporting access point that is specified in the S3 Object Lambda |
| 43 | + access point configuration.""" |
| 44 | + return self["supportingAccessPointArn"] |
| 45 | + |
| 46 | + @property |
| 47 | + def payload(self) -> str: |
| 48 | + """Custom data that is applied to the S3 Object Lambda access point configuration. |
| 49 | +
|
| 50 | + S3 Object Lambda treats this as an opaque string, so it might need to be decoded |
| 51 | + before use.""" |
| 52 | + return self["payload"] |
| 53 | + |
| 54 | + |
| 55 | +class S3ObjectUserRequest(DictWrapper): |
| 56 | + """ Information about the original call to S3 Object Lambda.""" |
| 57 | + |
| 58 | + @property |
| 59 | + def url(self) -> str: |
| 60 | + """The decoded URL of the request as received by S3 Object Lambda, excluding any |
| 61 | + authorization-related query parameters.""" |
| 62 | + return self["url"] |
| 63 | + |
| 64 | + @property |
| 65 | + def headers(self) -> Dict[str, str]: |
| 66 | + """A map of string to strings containing the HTTP headers and their values from the original call, |
| 67 | + excluding any authorization-related headers. |
| 68 | +
|
| 69 | + If the same header appears multiple times, their values are combined into a comma-delimited list. |
| 70 | + The case of the original headers is retained in this map.""" |
| 71 | + return self["headers"] |
| 72 | + |
| 73 | + def get_header_value( |
| 74 | + self, name: str, default_value: Optional[str] = None, case_sensitive: Optional[bool] = False |
| 75 | + ) -> Optional[str]: |
| 76 | + """Get header value by name |
| 77 | +
|
| 78 | + Parameters |
| 79 | + ---------- |
| 80 | + name: str |
| 81 | + Header name |
| 82 | + default_value: str, optional |
| 83 | + Default value if no value was found by name |
| 84 | + case_sensitive: bool |
| 85 | + Whether to use a case sensitive look up |
| 86 | + Returns |
| 87 | + ------- |
| 88 | + str, optional |
| 89 | + Header value |
| 90 | + """ |
| 91 | + return get_header_value(self.headers, name, default_value, case_sensitive) |
| 92 | + |
| 93 | + |
| 94 | +class S3ObjectSessionIssuer(DictWrapper): |
| 95 | + @property |
| 96 | + def get_type(self) -> str: |
| 97 | + """The source of the temporary security credentials, such as Root, IAMUser, or Role.""" |
| 98 | + return self["type"] |
| 99 | + |
| 100 | + @property |
| 101 | + def user_name(self) -> str: |
| 102 | + """The friendly name of the user or role that issued the session.""" |
| 103 | + return self["userName"] |
| 104 | + |
| 105 | + @property |
| 106 | + def principal_id(self) -> str: |
| 107 | + """The internal ID of the entity that was used to get credentials.""" |
| 108 | + return self["principalId"] |
| 109 | + |
| 110 | + @property |
| 111 | + def arn(self) -> str: |
| 112 | + """The ARN of the source (account, IAM user, or role) that was used to get temporary security credentials.""" |
| 113 | + return self["arn"] |
| 114 | + |
| 115 | + @property |
| 116 | + def account_id(self) -> str: |
| 117 | + """The account that owns the entity that was used to get credentials.""" |
| 118 | + return self["accountId"] |
| 119 | + |
| 120 | + |
| 121 | +class S3ObjectSessionAttributes(DictWrapper): |
| 122 | + @property |
| 123 | + def creation_date(self) -> str: |
| 124 | + """The date and time when the temporary security credentials were issued. |
| 125 | + Represented in ISO 8601 basic notation.""" |
| 126 | + return self["creationDate"] |
| 127 | + |
| 128 | + @property |
| 129 | + def mfa_authenticated(self) -> str: |
| 130 | + """The value is true if the root user or IAM user whose credentials were used for the request also was |
| 131 | + authenticated with an MFA device; otherwise, false..""" |
| 132 | + return self["mfaAuthenticated"] |
| 133 | + |
| 134 | + |
| 135 | +class S3ObjectSessionContext(DictWrapper): |
| 136 | + @property |
| 137 | + def session_issuer(self) -> S3ObjectSessionIssuer: |
| 138 | + """If the request was made with temporary security credentials, an element that provides information |
| 139 | + about how the credentials were obtained.""" |
| 140 | + return S3ObjectSessionIssuer(self["sessionIssuer"]) |
| 141 | + |
| 142 | + @property |
| 143 | + def attributes(self) -> S3ObjectSessionAttributes: |
| 144 | + """Session attributes.""" |
| 145 | + return S3ObjectSessionAttributes(self["attributes"]) |
| 146 | + |
| 147 | + |
| 148 | +class S3ObjectUserIdentity(DictWrapper): |
| 149 | + """Details about the identity that made the call to S3 Object Lambda. |
| 150 | +
|
| 151 | + Documentation: |
| 152 | + ------------- |
| 153 | + - https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html |
| 154 | + """ |
| 155 | + |
| 156 | + @property |
| 157 | + def get_type(self) -> str: |
| 158 | + """The type of identity. |
| 159 | +
|
| 160 | + The following values are possible: |
| 161 | +
|
| 162 | + - Root – The request was made with your AWS account credentials. If the userIdentity |
| 163 | + type is Root and you set an alias for your account, the userName field contains your account alias. |
| 164 | + For more information, see Your AWS Account ID and Its Alias. |
| 165 | + - IAMUser – The request was made with the credentials of an IAM user. |
| 166 | + - AssumedRole – The request was made with temporary security credentials that were obtained |
| 167 | + with a role via a call to the AWS Security Token Service (AWS STS) AssumeRole API. This can include |
| 168 | + roles for Amazon EC2 and cross-account API access. |
| 169 | + - FederatedUser – The request was made with temporary security credentials that were obtained via a |
| 170 | + call to the AWS STS GetFederationToken API. The sessionIssuer element indicates if the API was |
| 171 | + called with root or IAM user credentials. |
| 172 | + - AWSAccount – The request was made by another AWS account. |
| 173 | + - AWSService – The request was made by an AWS account that belongs to an AWS service. |
| 174 | + For example, AWS Elastic Beanstalk assumes an IAM role in your account to call other AWS services |
| 175 | + on your behalf. |
| 176 | + """ |
| 177 | + return self["type"] |
| 178 | + |
| 179 | + @property |
| 180 | + def account_id(self) -> str: |
| 181 | + """The account that owns the entity that granted permissions for the request. |
| 182 | +
|
| 183 | + If the request was made with temporary security credentials, this is the account that owns the IAM |
| 184 | + user or role that was used to obtain credentials.""" |
| 185 | + return self["accountId"] |
| 186 | + |
| 187 | + @property |
| 188 | + def access_key_id(self) -> str: |
| 189 | + """The access key ID that was used to sign the request. |
| 190 | +
|
| 191 | + If the request was made with temporary security credentials, this is the access key ID of |
| 192 | + the temporary credentials. For security reasons, accessKeyId might not be present, or might |
| 193 | + be displayed as an empty string.""" |
| 194 | + return self["accessKeyId"] |
| 195 | + |
| 196 | + @property |
| 197 | + def user_name(self) -> str: |
| 198 | + """The friendly name of the identity that made the call.""" |
| 199 | + return self["userName"] |
| 200 | + |
| 201 | + @property |
| 202 | + def principal_id(self) -> str: |
| 203 | + """The unique identifier for the identity that made the call. |
| 204 | +
|
| 205 | + For requests made with temporary security credentials, this value includes |
| 206 | + the session name that is passed to the AssumeRole, AssumeRoleWithWebIdentity, |
| 207 | + or GetFederationToken API call.""" |
| 208 | + return self["principalId"] |
| 209 | + |
| 210 | + @property |
| 211 | + def arn(self) -> str: |
| 212 | + """The ARN of the principal that made the call. |
| 213 | + The last section of the ARN contains the user or role that made the call.""" |
| 214 | + return self["arn"] |
| 215 | + |
| 216 | + @property |
| 217 | + def session_context(self) -> Optional[S3ObjectSessionContext]: |
| 218 | + """If the request was made with temporary security credentials, |
| 219 | + this element provides information about the session that was created for those credentials.""" |
| 220 | + session_context = self.get("sessionContext") |
| 221 | + |
| 222 | + if session_context is None: |
| 223 | + return None |
| 224 | + |
| 225 | + return S3ObjectSessionContext(session_context) |
| 226 | + |
| 227 | + |
| 228 | +class S3ObjectLambdaEvent(DictWrapper): |
| 229 | + """S3 object lambda event |
| 230 | +
|
| 231 | + Documentation: |
| 232 | + ------------- |
| 233 | + - https://docs.aws.amazon.com/AmazonS3/latest/userguide/olap-writing-lambda.html |
| 234 | +
|
| 235 | + Example |
| 236 | + ------- |
| 237 | + **Fetch and transform original object from Amazon S3** |
| 238 | +
|
| 239 | + import boto3 |
| 240 | + import requests |
| 241 | + from aws_lambda_powertools.utilities.data_classes.s3_object_event import S3ObjectLambdaEvent |
| 242 | +
|
| 243 | + session = boto3.Session() |
| 244 | + s3 = session.client("s3") |
| 245 | +
|
| 246 | + def lambda_handler(event, context): |
| 247 | + event = S3ObjectLambdaEvent(event) |
| 248 | +
|
| 249 | + # Get object from S3 |
| 250 | + response = requests.get(event.input_s3_url) |
| 251 | + original_object = response.content.decode("utf-8") |
| 252 | +
|
| 253 | + # Make changes to the object about to be returned |
| 254 | + transformed_object = original_object.upper() |
| 255 | +
|
| 256 | + # Write object back to S3 Object Lambda |
| 257 | + s3.write_get_object_response( |
| 258 | + Body=transformed_object, RequestRoute=event.request_route, RequestToken=event.request_token |
| 259 | + ) |
| 260 | + """ |
| 261 | + |
| 262 | + @property |
| 263 | + def request_id(self) -> str: |
| 264 | + """The Amazon S3 request ID for this request. We recommend that you log this value to help with debugging.""" |
| 265 | + return self["xAmzRequestId"] |
| 266 | + |
| 267 | + @property |
| 268 | + def object_context(self) -> S3ObjectContext: |
| 269 | + """The input and output details for connections to Amazon S3 and S3 Object Lambda.""" |
| 270 | + return S3ObjectContext(self["getObjectContext"]) |
| 271 | + |
| 272 | + @property |
| 273 | + def configuration(self) -> S3ObjectConfiguration: |
| 274 | + """Configuration information about the S3 Object Lambda access point.""" |
| 275 | + return S3ObjectConfiguration(self["configuration"]) |
| 276 | + |
| 277 | + @property |
| 278 | + def user_request(self) -> S3ObjectUserRequest: |
| 279 | + """Information about the original call to S3 Object Lambda.""" |
| 280 | + return S3ObjectUserRequest(self["userRequest"]) |
| 281 | + |
| 282 | + @property |
| 283 | + def user_identity(self) -> S3ObjectUserIdentity: |
| 284 | + """Details about the identity that made the call to S3 Object Lambda.""" |
| 285 | + return S3ObjectUserIdentity(self["userIdentity"]) |
| 286 | + |
| 287 | + @property |
| 288 | + def request_route(self) -> str: |
| 289 | + """A routing token that is added to the S3 Object Lambda URL when the Lambda function |
| 290 | + calls `WriteGetObjectResponse`.""" |
| 291 | + return self.object_context.output_route |
| 292 | + |
| 293 | + @property |
| 294 | + def request_token(self) -> str: |
| 295 | + """An opaque token used by S3 Object Lambda to match the WriteGetObjectResponse call |
| 296 | + with the original caller.""" |
| 297 | + return self.object_context.output_token |
| 298 | + |
| 299 | + @property |
| 300 | + def input_s3_url(self) -> str: |
| 301 | + """A pre-signed URL that can be used to fetch the original object from Amazon S3. |
| 302 | +
|
| 303 | + The URL is signed using the original caller’s identity, and their permissions |
| 304 | + will apply when the URL is used. If there are signed headers in the URL, the |
| 305 | + Lambda function must include these in the call to Amazon S3, except for the Host. |
| 306 | +
|
| 307 | + Example |
| 308 | + ------- |
| 309 | + **Fetch original object from Amazon S3** |
| 310 | +
|
| 311 | + import requests |
| 312 | + from aws_lambda_powertools.utilities.data_classes.s3_object_event import S3ObjectLambdaEvent |
| 313 | +
|
| 314 | + def lambda_handler(event, context): |
| 315 | + event = S3ObjectLambdaEvent(event) |
| 316 | +
|
| 317 | + response = requests.get(event.input_s3_url) |
| 318 | + original_object = response.content.decode("utf-8") |
| 319 | + ... |
| 320 | + """ |
| 321 | + return self.object_context.input_s3_url |
| 322 | + |
| 323 | + @property |
| 324 | + def protocol_version(self) -> str: |
| 325 | + """The version ID of the context provided. |
| 326 | +
|
| 327 | + The format of this field is `{Major Version}`.`{Minor Version}`. |
| 328 | + The minor version numbers are always two-digit numbers. Any removal or change to the semantics of a |
| 329 | + field will necessitate a major version bump and will require active opt-in. Amazon S3 can add new |
| 330 | + fields at any time, at which point you might experience a minor version bump. Due to the nature of |
| 331 | + software rollouts, it is possible that you might see multiple minor versions in use at once. |
| 332 | + """ |
| 333 | + return self["protocolVersion"] |
0 commit comments