Repository: smithy-lang/smithy
Pull request #1677 (2023-03-14 03:35:52)

Adding conditionKeyValue and conditionKeysResolvedByService traits 
*Issue #, if available:*

*Description of changes:*
Adding conditionKeyValue and conditionKeysResolvedByService traits 

conditionKeyValue
Uses the associated member’s value as this condition key’s value. Needed when the member name doesn't match the condition key name.

conditionKeysResolvedByService
Specifies the list of IAM condition keys which must be resolved by the service, as opposed to being pulled from the request.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


0xjjoyy: Updated based on feedback. Updated trait and class names to match new name. Added EOF to *.smithy and *.errors
kstich: Appears to need a rebase before merging, but content looks good.

Patch:
diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml
new file mode 100644
index 000000000..c590ca2a1
--- /dev/null
+++ b/.github/workflows/stale-issues.yml
@@ -0,0 +1,43 @@
+name: "Close stale issues"
+
+# Controls when the action will run.
+on:
+  schedule:
+    - cron: "0 0 * * *"
+
+jobs:
+  cleanup:
+    runs-on: ubuntu-latest
+    name: Stale issue job
+    steps:
+      - uses: aws-actions/stale-issue-cleanup@v3
+        with:
+          # Targeted types
+          issue-types: issues
+
+          # Issue timing
+          days-before-stale: 60
+          days-before-close: 14
+          days-before-ancient: 365
+
+          # Labels
+          stale-issue-label: closing-soon
+          exempt-issue-label: bug,documentation,feature-request,investigating,protocol-test
+          response-requested-label: response-requested
+
+          # Messages
+          stale-issue-message: This issue has not received a response in a while. If you still think there is a problem, please leave a comment to avoid the issue from automatically closing.
+          ancient-issue-message: We’re closing this issue because it has been open a long time and hasn’t been updated in a while and may not be getting the attention it deserves. We encourage you to check if this is still an issue in the latest release and if you find that this is still a problem, please feel free to comment or open a new issue.
+
+          # If you don't want to mark a issue as being ancient based on a
+          # threshold of "upvotes", you can set this here. An "upvote" is
+          # the total number of +1, heart, hooray, and rocket reactions
+          # on an issue.
+          minimum-upvotes-to-exempt: 5
+
+          # Don't set closed-for-staleness label to skip closing very old issues
+          # regardless of label
+          closed-for-staleness-label: closed-for-staleness
+
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+          loglevel: DEBUG
diff --git a/docs/source-2.0/aws/aws-iam.rst b/docs/source-2.0/aws/aws-iam.rst
index 6b0ea2997..06b70d2f1 100644
--- a/docs/source-2.0/aws/aws-iam.rst
+++ b/docs/source-2.0/aws/aws-iam.rst
@@ -434,6 +434,96 @@ The following example defines two operations:
     @actionName("OverridingActionName")
     operation OperationB {}
 
+.. smithy-trait:: aws.iam#serviceResolvedConditionKeys
+.. _aws.iam#serviceResolvedConditionKeys-trait:
+
+----------------------------------------------
+``aws.iam#serviceResolvedConditionKeys`` trait
+----------------------------------------------
+
+Summary
+    Specifies the list of IAM condition keys which must be resolved by the
+    service, as opposed to the value being pulled from the request.
+Trait selector
+    ``service``
+Value type
+    ``list<string>``
+
+All condition keys defined with the ``serviceResolvedConditionKeys`` trait
+MUST also be defined via the :ref:`aws.iam#defineConditionKeys-trait` trait.
+Derived resource condition keys MUST NOT be included
+with the ``serviceResolvedConditionKeys`` trait.
+
+The following example defines two service-specific condition keys:
+
+* ``myservice:ActionContextKey1`` is expected to be resolved by the service.
+* ``myservice:ActionContextKey2`` is expected to be pulled from the request.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    @defineConditionKeys(
+        "myservice:ActionContextKey1": { type: "String" },
+        "myservice:ActionContextKey2": { type: "String" }
+    )
+    @serviceResolvedConditionKeys(["myservice:ActionContextKey1"])
+    @service(sdkId: "My Value", arnNamespace: "myservice")
+    service MyService {
+        version: "2018-05-10"
+    }
+
+
+.. smithy-trait:: aws.iam#conditionKeyValue
+.. _aws.iam#conditionKeyValue-trait:
+
+-----------------------------------
+``aws.iam#conditionKeyValue`` trait
+-----------------------------------
+
+Summary
+    Uses the associated member’s value for the specified condition key.
+Trait selector
+    ``member``
+Value type
+    ``string``
+
+Members not annotated with the ``conditionKeyValue`` trait, default to the
+:ref:`shape name of the shape ID <shape-id>` of the targeted member. All
+condition keys defined with the ``conditionKeyValue`` trait MUST also be
+defined via the :ref:`aws.iam#defineConditionKeys-trait` trait.
+
+In the input shape for ``OperationA``, the trait ``conditionKeyValue``
+explicitly binds ``ActionContextKey1`` to the field ``key``.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace smithy.example
+
+    @defineConditionKeys(
+        "myservice:ActionContextKey1": { type: "String" }
+    )
+    @service(sdkId: "My Value", arnNamespace: "myservice")
+    service MyService {
+        version: "2020-07-02"
+        operations: [OperationA]
+    }
+
+    @conditionKeys(["myservice:ActionContextKey1"])
+    operation OperationA {
+        input := {
+            @conditionKeyValue("ActionContextKey1")
+            key: String
+        }
+        output := {
+            out: String
+        }
+    }
+
 
 .. _deriving-condition-keys:
 
