001// -------------------------------------------------------------------------------- 002// Copyright 2002-2026 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.RandomUtils; 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; 031import javax.enterprise.context.ApplicationScoped; 032import javax.inject.Inject; 033 034@ApplicationScoped 035public class PasswordGeneratorUtils { 036 037 @Inject 038 RandomUtils randomUtils; 039 040 protected PasswordGeneratorUtils() { 041 super(); 042 } 043 044 private final static List<Graph> graphs; 045 046 static { 047 graphs = Collections.unmodifiableList(Arrays.asList( 048 new Graph("a", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("a"))), 049 new Graph("A", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("a", "ae", "ai"))), 050 new Graph("b", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("b"))), 051 new Graph("ch", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("ch"))), 052 new Graph("d", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("d"))), 053 new Graph("e", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("e"))), 054 new Graph("E", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("e", "ee", "ie"))), 055 new Graph("f", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("f", "ph", "gh"))), 056 new Graph("g", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("g"))), 057 new Graph("h", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("h"))), 058 new Graph("i", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("i", "e"))), 059 new Graph("I", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("i", "ai"))), 060 new Graph("i", GraphType.VOWEL_OTHER, Collections.unmodifiableList(Arrays.asList("i", "ei"))), 061 new Graph("j", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("j", "g"))), 062 new Graph("k", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("k", "c"))), 063 new Graph("l", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("l"))), 064 new Graph("m", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("m"))), 065 new Graph("n", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("n"))), 066 new Graph("ng", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("ng"))), 067 new Graph("o", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("o", "a", "ah"))), 068 new Graph("O", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("o", "oh"))), 069 new Graph("oo", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("oo", "u"))), 070 new Graph("OO", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("oo", "w"))), 071 new Graph("p", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("p"))), 072 new Graph("qu", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("qu"))), 073 new Graph("r", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("r"))), 074 new Graph("s", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("s", "c"))), 075 new Graph("sh", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("sh", "s"))), 076 new Graph("t", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("t"))), 077 new Graph("th", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("th"))), 078 new Graph("TH", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("th"))), 079 new Graph("u", GraphType.VOWEL_SHORT, Collections.unmodifiableList(Arrays.asList("u"))), 080 new Graph("U", GraphType.VOWEL_LONG, Collections.unmodifiableList(Arrays.asList("u", "oo"))), 081 new Graph("v", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("v"))), 082 new Graph("x", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("x"))), 083 new Graph("y", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("y"))), 084 new Graph("z", GraphType.CONSONANT, Collections.unmodifiableList(Arrays.asList("z", "s"))) 085 )); 086 } 087 088 private Graph selectGraph(Random random, List<Graph> graphs) { 089 return graphs.get(random.nextInt(graphs.size())); 090 } 091 092 private String selectSpelling(Random random, Graph graph) { 093 return graph.spellings.get(random.nextInt(graph.spellings.size())); 094 } 095 096 private Set<GraphType> chooseNext(Random random, GraphType previous, GraphType current) { 097 Set<GraphType> graphTypes; 098 099 if(current == GraphType.CONSONANT) { 100 graphTypes = EnumSet.of(GraphType.VOWEL_SHORT, GraphType.VOWEL_LONG, GraphType.VOWEL_OTHER); 101 } else if(previous == null || previous == GraphType.VOWEL_SHORT || previous == GraphType.VOWEL_LONG || previous == GraphType.VOWEL_OTHER) { 102 graphTypes = EnumSet.of(GraphType.CONSONANT); 103 } else if(random.nextBoolean()) { 104 graphTypes = EnumSet.of(GraphType.VOWEL_SHORT, GraphType.VOWEL_LONG, GraphType.VOWEL_OTHER); 105 graphTypes.remove(current); 106 } else { 107 graphTypes = EnumSet.of(GraphType.CONSONANT); 108 } 109 110 return graphTypes; 111 } 112 113 public String getPassword(int minimumLength) { 114 var random = randomUtils.getRandom(); 115 Graph previous = null; 116 var current = selectGraph(random, graphs); 117 var password = new StringBuilder(); 118 119 while(password.length() < minimumLength) { 120 var nextGraphTypes = chooseNext(random, previous == null ? null : previous.graphType, current.graphType); 121 List<Graph> nextGraphs = new ArrayList<>(graphs.size()); 122 123 password.append(selectSpelling(random, current)); 124 125 graphs.stream().filter((graph) -> nextGraphTypes.contains(graph.graphType)).forEach((graph) -> { 126 nextGraphs.add(graph); 127 }); 128 129 previous = current; 130 current = selectGraph(random, nextGraphs); 131 } 132 133 return password.toString(); 134 } 135 136 public String getPassword(PartyType partyType) { 137 var partyControl = Session.getModelController(PartyControl.class); 138 var partyTypePasswordStringPolicyDetail = partyControl.getPartyTypePasswordStringPolicy(partyType).getLastDetail(); 139 var intMinimumLength = partyTypePasswordStringPolicyDetail.getMinimumLength(); 140 var intMaximumLength = partyTypePasswordStringPolicyDetail.getMaximumLength(); 141 var minimumLength = intMinimumLength == null ? 0 : intMinimumLength; 142 var maximumLength = intMaximumLength == null ? 38 : intMaximumLength; 143 144 return getPassword(minimumLength + (maximumLength - minimumLength) / 4); 145 } 146 147}