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