Skip to content

Commit 182aae1

Browse files
authored
Add aws.auth#cognitoUserPoolsScopes trait
API Gateway supports OAuth scopes on operations that use Cognito User Pools authorizers. The aws.apigateway#authorizationScopes trait added in #3084 targeted only the operations annotated with @Authorizer. That trait pairs scopes with the generic API Gateway authorizer trait, but scopes only apply when the authorizer is Cognito so the trait belongs alongside @cognitoUserPools in the aws.auth namespace. Add the `aws.auth#cognitoUserPoolsScopes` list trait, selectable only on operations bound to a service with the @cognitoUserPools trait applied. The new `AddCognitoUserPoolsScopes` OpenAPI mapper writes a per-operation security requirement using the Cognito scheme name when the trait is present.
1 parent 5f516a3 commit 182aae1

18 files changed

Lines changed: 322 additions & 268 deletions

File tree

.changes/next-release/feature-1d0fb9d2b93784accf545d038bd5b0658cc9ca13.json

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "feature",
3+
"description": "Added `aws.auth#cognitoUserPoolsScopes` trait for specifying OAuth scopes on operations that use an Amazon Cognito User Pools authorizer.",
4+
"pull_requests": [
5+
"[#3109](https://github.com/smithy-lang/smithy/pull/3109)"
6+
]
7+
}

docs/source-2.0/aws/amazon-apigateway.rst

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -584,72 +584,6 @@ endpoint access mode:
584584
This trait should be considered internal-only and not exposed to your
585585
customers.
586586

