Skip to content
This repository was archived by the owner on Oct 9, 2023. It is now read-only.

Commit de017e3

Browse files
Cluster password policy and user management support (#283)
## What is the goal of this PR? Add functionality relevant to the changes made in typedb/typedb-cluster#456: * Optional password expiry in days for each user. * The ability for admins to set passwords of other users * User password update, which requires old and new password ## What are the changes implemented in this PR? We've added the functionality that is being added in a recent TypeDB Cluster pull request: * Support updating password now requiring both the old and the new password. * Add support for retrieving the user password expiry in days. This is optional, depending on whether password expiration is enabled on the server. * Allow updating password as the admin requiring only a new password.
1 parent 17d70cd commit de017e3

File tree

9 files changed

+110
-45
lines changed

9 files changed

+110
-45
lines changed

.factory/automation.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ build:
4040
image: vaticle-ubuntu-22.04
4141
type: foreground
4242
command: |
43+
export PATH="$HOME/.local/bin:$PATH"
44+
sudo apt-get update
45+
sudo apt install python3-pip -y
46+
python3 -m pip install -U pip
47+
python3 -m pip install -r requirements_dev.txt
4348
export ARTIFACT_USERNAME=$REPO_VATICLE_USERNAME
4449
export ARTIFACT_PASSWORD=$REPO_VATICLE_PASSWORD
4550
bazel run @vaticle_dependencies//distribution/artifact:create-netrc

dependencies/vaticle/artifacts.bzl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def vaticle_typedb_artifacts():
2929
artifact_name = "typedb-server-{platform}-{version}.{ext}",
3030
tag_source = deployment["artifact.release"],
3131
commit_source = deployment["artifact.snapshot"],
32-
tag = "2.14.3",
32+
tag = "2.16.1",
3333
)
3434

3535
def vaticle_typedb_cluster_artifacts():
@@ -39,5 +39,5 @@ def vaticle_typedb_cluster_artifacts():
3939
artifact_name = "typedb-cluster-all-{platform}-{version}.{ext}",
4040
tag_source = deployment_private["artifact.release"],
4141
commit_source = deployment_private["artifact.snapshot"],
42-
tag = "2.13.0",
42+
tag = "2.16.1",
4343
)

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@
3535

3636
## Dependencies
3737

38-
typedb-protocol==2.14.1
38+
typedb-protocol==2.16.1
3939
grpcio>=1.43.0,<2
4040
protobuf>=3.15.5,<4

tests/behaviour/connection/user/user_steps.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,19 @@ def step_impl(context: Context, username: str, password: str):
4848
_get_client(context).users().create(username, password)
4949

5050

51-
@step("user password: {username}, {password}")
51+
@step("users delete: {username}")
52+
def step_impl(context: Context, username: str):
53+
_get_client(context).users().delete(username)
54+
55+
56+
@step("users password set: {username}, {password}")
5257
def step_impl(context: Context, username: str, password: str):
53-
_get_client(context).users().get(username).password(password)
58+
_get_client(context).users().password_set(username, password)
59+
60+
61+
@step("users password update: {username}, {password_old}, {password_new}")
62+
def step_impl(context: Context, username: str, password_old: str, password_new: str):
63+
_get_client(context).users().get(username).password_update(password_old, password_new)
5464

5565

5666
@step("user connect: {username}, {password}")
@@ -59,9 +69,3 @@ def step_impl(context: Context, username: str, password: str):
5969
credential = TypeDBCredential(username, password, root_ca_path)
6070
with TypeDB.cluster_client(addresses=["127.0.0.1:" + context.config.userdata["port"]], credential=credential) as client:
6171
client.databases().all()
62-
63-
64-
@step("user delete: {username}")
65-
def step_impl(context: Context, username: str):
66-
user = _get_client(context).users().get(username)
67-
user.delete()

typedb/api/connection/user.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
#
2121

2222
from abc import ABC, abstractmethod
23-
from typing import List
23+
from typing import List, Optional
2424

2525

2626
class User(ABC):
@@ -30,28 +30,36 @@ def username(self) -> str:
3030
pass
3131

3232
@abstractmethod
33-
def password(self, password: str) -> None:
33+
def password_expiry_days(self) -> Optional[int]:
3434
pass
3535

3636
@abstractmethod
37-
def delete(self) -> None:
37+
def password_update(self, password_old: str, password_new: str) -> None:
3838
pass
3939

4040

4141
class UserManager(ABC):
4242

4343
@abstractmethod
44-
def get(self, username: str) -> User:
44+
def contains(self, username: str) -> bool:
4545
pass
4646

4747
@abstractmethod
48-
def contains(self, username: str) -> bool:
48+
def create(self, username: str, password: str) -> None:
4949
pass
5050

5151
@abstractmethod
52-
def create(self, username: str, password: str) -> None:
52+
def delete(self, username: str) -> None:
53+
pass
54+
55+
@abstractmethod
56+
def get(self, username: str) -> User:
5357
pass
5458

