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}