Skip to content

Decouple auth from http #468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@
import static software.amazon.smithy.python.aws.codegen.AwsConfiguration.REGION;

import java.util.List;
import java.util.Set;
import software.amazon.smithy.aws.traits.auth.SigV4Trait;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.python.codegen.ApplicationProtocol;
import software.amazon.smithy.python.codegen.CodegenUtils;
import software.amazon.smithy.python.codegen.ConfigProperty;
import software.amazon.smithy.python.codegen.DerivedProperty;
import software.amazon.smithy.python.codegen.GenerationContext;
import software.amazon.smithy.python.codegen.SmithyPythonDependency;
import software.amazon.smithy.python.codegen.integrations.AuthScheme;
import software.amazon.smithy.python.codegen.integrations.PythonIntegration;
import software.amazon.smithy.python.codegen.integrations.RuntimeClientPlugin;
import software.amazon.smithy.python.codegen.writer.PythonWriter;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
Expand All @@ -38,7 +40,7 @@ public List<RuntimeClientPlugin> getClientPlugins(GenerationContext context) {
.name("aws_credentials_identity_resolver")
.documentation("Resolves AWS Credentials. Required for operations that use Sigv4 Auth.")
.type(Symbol.builder()
.name("IdentityResolver[AWSCredentialsIdentity, IdentityProperties]")
.name("IdentityResolver[AWSCredentialsIdentity, AWSIdentityProperties]")
.addReference(Symbol.builder()
.addDependency(SmithyPythonDependency.SMITHY_CORE)
.name("IdentityResolver")
Expand All @@ -51,8 +53,8 @@ public List<RuntimeClientPlugin> getClientPlugins(GenerationContext context) {
.build())
.addReference(Symbol.builder()
.addDependency(SmithyPythonDependency.SMITHY_CORE)
.name("IdentityProperties")
.namespace("smithy_core.interfaces.identity", ".")
.name("AWSIdentityProperties")
.namespace("smithy_aws_core.identity", ".")
.build())
.build())
// TODO: Initialize with the provider chain?
Expand All @@ -69,30 +71,26 @@ public void customize(GenerationContext context) {
return;
}
var trait = context.settings().service(context.model()).expectTrait(SigV4Trait.class);
var params = CodegenUtils.getHttpAuthParamsSymbol(context.settings());
var resolver = CodegenUtils.getHttpAuthSchemeResolverSymbol(context.settings());

// Add a function that generates the http auth option for api key auth.
// This needs to be generated because there's modeled parameters that
// must be accounted for.
context.writerDelegator().useFileWriter(resolver.getDefinitionFile(), resolver.getNamespace(), writer -> {
writer.addDependency(SmithyPythonDependency.SMITHY_HTTP);
writer.addImport("smithy_http.aio.interfaces.auth", "HTTPAuthOption");
writer.addImport("smithy_core.interfaces.auth", "AuthOption", "AuthOptionProtocol");
writer.addImports("smithy_core.auth", Set.of("AuthOption", "AuthParams"));
writer.pushState();

writer.write("""
def $1L(auth_params: $2T) -> HTTPAuthOption | None:
return HTTPAuthOption(
scheme_id=$3S,
identity_properties={},
signer_properties={
"service": $4S,
"region": auth_params.region
}
def $1L(auth_params: AuthParams[Any, Any]) -> AuthOptionProtocol | None:
return AuthOption(
scheme_id=$2S,
identity_properties={}, # type: ignore
signer_properties={} # type: ignore
)
""",
SIGV4_OPTION_GENERATOR_NAME,
params,
SigV4Trait.ID.toString(),
trait.getName());
writer.popState();
Expand All @@ -119,17 +117,6 @@ public ApplicationProtocol getApplicationProtocol() {
return ApplicationProtocol.createDefaultHttpApplicationProtocol();
}

@Override
public List<DerivedProperty> getAuthProperties() {
return List.of(
DerivedProperty.builder()
.name("region")
.source(DerivedProperty.Source.CONFIG)
.type(Symbol.builder().name("str").build())
.sourcePropertyName("region")
.build());
}

@Override
public Symbol getAuthOptionGenerator(GenerationContext context) {
var resolver = CodegenUtils.getHttpAuthSchemeResolverSymbol(context.settings());
Expand All @@ -148,5 +135,11 @@ public Symbol getAuthSchemeSymbol(GenerationContext context) {
.addDependency(AwsPythonDependency.SMITHY_AWS_CORE)
.build();
}

@Override
public void initializeScheme(GenerationContext context, PythonWriter writer, ServiceShape service) {
var trait = service.expectTrait(SigV4Trait.class);
writer.write("$T(service=$S)", getAuthSchemeSymbol(context), trait.getName());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,11 @@ private void generateOperationExecutor(PythonWriter writer) {
writer.consumer(w -> context.protocolGenerator().wrapInputStream(context, w)),
writer.consumer(w -> context.protocolGenerator().wrapOutputStream(context, w)));
}

writer.addStdlibImport("typing", "Any");
writer.addStdlibImport("asyncio", "iscoroutine");
writer.addImports("smithy_core.exceptions", Set.of("SmithyError", "CallError", "RetryError"));
writer.addImport("smithy_core.auth", "AuthParams");
writer.pushState();
writer.putContext("request", transportRequest);
writer.putContext("response", transportResponse);
Expand Down Expand Up @@ -438,53 +440,60 @@ await sleep(retry_token.retry_delay)

boolean supportsAuth = !ServiceIndex.of(model).getAuthSchemes(service).isEmpty();
writer.pushState(new ResolveIdentitySection());
if (context.applicationProtocol().isHttpProtocol() && supportsAuth) {
writer.pushState(new InitializeHttpAuthParametersSection());
writer.write("""
# Step 7b: Invoke service_auth_scheme_resolver.resolve_auth_scheme
auth_parameters: $1T = $1T(
operation=operation.schema.id.name,
${2C|}
)

""",
CodegenUtils.getHttpAuthParamsSymbol(context.settings()),
writer.consumer(this::initializeHttpAuthParameters));
writer.popState();
if (supportsAuth) {
// TODO: delete InitializeHttpAuthParametersSection

writer.addDependency(SmithyPythonDependency.SMITHY_CORE);
writer.addDependency(SmithyPythonDependency.SMITHY_HTTP);
writer.addImport("smithy_core.interfaces.identity", "Identity");
writer.addImports("smithy_http.aio.interfaces.auth", Set.of("HTTPSigner", "HTTPAuthOption"));
writer.addImport("smithy_core.interfaces.auth", "AuthOption");
writer.addImport("smithy_core.aio.interfaces.auth", "Signer");
writer.addImport("smithy_core.shapes", "ShapeID");
writer.addStdlibImport("typing", "Any");
writer.write("""
auth_options = config.http_auth_scheme_resolver.resolve_auth_scheme(
auth_parameters = AuthParams(
protocol_id=ShapeID($1S),
operation=operation,
context=context.properties,
)
auth_options = config.auth_scheme_resolver.resolve_auth_scheme(
auth_parameters=auth_parameters
)
auth_option: HTTPAuthOption | None = None

auth_option: AuthOption | None = None
for option in auth_options:
if option.scheme_id in config.http_auth_schemes:
if option.scheme_id in config.auth_schemes:
auth_option = option
break

signer: HTTPSigner[Any, Any] | None = None
signer: Signer[$2T, Any, Any] | None = None
identity: Identity | None = None
auth_scheme: Any = None

if auth_option:
auth_scheme = config.http_auth_schemes[auth_option.scheme_id]
auth_scheme = config.auth_schemes[auth_option.scheme_id]
context.properties["auth_scheme"] = auth_scheme

# Step 7c: Invoke auth_scheme.identity_resolver
identity_resolver = auth_scheme.identity_resolver(config=config)
identity_resolver = auth_scheme.identity_resolver(context=context.properties)
context.properties["identity_resolver"] = identity_resolver

# Step 7d: Invoke auth_scheme.signer
signer = auth_scheme.signer
signer = auth_scheme.signer()

# TODO: merge from auth_option
identity_properties = auth_scheme.identity_properties(
context=context.properties
)
context.properties["identity_properties"] = identity_properties

# Step 7e: Invoke identity_resolver.get_identity
identity = await identity_resolver.get_identity(
identity_properties=auth_option.identity_properties
identity_properties=identity_properties
)

""");
""",
context.protocolGenerator().getProtocol(),
transportRequest);
}
writer.popState();

Expand Down Expand Up @@ -543,48 +552,29 @@ await sleep(retry_token.retry_delay)

writer.pushState(new SignRequestSection());
writer.addStdlibImport("typing", "cast");
if (context.applicationProtocol().isHttpProtocol() && supportsAuth) {
if (supportsAuth) {
writer.addStdlibImport("re");
writer.addStdlibImport("typing", "Any");
writer.addImport("smithy_core.interfaces.identity", "Identity");
writer.addImport("smithy_core.types", "PropertyKey");
writer.write("""
# Step 7i: sign the request
if auth_option and signer:
logger.debug("HTTP request to sign: %s", context.transport_request)
logger.debug(
"Signer properties: %s",
auth_option.signer_properties
)
signer_properties = auth_scheme.signer_properties(context=context.properties)
context.properties["signer_properties"] = signer_properties

logger.debug("Request to sign: %s", context.transport_request)
logger.debug("Signer properties: %s", signer_properties)

context = replace(
context,
transport_request= await signer.sign(
http_request=context.transport_request,
transport_request = await signer.sign(
request=context.transport_request,
identity=identity,
signing_properties=auth_option.signer_properties,
properties=signer_properties,
)
)
logger.debug("Signed HTTP request: %s", context.transport_request)

# TODO - Move this to separate resolution/population function
fields = context.transport_request.fields
auth_value = fields["Authorization"].as_string() # type: ignore
signature = re.split("Signature=", auth_value)[-1] # type: ignore
context.properties["signature"] = signature.encode('utf-8')

identity_key = cast(
PropertyKey[Identity | None],
PropertyKey(
key="identity",
value_type=Identity | None # type: ignore
)
)
sp_key: PropertyKey[dict[str, Any]] = PropertyKey(
key="signer_properties",
value_type=dict[str, Any] # type: ignore
)
context.properties[identity_key] = identity
context.properties[sp_key] = auth_option.signer_properties
""");
}
writer.popState();
Expand Down Expand Up @@ -757,28 +747,6 @@ private boolean hasEventStream() {
return false;
}

private void initializeHttpAuthParameters(PythonWriter writer) {
var derived = new LinkedHashSet<DerivedProperty>();
for (PythonIntegration integration : context.integrations()) {
for (RuntimeClientPlugin plugin : integration.getClientPlugins(context)) {
if (plugin.matchesService(model, service)
&& plugin.getAuthScheme().isPresent()
&& plugin.getAuthScheme().get().getApplicationProtocol().isHttpProtocol()) {
derived.addAll(plugin.getAuthScheme().get().getAuthProperties());
}
}
}

for (DerivedProperty property : derived) {
var source = property.source().scopeLocation();
if (property.initializationFunction().isPresent()) {
writer.write("$L=$T($L),", property.name(), property.initializationFunction().get(), source);
} else if (property.sourcePropertyName().isPresent()) {
writer.write("$L=$L.$L,", property.name(), source, property.sourcePropertyName().get());
}
}
}

private void writeDefaultPlugins(PythonWriter writer, Collection<SymbolReference> plugins) {
for (SymbolReference plugin : plugins) {
writer.write("$T,", plugin);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,6 @@ public static Symbol getServiceError(PythonSettings settings) {
.build();
}

/**
* Gets the symbol for the http auth params.
*
* @param settings The client settings, used to account for module configuration.
* @return Returns the http auth params symbol.
*/
public static Symbol getHttpAuthParamsSymbol(PythonSettings settings) {
return Symbol.builder()
.name("HTTPAuthParams")
.namespace(String.format("%s.auth", settings.moduleName()), ".")
.definitionFile(String.format("./src/%s/auth.py", settings.moduleName()))
.build();
}

/**
* Gets the symbol for the http auth scheme resolver.
*
Expand Down
Loading