5559
@abstractmethod
5660
def all(self) -> List[User]:
5761
pass
62+
63+
@abstractmethod
64+
def password_set(self, username: str, password: str) -> None:
65+
pass

typedb/common/rpc/request_builder.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,29 +100,43 @@ def cluster_user_manager_create_req(username: str, password: str):
100100
return req
101101

102102

103+
def cluster_user_manager_delete_req(username: str):
104+
req = cluster_user_proto.ClusterUserManager.Delete.Req()
105+
req.username = username
106+
return req
107+
108+
103109
def cluster_user_manager_contains_req(username: str):
104110
req = cluster_user_proto.ClusterUserManager.Contains.Req()
105111
req.username = username
106112
return req
107113

108114

109-
# ClusterUser
110-
111-
def cluster_user_password_req(username: str, password: str):
112-
req = cluster_user_proto.ClusterUser.Password.Req()
115+
def cluster_user_manager_password_set_req(username: str, password: str):
116+
req = cluster_user_proto.ClusterUserManager.PasswordSet.Req()
113117
req.username = username
114118
req.password = password
115119
return req
116120

117121

118-
def cluster_user_token_req(username: str):
119-
req = cluster_user_proto.ClusterUser.Token.Req()
122+
def cluster_user_manager_get_req(username: str):
123+
req = cluster_user_proto.ClusterUserManager.Get.Req()
120124
req.username = username
121125
return req
122126

123127

124-
def cluster_user_delete_req(username: str):
125-
req = cluster_user_proto.ClusterUser.Delete.Req()
128+
# ClusterUser
129+
130+
def cluster_user_password_update_req(username: str, password_old: str, password_new: str):
131+
req = cluster_user_proto.ClusterUser.PasswordUpdate.Req()
132+
req.username = username
133+
req.password_old = password_old
134+
req.password_new = password_new
135+
return req
136+
137+
138+
def cluster_user_token_req(username: str):
139+
req = cluster_user_proto.ClusterUser.Token.Req()
126140
req.username = username
127141
return req
128142

