1515 */
1616package org .labkey .panoramapublic .pipeline ;
1717
18- import org .apache .commons .lang3 .RandomStringUtils ;
1918import org .apache .commons .lang3 .StringUtils ;
2019import org .apache .logging .log4j .Logger ;
2120import org .jetbrains .annotations .NotNull ;
3837import org .labkey .api .security .InvalidGroupMembershipException ;
3938import org .labkey .api .security .MemberType ;
4039import org .labkey .api .security .MutableSecurityPolicy ;
40+ import org .labkey .api .security .PasswordRule ;
4141import org .labkey .api .security .SecurityManager ;
4242import org .labkey .api .security .SecurityPolicy ;
4343import org .labkey .api .security .SecurityPolicyManager ;
8181import java .io .IOException ;
8282import java .nio .file .Files ;
8383import java .nio .file .Path ;
84+ import java .security .SecureRandom ;
85+ import java .util .ArrayList ;
8486import java .util .Arrays ;
8587import java .util .Collections ;
8688import java .util .Date ;
@@ -145,7 +147,7 @@ private void finishUp(PipelineJob job, CopyExperimentJobSupport jobSupport) thro
145147 ExperimentAnnotations previousCopy = getPreviousCopyRemoveShortUrl (latestCopiedSubmission , user );
146148
147149 // Update the row in panoramapublic.ExperimentAnnotations - set the shortURL and version
148- ExperimentAnnotations targetExperiment = updateExperimentAnnotations (container , sourceExperiment , js , previousCopy , jobSupport , user , log );
150+ ExperimentAnnotations targetExperiment = updateExperimentAnnotations (container , sourceExperiment , js , user , log );
149151
150152 // If there is a Panorama Public data catalog entry associated with the previous copy of the experiment, move it to the
151153 // new container.
@@ -340,11 +342,10 @@ private Pair<User, String> assignReviewer(JournalSubmission js, ExperimentAnnota
340342 {
341343 if (previousCopy == null || previousCopy .isPublic ())
342344 {
343- String reviewerPassword = createPassword ();
344- User reviewer ;
345+ ReviewerAndPassword reviewerAndPassword ;
345346 try
346347 {
347- reviewer = createReviewerAccount (jobSupport .getReviewerEmailPrefix (), reviewerPassword , user , log );
348+ reviewerAndPassword = createReviewerAccount (jobSupport .getReviewerEmailPrefix (), user , log );
348349 }
349350 catch (ValidEmail .InvalidEmailException e )
350351 {
@@ -354,6 +355,7 @@ private Pair<User, String> assignReviewer(JournalSubmission js, ExperimentAnnota
354355 {
355356 throw new PipelineJobException ("Error creating a new account for reviewer" , e );
356357 }
358+ User reviewer = reviewerAndPassword .getReviewer ();
357359 assignReader (reviewer , targetExperiment .getContainer (), user );
358360 js .getJournalExperiment ().setReviewer (reviewer .getUserId ());
359361 SubmissionManager .updateJournalExperiment (js .getJournalExperiment (), user );
@@ -363,7 +365,7 @@ private Pair<User, String> assignReviewer(JournalSubmission js, ExperimentAnnota
363365 // CONSIDER: Make this configurable through the Panorama Public admin console.
364366 addToGroup (reviewer , "Reviewers" , targetExperiment .getContainer ().getProject (), log );
365367
366- return new Pair <>(reviewer , reviewerPassword );
368+ return new Pair <>(reviewer , reviewerAndPassword . getPassword () );
367369 }
368370 }
369371 else
@@ -463,10 +465,8 @@ private ExperimentAnnotations getPreviousCopyRemoveShortUrl(Submission latestCop
463465
464466 @ NotNull
465467 private ExperimentAnnotations updateExperimentAnnotations (Container targetContainer , ExperimentAnnotations sourceExperiment , JournalSubmission js ,
466- ExperimentAnnotations previousCopy , CopyExperimentJobSupport jobSupport ,
467468 User user , Logger log ) throws PipelineJobException
468469 {
469-
470470 log .info ("Updating TargetedMS experiment entry in target folder " + targetContainer .getPath ());
471471 ExperimentAnnotations targetExperiment = ExperimentAnnotationsManager .getExperimentInContainer (targetContainer );
472472 if (targetExperiment == null )
@@ -625,7 +625,7 @@ private Set<User> getUsersWithRole(Container container, Role role)
625625
626626 }
627627
628- private User createReviewerAccount (String reviewerEmailPrefix , String password , User user , Logger log ) throws ValidEmail .InvalidEmailException , SecurityManager .UserManagementException
628+ private ReviewerAndPassword createReviewerAccount (String reviewerEmailPrefix , User user , Logger log ) throws ValidEmail .InvalidEmailException , SecurityManager .UserManagementException
629629 {
630630 if (StringUtils .isBlank (reviewerEmailPrefix ))
631631 {
@@ -644,10 +644,14 @@ private User createReviewerAccount(String reviewerEmailPrefix, String password,
644644
645645 log .info ("Creating a reviewer account." );
646646 SecurityManager .NewUserStatus newUser = SecurityManager .addUser (email , user , true );
647+ log .info ("Created reviewer with email: " + newUser .getUser ().getEmail ());
648+
649+ log .info ("Generating password." );
650+ String password = createPassword (newUser .getUser ());
647651 SecurityManager .setPassword (email , password );
652+ log .info ("Set reviewer password successfully." );
648653
649- log .info ("Created reviewer with email: User " + newUser .getUser ().getEmail ());
650- return newUser .getUser ();
654+ return new ReviewerAndPassword (newUser .getUser (), password );
651655 }
652656
653657 private void assignReader (UserPrincipal reader , Container target , User pipelineJobUser )
@@ -657,9 +661,91 @@ private void assignReader(UserPrincipal reader, Container target, User pipelineJ
657661 SecurityPolicyManager .savePolicy (newPolicy , pipelineJobUser );
658662 }
659663
660- public static String createPassword ()
664+ private static class ReviewerAndPassword
665+ {
666+ private final User _reviewer ;
667+ private final String _password ;
668+
669+ public ReviewerAndPassword (@ NotNull User reviewer , @ NotNull String password )
670+ {
671+ _reviewer = reviewer ;
672+ _password = password ;
673+ }
674+
675+ public User getReviewer ()
676+ {
677+ return _reviewer ;
678+ }
679+
680+ public String getPassword ()
681+ {
682+ return _password ;
683+ }
684+ }
685+
686+ private static String createPassword (User user )
687+ {
688+ String password ;
689+ do {
690+ password = PasswordGenerator .generate ();
691+ } while (!PasswordRule .Strong .isValidForLogin (password , user , null ));
692+
693+ return password ;
694+ }
695+
696+ private static class PasswordGenerator
661697 {
662- return RandomStringUtils .randomAlphabetic (8 );
698+ private static final List <Character > LOWERCASE = "abcdefghijklmnopqrstuvwxyz" .chars ().mapToObj (c -> (char )c ).collect (Collectors .toList ());
699+ private static final List <Character > UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" .chars ().mapToObj (c -> (char )c ).collect (Collectors .toList ());
700+ private static final List <Character > DIGITS = "0123456789" .chars ().mapToObj (c -> (char )c ).collect (Collectors .toList ());
701+ private static final List <Character > SYMBOLS = "!@#$%^&*+=?" .chars ().mapToObj (c -> (char )c ).collect (Collectors .toList ());
702+
703+ private static final int PASSWORD_LEN = 14 ;
704+
705+ public static String generate ()
706+ {
707+ SecureRandom random = new SecureRandom ();
708+
709+ List <Character > passwordChars = new ArrayList <>(PASSWORD_LEN );
710+ List <Character > allChars = new ArrayList <>();
711+
712+ // Initialize the list with all possible characters
713+ allChars .addAll (LOWERCASE );
714+ allChars .addAll (UPPERCASE );
715+ allChars .addAll (DIGITS );
716+ allChars .addAll (SYMBOLS );
717+
718+ // Ensure that there is at least one character from each character category.
719+ addChar (LOWERCASE , passwordChars , allChars , random );
720+ addChar (UPPERCASE , passwordChars , allChars , random );
721+ addChar (DIGITS , passwordChars , allChars , random );
722+ addChar (SYMBOLS , passwordChars , allChars , random );
723+
724+ // Shuffle the list of remaining characters
725+ Collections .shuffle (allChars );
726+
727+ // Add more characters until we are at the desired password length
728+ while (passwordChars .size () < PASSWORD_LEN )
729+ {
730+ addChar (allChars , passwordChars , allChars , random );
731+ }
732+
733+ Collections .shuffle (passwordChars );
734+
735+ return passwordChars .stream ().map (String ::valueOf ).collect (Collectors .joining ());
736+ }
737+
738+ /**
739+ * Pick a random character from the given character category, add it to the list of password characters, and
740+ * remove it from the list of all available characters to ensure character uniqueness in the password.
741+ */
742+ private static void addChar (List <Character > categoryChars , List <Character > passwordChars , List <Character > allChars , SecureRandom random )
743+ {
744+ int randomIdx = random .nextInt (0 , categoryChars .size ());
745+ Character selected = categoryChars .get (randomIdx );
746+ passwordChars .add (selected );
747+ allChars .remove (selected ); // Remove from the list of all available chars so that we have unique characters in the password
748+ }
663749 }
664750
665751 private void assignPxId (ExperimentAnnotations targetExpt , boolean useTestDb ) throws ProteomeXchangeServiceException
0 commit comments