diff --git a/docs/source-2.0/spec/authentication-traits.rst b/docs/source-2.0/spec/authentication-traits.rst
index d1d0b3e16..446560d86 100644
--- a/docs/source-2.0/spec/authentication-traits.rst
+++ b/docs/source-2.0/spec/authentication-traits.rst
@@ -276,6 +276,11 @@ the ``auth`` trait, then the operation is expected to support each of the
 service. Each entry in the ``auth`` trait is a shape ID that MUST refer to an
 authentication scheme trait applied to the service in which it is bound.
 
+.. note::
+    When a service has multiple authentication scheme traits applied and no
+    ``auth`` trait, the ordering of authentication schemes is alphabetical
+    based on the absolute shape ID of each authentication scheme trait.
+
 The following example defines all combinations in which ``auth`` can be applied
 to services and operations:
 
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionNameTrait.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionNameTrait.java
index da8e0936c..fcf491aca 100644
--- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionNameTrait.java
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionNameTrait.java
@@ -14,11 +14,11 @@ public final class ActionNameTrait extends StringTrait {
 
     public static final ShapeId ID = ShapeId.from("aws.iam#actionName");
 
-    private ActionNameTrait(String action) {
+    public ActionNameTrait(String action) {
         super(ID, action, SourceLocation.NONE);
     }
 
-    private ActionNameTrait(String action, FromSourceLocation sourceLocation) {
+    public ActionNameTrait(String action, FromSourceLocation sourceLocation) {
         super(ID, action, sourceLocation);
     }
 
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTrait.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTrait.java
index 154b3fac3..c818afdb1 100644
--- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTrait.java
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ActionPermissionDescriptionTrait.java
@@ -25,7 +25,11 @@ import software.amazon.smithy.model.traits.StringTrait;
 public final class ActionPermissionDescriptionTrait extends StringTrait {
     public static final ShapeId ID = ShapeId.from("aws.iam#actionPermissionDescription");
 
-    private ActionPermissionDescriptionTrait(String value, SourceLocation sourceLocation) {
+    public ActionPermissionDescriptionTrait(String value) {
+        super(ID, value, SourceLocation.NONE);
+    }
+
+    public ActionPermissionDescriptionTrait(String value, SourceLocation sourceLocation) {
         super(ID, value, sourceLocation);
     }
 
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeyValueTrait.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeyValueTrait.java
new file mode 100644
index 000000000..ce8f42def
--- /dev/null
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeyValueTrait.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.iam.traits;
+
+import software.amazon.smithy.model.FromSourceLocation;
+import software.amazon.smithy.model.SourceLocation;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.StringTrait;
+
+public final class ConditionKeyValueTrait extends StringTrait {
+    public static final ShapeId ID = ShapeId.from("aws.iam#conditionKeyValue");
+
+    public ConditionKeyValueTrait(String conditionKey) {
+        super(ID, conditionKey, SourceLocation.NONE);
+    }
+
+    public ConditionKeyValueTrait(String conditionKey, FromSourceLocation sourceLocation) {
+        super(ID, conditionKey, sourceLocation);
+    }
+
+    public static final class Provider extends StringTrait.Provider<ConditionKeyValueTrait> {
+        public Provider() {
+            super(ID, ConditionKeyValueTrait::new);
+        }
+    }
+}
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysValidator.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysValidator.java
index b7b80fc80..6d5aac250 100644
--- a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysValidator.java
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ConditionKeysValidator.java
@@ -16,12 +16,16 @@
 package software.amazon.smithy.aws.iam.traits;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 import software.amazon.smithy.aws.traits.ServiceTrait;
 import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.knowledge.OperationIndex;
 import software.amazon.smithy.model.knowledge.TopDownIndex;
+import software.amazon.smithy.model.shapes.MemberShape;
 import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.shapes.ServiceShape;
 import software.amazon.smithy.model.validation.AbstractValidator;
@@ -44,23 +48,67 @@ public final class ConditionKeysValidator extends AbstractValidator {
     public List<ValidationEvent> validate(Model model) {
         ConditionKeysIndex conditionIndex = ConditionKeysIndex.of(model);
         TopDownIndex topDownIndex = TopDownIndex.of(model);
+        OperationIndex operationIndex = OperationIndex.of(model);
 
         return model.shapes(ServiceShape.class)
                 .filter(service -> service.hasTrait(ServiceTrait.class))
                 .flatMap(service -> {
                     List<ValidationEvent> results = new ArrayList<>();
                     Set<String> knownKeys = conditionIndex.getDefinedConditionKeys(service).keySet();
+                    Set<String> serviceResolvedKeys = Collections.emptySet();
+
+                    if (service.hasTrait(ServiceResolvedConditionKeysTrait.class)) {
+                        ServiceResolvedConditionKeysTrait trait =
+                                service.expectTrait(ServiceResolvedConditionKeysTrait.class);
+                        //assign so we can compare against condition key values for any intersection
+                        serviceResolvedKeys = new HashSet<>(trait.getValues());
+                        //copy as this is a destructive action and will affect all future access
+                        List<String> invalidNames = new ArrayList<>(trait.getValues());
+                        invalidNames.removeAll(knownKeys);
+                        if (!invalidNames.isEmpty()) {
+                            results.add(error(service, trait, String.format(
+                                    "This condition keys resolved by the `%s` service "
+                                            + "refer to undefined "
+                                            + "condition key(s) [%s]. Expected one of the following "
+                                            + "defined condition keys: [%s]",
+                                    service.getId(),  ValidationUtils.tickedList(invalidNames),
+                                    ValidationUtils.tickedList(knownKeys))));
+                        }
+                    }
 
                     for (OperationShape operation : topDownIndex.getContainedOperations(service)) {
                         for (String name : conditionIndex.getConditionKeyNames(service, operation)) {
                             if (!knownKeys.contains(name) && !name.startsWith("aws:")) {
                                 results.add(error(operation, String.format(
                                         "This operation scoped within the `%s` service refers to an undefined "
-                                        + "condition key `%s`. Expected one of the following defined condition "
-                                        + "keys: [%s]",
+                                                + "condition key `%s`. Expected one of the following defined condition "
+                                                + "keys: [%s]",
                                         service.getId(), name, ValidationUtils.tickedList(knownKeys))));
                             }
                         }
+
+                        for (MemberShape memberShape : operationIndex.getInputMembers(operation).values()) {
+                            if (memberShape.hasTrait(ConditionKeyValueTrait.class)) {
+                                ConditionKeyValueTrait trait = memberShape.expectTrait(ConditionKeyValueTrait.class);
+                                String conditionKey = trait.getValue();
+                                if (!knownKeys.contains(conditionKey)) {
+                                    results.add(error(memberShape, trait, String.format(
+                                            "This operation `%s` scoped within the `%s` service with member `%s` "
+                                                    + "refers to an undefined "
+                                                    + "condition key `%s`. Expected one of the following defined "
+                                                    + "condition keys: [%s]",
+                                            operation.getId(), service.getId(), memberShape.getId(),
+                                            conditionKey, ValidationUtils.tickedList(knownKeys))));
+                                }
+                                if (serviceResolvedKeys.contains(conditionKey)) {
+                                    results.add(error(memberShape, trait, String.format(
+                                            "This operation `%s` scoped within the `%s` service with member `%s` refers"
+                                                    + " to a condition key `%s` that is also resolved by service.",
+                                            operation.getId(), service.getId(), memberShape.getId(),
+                                            conditionKey, ValidationUtils.tickedList(knownKeys))));
+                                }
+                            }
+                        }
                     }
 
                     return results.stream();
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ServiceResolvedConditionKeysTrait.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ServiceResolvedConditionKeysTrait.java
new file mode 100644
index 000000000..bd6ff8b67
--- /dev/null
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/ServiceResolvedConditionKeysTrait.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.iam.traits;
+
+import java.util.List;
+import software.amazon.smithy.model.FromSourceLocation;
+import software.amazon.smithy.model.SourceLocation;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.StringListTrait;
+
+public final class ServiceResolvedConditionKeysTrait extends StringListTrait {
+    public static final ShapeId ID = ShapeId.from("aws.iam#serviceResolvedConditionKeys");
+
+    public ServiceResolvedConditionKeysTrait(List<String> conditionKeys) {
+        super(ID, conditionKeys, SourceLocation.NONE);
+    }
+
+    public ServiceResolvedConditionKeysTrait(List<String> conditionKeys, FromSourceLocation sourceLocation) {
+        super(ID, conditionKeys, sourceLocation);
+    }
+
+    public static final class Provider extends StringListTrait.Provider<ServiceResolvedConditionKeysTrait> {
+        public Provider() {
+            super(ID, ServiceResolvedConditionKeysTrait::new);
+        }
+    }
+}
diff --git a/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService
index 2b8be5d13..2853cf73c 100644
--- a/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService
+++ b/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService
@@ -6,3 +6,5 @@ software.amazon.smithy.aws.iam.traits.RequiredActionsTrait$Provider
 software.amazon.smithy.aws.iam.traits.SupportedPrincipalTypesTrait$Provider
 software.amazon.smithy.aws.iam.traits.IamResourceTrait$Provider
 software.amazon.smithy.aws.iam.traits.ActionNameTrait$Provider
+software.amazon.smithy.aws.iam.traits.ServiceResolvedConditionKeysTrait$Provider
+software.amazon.smithy.aws.iam.traits.ConditionKeyValueTrait$Provider
diff --git a/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.json b/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.json
index 54bf32e32..67e616c2b 100644
--- a/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.json
+++ b/smithy-aws-iam-traits/src/main/resources/META-INF/smithy/aws.iam.json
@@ -273,6 +273,27 @@
                 },
                 "smithy.api#documentation": "Provides a custom IAM action name. By default, the action name is the same as the operation name."
             }
+        },
+        "aws.iam#serviceResolvedConditionKeys": {
+            "type": "list",
+            "member": {
+                "target": "aws.iam#IamIdentifier"
+            },
+            "traits": {
+                "smithy.api#trait": {
+                    "selector": "service"
+                },
+                "smithy.api#documentation": "Specifies the list of IAM condition keys which must be resolved by the service, as opposed to being pulled from the request."
+            }
+        },
+        "aws.iam#conditionKeyValue": {
+            "type": "string",
+            "traits": {
+                "smithy.api#trait": {
+                    "selector": "member"
+                },
+                "smithy.api#documentation": "Uses the associated member’s value as this condition key’s value. Needed when the member name doesn't match the condition key name."
+            }
         }
     }
 }
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/ServiceIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/ServiceIndex.java
index b458719ed..7b7935df1 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/ServiceIndex.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/ServiceIndex.java
@@ -127,6 +127,8 @@ public final class ServiceIndex implements KnowledgeIndex {
      * <p>An <em>auth defining trait</em> is a trait that is marked with
      * the {@code smithy.api#authDefinition} trait.
      *
+     * <p>The returned map is ordered alphabetically by absolute shape ID.
+     *
      * <p>An empty map is returned if {@code id} cannot be found in the
      * model or is not a service shape.
      *
@@ -149,7 +151,7 @@ public final class ServiceIndex implements KnowledgeIndex {
      *
      * <p>The returned map is provided in the same order as the values in the
      * {@code auth} trait if an auth trait is present, otherwise the result
-     * returned is ordered alphabetically by absolute shape id.
+     * returned is ordered alphabetically by absolute shape ID.
      *
      * <p>An empty map is returned if {@code service} cannot be found in the
      * model or is not a service shape.
@@ -213,7 +215,7 @@ public final class ServiceIndex implements KnowledgeIndex {
      *
      * <p>The returned map is provided in the same order as the values in the
      * {@code auth} trait if an auth trait is present, otherwise the result
-     * returned is ordered alphabetically by absolute shape id.
+     * returned is ordered alphabetically by absolute shape ID.
      *
      * <p>An empty map is returned if {@code service} shape cannot be found
      * in the model or is not a service shape. An empty map is returned if
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/ServiceAuthDefinitionsValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/ServiceAuthDefinitionsValidator.java
new file mode 100644
index 000000000..3e5965712
--- /dev/null
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/ServiceAuthDefinitionsValidator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ *  http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.smithy.model.validation.validators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.knowledge.ServiceIndex;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.traits.AuthTrait;
+import software.amazon.smithy.model.validation.AbstractValidator;
+import software.amazon.smithy.model.validation.ValidationEvent;
+
+/**
+ * Validates the @authDefinition traits applied to service shapes.
+ */
+public class ServiceAuthDefinitionsValidator extends AbstractValidator {
+
+    @Override
+    public List<ValidationEvent> validate(Model model) {
+        List<ValidationEvent> events = new ArrayList<>();
+        ServiceIndex index = ServiceIndex.of(model);
+
+        List<ServiceShape> services = model.getServiceShapes().stream()
+                .filter(serviceShape -> !serviceShape.hasTrait(AuthTrait.ID))
+                .filter(serviceShape -> index.getAuthSchemes(serviceShape).size() > 1)
+                .collect(Collectors.toList());
+
+        for (ServiceShape service : services) {
+            events.add(warning(service, "This service uses multiple authentication schemes but does not have "
+                                        + "the `@auth` trait applied. The `@auth` trait defines a priority ordering "
+                                        + "of auth schemes for a client to use. Without it, the ordering of auth "
+                                        + "schemes is alphabetical based on the absolute shape ID of the auth "
+                                        + "schemes."));
+
+        }
+        return events;
+    }
+}
diff --git a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
index 391872dd4..f278bac44 100644
--- a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
+++ b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
@@ -36,6 +36,7 @@ software.amazon.smithy.model.validation.validators.ResourceIdentifierBindingVali
 software.amazon.smithy.model.validation.validators.ResourceIdentifierValidator
 software.amazon.smithy.model.validation.validators.ResourceLifecycleValidator
 software.amazon.smithy.model.validation.validators.ResourceOperationInputOutputValidator
+software.amazon.smithy.model.validation.validators.ServiceAuthDefinitionsValidator
 software.amazon.smithy.model.validation.validators.ServiceValidator
 software.amazon.smithy.model.validation.validators.SetValidator
 software.amazon.smithy.model.validation.validators.ShapeIdConflictValidator


Test patch:
diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeyValueTraitTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeyValueTraitTest.java
new file mode 100644
index 000000000..15f1f3891
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeyValueTraitTest.java
@@ -0,0 +1,24 @@
+package software.amazon.smithy.aws.iam.traits;
+
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.shapes.ShapeId;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+public class ConditionKeyValueTraitTest {
+    @Test
+    public void loadsFromModel() {
+        Model result = Model.assembler()
+                .discoverModels(getClass().getClassLoader())
+                .addImport(getClass().getResource("condition-key-value.smithy"))
+                .assemble()
+                .unwrap();
+
+        Shape shape = result.expectShape(ShapeId.from("smithy.example#EchoInput$id1"));
+        ConditionKeyValueTrait trait = shape.expectTrait(ConditionKeyValueTrait.class);
+        assertThat(trait.getValue(), equalTo("smithy:ActionContextKey1"));
+    }
+}
diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java
index d649fe1ba..4e9281059 100644
--- a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java
+++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ConditionKeysIndexTest.java
@@ -47,23 +47,23 @@ public class ConditionKeysIndexTest {
         assertThat(index.getConditionKeyNames(service), containsInAnyOrder(
                 "aws:accountId", "foo:baz", "myservice:Resource1Id1", "myservice:ResourceTwoId2"));
         assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Operation1")),
-                   containsInAnyOrder("aws:accountId", "foo:baz"));
+                containsInAnyOrder("aws:accountId", "foo:baz"));
         assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource1")),
-                   containsInAnyOrder("aws:accountId", "foo:baz", "myservice:Resource1Id1"));
+                containsInAnyOrder("aws:accountId", "foo:baz", "myservice:Resource1Id1"));
         // Note that ID1 is not duplicated but rather reused on the child operation.
         assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#Resource2")),
-                   containsInAnyOrder("aws:accountId", "foo:baz",
-                                      "myservice:Resource1Id1", "myservice:ResourceTwoId2"));
+                containsInAnyOrder("aws:accountId", "foo:baz",
+                        "myservice:Resource1Id1", "myservice:ResourceTwoId2"));
         // Note that while this operation binds identifiers, it contains no unique ConditionKeys to bind.
         assertThat(index.getConditionKeyNames(service, ShapeId.from("smithy.example#GetResource2")), is(empty()));
 
         // Defined context keys are assembled from the names and mapped with the definitions.
         assertThat(index.getDefinedConditionKeys(service).get("myservice:Resource1Id1").getDocumentation(),
-                   not(Optional.empty()));
+                not(Optional.empty()));
         assertEquals(index.getDefinedConditionKeys(service).get("myservice:ResourceTwoId2").getDocumentation().get(),
                 "This is Foo");
         assertThat(index.getDefinedConditionKeys(service, ShapeId.from("smithy.example#GetResource2")).keySet(),
-                   is(empty()));
+                is(empty()));
     }
 
     @Test