typedb/connection/cluster/stub.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,17 @@ def users_contains(self, req: cluster_user_proto.ClusterUserManager.Contains.Req
6868
def users_create(self, req: cluster_user_proto.ClusterUserManager.Create.Req) -> cluster_user_proto.ClusterUserManager.Create.Res:
6969
return self.may_renew_token(lambda: self._cluster_stub.users_create(req))
7070

71-
def user_password(self, req: cluster_user_proto.ClusterUser.Delete.Req) -> cluster_user_proto.ClusterUser.Delete.Res:
72-
return self.may_renew_token(lambda: self._cluster_stub.user_password(req))
71+
def users_delete(self, req: cluster_user_proto.ClusterUserManager.Delete.Req) -> cluster_user_proto.ClusterUserManager.Delete.Res:
72+
return self.may_renew_token(lambda: self._cluster_stub.users_delete(req))
7373

74-
def user_delete(self, req: cluster_user_proto.ClusterUser.Delete.Req) -> cluster_user_proto.ClusterUser.Delete.Res:
75-
return self.may_renew_token(lambda: self._cluster_stub.user_delete(req))
74+
def users_password_set(self, req: cluster_user_proto.ClusterUserManager.PasswordSet.Req) -> cluster_user_proto.ClusterUserManager.PasswordSet.Res:
75+
return self.may_renew_token(lambda: self._cluster_stub.users_password_set(req))
76+
77+
def users_get(self, req: cluster_user_proto.ClusterUserManager.Get.Req) -> cluster_user_proto.ClusterUserManager.Get.Res:
78+
return self.may_renew_token(lambda: self._cluster_stub.users_get(req))
79+
80+
def user_password_update(self, req: cluster_user_proto.ClusterUser.PasswordUpdate.Req) -> cluster_user_proto.ClusterUser.PasswordUpdate.Res:
81+
return self.may_renew_token(lambda: self._cluster_stub.user_password_update(req))
7682

7783
def cluster_databases_all(self, req: cluster_database_proto.ClusterDatabaseManager.All.Req) -> cluster_database_proto.ClusterDatabaseManager.All.Res:
7884
return self.may_renew_token(lambda: self._cluster_stub.databases_all(req))

typedb/connection/cluster/user.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,42 @@
1818
# specific language governing permissions and limitations
1919
# under the License.
2020
#
21-
from typing import TYPE_CHECKING
21+
from typing import Optional, TYPE_CHECKING
22+
23+
import typedb_protocol.cluster.cluster_user_pb2 as cluster_user_proto
2224

2325
from typedb.api.connection.user import User
24-
from typedb.common.rpc.request_builder import cluster_user_password_req, cluster_user_delete_req
26+
from typedb.common.rpc.request_builder import cluster_user_password_update_req
2527
from typedb.connection.cluster.database import _FailsafeTask, _ClusterDatabase
2628

2729
if TYPE_CHECKING:
2830
from typedb.connection.cluster.client import _ClusterClient
2931

3032
class _ClusterUser(User):
3133

32-
def __init__(self, client: "_ClusterClient", username: str):
34+
def __init__(self, client: "_ClusterClient", username: str, password_expiry_days: Optional[int]):
3335
self._client = client
3436
self._username = username
37+
self._password_expiry_days = password_expiry_days
38+
39+
@staticmethod
40+
def of(user: cluster_user_proto.ClusterUser, client: "_ClusterClient"):
41+
if user.get_password_expiry_case() == cluster_user_proto.ClusterUser.PasswordExpiryCase.PASSWORDEXPIRY_NOT_SET:
42+
return _ClusterUser(client, user.get_username(), None)
43+
else:
44+
return _ClusterUser(client, user.get_username(), user.get_password_expiry_days())
3545

3646
def username(self) -> str:
3747
return self._username
3848

39-
def password(self, password: str) -> None:
40-
failsafe_task = _UserFailsafeTask(self._client, lambda replica: self._client._stub(replica.address()).user_password(cluster_user_password_req(self.username(), password)))
41-
failsafe_task.run_primary_replica()
49+
def password_expiry_days(self) -> Optional[int]:
50+
return self._password_expiry_days
4251

43-
def delete(self) -> None:
44-
failsafe_task = _UserFailsafeTask(self._client, lambda replica: self._client._stub(replica.address()).user_delete(cluster_user_delete_req(self.username())))
52+
def password_update(self, password_old: str, password_new: str) -> None:
53+
failsafe_task = _UserFailsafeTask(
54+
self._client,
55+
lambda replica: self._client._stub(replica.address()).user_password_update(cluster_user_password_update_req(self.username(), password_old, password_new))
56+
)
4557
failsafe_task.run_primary_replica()
4658

4759

typedb/connection/cluster/user_manager.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
from typing import List,TYPE_CHECKING
2222

2323
from typedb.api.connection.user import UserManager, User
24-
from typedb.common.rpc.request_builder import cluster_user_manager_create_req, cluster_user_manager_all_req, \
25-
cluster_user_manager_contains_req
24+
from typedb.common.rpc.request_builder import cluster_user_manager_contains_req, cluster_user_manager_create_req, \
25+
cluster_user_manager_delete_req, cluster_user_manager_all_req, cluster_user_manager_password_set_req, \
26+
cluster_user_manager_get_req
2627
from typedb.connection.cluster.database import _FailsafeTask, _ClusterDatabase
2728
from typedb.connection.cluster.user import _ClusterUser
28-
from typedb.common.exception import TypeDBClientException, CLUSTER_USER_DOES_NOT_EXIST
2929

3030
_SYSTEM_DB = "_system"
3131

@@ -45,6 +45,14 @@ def create(self, username: str, password: str) -> None:
4545
)
4646
failsafe_task.run_primary_replica()
4747

48+
def delete(self, username: str) -> None:
49+
failsafe_task = _UserManagerFailsafeTask(
50+
self._client,
51+
lambda replica: self._client._stub(replica.address()).users_delete(
52+
cluster_user_manager_delete_req(username))
53+
)
54+
failsafe_task.run_primary_replica()
55+
4856
def all(self) -> List[User]:
4957
failsafe_task = _UserManagerFailsafeTask(
5058
self._client,
@@ -65,10 +73,19 @@ def contains(self, username: str) -> bool:
6573
return failsafe_task.run_primary_replica()
6674

6775
def get(self, username: str) -> User:
68-
if (self.contains(username)):
69-
return _ClusterUser(self._client, username)
70-
else:
71-
raise TypeDBClientException.of(CLUSTER_USER_DOES_NOT_EXIST, username)
76+
failsafe_task = _UserManagerFailsafeTask(
77+
self._client,
78+
lambda replica: _ClusterUser.of(self._client._stub(replica.address()).users_get(cluster_user_manager_get_req(username)).get_user(), self._client)
79+
)
80+
return failsafe_task.run_primary_replica()
81+
82+
def password_set(self, username: str, password: str) -> None:
83+
failsafe_task = _UserManagerFailsafeTask(
84+
self._client,
85+
lambda replica: self._client._stub(replica.address()).users_password_set(
86+
cluster_user_manager_password_set_req(username, password))
87+
)
88+
failsafe_task.run_primary_replica()
7289

7390

7491
class _UserManagerFailsafeTask(_FailsafeTask):
@@ -79,4 +96,3 @@ def __init__(self, client: "_ClusterClient", task):
7996

8097
def run(self, replica: _ClusterDatabase.Replica):
8198
return self._task(replica)
82-

0 commit comments

Comments
 (0)