Skip to content

Commit f96b37e

Browse files
feat: add MFA OTP support for IAM members (scaleway#836)
Co-authored-by: Jonathan R. <[email protected]>
1 parent ee5878b commit f96b37e

File tree

8 files changed

+394
-24
lines changed

8 files changed

+394
-24
lines changed

scaleway-async/scaleway_async/iam/v1alpha1/__init__.py

+10
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@
4040
from .types import CreateJWTRequest
4141
from .types import CreatePolicyRequest
4242
from .types import CreateSSHKeyRequest
43+
from .types import CreateUserMFAOTPRequest
4344
from .types import CreateUserRequest
4445
from .types import DeleteAPIKeyRequest
4546
from .types import DeleteApplicationRequest
4647
from .types import DeleteGroupRequest
4748
from .types import DeleteJWTRequest
4849
from .types import DeletePolicyRequest
4950
from .types import DeleteSSHKeyRequest
51+
from .types import DeleteUserMFAOTPRequest
5052
from .types import DeleteUserRequest
5153
from .types import EncodedJWT
5254
from .types import GetAPIKeyRequest
@@ -84,6 +86,7 @@
8486
from .types import ListUsersRequest
8587
from .types import ListUsersResponse
8688
from .types import LockUserRequest
89+
from .types import MFAOTP
8790
from .types import OrganizationSecuritySettings
8891
from .types import RemoveGroupMemberRequest
8992
from .types import SetGroupMembersRequest
@@ -99,6 +102,8 @@
99102
from .types import UpdateUserPasswordRequest
100103
from .types import UpdateUserRequest
101104
from .types import UpdateUserUsernameRequest
105+
from .types import ValidateUserMFAOTPRequest
106+
from .types import ValidateUserMFAOTPResponse
102107
from .api import IamV1Alpha1API
103108

104109
__all__ = [
@@ -142,13 +147,15 @@
142147
"CreateJWTRequest",
143148
"CreatePolicyRequest",
144149
"CreateSSHKeyRequest",
150+
"CreateUserMFAOTPRequest",
145151
"CreateUserRequest",
146152
"DeleteAPIKeyRequest",
147153
"DeleteApplicationRequest",
148154
"DeleteGroupRequest",
149155
"DeleteJWTRequest",
150156
"DeletePolicyRequest",
151157
"DeleteSSHKeyRequest",
158+
"DeleteUserMFAOTPRequest",
152159
"DeleteUserRequest",
153160
"EncodedJWT",
154161
"GetAPIKeyRequest",
@@ -186,6 +193,7 @@
186193
"ListUsersRequest",
187194
"ListUsersResponse",
188195
"LockUserRequest",
196+
"MFAOTP",
189197
"OrganizationSecuritySettings",
190198
"RemoveGroupMemberRequest",
191199
"SetGroupMembersRequest",
@@ -201,5 +209,7 @@
201209
"UpdateUserPasswordRequest",
202210
"UpdateUserRequest",
203211
"UpdateUserUsernameRequest",
212+
"ValidateUserMFAOTPRequest",
213+
"ValidateUserMFAOTPResponse",
204214
"IamV1Alpha1API",
205215
]

scaleway-async/scaleway_async/iam/v1alpha1/api.py

+100-4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
ListSSHKeysResponse,
5555
ListUsersResponse,
5656
Log,
57+
MFAOTP,
5758
OrganizationSecuritySettings,
5859
PermissionSet,
5960
Policy,
@@ -75,6 +76,8 @@
7576
UpdateUserRequest,
7677
UpdateUserUsernameRequest,
7778
User,
79+
ValidateUserMFAOTPRequest,
80+
ValidateUserMFAOTPResponse,
7881
)
7982
from .marshalling import (
8083
unmarshal_JWT,
@@ -99,8 +102,10 @@
99102
unmarshal_ListRulesResponse,
100103
unmarshal_ListSSHKeysResponse,
101104
unmarshal_ListUsersResponse,
105+
unmarshal_MFAOTP,
102106
unmarshal_OrganizationSecuritySettings,
103107
unmarshal_SetRulesResponse,
108+
unmarshal_ValidateUserMFAOTPResponse,
104109
marshal_AddGroupMemberRequest,
105110
marshal_AddGroupMembersRequest,
106111
marshal_CreateAPIKeyRequest,
@@ -122,6 +127,7 @@
122127
marshal_UpdateUserPasswordRequest,
123128
marshal_UpdateUserRequest,
124129
marshal_UpdateUserUsernameRequest,
130+
marshal_ValidateUserMFAOTPRequest,
125131
)
126132

127133

