Make sensitive fields be SecretString
Enable sensitive handling of certain fields known to contain sensitive data (password, passcode, secret, token.id) Change-Id: Iae8cf9a084b981600fc0b1b94176c0ef4109c5df
This commit is contained in:
5 changed files with 94 additions and 4 deletions
@@ -109,6 +109,18 @@ class String(BasePrimitiveType):
return '"foo"'
class SecretString(String):
type_hint: str = "SecretString"
@property
def imports(self) -> set[str]:
return {
"secrecy::SecretString",
"crate::api::common::serialize_sensitive_string",
"crate::api::common::serialize_sensitive_optional_string",
}
class JsonValue(BasePrimitiveType):
type_hint: str = "Value"
builder_macros: set[str] = {"setter(into)"}
@@ -63,6 +63,12 @@ class String(common_rust.String):
return set()
class SecretString(String):
"""CLI SecretString"""
pass
class IntString(common.BasePrimitiveType):
"""CLI Integer or String"""
@@ -667,6 +673,14 @@ class RequestTypeManager(common_rust.TypeManager):
for field_name, field in type_model.fields.items():
is_nullable: bool = False
field_data_type = self.convert_model(field.data_type)
if (
field_name
in ["password", "original_password", "secret", "passcode"]
or self.get_model_name(type_model.reference) == "Token"
and field_name == "id"
) and isinstance(field_data_type, String):
field_data_type = SecretString(format=field_data_type.format)
if isinstance(field_data_type, self.option_type_class):
# Unwrap Option into "is_nullable"
# NOTE: but perhaps Option<Option> is better (not set vs set
@@ -17,6 +17,9 @@ import subprocess
from typing import Type, Any
from codegenerator.base import BaseGenerator
from codegenerator.common import BasePrimitiveType
from codegenerator.common import BaseCombinedType
from codegenerator.common import BaseCompoundType
from codegenerator import common
from codegenerator import model
from codegenerator.common import BaseCompoundType
@@ -95,6 +98,13 @@ class StructField(common_rust.StructField):
macros.add(f'rename="{self.remote_name}"')
if self.is_optional:
macros.add('skip_serializing_if = "Option::is_none"')
if isinstance(self.data_type, common_rust.SecretString):
if self.is_optional:
macros.add(
'serialize_with = "serialize_sensitive_optional_string"'
)
else:
macros.add('serialize_with = "serialize_sensitive_string"')
return f"#[serde({', '.join(sorted(macros))})]"
@@ -271,6 +281,59 @@ class TypeManager(common_rust.TypeManager):
param.setter_type = "list"
self.parameters[k] = param
def _get_struct_type(self, type_model: model.Struct) -> Struct:
"""Convert model.Struct into Rust `Struct`"""
struct_class = self.data_type_mapping[model.Struct]
mod = struct_class(
name=self.get_model_name(type_model.reference),
description=common_rust.sanitize_rust_docstrings(
type_model.description
),
)
field_class = mod.field_type_class_
for field_name, field in type_model.fields.items():
is_nullable: bool = False
field_data_type = self.convert_model(field.data_type)
if (
field_name
in ["password", "original_password", "secret", "passcode"]
or self.get_model_name(type_model.reference) == "Token"
and field_name == "id"
) and isinstance(field_data_type, String):
field_data_type = common_rust.SecretString(
format=field_data_type.format
)
if isinstance(field_data_type, self.option_type_class):
# Unwrap Option into "is_nullable" NOTE: but perhaps
# Option<Option> is better (not set vs set explicitly to None
# )
is_nullable = True
if isinstance(field_data_type.item_type, common_rust.Array):
# Unwrap Option<Option<Vec...>>
field_data_type = field_data_type.item_type
f = field_class(
local_name=self.get_local_attribute_name(field_name),
remote_name=self.get_remote_attribute_name(field_name),
description=common_rust.sanitize_rust_docstrings(
field.description
),
data_type=field_data_type,
is_optional=not field.is_required,
is_nullable=is_nullable,
)
mod.fields[field_name] = f
if type_model.additional_fields:
definition = type_model.additional_fields
# Structure allows additional fields
if isinstance(definition, bool):
mod.additional_fields_type = self.primitive_type_mapping[
model.PrimitiveAny
]
else:
mod.additional_fields_type = self.convert_model(definition)
return mod
class RustSdkGenerator(BaseGenerator):
def __init__(self):
@@ -23,7 +23,8 @@
}
{%- elif v.data_type.format is defined and v.data_type.format == "password" %}
if let Some(val) = &args.{{ v.local_name }} {
{{ builder_name }}.{{ v.remote_name }}(val);
{# val.clone() is necessary due to SecretBox not implementing From<&String> #}
{{ builder_name }}.{{ v.remote_name }}(val.clone());
} else {
let secret = Password::new()
{%- if v.description %}
@@ -270,7 +270,7 @@ Some({{ val }})
let {{ builder_name }}: openstack_sdk::api::{{ sdk_mod_path | join("::") }}::{{ param.data_type.name }}Builder = TryFrom::try_from(&{{ val_var}})?;
{{ dst_var }}.{{ param.remote_name }}({{ builder_name }}.build()?);
{%- elif param.data_type.__class__.__name__ == "String" %}
{%- elif param.data_type.__class__.__name__ in ["String", "SecretString"] %}
{%- if is_nullable and not param.is_optional %}
{{ dst_var }}.{{ param.remote_name }}({{ val_var | replace("&", "") }}.clone());
{%- elif is_nullable and param.is_optional %}
@@ -385,7 +385,7 @@ Some({{ val }})
{%- elif param.data_type.item_type.__class__.__name__ == "ArrayInput" and param.data_type.item_type.__class__.__name__ == "ArrayInput" %}
{#- Array of Arrays - we should have the SDK setter for that #}
{{ dst_var }}.{{ param.remote_name }}({{ val_var }}.into_iter());
{%- elif param.data_type.item_type.__class__.__name__ == "String" and original_item_type.__class__.__name__ == "StructInput" %}
{%- elif param.data_type.item_type.__class__.__name__ in ["String", "SecretString"] and original_item_type.__class__.__name__ == "StructInput" %}
{#- Single field structure replaced with only string #}
{%- set original_type = param.data_type.item_type.original_data_type %}
{%- set original_field = original_type.fields[param.data_type.item_type.original_data_type.fields.keys()|list|first] %}
@@ -398,7 +398,7 @@ Some({{ val }})
)
.collect();
{{ dst_var }}.{{ param.remote_name }}({{ builder_name }});
{%- elif param.data_type.item_type.__class__.__name__ == "String" and original_type.__class__.__name__ == "ArrayInput" %}
{%- elif param.data_type.item_type.__class__.__name__ in ["String", "SecretString"] and original_type.__class__.__name__ == "ArrayInput" %}
{#- Single field structure replaced with only string #}
{{ dst_var }}.{{ param.remote_name }}(
val.iter()
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.