diff --git a/catalystwan/api/configuration_groups/parcel.py b/catalystwan/api/configuration_groups/parcel.py index 471c18272..dde18cd16 100644 --- a/catalystwan/api/configuration_groups/parcel.py +++ b/catalystwan/api/configuration_groups/parcel.py @@ -65,7 +65,7 @@ class Global(ParcelAttribute, Generic[T]): value: T def __len__(self) -> int: - if isinstance(self.value, str): + if isinstance(self.value, (str, list)): return len(self.value) return -1 @@ -81,12 +81,16 @@ def __le__(self, other: Any) -> bool: class Variable(ParcelAttribute): - option_type: OptionType = OptionType.VARIABLE + option_type: OptionType = Field( + default=OptionType.VARIABLE, serialization_alias="optionType", validation_alias="optionType" + ) value: str = Field(pattern=r"^\{\{[.\/\[\]a-zA-Z0-9_-]+\}\}$", min_length=1, max_length=64) class Default(ParcelAttribute, Generic[T]): - option_type: OptionType = OptionType.DEFAULT + option_type: OptionType = Field( + default=OptionType.DEFAULT, serialization_alias="optionType", validation_alias="optionType" + ) value: Any diff --git a/catalystwan/api/configuration_groups/parcels/aaa_parcel.py b/catalystwan/api/configuration_groups/parcels/aaa_parcel.py deleted file mode 100644 index 5f8b0e29d..000000000 --- a/catalystwan/api/configuration_groups/parcels/aaa_parcel.py +++ /dev/null @@ -1,62 +0,0 @@ -from enum import Enum -from typing import List, Optional, Union - -from pydantic import BaseModel, Field - -from catalystwan.api.configuration_groups.parcel import Default, Global, _ParcelBase - - -class ServerAuthOrder(str, Enum): - LOCAL = "local" - RADIUS = "radius" - TACACS = "tacacs" - - -class User(BaseModel): - name: str - password: str - privilege: str - - -class RadiusServer(BaseModel): - class Config: - arbitrary_types_allowed = True - allow_population_by_field_name = True - extra = "ignore" - - address: str - key: str - keyType: str = Field(default="key", alias="group_name") - acctPort: int = Field(default=1813, alias="acct_port") - authPort: int = Field(default=1812, alias="auth_port") - timeout: int = 5 - retransmit: int = 3 - - -class Radius(BaseModel): - class Config: - arbitrary_types_allowed = True - allow_population_by_field_name = True - - groupName: str = Field(alias="group_name") - vpn: int = 0 - sourceInterface: Optional[str] = Field(alias="source_interface") - server: List[RadiusServer] - - -# TODO Get model from schema -# Created only for demo purpouses -class AAAParcel(_ParcelBase): - user: Optional[Global[List[User]]] = Field(default=None) - authentication_group: Union[Global[bool], Default[bool]] = Field( - default=Default(value=False), alias="authenticationGroup" - ) - accounting_group: Union[Global[bool], Default[bool]] = Field(default=Default(value=False), alias="accountingGroup") - server_auth_order: Global[List[str]] = Field(alias="serverAuthOrder") - authorization_console: Union[Global[bool], Default[bool]] = Field( - default=Default(value=False), alias="authorizationConsole" - ) - authorization_config_commands: Union[Global[bool], Default[bool]] = Field( - default=Default(value=False), alias="authorizationConfigCommands" - ) - radius: Optional[List[Radius]] diff --git a/catalystwan/endpoints/configuration/feature_profile/sdwan/system.py b/catalystwan/endpoints/configuration/feature_profile/sdwan/system.py new file mode 100644 index 000000000..3ca5c33f4 --- /dev/null +++ b/catalystwan/endpoints/configuration/feature_profile/sdwan/system.py @@ -0,0 +1,50 @@ +# mypy: disable-error-code="empty-body" +from typing import Optional + +from catalystwan.api.configuration_groups.parcel import _ParcelBase +from catalystwan.endpoints import JSON, APIEndpoints, delete, get, post, put, versions +from catalystwan.models.configuration.feature_profile.common import ( + FeatureProfileCreationPayload, + FeatureProfileCreationResponse, + FeatureProfileInfo, + GetFeatureProfilesPayload, + ParcelId, + SchemaTypeQuery, +) +from catalystwan.typed_list import DataSequence + + +class SystemFeatureProfile(APIEndpoints): + @versions(supported_versions=(">=20.9"), raises=False) + @get("/v1/feature-profile/sdwan/system/aaa/schema", resp_json_key="request") + def get_sdwan_system_aaa_parcel_schema(self, params: SchemaTypeQuery) -> JSON: + ... + + @versions(supported_versions=(">=20.9"), raises=False) + @get("/v1/feature-profile/sdwan/system") + def get_sdwan_system_feature_profiles( + self, payload: Optional[GetFeatureProfilesPayload] + ) -> DataSequence[FeatureProfileInfo]: + ... + + @versions(supported_versions=(">=20.9"), raises=False) + @post("/v1/feature-profile/sdwan/system") + def create_sdwan_system_feature_profile( + self, payload: FeatureProfileCreationPayload + ) -> FeatureProfileCreationResponse: + ... + + @versions(supported_versions=(">=20.9"), raises=False) + @delete("/v1/feature-profile/sdwan/system/{system_id}") + def delete_sdwan_system_feature_profile(self, system_id: str) -> None: + ... + + @versions(supported_versions=(">=20.9"), raises=False) + @post("/v1/feature-profile/sdwan/system/{system_id}/aaa") + def create_aaa_profile_parcel_for_system(self, system_id: str, payload: _ParcelBase) -> ParcelId: + ... + + @versions(supported_versions=(">=20.9"), raises=False) + @put("/v1/feature-profile/sdwan/system/{system_id}/aaa/{parcel_id}") + def edit_aaa_profile_parcel_for_system(self, system_id: str, parcel_id: str, payload: _ParcelBase) -> ParcelId: + ... diff --git a/catalystwan/endpoints/endpoints_container.py b/catalystwan/endpoints/endpoints_container.py index 8b5404102..3d725005f 100644 --- a/catalystwan/endpoints/endpoints_container.py +++ b/catalystwan/endpoints/endpoints_container.py @@ -9,6 +9,7 @@ from catalystwan.endpoints.cluster_management import ClusterManagement from catalystwan.endpoints.configuration.device.software_update import ConfigurationDeviceSoftwareUpdate from catalystwan.endpoints.configuration.disaster_recovery import ConfigurationDisasterRecovery +from catalystwan.endpoints.configuration.feature_profile.sdwan.system import SystemFeatureProfile from catalystwan.endpoints.configuration.feature_profile.sdwan.transport import TransportFeatureProfile from catalystwan.endpoints.configuration.policy.definition.access_control_list import ConfigurationPolicyAclDefinition from catalystwan.endpoints.configuration.policy.definition.access_control_list_ipv6 import ( @@ -156,6 +157,7 @@ def __init__(self, session: ManagerSession): class ConfigurationSDWANFeatureProfileContainer: def __init__(self, session: ManagerSession): self.transport = TransportFeatureProfile(client=session) + self.system = SystemFeatureProfile(client=session) class ConfigurationFeatureProfileContainer: diff --git a/catalystwan/models/configuration/feature_profile/README.md b/catalystwan/models/configuration/feature_profile/README.md index f81496eef..912f3d428 100644 --- a/catalystwan/models/configuration/feature_profile/README.md +++ b/catalystwan/models/configuration/feature_profile/README.md @@ -2,6 +2,291 @@ This README document provides guidelines on creating configuration groups within the `Cisco Catalyst WAN SDK` repository. +## How to create Parcel model + +Cellular controller Parcel will be used for example purposes. Please change the parcel according to which one you are implementing. + +1. Add new endpoints following the guide: https://github.com/CiscoDevNet/vManage-client/blob/main/CONTRIBUTING.md +2. Download a schema with new endpoint (TODO where to add): +```python + @versions(supported_versions=(">=20.9"), raises=False) + @get("/v1/feature-profile/sdwan/transport/cellular-controller/schema", resp_json_key="request") + def get_sdwan_transport_cellular_controller_parcel_schema(self, params: SchemaTypeQuery) -> JSON: + ... +``` + +Usage: +```python +schema = session.endpoints.configuration_feature_profile.get_sdwan_transport_cellular_controller_parcel_schema( + params=SchemaTypeQuery(schemaType=SchemaType.POST) +) + +with open("sdwan_transport_cellular_controller_parcel_schema.json", "w") as f: + json.dump(schema, f, indent=4) +``` + +TODO: Possible to automate this step + +3. Generate `model.py` + +`datamodel-codegen --input sdwan_transport_cellular_controller_parcel_schema.json --output model.py --output-model-type pydantic_v2.BaseModel --enum-field-as-literal all --input-file-type jsonschema --field-constraints --target-python-version 3.8 --snake-case-field` + +TODO: Improve `cmd` & check if there is a possibility to improve generator + +`model.py` +```python +# generated by datamodel-codegen: +# filename: sdwan_transport_cellular_controller_parcel_schema.json +# timestamp: 2023-12-20T15:25:55+00:00 + +from __future__ import annotations + +from typing import Any, Literal, Optional, Union + +from pydantic import BaseModel, ConfigDict, Field + + +class ConfigType(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['default'] + value: Literal['non-eSim'] + + +class Id(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['global'] + value: str = Field(..., max_length=5, min_length=1) + + +class Id1(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['variable'] + value: str = Field( + ..., + max_length=64, + min_length=1, + pattern='^\\{\\{[.\\/\\[\\]a-zA-Z0-9_-]+\\}\\}$', + ) + + +class Slot(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['global'] + value: int = Field(..., ge=0, le=1) + + +class Slot1(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['variable'] + value: str = Field( + ..., + max_length=64, + min_length=1, + pattern='^\\{\\{[.\\/\\[\\]a-zA-Z0-9_-]+\\}\\}$', + ) + + +class Slot2(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['default'] + + +class MaxRetry(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['global'] + value: int = Field(..., ge=0, le=65535) + + +class MaxRetry1(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['variable'] + value: str = Field( + ..., + max_length=64, + min_length=1, + pattern='^\\{\\{[.\\/\\[\\]a-zA-Z0-9_-]+\\}\\}$', + ) + + +class MaxRetry2(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['default'] + + +class Failovertimer(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['global'] + value: int = Field(..., ge=3, le=7) + + +class Failovertimer1(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['variable'] + value: str = Field( + ..., + max_length=64, + min_length=1, + pattern='^\\{\\{[.\\/\\[\\]a-zA-Z0-9_-]+\\}\\}$', + ) + + +class Failovertimer2(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['default'] + + +class AutoSim(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['global'] + value: bool + + +class AutoSim1(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['variable'] + value: str = Field( + ..., + max_length=64, + min_length=1, + pattern='^\\{\\{[.\\/\\[\\]a-zA-Z0-9_-]+\\}\\}$', + ) + + +class AutoSim2(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + optionType: Literal['default'] + value: Literal[True] + + +class ControllerConfig(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: Union[Id, Id1] = Field(..., description='Cellular ID') + slot: Optional[Union[Slot, Slot1, Slot2]] = Field( + None, description='Set primary SIM slot' + ) + maxRetry: Optional[Union[MaxRetry, MaxRetry1, MaxRetry2]] = Field( + None, description='Set SIM failover retries' + ) + failovertimer: Optional[Union[Failovertimer, Failovertimer1, Failovertimer2]] = ( + Field(None, description='Set SIM failover timeout in minutes') + ) + autoSim: Optional[Union[AutoSim, AutoSim1, AutoSim2]] = Field( + None, description='Enable/Disable Firmware Auto Sim' + ) + + +class Data(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + configType: ConfigType + controllerConfig: ControllerConfig + + +class CellularController(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + name: str = Field( + ..., + description='Set the parcel name', + max_length=128, + min_length=1, + pattern='^[^&<>! "]+$', + ) + description: Optional[str] = Field(None, description='Set the parcel description') + data: Data + metadata: Optional[Any] = None + documentation: Optional[Any] = Field( + None, + description='This is the documentation for POST request schema for Transport CellularController profile parcel', + examples=[ + { + 'name': 'Cedge_CG1_Transport_CellularController_parcel1', + 'data': { + 'configType': {'optionType': 'default', 'value': 'non-eSim'}, + 'controllerConfig': { + 'id': {'optionType': 'global', 'value': '1'}, + 'slot': {'optionType': 'global', 'value': 0}, + 'maxRetry': {'optionType': 'global', 'value': 5}, + 'failovertimer': {'optionType': 'global', 'value': 3}, + 'autoSim': {'optionType': 'default', 'value': True}, + }, + }, + 'description': 'Cedge Transport CellularController Parcel config', + } + ], + ) +``` + +5. FIx `model.py` file + +`cellular_controller.py` +```python +from enum import Enum +from typing import Union + +from pydantic import Field + +from vmngclient.api.configuration_groups.parcel import Default, Global, Parcel, Variable + + +class ConfigTypeValue(Enum): + NON_E_SIM = "non-eSim" + + +class ControllerConfig(Parcel): + id: Union[Variable, Global[str]] = Field(min_length=1, max_length=5, description="Cellular ID") + slot: Union[Variable, Global[int], Default[int], None] = Field( + default=None, description="Set primary SIM slot", ge=0, le=1 + ) + maxRetry: Union[Variable, Global[int], Default[None], None] = Field( + default=None, description="Set SIM failover retries", ge=0, le=65535 + ) + failovertimer: Union[Variable, Global[int], Default[None], None] = Field( + default=None, description="Set SIM failover timeout in minutes", ge=3, le=7 + ) + autoSim: Union[Variable, Global[bool], Default[None], None] = Field( + default=None, description="Enable/Disable Firmware Auto Sim" + ) + + +class CellularControllerParcel(Parcel): + config_type: Default[ConfigTypeValue] = Field(default=Default(value=ConfigTypeValue.NON_E_SIM), alias="configType") + controller_config: ControllerConfig = Field(alias="controllerConfig") +``` ## Guidelines for Creating Config Groups ### 1. Directory Structure diff --git a/catalystwan/models/configuration/feature_profile/sdwan/system/aaa.py b/catalystwan/models/configuration/feature_profile/sdwan/system/aaa.py new file mode 100644 index 000000000..7a1f09bed --- /dev/null +++ b/catalystwan/models/configuration/feature_profile/sdwan/system/aaa.py @@ -0,0 +1,356 @@ +from ipaddress import IPv4Address, IPv6Address +from typing import List, Optional, Union + +from pydantic import AliasPath, BaseModel, ConfigDict, Field + +from catalystwan.api.configuration_groups.parcel import Default, Global, Variable, _ParcelBase, as_default, as_global + + +class PubkeyChainItem(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + key_string: Global[str] = Field( + validation_alias="keyString", + serialization_alias="keyString", + pattern="^AAAA[0-9A-Za-z+/]+[=]{0,3}$", + description="Set the RSA key string", + ) + + # Literal["ssh-rsa"] + key_type: Global[str] = Field( + default=Global[str](value="ssh-rsa"), + serialization_alias="keyType", + validation_alias="keyType", + description="Only RSA is supported", + ) + + +class UserItem(BaseModel): + model_config = ConfigDict(extra="forbid", populate_by_name=True) + + name: Union[Global[str], Variable] = Field(description="Set the username") + password: Union[Global[str], Variable] = Field( + description=( + "Set the user password [Note: Catalyst SD-WAN Manager will encrypt this field before saving." + "Cleartext strings will not be returned back to the user in GET responses for sensitive fields.]" + ) + ) + privilege: Union[Global[str], Variable, Default[str], None] = Field( + None, description="Set Privilege Level for this user" + ) + pubkey_chain: Optional[List[PubkeyChainItem]] = Field( + default=None, + validation_alias="pubkeyChain", + serialization_alias="pubkeyChain", + description="List of RSA public-keys per user", + max_length=2, + ) + + def add_pubkey_chain_item(self, key: str) -> PubkeyChainItem: + item = PubkeyChainItem(key_string=as_global(key)) + + if self.pubkey_chain: + self.pubkey_chain.append(item) + else: + self.pubkey_chain = [item] + + return item + + +class RadiusServerItem(BaseModel): + model_config = ConfigDict(extra="forbid", populate_by_name=True) + + address: Union[Global[IPv4Address], Global[IPv6Address]] = Field(description="Set IP address of Radius server") + auth_port: Union[Global[int], Default[int], Variable, None] = Field( + default=as_default(1812), + validation_alias="authPort", + serialization_alias="authPort", + description="Set Authentication port to use to connect to Radius server", + ) + acct_port: Union[Global[int], Default[int], Variable, None] = Field( + default=as_default(1813), + validation_alias="acctPort", + serialization_alias="acctPort", + description="Set Accounting port to use to connect to Radius server", + ) + + timeout: Union[Variable, Global[int], Default[int], None] = Field( + default=as_default(5), + description="Configure how long to wait for replies from the Radius server", + ) + key: Global[str] = Field( + description=( + "Set the Radius server shared key [Note: Catalyst SD-WAN Manager will encrypt " + "this field before saving. Cleartext strings will not be returned back " + "to the user in GET responses for sensitive fields.]" + ) + ) + secret_key: Union[Global[str], Variable, None] = Field( + default=None, + validation_alias="secretKey", + serialization_alias="secretKey", + description="Set the TACACS server shared type 7 encrypted key", + ) + # Literal["6", "7"] + key_enum: Union[Global[str], Default[None], None] = Field( + default=None, + validation_alias="keyEnum", + serialization_alias="keyEnum", + description="Type of encyption. To be used for type 6", + ) + # Literal["key", "pac"] + key_type: Union[Global[str], Default[str], Variable, None] = Field( + default=as_default("key"), validation_alias="keyType", serialization_alias="keyType", description="key type" + ) + + retransmit: Union[Variable, Global[int], Default[int], None] = Field( + default=as_default(3), description="Configure how many times to contact this Radius server" + ) + + +class Radius(BaseModel): + model_config = ConfigDict(extra="forbid", populate_by_name=True) + group_name: Global[str] = Field( + validation_alias="groupName", serialization_alias="groupName", description="Set Radius server Group Name" + ) + + vpn: Union[Global[int], Default[int], None] = Field( + default=None, description="Set VPN in which Radius server is located" + ) + source_interface: Union[Global[str], Default[None], Variable, None] = Field( + default=None, + validation_alias="sourceInterface", + serialization_alias="sourceInterface", + description="Set interface to use to reach Radius server", + ) + server: List[RadiusServerItem] = Field(description="Configure the Radius server") + + @staticmethod + def generate_radius_server( + address: Union[IPv4Address, IPv6Address], + key: str, + auth_port: Optional[int] = None, + acct_port: Optional[int] = None, + timeout: Optional[int] = None, + secret_key: Optional[str] = None, + key_enum: Optional[str] = None, + key_type: Optional[str] = None, + retransmit: Optional[int] = None, + ) -> RadiusServerItem: + item = RadiusServerItem( + address=as_global(address), + auth_port=as_global(auth_port), + acct_port=as_global(acct_port), + key=as_global(key), + retransmit=as_global(retransmit), + timeout=as_global(timeout), + secret_key=as_global(secret_key), + key_enum=as_global(key_enum), + key_type=as_global(key_type), + ) + + return item + + +class TacacsServerItem(BaseModel): + model_config = ConfigDict(extra="forbid", populate_by_name=True) + + address: Union[Global[IPv4Address], Global[IPv6Address]] = Field(description="Set IP address of TACACS server") + port: Union[Variable, Global[int], Default[int], None] = Field(default=None, description="TACACS Port") + timeout: Union[Variable, Global[int], Default[int], None] = Field( + default=None, + description="Configure how long to wait for replies from the TACACS server", + ) + key: Global[str] = Field( + description=( + "Set the TACACS server shared key [Note: Catalyst SD-WAN Manager will encrypt" + "this field before saving. Cleartext strings will not be returned back" + "to the user in GET responses for sensitive fields.]" + ) + ) + secret_key: Union[Global[str], Variable, None] = Field( + default=None, + validation_alias="secretKey", + serialization_alias="secretKey", + description="Set the TACACS server shared type 7 encrypted key", + ) + # Literal["6", "7"] + key_enum: Union[Global[str], Default[None], None] = Field( + default=None, + validation_alias="keyEnum", + serialization_alias="keyEnum", + description="Type of encyption. To be used for type 6", + ) + + +class Tacacs(BaseModel): + model_config = ConfigDict(extra="forbid", populate_by_name=True) + + group_name: Global[str] = Field( + validation_alias="groupName", serialization_alias="groupName", description="Set TACACS server Group Name" + ) + vpn: Union[Global[int], Default[int], None] = Field( + default=None, description="Set VPN in which TACACS server is located" + ) + source_interface: Union[Global[str], Default[str], None] = Field( + default=None, + validation_alias="sourceInterface", + serialization_alias="sourceInterface", + description="Set interface to use to reach TACACS server", + ) + server: List[TacacsServerItem] = Field(description="Configure the TACACS server") + + @staticmethod + def generate_tacacs_server( + address: Union[IPv4Address, IPv6Address], + key: str, + port: Optional[int] = None, + timeout: Optional[int] = None, + secret_key: Optional[str] = None, + key_enum: Optional[str] = None, + ) -> TacacsServerItem: + item = TacacsServerItem( + address=as_global(address), + key=as_global(key), + port=as_global(port), + timeout=as_global(timeout), + secret_key=as_global(secret_key), + key_enum=as_global(key_enum), + ) + + return item + + +class AccountingRuleItem(BaseModel): + model_config = ConfigDict(extra="forbid", populate_by_name=True) + rule_id: Global[str] = Field( + validation_alias="ruleId", serialization_alias="ruleId", description="Configure Accounting Rule ID" + ) + # Literal['commands', 'exec', 'network', 'system'] + method: Global[str] = Field(description="Configure Accounting Method") + # Literal['1', '15'] + level: Union[Global[str], Default[None], None] = Field(None, description="Privilege level when method is commands") + start_stop: Union[Variable, Global[bool], Default[bool], None] = Field( + default=None, + validation_alias="startStop", + serialization_alias="startStop", + description="Record start and stop without waiting", + ) + group: Global[List[str]] = Field(description="Use Server-group") + + +class AuthorizationRuleItem(BaseModel): + model_config = ConfigDict(extra="forbid", populate_by_name=True) + rule_id: Global[str] = Field( + validation_alias="ruleId", serialization_alias="ruleId", description="Configure Authorization Rule ID" + ) + # Literal["commands"] + method: Global[str] + # Literal['1', '15'] + level: Global[str] = Field(description="Privilege level when method is commands") + group: Global[List[str]] = Field(description="Use Server-group") + if_authenticated: Union[Global[bool], Default[bool], None] = Field( + default=None, + validation_alias="ifAuthenticated", + serialization_alias="ifAuthenticated", + description="Succeed if user has authenticated", + ) + + +class AAA(_ParcelBase): + authentication_group: Union[Variable, Global[bool], Default[bool]] = Field( + default=as_default(False), + validation_alias=AliasPath("data", "authenticationGroup"), + description="Authentication configurations parameters", + ) + accounting_group: Union[Variable, Global[bool], Default[bool]] = Field( + default=as_default(False), + validation_alias=AliasPath("data", "accountingGroup"), + description="Accounting configurations parameters", + ) + # local, radius, tacacs + server_auth_order: Global[List[str]] = Field( + validation_alias=AliasPath("data", "serverAuthOrder"), + min_length=1, + max_length=4, + description="ServerGroups priority order", + ) + user: Optional[List[UserItem]] = Field(default=None, description="Create local login account", min_length=1) + radius: Optional[List[Radius]] = Field( + default=None, validation_alias=AliasPath("data", "radius"), description="Configure the Radius serverGroup" + ) + tacacs: Optional[List[Tacacs]] = Field( + default=None, validation_alias=AliasPath("data", "tacacs"), description="Configure the TACACS serverGroup" + ) + accounting_rule: Optional[List[AccountingRuleItem]] = Field( + default=None, validation_alias=AliasPath("data", "accountingRule"), description="Configure the accounting rules" + ) + authorization_console: Union[Variable, Global[bool], Default[bool]] = Field( + default=as_default(False), + validation_alias=AliasPath("data", "authorizationConsole"), + description="For enabling console authorization", + ) + authorization_config_commands: Union[Variable, Global[bool], Default[bool]] = Field( + default=as_default(False), + validation_alias=AliasPath("data", "authorizationConfigCommands"), + description="For configuration mode commands.", + ) + authorization_rule: Optional[List[AuthorizationRuleItem]] = Field( + default=None, + validation_alias=AliasPath("data", "authorizationRule"), + description="Configure the Authorization Rules", + ) + + def add_authorization_rule( + self, rule_id: str, method: str, level: str, group: List[str], if_authenticated: bool + ) -> AuthorizationRuleItem: + item = AuthorizationRuleItem( + rule_id=as_global(rule_id), + method=as_global(method), + level=as_global(level), + group=Global[List[str]](value=group), + if_authenticated=as_global(if_authenticated), + ) + + if self.authorization_rule: + self.authorization_rule.append(item) + else: + self.authorization_rule = [item] + + return item + + def add_accounting_rule( + self, rule_id: str, method: str, level: str, group: List[str], start_stop: bool + ) -> AccountingRuleItem: + item = AccountingRuleItem( + rule_id=as_global(rule_id), + method=as_global(method), + level=as_global(level), + start_stop=as_global(start_stop), + group=Global[List[str]](value=group), + ) + + if self.accounting_rule: + self.accounting_rule.append(item) + else: + self.accounting_rule = [item] + + return item + + def add_radius_group( + self, group_name: str, vpn: int, radius_servers: List[RadiusServerItem], source_interface: Optional[str] = None + ) -> Radius: + radius = Radius( + group_name=as_global(group_name), + vpn=as_global(vpn), + server=radius_servers, + source_interface=Default[None](value=None), + ) + + if self.radius: + self.radius.append(radius) + else: + self.radius = [radius] + + return radius