587-
.. smithy-trait:: aws.apigateway#authorizationScopes
588-
.. _aws.apigateway#authorizationScopes-trait:
589-
590-
--------------------------------------------
591-
``aws.apigateway#authorizationScopes`` trait
592-
--------------------------------------------
593-
594-
Summary
595-
Defines the list of OAuth scopes required for an API Gateway operation
596-
that uses a `Cognito`_ authorizer. Applied alongside the
597-
:ref:`aws.apigateway#authorizer-trait` to specify which scopes the
598-
caller must have.
599-
Trait selector
600-
``operation[trait|aws.apigateway#authorizer]``
601-
602-
*An operation with the aws.apigateway#authorizer trait applied*
603-
Value type
604-
``list`` of ``string``
605-
See also
606-
- `Control access using Cognito user pools`_ for more information on
607-
how scopes work with Cognito authorizers
608-
609-
.. note::
610-
611-
Authorization scopes are only supported with ``COGNITO_USER_POOLS``
612-
authorizers. API Gateway validates the scope values at import time.
613-
614-
The following example requires the ``email`` and ``profile`` scopes on an
615-
operation that uses a Cognito authorizer:
616-
617-
.. code-block:: smithy
618-
619-
$version: "2"
620-
621-
namespace smithy.example
622-
623-
use aws.apigateway#authorizer
624-
use aws.apigateway#authorizers
625-
use aws.apigateway#authorizationScopes
626-
use aws.auth#sigv4
627-
628-
@sigv4(name: "service")
629-
@authorizer("my-cognito-auth")
630-
@authorizers(
631-
"my-cognito-auth": {
632-
scheme: "aws.auth#sigv4"
633-
type: "cognito_user_pools"
634-
}
635-
)
636-
service MyService {
637-
version: "2024-01-01"
638-
operations: [GetUserProfile]
639-
}
640-
641-
@authorizer("my-cognito-auth")
642-
@authorizationScopes(["email", "profile"])
643-
operation GetUserProfile {
644-
input := {}
645-
output := {}
646-
}
647-
648-
.. note::
649-
650-
This trait should be considered internal-only and not exposed to your
651-
customers.
652-
653587
.. smithy-trait:: aws.apigateway#integration
654588
.. _aws.apigateway#integration-trait:
655589

@@ -1334,5 +1268,3 @@ integration response to two ``header`` parameters of the method response.
13341268
.. _x-amazon-apigateway-endpoint-configuration: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-endpoint-configuration.html
13351269
.. _API endpoint types for REST APIs: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-endpoint-types.html
13361270
.. _endpoint IDs: https://docs.aws.amazon.com/vpc/latest/privatelink/concepts.html#concepts-vpc-endpoints
1337-
.. _Control access using Cognito user pools: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html
1338-
.. _Cognito: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools.html

docs/source-2.0/aws/aws-auth.rst

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,61 @@ Trait value
210210
}
211211
212212
213+
.. smithy-trait:: aws.auth#cognitoUserPoolsScopes
214+
.. _aws.auth#cognitoUserPoolsScopes-trait:
215+
216+
-----------------------------------------
217+
``aws.auth#cognitoUserPoolsScopes`` trait
218+
-----------------------------------------
219+
220+
Trait summary
221+
The ``aws.auth#cognitoUserPoolsScopes`` trait defines the list of
222+
OAuth scopes required to invoke an operation that uses an
223+
:ref:`aws.auth#cognitoUserPools-trait` authorizer.
224+
Trait selector
225+
``service[trait|aws.auth#cognitoUserPools] ~> operation``
226+
227+
*An operation in a service that has the ``aws.auth#cognitoUserPools``
228+
trait applied.*
229+
Trait value
230+
A list of ``string`` values.
231+
232+
When the scopes list is non-empty, the generated OpenAPI operation emits a
233+
``security`` requirement that uses the ``aws.auth.cognitoUserPools`` security
234+
scheme name and carries the listed scopes. See :ref:`smithy-to-openapi` for
235+
details.
236+
237+
.. code-block:: smithy
238+
239+
$version: "2"
240+
241+
namespace aws.fooBaz
242+
243+
use aws.api#service
244+
use aws.auth#cognitoUserPools
245+
use aws.auth#cognitoUserPoolsScopes
246+
use aws.protocols#restJson1
247+
248+
@service(sdkId: "Some Value")
249+
@cognitoUserPools(
250+
providerArns: ["arn:aws:cognito-idp:us-east-1:123:userpool/123"])
251+
@restJson1
252+
service FooBaz {
253+
version: "2018-03-17"
254+
operations: [GetThing]
255+
}
256+
257+
@cognitoUserPoolsScopes(["email", "profile"])
258+
@http(method: "GET", uri: "/things/{id}")
259+
operation GetThing {
260+
input := {
261+
@httpLabel
262+
@required
263+
id: String
264+
}
265+
}
266+
267+
213268
.. _AWS signature version 4: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
214269
.. _AWS Signature Version 4 Asymmetric (SigV4A): https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html#how-sigv4a-works
215270
.. _credential scope: https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html

docs/source-2.0/guides/model-translations/converting-to-openapi.rst

Lines changed: 43 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,6 +2158,49 @@ entry:
21582158
In the entry, ``providerARNs`` will be populated from the ``providerArns`` list
21592159
from the trait.
21602160

2161+
Per-operation scopes can be required by applying the
2162+
:ref:`aws.auth#cognitoUserPoolsScopes-trait` to an operation. When scopes
2163+
are present, Smithy emits a ``security`` requirement on the generated OpenAPI
2164+
operation that uses the ``aws.auth.cognitoUserPools`` scheme name:
2165+
2166+
.. code-block:: smithy
2167+
2168+
$version: "2"
2169+
namespace smithy.example
2170+
2171+
use aws.auth#cognitoUserPools
2172+
use aws.auth#cognitoUserPoolsScopes
2173+
use aws.protocols#restJson1
2174+
2175+
@restJson1
2176+
@cognitoUserPools(
2177+
providerArns: ["arn:aws:cognito-idp:us-east-1:123:userpool/123"])
2178+
service Example {
2179+
version: "2019-06-17"
2180+
operations: [GetThing]
2181+
}
2182+
2183+
@cognitoUserPoolsScopes(["email", "profile"])
2184+
@http(method: "GET", uri: "/things")
2185+
operation GetThing {}
2186+
2187+
The ``GetThing`` operation in the generated OpenAPI will include:
2188+
2189+
.. code-block:: json
2190+
2191+
{
2192+
"security": [
2193+
{
2194+
"aws.auth.cognitoUserPools": [
2195+
"email",
2196+
"profile"
2197+
]
2198+
}
2199+
]
2200+
}
2201+
2202+
Operations without the trait inherit the service-level security requirement.
2203+
21612204
Amazon API Gateway API key usage plans
21622205
======================================
21632206

@@ -2316,73 +2359,6 @@ is converted to the following OpenAPI model:
23162359
}
23172360
23182361
2319-
.. _apigateway-authorization-scopes:
2320-
2321-
Authorization scopes
2322-
====================
2323-
2324-
When an operation uses a `Cognito`_ authorizer, OAuth scopes can be added to
2325-
the security requirement using the
2326-
:ref:`aws.apigateway#authorizationScopes-trait`. The trait is applied
2327-
alongside the :ref:`aws.apigateway#authorizer-trait` on an operation and
2328-
specifies which scopes the caller must have.
2329-
2330-
The following Smithy model requires the ``email`` and ``profile`` scopes
2331-
on the ``GetUserProfile`` operation:
2332-
2333-
.. code-block:: smithy
2334-
2335-
$version: "2"
2336-
namespace smithy.example
2337-
2338-
use aws.apigateway#authorizer
2339-
use aws.apigateway#authorizers
2340-
use aws.apigateway#authorizationScopes
2341-
use aws.auth#sigv4
2342-
use aws.protocols#restJson1
2343-
2344-
@restJson1
2345-
@sigv4(name: "service")
2346-
@authorizer("my-cognito-auth")
2347-
@authorizers(
2348-
"my-cognito-auth": {scheme: "aws.auth#sigv4", type: "cognito_user_pools"}
2349-
)
2350-
service Example {
2351-
version: "2019-06-17"
2352-
operations: [GetUserProfile]
2353-
}
2354-
2355-
@authorizer("my-cognito-auth")
2356-
@authorizationScopes(["email", "profile"])
2357-
@http(uri: "/profile", method: "GET")
2358-
operation GetUserProfile {}
2359-
2360-
The scopes are included in the OpenAPI security requirement for the
2361-
operation:
2362-
2363-
.. code-block:: json
2364-
2365-
{
2366-
"paths": {
2367-
"/profile": {
2368-
"get": {
2369-
"operationId": "GetUserProfile",
2370-
"responses": {
2371-
"200": {
2372-
"description": "GetUserProfile response"
2373-
}
2374-
},
2375-
"security": [
2376-
{
2377-
"my-cognito-auth": ["email", "profile"]
2378-
}
2379-
]
2380-
}
2381-
}
2382-
}
2383-
}
2384-
2385-
23862362
.. _other-traits:
23872363

