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:
parent
51403407d7
commit
a6002e661b
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user