12
12
from ...proto import router_pb2 , record_pb2
13
13
from keepercommander .discovery_common .jobs import Jobs
14
14
from keepercommander .discovery_common .process import Process , QuitException , NoDiscoveryDataException
15
- from keepercommander .discovery_common .types import (DiscoveryObject , UserAcl , PromptActionEnum , PromptResult ,
16
- BulkRecordAdd , BulkRecordConvert , BulkProcessResults , BulkRecordSuccess ,
17
- BulkRecordFail , DirectoryInfo , NormalizedRecord , RecordField )
15
+ from keepercommander .discovery_common .types import (
16
+ DiscoveryObject , UserAcl , PromptActionEnum , PromptResult , BulkRecordAdd , BulkRecordConvert , BulkProcessResults ,
17
+ BulkRecordSuccess , BulkRecordFail , DirectoryInfo , NormalizedRecord , RecordField )
18
18
from pydantic import BaseModel
19
19
from typing import Optional , List , Any , TYPE_CHECKING
20
20
@@ -46,6 +46,7 @@ class AdminSearchResult(BaseModel):
46
46
record : Any
47
47
is_directory_user : bool
48
48
is_pam_user : bool
49
+ being_used : bool = False
49
50
50
51
51
52
class PAMGatewayActionDiscoverResultProcessCommand (PAMGatewayActionDiscoverCommandBase ):
@@ -582,7 +583,10 @@ def _prompt(self,
582
583
raise QuitException ()
583
584
print ()
584
585
585
- def _find_user_record (self , params : KeeperParams , context : Optional [Any ] = None ) -> Optional [TypedRecord ]:
586
+ def _find_user_record (self ,
587
+ params : KeeperParams ,
588
+ bulk_convert_records : List [BulkRecordConvert ],
589
+ context : Optional [Any ] = None ) -> (Optional [TypedRecord ], bool ):
586
590
587
591
gateway_context = context .get ("gateway_context" ) # type: GatewayContext
588
592
record_link = context .get ("record_link" ) # type: RecordLink
@@ -599,11 +603,15 @@ def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None)
599
603
for record in folder ["records" ]:
600
604
shared_record_uids .append (record .get ("record_uid" ))
601
605
606
+ # Make a list of record we are already converting so we don't show them again.
607
+ converting_list = [x .record_uid for x in bulk_convert_records ]
608
+
602
609
logging .debug (f"shared folders record uid { shared_record_uids } " )
603
610
604
611
while True :
605
612
user_search = input ("Enter an user to search for [ENTER/RETURN to quit]> " )
606
613
if user_search == "" :
614
+ print (f"{ bcolors .FAIL } No search terms, not performing search.{ bcolors .ENDC } " )
607
615
return None
608
616
609
617
# Search for record with the search string.
@@ -613,39 +621,40 @@ def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None)
613
621
search_str = user_search ,
614
622
record_version = 3
615
623
))
624
+ # If not record are returned by the search just return None,
616
625
if len (user_record ) == 0 :
617
- print (f"{ bcolors .FAIL } Could not find any record.{ bcolors .ENDC } " )
626
+ print (f"{ bcolors .FAIL } Could not find any records that contain the search text.{ bcolors .ENDC } " )
627
+ return None
618
628
619
629
# Find usable admin records.
620
630
admin_search_results = [] # type: List[AdminSearchResult]
621
631
for record in user_record :
622
632
623
633
user_record = vault .KeeperRecord .load (params , record .record_uid )
624
634
if user_record .record_type == "pamUser" :
635
+ logging .debug (f"{ record .record_uid } is a pamUser" )
636
+
637
+ # If we are already converting this pamUser record, then don't show it.
638
+ if record .record_uid in converting_list :
639
+ logging .debug (f"pamUser { user_record .title } , { user_record .record_uid } is being converted; "
640
+ "BAD for search" )
641
+ admin_search_results .append (
642
+ AdminSearchResult (
643
+ record = user_record ,
644
+ is_directory_user = False ,
645
+ is_pam_user = True ,
646
+ being_used = True
647
+ )
648
+ )
649
+ continue
625
650
626
651
# Does the record exist in the gateway shared folder?
627
652
# We want to filter our other gateway's pamUser, or it will get overwhelming.
628
653
if user_record .record_uid not in shared_record_uids :
629
654
logging .debug (f"pamUser { record .title } , { user_record .record_uid } not in shared "
630
- "folder, skip " )
655
+ "folder, BAD for search " )
631
656
continue
632
657
633
- # # If a pamUser, make sure the user is part of our configuration
634
- # record_rotation = params.record_rotation_cache.get(record.record_uid)
635
- # if record_rotation is not None:
636
- # configuration_uid = record_rotation.get("configuration_uid")
637
- # if configuration_uid is None or configuration_uid == "":
638
- # logging.debug(f"pamUser {record.title}, {record.record_uid} does not have a controller, "
639
- # "skip")
640
- # continue
641
- # if configuration_uid != gateway_context.configuration_uid:
642
- # logging.debug(f"pamUser {record.title}, {record.record_uid} controller is not this "
643
- # " controller, skip")
644
- # continue
645
- # else:
646
- # logging.debug(f"pamUser {record.title}, {record.record_uid} does not have a rotation
647
- # settings.")
648
-
649
658
# If the record does not exist in the record linking, it's orphaned; accept it
650
659
# If it does exist, then check if it belonged to a directory.
651
660
# Very unlikely a user that belongs to a database or another machine can be used.
@@ -659,28 +668,32 @@ def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None)
659
668
is_directory_user = self ._is_directory_user (parent_record .record_type )
660
669
if is_directory_user is False :
661
670
logging .debug (f"pamUser parent for { user_record .title } , "
662
- "{user_record.record_uid} is not a directory, skip " )
671
+ "{user_record.record_uid} is not a directory; BAD for search " )
663
672
continue
664
673
674
+ logging .debug (f"pamUser { user_record .title } , { user_record .record_uid } is a directory user; "
675
+ "good for search" )
676
+
665
677
else :
666
- logging .debug (f"pamUser { user_record .title } , { user_record .record_uid } does not have record "
667
- "linking vertex. " )
678
+ logging .debug (f"pamUser { user_record .title } , { user_record .record_uid } does not a parent; "
679
+ "good for search " )
668
680
else :
669
681
logging .debug (f"pamUser { user_record .title } , { user_record .record_uid } does not have record "
670
- "linking vertex. " )
682
+ "linking vertex; good for search " )
671
683
672
684
admin_search_results .append (
673
685
AdminSearchResult (
674
686
record = user_record ,
675
687
is_directory_user = is_directory_user ,
676
- is_pam_user = True
688
+ is_pam_user = True ,
689
+ being_used = False
677
690
)
678
691
)
679
692
680
693
# Else this is a non-PAM record.
681
694
# Make sure it has a login, password, private key
682
695
else :
683
- logging .debug (f"{ record .record_uid } is not a pamUser" )
696
+ logging .debug (f"{ record .record_uid } is NOT a pamUser" )
684
697
login_field = next ((x for x in record .fields if x .type == "login" ), None )
685
698
password_field = next ((x for x in record .fields if x .type == "password" ), None )
686
699
private_key_field = next ((x for x in record .fields if x .type == "keyPair" ), None )
@@ -693,11 +706,16 @@ def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None)
693
706
is_pam_user = False
694
707
)
695
708
)
709
+ logging .debug (f"{ record .title } is has credentials, good for search" )
696
710
else :
697
- logging .debug (f"{ record .title } is missing full credentials, skip " )
711
+ logging .debug (f"{ record .title } is missing full credentials, BAD for search " )
698
712
699
- user_index = 1
713
+ # If all the users have been filtered out, then just return None
714
+ if len (admin_search_results ) == 0 :
715
+ print (f"{ bcolors .FAIL } Could not find any available records.{ bcolors .ENDC } " )
716
+ return None
700
717
718
+ user_index = 1
701
719
admin_search_results = sorted (admin_search_results ,
702
720
key = lambda x : x .is_pam_user ,
703
721
reverse = True )
@@ -709,32 +727,50 @@ def _find_user_record(self, params: KeeperParams, context: Optional[Any] = None)
709
727
has_local_user = True
710
728
is_local_user = True
711
729
712
- print (f"{ bcolors .HEADER } [{ user_index } ] { bcolors .ENDC } "
713
- f"{ _b ('* ' ) if is_local_user is True else '' } "
714
- f"{ admin_search_result .record .title } "
715
- f'{ "(Directory User)" if admin_search_result .is_directory_user is True else "" } ' )
730
+ hc = bcolors .HEADER
731
+ b = bcolors .BOLD
732
+ tc = ""
733
+ index_str = user_index
734
+ if admin_search_result .being_used is True :
735
+ hc = bcolors .WARNING
736
+ b = bcolors .WARNING
737
+ tc = bcolors .WARNING
738
+ index_str = "-" * len (str (index_str ))
739
+
740
+ print (f"{ hc } [{ index_str } ] { bcolors .ENDC } "
741
+ f"{ b + '* ' + bcolors .ENDC if is_local_user is True else '' } "
742
+ f"{ tc } { admin_search_result .record .title } { bcolors .ENDC } "
743
+ f'{ "(Directory User) " if admin_search_result .is_directory_user is True else "" } '
744
+ f'{ tc + "(Already taken)" + bcolors .ENDC if admin_search_result .being_used is True else "" } ' )
716
745
user_index += 1
717
746
718
747
if has_local_user is True :
719
748
print (f"{ bcolors .BOLD } * Not a PAM User record. "
720
749
f"A PAM User would be generated from this record.{ bcolors .ENDC } " )
721
750
722
- select = input ("Enter line number of user record to use, enter/return to refind the search, "
751
+ select = input ("Enter line number of user record to use, enter/return to refine the search, "
723
752
f"or { _b ('Q' )} to quit search. > " ).lower ()
724
753
if select == "" :
725
754
continue
726
755
elif select [0 ] == "q" :
727
756
return None
728
757
else :
729
758
try :
730
- return admin_search_results [int (select ) - 1 ].record # type: TypedRecord
759
+ selected = admin_search_results [int (select ) - 1 ]
760
+ if selected .being_used is True :
761
+ print (f"{ bcolors .FAIL } Cannot select a record that has already been taken. "
762
+ f"Another record is using this local user as its administrator.{ bcolors .ENDC } " )
763
+ return None
764
+ admin_record = selected .record # type: TypedRecord
765
+ return admin_record , selected .is_directory_user
731
766
except IndexError :
732
767
print (f"{ bcolors .FAIL } Entered row index does not exists.{ bcolors .ENDC } " )
733
768
continue
734
769
735
770
@staticmethod
736
- def _handle_admin_record_from_record (record : TypedRecord , content : DiscoveryObject , context : Optional [Any ] = None ) \
737
- -> Optional [PromptResult ]:
771
+ def _handle_admin_record_from_record (record : TypedRecord ,
772
+ content : DiscoveryObject ,
773
+ context : Optional [Any ] = None ) -> Optional [PromptResult ]:
738
774
739
775
params = context .get ("param" ) # type: KeeperParams
740
776
gateway_context = context .get ("gateway_context" ) # type: GatewayContext
@@ -817,8 +853,13 @@ def _handle_admin_record_from_record(record: TypedRecord, content: DiscoveryObje
817
853
"The password on that record will not be rotated."
818
854
)
819
855
820
- def _prompt_admin (self , parent_vertex : DAGVertex , content : DiscoveryObject , acl : UserAcl ,
821
- indent : int = 0 , context : Optional [Any ] = None ) -> PromptResult :
856
+ def _prompt_admin (self ,
857
+ parent_vertex : DAGVertex ,
858
+ content : DiscoveryObject ,
859
+ acl : UserAcl ,
860
+ bulk_convert_records : List [BulkRecordConvert ],
861
+ indent : int = 0 ,
862
+ context : Optional [Any ] = None ) -> PromptResult :
822
863
823
864
if content is None :
824
865
raise Exception ("The admin content was not passed in to prompt the user." )
@@ -863,7 +904,9 @@ def _prompt_admin(self, parent_vertex: DAGVertex, content: DiscoveryObject, acl:
863
904
return prompt_result
864
905
elif action [0 ] == 'f' :
865
906
print ("" )
866
- record = self ._find_user_record (params , context = context )
907
+ record , is_directory_user = self ._find_user_record (params ,
908
+ context = context ,
909
+ bulk_convert_records = bulk_convert_records )
867
910
if record is not None :
868
911
admin_prompt_result = self ._handle_admin_record_from_record (
869
912
record = record ,
@@ -872,6 +915,7 @@ def _prompt_admin(self, parent_vertex: DAGVertex, content: DiscoveryObject, acl:
872
915
)
873
916
if admin_prompt_result is not None :
874
917
if admin_prompt_result .action == PromptActionEnum .ADD :
918
+ admin_prompt_result .is_directory_user = is_directory_user
875
919
print (f"{ bcolors .OKGREEN } Adding admin record to save queue.{ bcolors .ENDC } " )
876
920
return admin_prompt_result
877
921
elif action [0 ] == 's' :
@@ -907,7 +951,7 @@ def _prompt_confirm_add(bulk_add_records: List[BulkRecordAdd]):
907
951
msg = (f"{ bcolors .BOLD } There is 1 record queued to be added to your vault. "
908
952
f"Do you wish to add it? [Y/N]> { bcolors .ENDC } " )
909
953
else :
910
- msg = (f"{ bcolors .BOLD } There are { count } records queue to be added to your vault. "
954
+ msg = (f"{ bcolors .BOLD } There are { count } records queued to be added to your vault. "
911
955
f"Do you wish to add them? [Y/N]> { bcolors .ENDC } " )
912
956
while True :
913
957
yn = input (msg ).lower ()
@@ -1038,8 +1082,15 @@ def _create_records(cls, bulk_add_records: List[BulkRecordAdd], context: Optiona
1038
1082
# STEP 3 - Add rotation settings.
1039
1083
# Use the list we passed in, find the results, and add if the additions were successful.
1040
1084
1085
+ # Keep track of each record we create a rotation for to avoid version problems, if there was a dup.
1086
+ created_cache = []
1087
+
1041
1088
# For the records passed in to be created.
1042
1089
for bulk_record in bulk_add_records :
1090
+ if bulk_record .record_uid in created_cache :
1091
+ logging .debug (f"found a duplicate of record uid: { bulk_record .record_uid } " )
1092
+ continue
1093
+
1043
1094
# Grab the type Keeper record instance, and title from that record.
1044
1095
pb_add_record = bulk_record .record
1045
1096
title = bulk_record .title
@@ -1088,7 +1139,7 @@ def _create_records(cls, bulk_add_records: List[BulkRecordAdd], context: Optiona
1088
1139
1089
1140
# Only set the resource if the record type is a PAM User.
1090
1141
# Machines, databases, and directories have a login/password in the record that indicates who the admin is.
1091
- if bulk_record .record_type == "pamUser" :
1142
+ if bulk_record .record_type == "pamUser" and bulk_record . parent_record_uid is not None :
1092
1143
rq .resourceUid = url_safe_str_to_bytes (bulk_record .parent_record_uid )
1093
1144
1094
1145
# Right now, the schedule and password complexity are not set. This would be part of a rule engine.
@@ -1098,6 +1149,8 @@ def _create_records(cls, bulk_add_records: List[BulkRecordAdd], context: Optiona
1098
1149
1099
1150
router_set_record_rotation_information (params , rq )
1100
1151
1152
+ created_cache .append (bulk_record .record_uid )
1153
+
1101
1154
build_process_results .success .append (
1102
1155
BulkRecordSuccess (
1103
1156
title = title ,
@@ -1123,6 +1176,8 @@ def _convert_records(cls, bulk_convert_records: List[BulkRecordConvert], context
1123
1176
1124
1177
rq = router_pb2 .RouterRecordRotationRequest ()
1125
1178
rq .recordUid = url_safe_str_to_bytes (bulk_convert_record .record_uid )
1179
+
1180
+ # We can't set the version to 0 if it's greater than 0, look up prior version.
1126
1181
record_rotation_revision = params .record_rotation_cache .get (bulk_convert_record .record_uid )
1127
1182
rq .revision = record_rotation_revision .get ('revision' ) if record_rotation_revision else 0
1128
1183
@@ -1131,7 +1186,7 @@ def _convert_records(cls, bulk_convert_records: List[BulkRecordConvert], context
1131
1186
1132
1187
# Only set the resource if the record type is a PAM User.
1133
1188
# Machines, databases, and directories have a login/password in the record that indicates who the admin is.
1134
- if record .record_type == "pamUser" :
1189
+ if record .record_type == "pamUser" and bulk_convert_record . parent_record_uid is not None :
1135
1190
rq .resourceUid = url_safe_str_to_bytes (bulk_convert_record .parent_record_uid )
1136
1191
else :
1137
1192
rq .resourceUid = None
0 commit comments