23882364
Other traits that influence API Gateway
@@ -2489,4 +2465,3 @@ The conversion process is highly extensible through
24892465
.. _x-amazon-apigateway-gateway-responses: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-gateway-responses.html
24902466
.. _x-amazon-apigateway-security-policy: https://docs.aws.amazon.com/apigateway/latest/developerguide/openapi-extensions-security-policy.html
24912467
.. _x-amazon-apigateway-endpoint-access-mode: https://docs.aws.amazon.com/apigateway/latest/developerguide/openapi-extensions-endpoint-access-mode.html
2492-
.. _Cognito: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools.html

smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddAuthorizers.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import java.util.Objects;
1010
import java.util.Optional;
1111
import java.util.logging.Logger;
12-
import software.amazon.smithy.aws.apigateway.traits.AuthorizationScopesTrait;
1312
import software.amazon.smithy.aws.apigateway.traits.AuthorizerDefinition;
1413
import software.amazon.smithy.aws.apigateway.traits.AuthorizerIndex;
1514
import software.amazon.smithy.aws.apigateway.traits.AuthorizerTrait;
@@ -107,18 +106,12 @@ public OperationObject updateOperation(
107106
// ...API Gateway's built-in API keys are being used. It requires the
108107
// security to be specified on every operation.
109108
// See https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-setup-api-key-with-console.html#api-gateway-usage-plan-configure-apikey-on-method
110-
List<String> scopes = shape.getTrait(AuthorizationScopesTrait.class)
111-
.map(AuthorizationScopesTrait::getValues)
112-
.orElse(ListUtils.of());
113-
114-
if (Objects.equals(operationAuth, serviceAuth)
115-
&& !usesApiGatewayApiKeys(service, operationAuth)
116-
&& scopes.isEmpty()) {
109+
if (Objects.equals(operationAuth, serviceAuth) && !usesApiGatewayApiKeys(service, operationAuth)) {
117110
return operation;
118111
}
119112

120113
return operation.toBuilder()
121-
.addSecurity(MapUtils.of(operationAuth, scopes))
114+
.addSecurity(MapUtils.of(operationAuth, ListUtils.of()))
122115
.build();
123116
}
124117

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.aws.apigateway.openapi;
6+
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.logging.Logger;
10+
import software.amazon.smithy.aws.traits.auth.CognitoUserPoolsScopesTrait;
11+
import software.amazon.smithy.aws.traits.auth.CognitoUserPoolsTrait;
12+
import software.amazon.smithy.model.knowledge.ServiceIndex;
13+
import software.amazon.smithy.model.shapes.OperationShape;
14+
import software.amazon.smithy.model.traits.Trait;
15+
import software.amazon.smithy.openapi.fromsmithy.Context;
16+
import software.amazon.smithy.openapi.model.OperationObject;
17+
import software.amazon.smithy.utils.ListUtils;
18+
import software.amazon.smithy.utils.MapUtils;
19+
20+
/**
21+
* Adds per-operation OAuth scopes to the Cognito User Pools security
22+
* requirement when an operation is annotated with
23+
* {@link CognitoUserPoolsScopesTrait}.
24+
*
25+
* <p>The mapper only applies when Cognito User Pools is an effective
26+
* authentication scheme for the operation. Operations that opt out of
27+
* authentication with {@code @auth([])} or use a different scheme are
28+
* left unchanged. When scopes are present the mapper appends a security
29+
* requirement to the operation using the Cognito scheme name, which
30+
* takes precedence over the service-level security requirement.
31+
*
32+
* @see <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-enable-cognito-user-pool.html">Integrate a REST API with a User Pool</a>
33+
*/
34+
final class AddCognitoUserPoolsScopes implements ApiGatewayMapper {
35+
36+
private static final Logger LOGGER = Logger.getLogger(AddCognitoUserPoolsScopes.class.getName());
37+
38+
@Override
39+
public List<ApiGatewayConfig.ApiType> getApiTypes() {
40+
return ListUtils.of(ApiGatewayConfig.ApiType.REST);
41+
}
42+
43+
@Override
44+
public OperationObject updateOperation(
45+
Context<? extends Trait> context,
46+
OperationShape shape,
47+
OperationObject operation,
48+
String httpMethodName,
49+
String path
50+
) {
51+
if (!context.getService().hasTrait(CognitoUserPoolsTrait.class)) {
52+
return operation;
53+
}
54+
55+
if (!shape.hasTrait(CognitoUserPoolsScopesTrait.ID)) {
56+
return operation;
57+
}
58+
59+
List<String> scopes = shape.expectTrait(CognitoUserPoolsScopesTrait.class).getValues();
60+
61+
// Only emit a scoped security requirement when Cognito is actually an
62+
// effective auth scheme for this operation. This respects operations
63+
// that opt out of auth via @auth([]) or that use a different scheme
64+
// via @auth.
65+
ServiceIndex serviceIndex = ServiceIndex.of(context.getModel());
66+
if (!serviceIndex.getEffectiveAuthSchemes(context.getService(), shape)
67+
.containsKey(CognitoUserPoolsTrait.ID)) {
68+
return operation;
69+
}
70+
71+
String schemeName = CognitoUserPoolsTrait.ID.toString().replace("#", ".");
72+
Map<String, List<String>> requirement = MapUtils.of(schemeName, scopes);
73+
74+
LOGGER.fine(() -> String.format(
75+
"Adding Cognito User Pools scopes %s to operation %s",
76+
scopes,
77+
shape.getId()));
78+
79+
return operation.toBuilder()
80+
.addSecurity(requirement)
81+
.build();
82+
}
83+
}

0 commit comments

Comments
 (0)