Skip to content

Commit 356a3f8

Browse files
Update keeper_dag and dicovery_common modules.
1 parent 5fbb614 commit 356a3f8

13 files changed

Lines changed: 869 additions & 467 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.1.4'
1+
__version__ = '1.1.10'

keepercommander/discovery_common/infrastructure.py

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
from ..keeper_dag import DAG, EdgeType
55
from ..keeper_dag.exceptions import DAGVertexException
66
from ..keeper_dag.crypto import urlsafe_str_to_bytes
7-
from ..keeper_dag.types import PamGraphId, PamEndpoints
7+
from ..keeper_dag.types import PamGraphId
8+
from discovery_common.types import DiscoveryObject
89
import os
910
import importlib
1011
import time
11-
from typing import Any, Optional, TYPE_CHECKING
12+
from typing import Any, Optional, Dict, List, TYPE_CHECKING
1213

1314
if TYPE_CHECKING:
1415
from ..keeper_dag.vertex import DAGVertex
@@ -59,6 +60,8 @@ def __init__(self, record: Any, logger: Optional[Any] = None, history_level: int
5960

6061
self.conn = get_connection(logger=logger, **kwargs)
6162

63+
self._cache: Optional[Dict] = None
64+
6265
@property
6366
def dag(self) -> DAG:
6467
if self._dag is None:
@@ -123,6 +126,12 @@ def close(self):
123126
Clean up resources held by this Infrastructure instance.
124127
Releases the DAG instance and connection to prevent memory leaks.
125128
"""
129+
if self._cache:
130+
for v in self._cache.values():
131+
v["vertex"] = None
132+
v["content"] = None
133+
self._cache.clear()
134+
126135
if self._dag is not None:
127136
self._dag = None
128137
self.conn = None
@@ -150,6 +159,86 @@ def save(self, delta_graph: Optional[bool] = None):
150159
self._dag.save(delta_graph=delta_graph)
151160
self.logger.debug(f"infrastructure took {time.time()-ts} secs to save")
152161

162+
def cache_objects(self):
163+
164+
self.logger.debug(f"building id to infrastructure cache")
165+
166+
self._cache = {}
167+
168+
def _cache(v: DAGVertex, parent_content: Optional[DiscoveryObject] = None):
169+
c = DiscoveryObject.get_discovery_object(v)
170+
key = c.object_type_value.lower() + c.id.lower()
171+
self._cache[key] = {
172+
"key": key,
173+
"uid": v.uid,
174+
"parent_uid": parent_content.uid if parent_content else None,
175+
"vertex": v,
176+
"content": c,
177+
"was_found": False,
178+
"could_login": True,
179+
"is_new": False,
180+
"md5": c.md5
181+
}
182+
183+
for next_v in v.has_vertices():
184+
_cache(next_v, c)
185+
186+
if self.has_discovery_data:
187+
ts = time.time()
188+
_cache(self.get_configuration, None)
189+
self.logger.info(f" infrastructure cache build time: {time.time()-ts} seconds")
190+
else:
191+
self.logger.info(f" no infrastructure data to cache")
192+
193+
def get_cache_info(self, object_type_value: str, object_id: str) -> Dict:
194+
return self._cache.get(object_type_value.lower() + object_id.lower())
195+
196+
def get_cache_info_by_key(self, key: str) -> Dict:
197+
return self._cache.get(key.lower())
198+
199+
def get_missing_cache_list(self, uid: Optional[str] = None) -> List[str]:
200+
not_found_list = []
201+
for k, v in self._cache.items():
202+
if not v["is_new"] and not v["was_found"]:
203+
if uid is None or uid == v["uid"] or uid == v["parent_uid"]:
204+
not_found_list.append(k)
205+
return not_found_list
206+
207+
def add_info_to_cache(self, vertex: DAGVertex, content: DiscoveryObject, parent_vertex: Optional[DAGVertex] = None):
208+
if self._cache is None:
209+
self._cache = {}
210+
211+
key = content.object_type_value.lower() + content.id.lower()
212+
self._cache[key] = {
213+
"key": key,
214+
"uid": vertex.uid,
215+
"parent_uid": parent_vertex.uid if parent_vertex else None,
216+
"vertex": vertex,
217+
"content": content,
218+
"was_found": True,
219+
"could_login": True,
220+
"is_new": True,
221+
"md5": content.md5
222+
}
223+
224+
def update_cache_info(self, info: Dict):
225+
key = info["key"]
226+
self._cache[key] = info
227+
228+
def find_content(self, query: Dict, ignore_case: bool = False) -> Optional[DAGVertex]:
229+
"""
230+
Find the vertex that matches the query.
231+
232+
Will only find one.
233+
If it does not match, return None
234+
If matches on more, return None
235+
"""
236+
237+
vertices = self.dag.search_content(query=query, ignore_case=ignore_case)
238+
if len(vertices) != 1:
239+
return None
240+
return vertices[0]
241+
153242
def to_dot(self, graph_format: str = "svg", show_hex_uid: bool = False,
154243
show_version: bool = True, show_only_active_vertices: bool = False,
155244
show_only_active_edges: bool = False, sync_point: int = None, graph_type: str = "dot"):

keepercommander/discovery_common/jobs.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from .utils import get_connection, make_agent
33
from .types import JobContent, JobItem, Settings, DiscoveryDelta
44
from ..keeper_dag import DAG, EdgeType
5-
from ..keeper_dag.types import PamGraphId, PamEndpoints
5+
from ..keeper_dag.types import PamGraphId
66
import logging
77
import os
88
import base64
@@ -320,26 +320,12 @@ def get_job(self, job_id) -> Optional[JobItem]:
320320
# Get the job item from the job vertex DATA edge.
321321
# Replace the one from the job history if we have it.
322322
try:
323-
job = job_vertex.content_as_object(JobItem)
323+
found_job = job_vertex.content_as_object(JobItem)
324+
if found_job is not None:
325+
job = found_job
324326
except Exception as err:
325327
self.logger.debug(f"could not find job item on job vertex, use job histry entry: {err}")
326328

327-
# If the job delta is None, check to see if it chunked as vertices.
328-
delta_lookup = {}
329-
vertices = job_vertex.has_vertices()
330-
self.logger.debug(f"found {len(vertices)} delta vertices")
331-
for vertex in vertices:
332-
edge = vertex.get_edge(job_vertex, edge_type=EdgeType.KEY)
333-
delta_lookup[int(edge.path)] = vertex
334-
335-
json_value = ""
336-
# Sort numerically increasing and then append their content.
337-
# This will re-assemble the JSON
338-
for key in sorted(delta_lookup):
339-
json_value += delta_lookup[key].content_as_str
340-
if json_value != "":
341-
self.logger.debug(f"delta content length is {len(json_value)}")
342-
job.delta = DiscoveryDelta.model_validate_json(json_value)
343329
else:
344330
self.logger.debug("could not find job vertex")
345331

keepercommander/discovery_common/process.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ def _process_level(self,
10191019

10201020
if admin_uid is not None:
10211021
self.logger.debug(" found directory user admin, connect to resource")
1022-
# self.record_link.belongs_to(admin_uid, add_content.record_uid, acl=acl)
1022+
self.record_link.belongs_to(admin_uid, add_content.record_uid, acl=acl)
10231023
should_prompt_for_admin = False
10241024
else:
10251025
self.logger.debug(" did not find the directory user for the admin, "
@@ -1562,7 +1562,4 @@ def run(self,
15621562
self.infra.save(delta_graph=False)
15631563
self.logger.debug("# ####################################################################################")
15641564

1565-
# Update the user service mapping
1566-
self.user_service.run(infra=self.infra)
1567-
15681565
return bulk_process_results

keepercommander/discovery_common/rm_types.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,30 @@ class RmAzureGroupAddMeta(RmMetaBase):
137137
group_types: List[str] = []
138138

139139

140+
class RmGcpUserAddMeta(RmMetaBase):
141+
account_enabled: Optional[bool] = True
142+
display_name: Optional[str] = None
143+
password_reset_required: Optional[bool] = False
144+
password_reset_required_with_mfa: Optional[bool] = False
145+
groups: List[str] = []
146+
147+
148+
class RmGcpGroupAddMeta(RmMetaBase):
149+
group_types: List[str] = []
150+
151+
152+
class RmOktaUserAddMeta(RmMetaBase):
153+
account_enabled: Optional[bool] = True
154+
display_name: Optional[str] = None
155+
password_reset_required: Optional[bool] = False
156+
password_reset_required_with_mfa: Optional[bool] = False
157+
groups: List[str] = []
158+
159+
160+
class RmOktaGroupAddMeta(RmMetaBase):
161+
group_types: List[str] = []
162+
163+
140164
class RmDomainUserAddMeta(RmMetaBase):
141165
roles: List[str] = []
142166
groups: List[str] = []
@@ -253,6 +277,10 @@ class RmMongoDbRoleAddMeta(RmMetaBase):
253277
# MACHINE
254278

255279

280+
class RmUserDeleteBaseMeta(RmMetaBase):
281+
remove_home_dir: Optional[bool] = True
282+
283+
256284
class RmLinuxGroupAddMeta(RmMetaBase):
257285
gid: Optional[int] = None
258286
system_group: Optional[bool] = False
@@ -291,8 +319,7 @@ class RmLinuxUserAddMeta(RmMachineUserAddMeta):
291319
non_system_dir_mode: Optional[str] = None
292320

293321

294-
class RmLinuxUserDeleteMeta(RmMetaBase):
295-
remove_home_dir: Optional[bool] = True
322+
class RmLinuxUserDeleteMeta(RmUserDeleteBaseMeta):
296323
remove_user_group: Optional[bool] = True
297324

298325

@@ -308,6 +335,10 @@ class RmWindowsUserAddMeta(RmMachineUserAddMeta):
308335
groups: List[str] = []
309336

310337

338+
class RmWindowsUserDeleteMeta(RmUserDeleteBaseMeta):
339+
pass
340+
341+
311342
class RmMacOsUserAddMeta(RmMachineUserAddMeta):
312343
display_name: Optional[str] = None
313344
uid: Optional[str] = None
@@ -325,6 +356,10 @@ class RmMacOsRoleAddMeta(RmMetaBase):
325356
record_name: Optional[str] = None
326357

327358

359+
class RmMacOsUserDeleteMeta(RmUserDeleteBaseMeta):
360+
pass
361+
362+
328363
# DIRECTORY
329364

330365

keepercommander/discovery_common/types.py

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import datetime
66
import base64
77
import json
8+
import hashlib
89
from keeper_secrets_manager_core.crypto import CryptoUtils
910
from typing import Any, Union, Optional, List, TYPE_CHECKING
1011

@@ -524,6 +525,18 @@ class DiscoveryObject(BaseModel):
524525
# Specific information for a record type.
525526
item: Union[DiscoveryConfiguration, DiscoveryUser, DiscoveryMachine, DiscoveryDatabase, DiscoveryDirectory]
526527

528+
@property
529+
def md5(self) -> str:
530+
data = self.model_dump()
531+
532+
# Don't include these in the MD5
533+
data.pop("missing_since_ts", None)
534+
data.pop("access_user", None)
535+
536+
m = hashlib.md5()
537+
m.update(json.dumps(data).encode('utf-8'))
538+
return m.hexdigest()
539+
527540
@property
528541
def record_exists(self):
529542
return self.record_uid is not None
@@ -603,29 +616,98 @@ class NormalizedRecord(BaseModel):
603616
title: str
604617
fields: List[RecordField] = []
605618
note: Optional[str] = None
619+
record_exists: bool = True
620+
621+
def _field(self,
622+
field_type: Optional[str] = None,
623+
label: Optional[str] = None) -> Optional[RecordField]:
624+
if field_type is None and label is None:
625+
raise ValueError("either field_type or label needs to be set to find field in NormalizedRecord.")
606626

607-
def _field(self, field_type, label) -> Optional[RecordField]:
608627
for field in self.fields:
609-
value = field.value
610-
if value is None or len(value) == 0:
611-
continue
612-
if field.label == field_type and value[0].lower() == label.lower():
628+
if field_type is not None and field_type == field.type:
629+
return field
630+
if label is not None and label == field.label:
613631
return field
614632
return None
615633

616-
def find_user(self, user):
634+
def find_field(self,
635+
field_type: Optional[str] = None,
636+
label: Optional[str] = None) -> Optional[RecordField]:
637+
638+
return self._field(field_type=field_type, label=label)
639+
640+
def get_value(self,
641+
field_type: Optional[str] = None,
642+
label: Optional[str] = None) -> Optional[Any]:
643+
644+
field = self.find_field(field_type=field_type, label=label)
645+
if field is None or field.value is None or len(field.value) == 0:
646+
return None
647+
return field.value[0]
648+
649+
def get_user(self) -> Optional[str]:
650+
field = self._field(field_type="login")
651+
if field is None:
652+
return None
653+
value = field.value
654+
if isinstance(value, list):
655+
if len(value) == 0:
656+
return None
657+
value = value[0]
658+
return value
659+
660+
def get_dn(self) -> Optional[str]:
661+
field = self._field(label="distinguishedName")
662+
if field is None:
663+
return None
664+
value = field.value
665+
if isinstance(value, list):
666+
if len(value) == 0:
667+
return None
668+
value = value[0]
669+
return value
670+
671+
def has_user(self, user) -> bool:
617672

618673
from .utils import split_user_and_domain
619674

620-
res = self._field("login", user)
621-
if res is None:
622-
user, _ = split_user_and_domain(user)
623-
res = self._field("login", user)
675+
user, _ = split_user_and_domain(user)
676+
677+
field = self._field(field_type="login")
678+
if field is None:
679+
return False
680+
681+
value = field.value
682+
if isinstance(value, list):
683+
if len(value) == 0:
684+
return False
685+
value = value[0]
686+
elif isinstance(value, str):
687+
value = value.lower()
688+
689+
if user.lower() == value:
690+
return True
691+
692+
return False
693+
694+
def has_dn(self, user) -> bool:
695+
field = self._field(label="distinguishedName")
696+
if field is None:
697+
return False
624698

625-
return res
699+
value = field.value
700+
if isinstance(value, list):
701+
if len(value) == 0:
702+
return False
703+
value = value[0]
704+
elif isinstance(value, str):
705+
value = value.lower()
626706

627-
def find_dn(self, user):
628-
return self._field("distinguishedName", user)
707+
if user.lower() == value:
708+
return True
709+
710+
return False
629711

630712

631713
class PromptResult(BaseModel):

0 commit comments

Comments
 (0)