Skip to content

Commit d7bff5f

Browse files
authored
Merge branch 'main' into tjroach/cloudwatch-16kb-page-size-support
2 parents 01388ec + 43f6e90 commit d7bff5f

File tree

54 files changed

+2276
-868
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2276
-868
lines changed

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1+
## [Release 2.24.1](https://github.com/aws-amplify/amplify-android/releases/tag/release_v2.24.1)
2+
3+
### Bug Fixes
4+
- **analytics:** Fix accessing the Analytics category from RxAmplify or Kotlin Facade ([#2944](https://github.com/aws-amplify/amplify-android/issues/2944))
5+
- **api:** Fix connecting to AppSync from China with API category ([#2948](https://github.com/aws-amplify/amplify-android/issues/2948))
6+
7+
[See all changes between 2.24.0 and 2.24.1](https://github.com/aws-amplify/amplify-android/compare/release_v2.24.0...release_v2.24.1)
8+
9+
## [Release 2.24.0](https://github.com/aws-amplify/amplify-android/releases/tag/release_v2.24.0)
10+
11+
### Features
12+
- **auth:** Add support for Email MFA ([#2935](https://github.com/aws-amplify/amplify-android/issues/2935))
13+
14+
[See all changes between 2.23.0 and 2.24.0](https://github.com/aws-amplify/amplify-android/compare/release_v2.23.0...release_v2.24.0)
15+
16+
## [Release 2.23.0](https://github.com/aws-amplify/amplify-android/releases/tag/release_v2.23.0)
17+
18+
### Features
19+
- **predictions:** Added region handling for creating correct streaming endpoint from region ([#2923](https://github.com/aws-amplify/amplify-android/issues/2923))
20+
- **api:** Pass authorization in header instead of query parameter for API category ([#2918](https://github.com/aws-amplify/amplify-android/issues/2918))
21+
22+
[See all changes between 2.22.0 and 2.23.0](https://github.com/aws-amplify/amplify-android/compare/release_v2.22.0...release_v2.23.0)
23+
24+
## [Release 2.22.0](https://github.com/aws-amplify/amplify-android/releases/tag/release_v2.22.0)
25+
26+
### Features
27+
- **storage:** implement multiple buckets support ([#2904](https://github.com/aws-amplify/amplify-android/issues/2904))
28+
29+
[See all changes between 2.21.1 and 2.22.0](https://github.com/aws-amplify/amplify-android/compare/release_v2.21.1...release_v2.22.0)
30+
131
## [Release 2.21.1](https://github.com/aws-amplify/amplify-android/releases/tag/release_v2.21.1)
232

333
### Bug Fixes

README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,14 @@ dependencies section:
7171
```groovy
7272
dependencies {
7373
// Only specify modules that provide functionality your app will use
74-
implementation 'com.amplifyframework:aws-analytics-pinpoint:2.21.1'
75-
implementation 'com.amplifyframework:aws-api:2.21.1'
76-
implementation 'com.amplifyframework:aws-auth-cognito:2.21.1'
77-
implementation 'com.amplifyframework:aws-datastore:2.21.1'
78-
implementation 'com.amplifyframework:aws-predictions:2.21.1'
79-
implementation 'com.amplifyframework:aws-storage-s3:2.21.1'
80-
implementation 'com.amplifyframework:aws-geo-location:2.21.1'
81-
implementation 'com.amplifyframework:aws-push-notifications-pinpoint:2.21.1'
74+
implementation 'com.amplifyframework:aws-analytics-pinpoint:2.24.1'
75+
implementation 'com.amplifyframework:aws-api:2.24.1'
76+
implementation 'com.amplifyframework:aws-auth-cognito:2.24.1'
77+
implementation 'com.amplifyframework:aws-datastore:2.24.1'
78+
implementation 'com.amplifyframework:aws-predictions:2.24.1'
79+
implementation 'com.amplifyframework:aws-storage-s3:2.24.1'
80+
implementation 'com.amplifyframework:aws-geo-location:2.24.1'
81+
implementation 'com.amplifyframework:aws-push-notifications-pinpoint:2.24.1'
8282
}
8383
```
8484

apollo/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## [Release 1.1.0](https://github.com/aws-amplify/amplify-android/releases/tag/release_apollo_v1.1.0)
2+
3+
### Features
4+
- **apollo:** Use HTTP headers instead of query parameters for authorization ([#2916](https://github.com/aws-amplify/amplify-android/issues/2916))
5+
6+
[See all changes between 1.0.0 and 1.1.0](https://github.com/aws-amplify/amplify-android/compare/release_apollo_v1.0.0...release_apollo_v1.1.0)
7+
18
# Release 1.0.0
29

310
Initial Release

apollo/README.md

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ Add the dependency you prefer to your `build.gradle.kts` file.
1717

1818
```kotlin
1919
// To only use Apollo to speak to AppSync, without using Amplify
20-
implementation("com.amplifyframework:apollo-appsync:1.0.0")
20+
implementation("com.amplifyframework:apollo-appsync:1.1.0")
2121

2222
// To connect Apollo to AppSync using your existing Amplify Gen2 Backend
23-
implementation("com.amplifyframework:apollo-appsync-amplify:1.0.0")
23+
implementation("com.amplifyframework:apollo-appsync-amplify:1.1.0")
2424
```
2525

2626
For applications using `apollo-appsync` directly, instantiate the Endpoint and the desired Authorizer instance, and then call the Apollo builder extension.
@@ -40,12 +40,86 @@ For applications using `apollo-appsync-amplify`, you can connect directly to you
4040
val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs))
4141

4242
val apolloClient = ApolloClient.Builder()
43-
.appSync(connector.endpoint, connect.apiKeyAuthorizer())
43+
.appSync(connector.endpoint, connector.apiKeyAuthorizer())
4444
.build()
4545
```
4646

4747
Once you have constructed the Apollo client you can use it as normal for queries, mutations, and subscriptions to AppSync.
4848

49+
## Authorization Modes
50+
51+
AWS AppSync supports [five different authorization modes](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html):
52+
53+
- API Key
54+
- AWS Lambda Function
55+
- AWS IAM Permissions
56+
- OIDC Provider
57+
- Amazon Cognito User Pool
58+
59+
The Apollo AppSync Extensions libraries expose three authorizer types to support these different authorization modes.
60+
61+
### ApiKeyAuthorizer
62+
63+
An `ApiKeyAuthorizer` is used to provide a key for [API Key authorization](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#api-key-authorization) requests.
64+
65+
This Authorizer can be used with a hardcoded API key, by fetching the key from some source, or reading it from `amplify_outputs.json`:
66+
67+
```kotlin
68+
// Create an authorizer directly with your API key:
69+
val authorizer = ApiKeyAuthorizer("[YOUR_API_KEY")
70+
```
71+
```kotlin
72+
// Create an authorizer that fetches your API key. The fetching function may be called many times,
73+
// and should internally implement an appropriate caching mechanism.
74+
val authorizer = ApiKeyAuthorizer { fetchApiKey() }
75+
```
76+
```kotlin
77+
// Using ApolloAmplifyConnector to read API key from amplify_outputs.json
78+
val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs))
79+
val authorizer = connector.apiKeyAuthorizer()
80+
```
81+
82+
### AuthTokenAuthorizer
83+
84+
An `AuthTokenAuthorizer` sets an authentication header for use with [AWS Lambda](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-lambda-authorization),
85+
[OIDC provider](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#openid-connect-authorization), and
86+
[Amazon Cognito User Pool](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#amazon-cognito-user-pools-authorization)
87+
authorization modes.
88+
89+
Using `ApolloAmplifyConnector` allows you to automatically authorize requests for the signed-in Amplify user, or you can implement the Authorizer's function parameter yourself to provide other types of tokens.
90+
91+
```kotlin
92+
// Provide a token from e.g. an OIDC provider. The fetching function may be called many times,
93+
// and should internally implement an appropriate caching mechanism.
94+
val authorizer = AuthTokenAuthorizer { fetchUserToken() }
95+
```
96+
```kotlin
97+
// Use ApolloAmplifyConnector to get Cognito tokens from Amplify for the signed-in user
98+
val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs))
99+
val authorizer = connector.authTokenAuthorizer()
100+
// or
101+
val authorizer = AuthTokenAuthorizer { ApolloAmplifyConnector.fetchLatestCognitoAuthToken() }
102+
```
103+
104+
### IamAuthorizer
105+
106+
An `IamAuthorizer` is used to provide request signature headers for using [AWS IAM-based authorization](https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-iam-authorization).
107+
108+
Using `ApolloAmplifyConnector` is the easiest way to use this authorizer, but you can also implement the signing function yourself, by e.g. delegating to the [AWS Kotlin SDK](https://github.com/awslabs/aws-sdk-kotlin).
109+
110+
```kotlin
111+
// Provide an implementation of the signing function. This function should implement the
112+
// AWS Sig-v4 signing logic and return the authorization headers containing the token and signature.
113+
val authorizer = IamAuthorizer { signRequestAndReturnHeaders(it) }
114+
```
115+
```kotlin
116+
// Use ApolloAmplifyConnector to sign the request
117+
val connector = ApolloAmplifyConnector(context, AmplifyOutputs(R.raw.amplify_outputs))
118+
val authorizer = connector.iamAuthorizer()
119+
// or supply a region to sign via the companion function
120+
val authorizer = IamAuthorizer { ApolloAmplifyConnector.signAppSyncRequest(it, "us-east-1") }
121+
```
122+
49123
## Contributing
50124

51125
- [CONTRIBUTING.md](../CONTRIBUTING.md)
@@ -56,4 +130,4 @@ See [CONTRIBUTING](../CONTRIBUTING.md#security-issue-notifications) for more inf
56130

57131
## License
58132

59-
This project is licensed under the Apache-2.0 License.
133+
This project is licensed under the Apache-2.0 License.

apollo/apollo-appsync/src/main/java/com/amplifyframework/apollo/appsync/ApolloExtensions.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818
package com.amplifyframework.apollo.appsync
1919

2020
import com.amplifyframework.apollo.appsync.util.UserAgentHeader
21+
import com.amplifyframework.apollo.appsync.util.WebSocketConnectionInterceptor
2122
import com.apollographql.apollo.ApolloClient
2223
import com.apollographql.apollo.api.ApolloRequest
2324
import com.apollographql.apollo.api.NullableAnyAdapter
2425
import com.apollographql.apollo.api.Operation
2526
import com.apollographql.apollo.api.http.DefaultHttpRequestComposer
2627
import com.apollographql.apollo.api.toJsonString
28+
import com.apollographql.apollo.network.ws.DefaultWebSocketEngine
2729
import com.apollographql.apollo.network.ws.WebSocketNetworkTransport
30+
import okhttp3.OkHttpClient
2831

2932
// Use the requestUuid as the subscriptionId
3033
internal val <D : Operation.Data> ApolloRequest<D>.subscriptionId: String
@@ -38,15 +41,25 @@ internal fun ApolloRequest<*>.toJson() =
3841
* Convenience function that configures the [WebSocketNetworkTransport] to connect to AppSync. This function:
3942
* 1. Sets the serverUrl
4043
* 2. Sets up an [AppSyncProtocol] using the given endpoint and authorizer
44+
* 3. Adds an interceptor to append the authorization payload to the connection request
4145
* @param endpoint The [AppSyncEndpoint] to connect to
4246
* @param authorizer The [AppSyncAuthorizer] that determines the authorization mode to use when connecting to AppSync
4347
* @return The builder instance for chaining
4448
*/
4549
fun WebSocketNetworkTransport.Builder.appSync(endpoint: AppSyncEndpoint, authorizer: AppSyncAuthorizer) = apply {
46-
serverUrl { endpoint.createWebsocketServerUrl(authorizer) }
50+
// Set the connection URL
51+
serverUrl(endpoint.websocketConnection.toString())
4752

53+
// Add User-agent header
4854
addHeader(UserAgentHeader.NAME, UserAgentHeader.value)
4955

56+
// Add an interceptor that appends the authorization headers
57+
val client = OkHttpClient.Builder()
58+
.addInterceptor(WebSocketConnectionInterceptor(endpoint, authorizer))
59+
.build()
60+
webSocketEngine(DefaultWebSocketEngine(client))
61+
62+
// Set the WebSocket protocol
5063
protocol(
5164
AppSyncProtocol.Factory(
5265
endpoint = endpoint,

apollo/apollo-appsync/src/main/java/com/amplifyframework/apollo/appsync/AppSyncEndpoint.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import java.net.URL
2121
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
2222

2323
private val standardEndpointRegex =
24-
"^https://\\w{26}\\.appsync-api\\.\\w{2}(?:-\\w{2,})+-\\d\\.amazonaws.com/graphql$".toRegex()
24+
"^https://\\w{26}\\.appsync-api\\.\\w{2}(?:-\\w{2,})+-\\d\\.amazonaws.com(?:\\.cn)?/graphql$".toRegex()
2525

2626
/**
2727
* Class representing the AppSync endpoint. There are multiple URLs associated with each AppSync endpoint: the
@@ -62,6 +62,7 @@ class AppSyncEndpoint(serverUrl: String) {
6262
* Creates the serverUrl to be used for the WebSocketTransport's serverUrl. For AppSync, this URL has authorization
6363
* information appended in query parameters. Set this value as the serverUrl for the WebSocketTransport.
6464
*/
65+
@Deprecated("Use HTTP header authorization instead of appending a query parameter")
6566
suspend fun createWebsocketServerUrl(authorizer: AppSyncAuthorizer): String {
6667
val headers = mapOf("host" to serverUrl.host) + authorizer.getWebsocketConnectionHeaders(this)
6768
val authorization = headers.base64()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package com.amplifyframework.apollo.appsync.util
17+
18+
import com.amplifyframework.apollo.appsync.AppSyncAuthorizer
19+
import com.amplifyframework.apollo.appsync.AppSyncEndpoint
20+
import kotlinx.coroutines.runBlocking
21+
import okhttp3.Interceptor
22+
import okhttp3.Response
23+
24+
/**
25+
* Intercepts the WebSocket connection request to append the authorization headers
26+
*/
27+
internal class WebSocketConnectionInterceptor(
28+
private val endpoint: AppSyncEndpoint,
29+
private val authorizer: AppSyncAuthorizer
30+
) : Interceptor {
31+
override fun intercept(chain: Interceptor.Chain): Response {
32+
// runBlocking is okay because we are on an IO thread when the interceptor is called
33+
val headers = runBlocking { authorizer.getWebsocketConnectionHeaders(endpoint) }
34+
val builder = chain.request().newBuilder()
35+
headers.forEach { header -> builder.header(header.key, header.value) }
36+
builder.header("host", endpoint.serverUrl.host)
37+
return chain.proceed(builder.build())
38+
}
39+
}

apollo/apollo-appsync/src/test/java/com/amplifyframework/apollo/appsync/ApolloExtensionsTest.kt

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,12 @@ import com.apollographql.apollo.api.AnyAdapter
2222
import com.apollographql.apollo.api.CustomScalarAdapters
2323
import com.apollographql.apollo.api.json.BufferedSourceJsonReader
2424
import com.apollographql.apollo.network.ws.WebSocketNetworkTransport
25-
import io.kotest.matchers.shouldBe
2625
import io.mockk.mockk
27-
import io.mockk.slot
2826
import io.mockk.spyk
2927
import io.mockk.verify
3028
import kotlinx.coroutines.test.runTest
31-
import okhttp3.HttpUrl.Companion.toHttpUrl
3229
import okio.Buffer
3330
import okio.ByteString
34-
import okio.ByteString.Companion.decodeBase64
3531
import org.junit.Test
3632

3733
class ApolloExtensionsTest {
@@ -84,23 +80,21 @@ class ApolloExtensionsTest {
8480
val transportBuilder = mockk<WebSocketNetworkTransport.Builder>(relaxed = true)
8581
builder.appSync(endpoint, authorizer, transportBuilder)
8682

87-
val slot = slot<suspend () -> String>()
8883
verify {
89-
transportBuilder.serverUrl(capture(slot))
84+
transportBuilder.serverUrl(
85+
"https://example1234567890123456789.appsync-realtime-api.us-east-1.amazonaws.com/graphql/connect"
86+
)
9087
}
88+
}
9189

92-
val serverUrl = slot.captured().toHttpUrl()
93-
94-
// Expected URL:
95-
// https://example1234567890123456789.appsync-realtime-api.us-east-1.amazonaws.com/graphql/connect
96-
serverUrl.host shouldBe "example1234567890123456789.appsync-realtime-api.us-east-1.amazonaws.com"
97-
serverUrl.encodedPath shouldBe "/graphql/connect"
98-
99-
val header = serverUrl.queryParameter("header")?.decodeBase64()!!.toJsonMap()
100-
header["host"] shouldBe "example1234567890123456789.appsync-api.us-east-1.amazonaws.com"
101-
header["x-api-key"] shouldBe "apiKey"
90+
@Test
91+
fun `sets websocket engine`() {
92+
val transportBuilder = mockk<WebSocketNetworkTransport.Builder>(relaxed = true)
93+
builder.appSync(endpoint, authorizer, transportBuilder)
10294

103-
serverUrl.queryParameter("payload") shouldBe "e30="
95+
verify {
96+
transportBuilder.webSocketEngine(any())
97+
}
10498
}
10599

106100
@Suppress("UNCHECKED_CAST")

apollo/apollo-appsync/src/test/java/com/amplifyframework/apollo/appsync/AppSyncEndpointTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import org.junit.Test
2626

2727
class AppSyncEndpointTest {
2828
private val standardAppSyncUrl = "https://example1234567890123456789.appsync-api.us-east-1.amazonaws.com/graphql"
29+
private val standardAppSyncUrlChina =
30+
"https://example1234567890123456789.appsync-api.us-east-1.amazonaws.com.cn/graphql"
2931
private val customAppSyncUrl = "https://api.example.com/graphql"
3032

3133
@Test
@@ -48,6 +50,13 @@ class AppSyncEndpointTest {
4850
"https://example1234567890123456789.appsync-realtime-api.us-east-1.amazonaws.com/graphql/connect"
4951
}
5052

53+
@Test
54+
fun `uses expected realtime URL for standard endpoint in China`() {
55+
val endpoint = AppSyncEndpoint(standardAppSyncUrlChina)
56+
endpoint.websocketConnection.toString() shouldBe
57+
"https://example1234567890123456789.appsync-realtime-api.us-east-1.amazonaws.com.cn/graphql/connect"
58+
}
59+
5160
@Test
5261
fun `uses expected realtime URL for custom endpoint`() {
5362
val endpoint = AppSyncEndpoint(customAppSyncUrl)

0 commit comments

Comments
 (0)