@@ -630,13 +636,11 @@ async def update_user_password(
630636
*,
631637
user_id: str,
632638
password: str,
633-
send_email: bool,
634639
) -> User:
635640
"""
636641
Update an user's password. Private Beta feature.
637642
:param user_id: ID of the user to update.
638643
:param password: The new password.
639-
:param send_email: Whether or not to send an email alerting the user their password has changed.
640644
:return: :class:`User <User>`
641645
642646
Usage:
@@ -645,7 +649,6 @@ async def update_user_password(
645649
result = await api.update_user_password(
646650
user_id="example",
647651
password="example",
648-
send_email=False,
649652
)
650653
"""
651654

@@ -658,7 +661,6 @@ async def update_user_password(
658661
UpdateUserPasswordRequest(
659662
user_id=user_id,
660663
password=password,
661-
send_email=send_email,
662664
),
663665
self.client,
664666
),
@@ -667,6 +669,100 @@ async def update_user_password(
667669
self._throw_on_error(res)
668670
return unmarshal_User(res.json())
669671

672+
async def create_user_mfaotp(
673+
self,
674+
*,
675+
user_id: str,
676+
) -> MFAOTP:
677+
"""
678+
Create a MFA OTP. Private Beta feature.
679+
:param user_id: User ID of the MFA OTP.
680+
:return: :class:`MFAOTP <MFAOTP>`
681+
682+
Usage:
683+
::
684+
685+
result = await api.create_user_mfaotp(
686+
user_id="example",
687+
)
688+
"""
689+
690+
param_user_id = validate_path_param("user_id", user_id)
691+
692+
res = self._request(
693+
"POST",
694+
f"/iam/v1alpha1/users/{param_user_id}/mfa-otp",
695+
body={},
696+
)
697+
698+
self._throw_on_error(res)
699+
return unmarshal_MFAOTP(res.json())
700+
701+
async def validate_user_mfaotp(
702+
self,
703+
*,
704+
user_id: str,
705+
one_time_password: str,
706+
) -> ValidateUserMFAOTPResponse:
707+
"""
708+
Validate a MFA OTP. Private Beta feature.
709+
:param user_id: User ID of the MFA OTP.
710+
:param one_time_password: A password generated using the OTP.
711+
:return: :class:`ValidateUserMFAOTPResponse <ValidateUserMFAOTPResponse>`
712+
713+
Usage:
714+
::
715+
716+
result = await api.validate_user_mfaotp(
717+
user_id="example",
718+
one_time_password="example",
719+
)
720+
"""
721+
722+
param_user_id = validate_path_param("user_id", user_id)
723+
724+
res = self._request(
725+
"POST",
726+
f"/iam/v1alpha1/users/{param_user_id}/validate-mfa-otp",
727+
body=marshal_ValidateUserMFAOTPRequest(
728+
ValidateUserMFAOTPRequest(
729+
user_id=user_id,
730+
one_time_password=one_time_password,
731+
),
732+
self.client,
733+
),
734+
)
735+
736+
self._throw_on_error(res)
737+
return unmarshal_ValidateUserMFAOTPResponse(res.json())
738+
739+
async def delete_user_mfaotp(
740+
self,
741+
*,
742+
user_id: str,
743+
) -> None:
744+
"""
745+
Delete a MFA OTP. Private Beta feature.
746+
:param user_id: User ID of the MFA OTP.
747+
748+
Usage:
749+
::
750+
751+
result = await api.delete_user_mfaotp(
752+
user_id="example",
753+
)
754+
"""
755+
756+
param_user_id = validate_path_param("user_id", user_id)
757+
758+
res = self._request(
759+
"DELETE",
760+
f"/iam/v1alpha1/users/{param_user_id}/mfa-otp",
761+
body={},
762+
)
763+
764+
self._throw_on_error(res)
765+
670766
async def lock_user(
671767
self,
672768
*,

scaleway-async/scaleway_async/iam/v1alpha1/marshalling.py

+45-3
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535
ListRulesResponse,
3636
ListSSHKeysResponse,
3737
ListUsersResponse,
38+
MFAOTP,
3839
OrganizationSecuritySettings,
3940
SetRulesResponse,
41+
ValidateUserMFAOTPResponse,
4042
AddGroupMemberRequest,
4143
AddGroupMembersRequest,
4244
CreateAPIKeyRequest,
@@ -60,6 +62,7 @@
6062
UpdateUserPasswordRequest,
6163
UpdateUserRequest,
6264
UpdateUserUsernameRequest,
65+
ValidateUserMFAOTPRequest,
6366
)
6467

6568

@@ -996,6 +999,21 @@ def unmarshal_ListUsersResponse(data: Any) -> ListUsersResponse:
996999
return ListUsersResponse(**args)
9971000

9981001

1002+
def unmarshal_MFAOTP(data: Any) -> MFAOTP:
1003+
if not isinstance(data, dict):
1004+
raise TypeError(
1005+
"Unmarshalling the type 'MFAOTP' failed as data isn't a dictionary."
1006+
)
1007+
1008+
args: Dict[str, Any] = {}
1009+
1010+
field = data.get("secret", None)
1011+
if field is not None:
1012+
args["secret"] = field
1013+
1014+
return MFAOTP(**args)
1015+
1016+
9991017
def unmarshal_OrganizationSecuritySettings(data: Any) -> OrganizationSecuritySettings:
10001018
if not isinstance(data, dict):
10011019
raise TypeError(
@@ -1038,6 +1056,21 @@ def unmarshal_SetRulesResponse(data: Any) -> SetRulesResponse:
10381056
return SetRulesResponse(**args)
10391057

10401058

1059+
def unmarshal_ValidateUserMFAOTPResponse(data: Any) -> ValidateUserMFAOTPResponse:
1060+
if not isinstance(data, dict):
1061+
raise TypeError(
1062+
"Unmarshalling the type 'ValidateUserMFAOTPResponse' failed as data isn't a dictionary."
1063+
)
1064+
1065+
args: Dict[str, Any] = {}
1066+
1067+
field = data.get("recovery_codes", None)
1068+
if field is not None:
1069+
args["recovery_codes"] = field
1070+
1071+
return ValidateUserMFAOTPResponse(**args)
1072+
1073+
10411074
def marshal_AddGroupMemberRequest(
10421075
request: AddGroupMemberRequest,
10431076
defaults: ProfileDefaults,
@@ -1455,9 +1488,6 @@ def marshal_UpdateUserPasswordRequest(
14551488
if request.password is not None:
14561489
output["password"] = request.password
14571490

1458-
if request.send_email is not None:
1459-
output["send_email"] = request.send_email
1460-
14611491
return output
14621492

14631493

@@ -1486,3 +1516,15 @@ def marshal_UpdateUserUsernameRequest(
14861516
output["username"] = request.username
14871517

14881518
return output
1519+
1520+
1521+
def marshal_ValidateUserMFAOTPRequest(
1522+
request: ValidateUserMFAOTPRequest,
1523+
defaults: ProfileDefaults,
1524+
) -> Dict[str, Any]:
1525+
output: Dict[str, Any] = {}
1526+
1527+
if request.one_time_password is not None:
1528+
output["one_time_password"] = request.one_time_password
1529+
1530+
return output

scaleway-async/scaleway_async/iam/v1alpha1/types.py

+42-5
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,14 @@ class CreateSSHKeyRequest:
986986
"""
987987

988988

989+
@dataclass
990+
class CreateUserMFAOTPRequest:
991+
user_id: str
992+
"""
993+
User ID of the MFA OTP.
994+
"""
995+
996+
989997
@dataclass
990998
class CreateUserRequest:
991999
organization_id: Optional[str]
@@ -1048,6 +1056,14 @@ class DeleteSSHKeyRequest:
10481056
ssh_key_id: str
10491057

10501058

1059+
@dataclass
1060+
class DeleteUserMFAOTPRequest:
1061+
user_id: str
1062+
"""
1063+
User ID of the MFA OTP.
1064+
"""
1065+
1066+
10511067
@dataclass
10521068
class DeleteUserRequest:
10531069
user_id: str
@@ -1757,6 +1773,11 @@ class LockUserRequest:
17571773
"""
17581774

17591775

1776+
@dataclass
1777+
class MFAOTP:
1778+
secret: str
1779+
1780+
17601781
@dataclass
17611782
class OrganizationSecuritySettings:
17621783
enforce_password_renewal: bool
@@ -1970,11 +1991,6 @@ class UpdateUserPasswordRequest:
19701991
The new password.
19711992
"""
19721993

1973-
send_email: bool
1974-
"""
1975-
Whether or not to send an email alerting the user their password has changed.
1976-
"""
1977-
19781994

19791995
@dataclass
19801996
class UpdateUserRequest:
@@ -2005,3 +2021,24 @@ class UpdateUserUsernameRequest:
20052021
"""
20062022
The new username.
20072023
"""
2024+
2025+
2026+
@dataclass
2027+
class ValidateUserMFAOTPRequest:
2028+
user_id: str
2029+
"""
2030+
User ID of the MFA OTP.
2031+
"""
2032+
2033+
one_time_password: str
2034+
"""
2035+
A password generated using the OTP.
2036+
"""
2037+
2038+
2039+
@dataclass
2040+
class ValidateUserMFAOTPResponse:
2041+
recovery_codes: List[str]
2042+
"""
2043+
List of recovery codes usable for this OTP method.
2044+
"""

0 commit comments

Comments
 (0)