Skip to content

Commit

Permalink
[ElastiCache] Add support for AuthenticationMode for create_user (#8494)
Browse files Browse the repository at this point in the history
  • Loading branch information
jflim authored Jan 19, 2025
1 parent 7d80859 commit a3472d3
Show file tree
Hide file tree
Showing 5 changed files with 374 additions and 9 deletions.
20 changes: 20 additions & 0 deletions moto/elasticache/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,26 @@ def __init__(self) -> None:
)


class InvalidParameterValueException(ElastiCacheException):
code = 404

def __init__(self, message: str) -> None:
super().__init__(
"InvalidParameterValue",
message=message,
)


class InvalidParameterCombinationException(ElastiCacheException):
code = 404

def __init__(self, message: str) -> None:
super().__init__(
"InvalidParameterCombination",
message=message,
)


class UserAlreadyExists(ElastiCacheException):
code = 404

Expand Down
33 changes: 32 additions & 1 deletion moto/elasticache/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
CacheClusterAlreadyExists,
CacheClusterNotFound,
InvalidARNFault,
InvalidParameterCombinationException,
InvalidParameterValueException,
UserAlreadyExists,
UserNotFound,
)
from .utils import PAGINATION_MODEL
from .utils import PAGINATION_MODEL, AuthenticationTypes


class User(BaseModel):
Expand All @@ -29,10 +31,12 @@ def __init__(
engine: str,
no_password_required: bool,
passwords: Optional[List[str]] = None,
authentication_type: Optional[str] = None,
):
self.id = user_id
self.name = user_name
self.engine = engine

self.passwords = passwords or []
self.access_string = access_string
self.no_password_required = no_password_required
Expand All @@ -41,6 +45,7 @@ def __init__(
self.usergroupids: List[str] = []
self.region = region
self.arn = f"arn:{get_partition(self.region)}:elasticache:{self.region}:{account_id}:user:{self.id}"
self.authentication_type = authentication_type


class CacheCluster(BaseModel):
Expand Down Expand Up @@ -161,9 +166,34 @@ def create_user(
passwords: List[str],
access_string: str,
no_password_required: bool,
authentication_type: str, # contain it to the str in the enums TODO
) -> User:
if user_id in self.users:
raise UserAlreadyExists

if authentication_type not in AuthenticationTypes._value2member_map_:
raise InvalidParameterValueException(
f"Input Authentication type: {authentication_type} is not in the allowed list: [password,no-password-required,iam]"
)

if (
no_password_required
and authentication_type != AuthenticationTypes.NOPASSWORD
):
raise InvalidParameterCombinationException(
f"No password required flag is true but provided authentication type is {authentication_type}"
)

if passwords and authentication_type != AuthenticationTypes.PASSWORD:
raise InvalidParameterCombinationException(
f"Password field is not allowed with authentication type: {authentication_type}"
)

if not passwords and authentication_type == AuthenticationTypes.PASSWORD:
raise InvalidParameterCombinationException(
"A user with Authentication Mode: password, must have at least one password"
)

user = User(
account_id=self.account_id,
region=self.region_name,
Expand All @@ -173,6 +203,7 @@ def create_user(
passwords=passwords,
access_string=access_string,
no_password_required=no_password_required,
authentication_type=authentication_type,
)
self.users[user_id] = user
return user
Expand Down
49 changes: 43 additions & 6 deletions moto/elasticache/responses.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from moto.core.responses import BaseResponse

from .exceptions import PasswordRequired, PasswordTooShort
from .exceptions import (
InvalidParameterCombinationException,
InvalidParameterValueException,
PasswordTooShort,
)
from .models import ElastiCacheBackend, elasticache_backends
from .utils import AuthenticationTypes


class ElastiCacheResponse(BaseResponse):
Expand All @@ -21,12 +26,41 @@ def create_user(self) -> str:
user_name = params.get("UserName")
engine = params.get("Engine")
passwords = params.get("Passwords", [])
no_password_required = self._get_bool_param("NoPasswordRequired", False)
password_required = not no_password_required
if password_required and not passwords:
raise PasswordRequired
no_password_required = self._get_bool_param("NoPasswordRequired")
authentication_mode = params.get("AuthenticationMode")
authentication_type = "null"

if no_password_required is not None:
authentication_type = (
AuthenticationTypes.NOPASSWORD.value
if no_password_required
else AuthenticationTypes.PASSWORD.value
)

if passwords:
authentication_type = AuthenticationTypes.PASSWORD.value

if authentication_mode:
for key in authentication_mode.keys():
if key not in ["Type", "Passwords"]:
raise InvalidParameterValueException(
f'Unknown parameter in AuthenticationMode: "{key}", must be one of: Type, Passwords'
)

authentication_type = authentication_mode.get("Type")
authentication_passwords = authentication_mode.get("Passwords", [])

if passwords and authentication_passwords:
raise InvalidParameterCombinationException(
"Passwords provided via multiple arguments. Use only one argument"
)

# if passwords is empty, then we can use the authentication_passwords
passwords = passwords if passwords else authentication_passwords

if any([len(p) < 16 for p in passwords]):
raise PasswordTooShort

access_string = params.get("AccessString")
user = self.elasticache_backend.create_user(
user_id=user_id, # type: ignore[arg-type]
Expand All @@ -35,6 +69,7 @@ def create_user(self) -> str:
passwords=passwords,
access_string=access_string, # type: ignore[arg-type]
no_password_required=no_password_required,
authentication_type=authentication_type,
)
template = self.response_template(CREATE_USER_TEMPLATE)
return template.render(user=user)
Expand Down Expand Up @@ -167,7 +202,9 @@ def list_tags_for_resource(self) -> str:
{% if user.no_password_required %}
<Type>no-password</Type>
{% else %}
<Type>password</Type>
<Type>{{ user.authentication_type }}</Type>
{% endif %}
{% if user.passwords %}
<PasswordCount>{{ user.passwords|length }}</PasswordCount>
{% endif %}
</Authentication>
Expand Down
8 changes: 8 additions & 0 deletions moto/elasticache/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from enum import Enum

PAGINATION_MODEL = {
"describe_cache_clusters": {
"input_token": "marker",
Expand All @@ -6,3 +8,9 @@
"unique_attribute": "cache_cluster_id",
},
}


class AuthenticationTypes(str, Enum):
NOPASSWORD = "no-password-required"
PASSWORD = "password"
IAM = "iam"
Loading

0 comments on commit a3472d3

Please sign in to comment.