001// -------------------------------------------------------------------------------- 002// Copyright 2002-2025 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017package com.echothree.util.server.string; 018 019import com.echothree.model.control.party.server.control.PartyControl; 020import com.echothree.model.data.party.server.entity.PartyType; 021import com.echothree.util.server.persistence.EncryptionUtils; 022import com.echothree.util.server.persistence.Session; 023import com.echothree.util.server.string.Graph.GraphType; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.EnumSet; 028import java.util.List; 029import java.util.Random; 030import java.util.Set; 031 032public class PasswordGeneratorUtils { 033 034 private PasswordGeneratorUtils() { 035 super(); 036 } 037 038 private static class PasswordGeneratorUtilsHolder { 039 static PasswordGeneratorUtils instance = new PasswordGeneratorUtils(); 040 } 041 042 public static PasswordGeneratorUtils getInstance() { 043 return PasswordGeneratorUtilsHolder.instance; 044 } 045 046 private final static List<Graph> graphs; 047 048 static { 049 graphs = Collections.unmodifiableList(Arrays.asList( 050 new Graph("a", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("a"))), 051 new Graph("A", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("a", "ae", "ai"))), 052 new Graph("b", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("b"))), 053 new Graph("ch", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("ch"))), 054 new Graph("d", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("d"))), 055 new Graph("e", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("e"))), 056 new Graph("E", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("e", "ee", "ie"))), 057 new Graph("f", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("f", "ph", "gh"))), 058 new Graph("g", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("g"))), 059 new Graph("h", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("h"))), 060 new Graph("i", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("i", "e"))), 061 new Graph("I", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("i", "ai"))), 062 new Graph("i", GraphType.VOWEL_OTHER, Collections.unmodifiableList(Arrays.asList("i", "ei"))), 063 new Graph("j", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("j", "g"))), 064 new Graph("k", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("k", "c"))), 065 new Graph("l", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("l"))), 066 new Graph("m", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("m"))), 067 new Graph("n", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("n"))), 068 new Graph("ng", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("ng"))), 069 new Graph("o", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("o", "a", "ah"))), 070 new Graph("O", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("o", "oh"))), 071 new Graph("oo", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("oo", "u"))), 072 new Graph("OO", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("oo", "w"))), 073 new Graph("p", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("p"))), 074 new Graph("qu", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("qu"))), 075 new Graph("r", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("r"))), 076 new Graph("s", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("s", "c"))), 077 new Graph("sh", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("sh", "s"))), 078 new Graph("t", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("t"))), 079 new Graph("th", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("th"))), 080 new Graph("TH", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("th"))), 081 new Graph("u", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("u"))), 082 new Graph("U", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("u", "oo"))), 083 new Graph("v", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("v"))), 084 new Graph("x", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("x"))), 085 new Graph("y", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("y"))), 086 new Graph("z", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("z", "s"))) 087 )); 088 } 089 090 private Graph selectGraph(Random random, List<Graph> graphs) { 091 return graphs.get(random.nextInt(graphs.size())); 092 } 093 094 private String selectSpelling(Random random, Graph graph) { 095 return graph.spellings.get(random.nextInt(graph.spellings.size())); 096 } 097 098 private Set<GraphType> chooseNext(Random random, GraphType previous, GraphType current) { 099 Set<GraphType> graphTypes; 100 101 if(current == GraphType.CONSONANT) { 102 graphTypes = EnumSet.of(GraphType.VOWEL_SHORT, GraphType.VOWEL_LONG, GraphType.VOWEL_OTHER); 103 } else if(previous == null || previous == GraphType.VOWEL_SHORT || previous == GraphType.VOWEL_LONG || previous == GraphType.VOWEL_OTHER) { 104 graphTypes = EnumSet.of(GraphType.CONSONANT); 105 } else if(random.nextBoolean()) { 106 graphTypes = EnumSet.of(GraphType.VOWEL_SHORT, GraphType.VOWEL_LONG, GraphType.VOWEL_OTHER); 107 graphTypes.remove(current); 108 } else { 109 graphTypes = EnumSet.of(GraphType.CONSONANT); 110 } 111 112 return graphTypes; 113 } 114 115 public String getPassword(int minimumLength) { 116 var random = EncryptionUtils.getInstance().getRandom(); 117 Graph previous = null; 118 var current = selectGraph(random, graphs); 119 var password = new StringBuilder(); 120 121 while(password.length() < minimumLength) { 122 var nextGraphTypes = chooseNext(random, previous == null ? null : previous.graphType, current.graphType); 123 List<Graph> nextGraphs = new ArrayList<>(graphs.size()); 124 125 password.append(selectSpelling(random, current)); 126 127 graphs.stream().filter((graph) -> nextGraphTypes.contains(graph.graphType)).forEach((graph) -> { 128 nextGraphs.add(graph); 129 }); 130 131 previous = current; 132 current = selectGraph(random, nextGraphs); 133 } 134 135 return password.toString(); 136 } 137 138 public String getPassword(PartyType partyType) { 139 var partyControl = Session.getModelController(PartyControl.class); 140 var partyTypePasswordStringPolicyDetail = partyControl.getPartyTypePasswordStringPolicy(partyType).getLastDetail(); 141 var intMinimumLength = partyTypePasswordStringPolicyDetail.getMinimumLength(); 142 var intMaximumLength = partyTypePasswordStringPolicyDetail.getMaximumLength(); 143 var minimumLength = intMinimumLength == null ? 0 : intMinimumLength; 144 var maximumLength = intMaximumLength == null ? 38 : intMaximumLength; 145 146 return getPassword(minimumLength + (maximumLength - minimumLength) / 4); 147 } 148 149}