@@ -75,8 +75,8 @@ public class ConditionKeysIndexTest {
 
         assertTrue(result.isBroken());
         assertThat(result.getValidationEvents(Severity.ERROR).stream()
-                           .map(ValidationEvent::getId)
-                           .collect(Collectors.toSet()),
+                        .map(ValidationEvent::getId)
+                        .collect(Collectors.toSet()),
                 contains("ConditionKeys"));
     }
 }
diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ServiceResolvedConditionKeysTraitTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ServiceResolvedConditionKeysTraitTest.java
new file mode 100644
index 000000000..247c7dba4
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/ServiceResolvedConditionKeysTraitTest.java
@@ -0,0 +1,26 @@
+package software.amazon.smithy.aws.iam.traits;
+
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.shapes.ShapeId;
+
+import java.util.Collections;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+public class ServiceResolvedConditionKeysTraitTest {
+    @Test
+    public void loadsFromModel() {
+        Model result = Model.assembler()
+                .discoverModels(getClass().getClassLoader())
+                .addImport(getClass().getResource("service-resolved-condition-keys.smithy"))
+                .assemble()
+                .unwrap();
+
+        Shape shape = result.expectShape(ShapeId.from("smithy.example#MyService"));
+        ServiceResolvedConditionKeysTrait trait = shape.expectTrait(ServiceResolvedConditionKeysTrait.class);
+        assertThat(trait.getValues(), equalTo(Collections.singletonList("smithy:ServiceResolveContextKey")));
+    }
+}
diff --git a/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/SmithyErrorFilesTest.java b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/SmithyErrorFilesTest.java
new file mode 100644
index 000000000..67bf4d3b4
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/java/software/amazon/smithy/aws/iam/traits/SmithyErrorFilesTest.java
@@ -0,0 +1,21 @@
+package software.amazon.smithy.aws.iam.traits;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import software.amazon.smithy.model.validation.testrunner.SmithyTestCase;
+import software.amazon.smithy.model.validation.testrunner.SmithyTestSuite;
+
+import java.util.concurrent.Callable;
+import java.util.stream.Stream;
+
+public class SmithyErrorFilesTest {
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("source")
+    public void testRunner(String filename, Callable<SmithyTestCase.Result> callable) throws Exception {
+        callable.call();
+    }
+
+    public static Stream<?> source() {
+        return SmithyTestSuite.defaultParameterizedTestSource(SmithyErrorFilesTest.class);
+    }
+}
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/condition-key-value.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/condition-key-value.smithy
new file mode 100644
index 000000000..ffdd45f9f
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/condition-key-value.smithy
@@ -0,0 +1,20 @@
+$version: "2.0"
+
+namespace smithy.example
+
+@aws.iam#defineConditionKeys(
+    "smithy:ActionContextKey1": { type: "String" }
+)
+service MyService {
+    version: "2019-02-20",
+    operations: [Echo]
+}
+
+operation Echo {
+    input: EchoInput
+}
+
+structure EchoInput {
+    @aws.iam#conditionKeyValue("smithy:ActionContextKey1")
+    id1: String
+}
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-also-service-resolved.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-also-service-resolved.errors
new file mode 100644
index 000000000..bcf2fb114
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-also-service-resolved.errors
@@ -0,0 +1,1 @@
+[ERROR] smithy.example#EchoInput$id1: This operation `smithy.example#Echo` scoped within the `smithy.example#MyService` service with member `smithy.example#EchoInput$id1` refers to a condition key `smithy:ServiceResolveContextKey` that is also resolved by service. | ConditionKeys
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-also-service-resolved.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-also-service-resolved.smithy
new file mode 100644
index 000000000..b263f4015
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-also-service-resolved.smithy
@@ -0,0 +1,24 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use aws.iam#conditionKeyValue
+
+@aws.iam#defineConditionKeys(
+    "smithy:ServiceResolveContextKey": { type: "String" }
+)
+@aws.iam#serviceResolvedConditionKeys(["smithy:ServiceResolveContextKey"])
+@aws.api#service(sdkId: "My")
+service MyService {
+    version: "2019-02-20",
+    operations: [Echo]
+}
+
+operation Echo {
+    input: EchoInput
+}
+
+structure EchoInput {
+    @aws.iam#conditionKeyValue("smithy:ServiceResolveContextKey")
+    id1: String
+}
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-value.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-value.errors
new file mode 100644
index 000000000..ffa064fbd
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-value.errors
@@ -0,0 +1,1 @@
+[ERROR] smithy.example#EchoInput$id1: This operation `smithy.example#Echo` scoped within the `smithy.example#MyService` service with member `smithy.example#EchoInput$id1` refers to an undefined condition key `smithy:InvalidConditionKey`. Expected one of the following defined condition keys: [`smithy:ActionContextKey1`] | ConditionKeys
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-value.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-value.smithy
new file mode 100644
index 000000000..b4ae7276a
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-key-value.smithy
@@ -0,0 +1,21 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use aws.iam#conditionKeyValue
+
+@aws.iam#defineConditionKeys(
+    "smithy:ActionContextKey1": { type: "String" }
+)
+@aws.api#service(sdkId: "My")
+service MyService {
+    version: "2019-02-20",
+    operations: [Echo]
+}
+
+operation Echo {
+    input := {
+        @aws.iam#conditionKeyValue("smithy:InvalidConditionKey")
+        id1: String
+    }
+}
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-keys-service-resolved.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-keys-service-resolved.errors
new file mode 100644
index 000000000..653079be2
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-keys-service-resolved.errors
@@ -0,0 +1,1 @@
+[ERROR] smithy.example#MyService: This condition keys resolved by the `smithy.example#MyService` service refer to undefined condition key(s) [`smithy:invalidkey`]. Expected one of the following defined condition keys: [`smithy:ActionContextKey1`] | ConditionKeys
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-keys-service-resolved.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-keys-service-resolved.smithy
new file mode 100644
index 000000000..4120bb1aa
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/invalid-condition-keys-service-resolved.smithy
@@ -0,0 +1,15 @@
+$version: "2.0"
+
+namespace smithy.example
+
+@aws.api#service(sdkId: "My")
+@aws.iam#defineConditionKeys(
+    "smithy:ActionContextKey1": { type: "String" }
+)
+@aws.iam#serviceResolvedConditionKeys(["smithy:invalidkey"])
+service MyService {
+    version: "2019-02-20",
+    operations: [Echo]
+}
+
+operation Echo {}
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/valid-condition-key-value.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/valid-condition-key-value.errors
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/valid-condition-key-value.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/valid-condition-key-value.smithy
new file mode 100644
index 000000000..77a4214c7
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/valid-condition-key-value.smithy
@@ -0,0 +1,23 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use aws.iam#conditionKeyValue
+
+@aws.iam#defineConditionKeys(
+    "smithy:ActionContextKey1": { type: "String" }
+)
+@aws.api#service(sdkId: "My")
+service MyService {
+    version: "2019-02-20",
+    operations: [Echo]
+}
+
+operation Echo {
+    input: EchoInput
+}
+
+structure EchoInput {
+    @aws.iam#conditionKeyValue("smithy:ActionContextKey1")
+    id1: String
+}
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/valid-condition-keys-service-resolved.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/valid-condition-keys-service-resolved.errors
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/valid-condition-keys-service-resolved.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/valid-condition-keys-service-resolved.smithy
new file mode 100644
index 000000000..5b16425a9
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/valid-condition-keys-service-resolved.smithy
@@ -0,0 +1,16 @@
+$version: "2.0"
+
+namespace smithy.example
+
+@aws.api#service(sdkId: "My")
+@aws.iam#defineConditionKeys(
+    "smithy:ServiceResolveContextKey": { type: "String" }
+)
+@aws.iam#serviceResolvedConditionKeys(["smithy:ServiceResolveContextKey"])
+service MyService {
+    version: "2019-02-20",
+    operations: [Echo]
+}
+
+
+operation Echo {}
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/service-resolved-condition-keys.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/service-resolved-condition-keys.smithy
new file mode 100644
index 000000000..c443fcb5c
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/service-resolved-condition-keys.smithy
@@ -0,0 +1,11 @@
+$version: "2.0"
+namespace smithy.example
+
+@aws.api#service(sdkId: "My")
+@aws.iam#defineConditionKeys(
+    "smithy:ServiceResolveContextKey": { type: "String" }
+)
+@aws.iam#serviceResolvedConditionKeys(["smithy:ServiceResolveContextKey"])
+service MyService {
+    version: "2019-02-20",
+}
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-service-schemes.json b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-service-schemes.json
index 572a88684..ebfd40d15 100644
--- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-service-schemes.json
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/auth/auth-trait-must-target-service-schemes.json
@@ -10,7 +10,11 @@
             ],
             "traits": {
                 "smithy.api#httpBasicAuth": {},
-                "smithy.api#httpDigestAuth": {}
+                "smithy.api#httpDigestAuth": {},
+                "smithy.api#auth": [
+                    "smithy.api#httpDigestAuth",
+                    "smithy.api#httpBasicAuth"
+                ]
             }
         },
         "ns.foo#ValidOperation": {
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/multiple-auth-schemes.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/multiple-auth-schemes.errors
new file mode 100644
index 000000000..a4a677ed5
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/multiple-auth-schemes.errors
@@ -0,0 +1,1 @@
+[WARNING] smithy.example#FooService: This service uses multiple authentication schemes but does not have the `@auth` trait applied. The `@auth` trait defines a priority ordering of auth schemes for a client to use. Without it, the ordering of auth schemes is alphabetical based on the absolute shape ID of the auth schemes. | ServiceAuthDefinitions
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/multiple-auth-schemes.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/multiple-auth-schemes.smithy
new file mode 100644
index 000000000..e188ee3f3
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/multiple-auth-schemes.smithy
@@ -0,0 +1,18 @@
+$version: "2"
+
+namespace smithy.example
+
+// Service shape with multiple auth schemes and no auth trait should cause warning
+@httpBasicAuth
+@httpDigestAuth
+@httpBearerAuth
+service FooService {
+    version: "2023-08-15"
+    operations: [GetFoo]
+}
+
+operation GetFoo {
+    output: GetFooOutput
+}
+
+structure GetFooOutput {}
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/one-auth-scheme.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/one-auth-scheme.errors
new file mode 100644
index 000000000..e69de29bb
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/one-auth-scheme.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/one-auth-scheme.smithy
new file mode 100644
index 000000000..5974d929b
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/service-auth-definitions/one-auth-scheme.smithy
@@ -0,0 +1,16 @@
+$version: "2"
+
+namespace smithy.example
+
+// Service shape with one auth schemes and no auth trait should NOT cause warning
+@httpBasicAuth
+service FooService {
+    version: "2023-08-15"
+    operations: [GetFoo]
+}
+
+operation GetFoo {
+    output: GetFooOutput
+}
+
+structure GetFooOutput {}