ソースを参照

refactoring - ProfileID

Jason Rivard 2 年 前
コミット
6a2cfa0d5d
100 ファイル変更1142 行追加972 行削除
  1. 1 0
      data-service/pom.xml
  2. 1 0
      data-service/src/main/java/password/pwm/receiver/SummaryBean.java
  3. 1 1
      docker/pom.xml
  4. 1 0
      docker/src/main/image-files/app/java.vmoptions
  5. 53 66
      lib-util/src/main/java/password/pwm/util/java/CollectionUtil.java
  6. 106 0
      lib-util/src/main/java/password/pwm/util/java/CollectorUtil.java
  7. 32 6
      lib-util/src/test/java/password/pwm/util/java/CollectorUtilTest.java
  8. 6 13
      server/src/main/java/password/pwm/PwmAboutProperty.java
  9. 8 7
      server/src/main/java/password/pwm/PwmApplication.java
  10. 3 2
      server/src/main/java/password/pwm/PwmApplicationUtil.java
  11. 0 2
      server/src/main/java/password/pwm/PwmConstants.java
  12. 2 1
      server/src/main/java/password/pwm/PwmDomain.java
  13. 41 16
      server/src/main/java/password/pwm/bean/DomainID.java
  14. 139 0
      server/src/main/java/password/pwm/bean/ProfileID.java
  15. 11 12
      server/src/main/java/password/pwm/bean/UserIdentity.java
  16. 25 51
      server/src/main/java/password/pwm/config/AppConfig.java
  17. 62 40
      server/src/main/java/password/pwm/config/DomainConfig.java
  18. 4 3
      server/src/main/java/password/pwm/config/PwmSetting.java
  19. 3 2
      server/src/main/java/password/pwm/config/PwmSettingCategory.java
  20. 12 14
      server/src/main/java/password/pwm/config/StoredSettingReader.java
  21. 9 7
      server/src/main/java/password/pwm/config/profile/AbstractProfile.java
  22. 3 2
      server/src/main/java/password/pwm/config/profile/AccountInformationProfile.java
  23. 3 2
      server/src/main/java/password/pwm/config/profile/ActivateUserProfile.java
  24. 7 6
      server/src/main/java/password/pwm/config/profile/ChallengeProfile.java
  25. 3 2
      server/src/main/java/password/pwm/config/profile/ChangePasswordProfile.java
  26. 3 2
      server/src/main/java/password/pwm/config/profile/DeleteAccountProfile.java
  27. 3 10
      server/src/main/java/password/pwm/config/profile/EmailServerProfile.java
  28. 3 2
      server/src/main/java/password/pwm/config/profile/ForgottenPasswordProfile.java
  29. 3 2
      server/src/main/java/password/pwm/config/profile/HelpdeskProfile.java
  30. 61 25
      server/src/main/java/password/pwm/config/profile/LdapProfile.java
  31. 15 12
      server/src/main/java/password/pwm/config/profile/NewUserProfile.java
  32. 3 2
      server/src/main/java/password/pwm/config/profile/PeopleSearchProfile.java
  33. 3 2
      server/src/main/java/password/pwm/config/profile/Profile.java
  34. 10 13
      server/src/main/java/password/pwm/config/profile/ProfileUtility.java
  35. 8 8
      server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java
  36. 3 2
      server/src/main/java/password/pwm/config/profile/SetupOtpProfile.java
  37. 3 2
      server/src/main/java/password/pwm/config/profile/SetupResponsesProfile.java
  38. 3 2
      server/src/main/java/password/pwm/config/profile/UpdateProfileProfile.java
  39. 15 10
      server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java
  40. 24 17
      server/src/main/java/password/pwm/config/stored/StoredConfigKey.java
  41. 7 6
      server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java
  42. 13 11
      server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java
  43. 14 9
      server/src/main/java/password/pwm/config/value/StringValue.java
  44. 15 18
      server/src/main/java/password/pwm/config/value/ValueTypeConverter.java
  45. 5 3
      server/src/main/java/password/pwm/config/value/data/UserPermission.java
  46. 1 1
      server/src/main/java/password/pwm/health/CertificateChecker.java
  47. 33 27
      server/src/main/java/password/pwm/health/ConfigurationChecker.java
  48. 41 41
      server/src/main/java/password/pwm/health/LDAPHealthChecker.java
  49. 4 3
      server/src/main/java/password/pwm/http/ClientConnectionHolder.java
  50. 1 1
      server/src/main/java/password/pwm/http/ContextManager.java
  51. 7 7
      server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java
  52. 2 1
      server/src/main/java/password/pwm/http/PwmRequest.java
  53. 8 11
      server/src/main/java/password/pwm/http/PwmRequestUtil.java
  54. 2 1
      server/src/main/java/password/pwm/http/bean/ActivateUserBean.java
  55. 4 91
      server/src/main/java/password/pwm/http/bean/ChangePasswordBean.java
  56. 4 11
      server/src/main/java/password/pwm/http/bean/DeleteAccountBean.java
  57. 3 2
      server/src/main/java/password/pwm/http/bean/ForgottenPasswordBean.java
  58. 4 10
      server/src/main/java/password/pwm/http/bean/LoginServletBean.java
  59. 2 1
      server/src/main/java/password/pwm/http/bean/NewUserBean.java
  60. 5 94
      server/src/main/java/password/pwm/http/bean/SetupOtpBean.java
  61. 7 6
      server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java
  62. 5 3
      server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java
  63. 7 2
      server/src/main/java/password/pwm/http/servlet/LoginServlet.java
  64. 1 1
      server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java
  65. 2 2
      server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java
  66. 6 4
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java
  67. 3 2
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java
  68. 2 1
      server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataBean.java
  69. 7 6
      server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java
  70. 14 13
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  71. 3 2
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java
  72. 2 2
      server/src/main/java/password/pwm/http/servlet/configeditor/SearchResultItem.java
  73. 1 1
      server/src/main/java/password/pwm/http/servlet/configeditor/data/CategoryInfo.java
  74. 9 8
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java
  75. 6 2
      server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java
  76. 23 0
      server/src/main/java/password/pwm/http/servlet/configeditor/function/AbstractUriCertImportFunction.java
  77. 1 25
      server/src/main/java/password/pwm/http/servlet/configeditor/function/ActionCertImportFunction.java
  78. 2 24
      server/src/main/java/password/pwm/http/servlet/configeditor/function/OAuthCertImportFunction.java
  79. 1 24
      server/src/main/java/password/pwm/http/servlet/configeditor/function/RemoteWebServiceCertImportFunction.java
  80. 3 23
      server/src/main/java/password/pwm/http/servlet/configeditor/function/SMSGatewayCertImportFunction.java
  81. 2 1
      server/src/main/java/password/pwm/http/servlet/configeditor/function/SmtpCertImportFunction.java
  82. 1 1
      server/src/main/java/password/pwm/http/servlet/configeditor/function/SyslogCertImportFunction.java
  83. 4 3
      server/src/main/java/password/pwm/http/servlet/configeditor/function/UserMatchViewerFunction.java
  84. 10 4
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java
  85. 2 1
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java
  86. 5 4
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java
  87. 28 13
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  88. 6 5
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStageProcessor.java
  89. 12 10
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java
  90. 19 12
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  91. 5 5
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java
  92. 6 6
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  93. 14 11
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  94. 2 1
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserTokenData.java
  95. 2 2
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  96. 2 1
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java
  97. 3 2
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java
  98. 3 2
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthState.java
  99. 28 11
      server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  100. 12 11
      server/src/main/java/password/pwm/ldap/LdapBrowser.java

+ 1 - 0
data-service/pom.xml

@@ -128,6 +128,7 @@
                                 <jvmFlags>
                                     <jvmFlag>-server</jvmFlag>
                                     <jvmFlag>-Xmx256m</jvmFlag>
+                                    <jvmFlag>-XX:+UseStringDeduplication</jvmFlag>
                                 </jvmFlags>
                                 <environment>
                                     <DATA_SERVICE_PROPS>/config/data-service.properties</DATA_SERVICE_PROPS>

+ 1 - 0
data-service/src/main/java/password/pwm/receiver/SummaryBean.java

@@ -88,6 +88,7 @@ public class SummaryBean
                         .installAge( TimeDuration.fromCurrent( bean.getInstallTime() ).asDuration() )
                         .updateAge( TimeDuration.fromCurrent( bean.getTimestamp() ).asDuration() )
                         .ldapVendor( ldapVendor )
+
                         .osName( bean.getAbout().get( PwmAboutProperty.java_osName.name() ) )
                         .osVersion( bean.getAbout().get( PwmAboutProperty.java_osVersion.name() ) )
                         .servletName( bean.getAbout().get( PwmAboutProperty.java_appServerInfo.name() ) )

+ 1 - 1
docker/pom.xml

@@ -45,7 +45,7 @@
                         <configuration>
                             <skip>${skipDocker}</skip>
                             <from>
-                                <image>openjdk:17-alpine</image>
+                                <image>eclipse-temurin:18-jre</image>
                             </from>
                             <to>
                                 <image>${dockerImageTag}</image>

+ 1 - 0
docker/src/main/image-files/app/java.vmoptions

@@ -1,4 +1,5 @@
 -server
 -Xmx1g
 -Xms1g
+-XX:+UseStringDeduplication
 -Xlog:gc:file=/config/logs/gc.log:time,uptime,level,tags:filecount=10,filesize=10M

+ 53 - 66
lib-util/src/main/java/password/pwm/util/java/CollectionUtil.java

@@ -38,7 +38,6 @@ import java.util.Set;
 import java.util.Spliterator;
 import java.util.Spliterators;
 import java.util.function.Function;
-import java.util.stream.Collector;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
@@ -47,7 +46,9 @@ public class CollectionUtil
 {
     public static <T> Stream<T> iteratorToStream( final Iterator<T> iterator )
     {
-        return StreamSupport.stream( Spliterators.spliteratorUnknownSize( iterator, Spliterator.ORDERED ), false );
+        return Optional.ofNullable( iterator )
+                .map( it -> StreamSupport.stream( Spliterators.spliteratorUnknownSize( it, Spliterator.ORDERED ), false ) )
+                .orElse( Stream.empty() );
     }
 
     public static <V> List<V> stripNulls( final List<V> input )
@@ -81,38 +82,41 @@ public class CollectionUtil
             return Collections.emptyMap();
         }
 
-        return input.entrySet().stream()
-                .filter( e -> e.getKey() != null && e.getValue() != null )
-                .collect( collectorToLinkedMap( Map.Entry::getKey, Map.Entry::getValue ) );
+        final Stream<Map.Entry<K, V>> stream = input.entrySet().stream()
+                .filter( CollectionUtil::testMapEntryForNotNull );
+
+        final boolean ordered = input instanceof LinkedHashMap;
+        return ordered
+                ? stream.collect( CollectorUtil.toUnmodifiableLinkedMap( Map.Entry::getKey, Map.Entry::getValue ) )
+                : stream.collect( Collectors.toUnmodifiableMap( Map.Entry::getKey, Map.Entry::getValue ) );
     }
 
     public static <K extends Enum<K>, V> EnumMap<K, V> copiedEnumMap( final Map<K, V> source, final Class<K> classOfT )
     {
-        if ( source == null )
+        if ( CollectionUtil.isEmpty( source ) )
         {
-            return new EnumMap<>( classOfT );
+            return new EnumMap<K, V>( classOfT );
         }
 
-        final EnumMap<K, V> returnMap = new EnumMap<>( classOfT );
-        for ( final Map.Entry<K, V> entry : source.entrySet() )
-        {
-            final K key = entry.getKey();
-            if ( key != null )
-            {
-                returnMap.put( key, entry.getValue() );
-            }
-        }
-        return returnMap;
+        return source.entrySet().stream()
+                .filter( CollectionUtil::testMapEntryForNotNull )
+                .collect( Collectors.toMap(
+                        Map.Entry::getKey,
+                        Map.Entry::getValue,
+                        CollectorUtil::errorOnDuplicateMergeOperator,
+                        () -> new EnumMap<>( classOfT ) ) );
+
     }
 
     public static <E extends Enum<E>> Set<E> readEnumSetFromStringCollection( final Class<E> enumClass, final Collection<String> inputs )
     {
-        if ( inputs == null )
+        if ( CollectionUtil.isEmpty( inputs ) )
         {
             return Collections.emptySet();
         }
 
         final Set<E> set = inputs.stream()
+                .filter( Objects::nonNull )
                 .map( input -> JavaHelper.readEnumFromString( enumClass, input ) )
                 .flatMap( Optional::stream )
                 .collect( Collectors.toSet() );
@@ -132,10 +136,16 @@ public class CollectionUtil
             final Function<E, String> keyToStringFunction
     )
     {
-        return Collections.unmodifiableMap( inputMap.entrySet().stream()
-                .collect( collectorToLinkedMap(
+        if ( CollectionUtil.isEmpty( inputMap ) )
+        {
+            return Collections.emptyMap();
+        }
+
+        return inputMap.entrySet().stream()
+                .filter( CollectionUtil::testMapEntryForNotNull )
+                .collect( CollectorUtil.toUnmodifiableLinkedMap(
                         entry -> keyToStringFunction.apply( entry.getKey() ),
-                        Map.Entry::getValue ) ) );
+                        Map.Entry::getValue ) );
     }
 
     public static <E extends Enum<E>> Map<String, String> enumMapToStringMap( final Map<E, String> inputMap )
@@ -162,11 +172,6 @@ public class CollectionUtil
 
     public static <E> List<E> iteratorToList( final Iterator<E> iterator )
     {
-        if ( iterator == null )
-        {
-            return Collections.emptyList();
-        }
-
         return iteratorToStream( iterator )
                 .collect( Collectors.toUnmodifiableList() );
     }
@@ -177,47 +182,18 @@ public class CollectionUtil
      * {@link Collections#unmodifiableMap(Map)}.
      */
     @SuppressFBWarnings( "OCP_OVERLY_CONCRETE_PARAMETER" )
-    public static <K, V> Map<K, V> combineOrderedMaps( final List<Map<K, V>> maps )
+    public static <K, V> Map<K, V> combineOrderedMaps( final List<Map<K, V>> listOfMaps )
     {
-        final Map<K, V> returnMap = new LinkedHashMap<>();
-        for ( final Map<K, V> loopMap : maps )
+        if ( CollectionUtil.isEmpty( listOfMaps ) )
         {
-            returnMap.putAll( loopMap );
+            return Collections.emptyMap();
         }
-        return Collections.unmodifiableMap( returnMap );
-    }
 
-    public static <T, K, U> Collector<T, ?, Map<K, U>> collectorToLinkedMap(
-            final Function<? super T, ? extends K> keyMapper,
-            final Function<? super T, ? extends U> valueMapper
-    )
-    {
-        return Collectors.toMap(
-                keyMapper,
-                valueMapper,
-                ( key1, key2 ) ->
-                {
-                    throw new IllegalStateException( "Duplicate key " + key1 );
-                },
-                LinkedHashMap::new
-        );
-    }
-
-    public static <T, K extends Enum<K>, U> Collector<T, ?, Map<K, U>> collectorToEnumMap(
-            final Class<K> keyClass,
-            final Function<? super T, ? extends K> keyMapper,
-            final Function<? super T, ? extends U> valueMapper
-    )
-    {
-        return Collectors.toMap(
-                keyMapper,
-                valueMapper,
-                ( key1, key2 ) ->
-                {
-                    throw new IllegalStateException( "Duplicate key " + key1 );
-                },
-                () -> new EnumMap<>( keyClass )
-        );
+        return listOfMaps.stream()
+                .filter( Objects::nonNull )
+                .flatMap( kvMap -> kvMap.entrySet().stream() )
+                .filter( CollectionUtil::testMapEntryForNotNull )
+                .collect( CollectorUtil.toUnmodifiableLinkedMap( Map.Entry::getKey, Map.Entry::getValue ) );
     }
 
     public static <E extends Enum<E>> Stream<E> enumStream( final Class<E> enumClass )
@@ -227,8 +203,19 @@ public class CollectionUtil
 
     public static <T> Set<T> setUnion( final Set<T> set1, final Set<T> set2 )
     {
-        final Set<T> s = new HashSet<>( set1 == null ? Collections.emptySet() : set1 );
-        s.retainAll( set2 == null ? Collections.<T>emptySet() : set2 );
-        return Set.copyOf( s );
+        final Set<T> unionSet = new HashSet<>( set1 == null ? Collections.emptySet() : set1 );
+        unionSet.retainAll( set2 == null ? Collections.<T>emptySet() : set2 );
+        return Set.copyOf( unionSet );
+    }
+
+    public static <T, R> List<R> convertListType( final List<T> input, final Function<T, R> convertFunction )
+
+    {
+        return stripNulls( input ).stream().map( convertFunction ).collect( Collectors.toUnmodifiableList() );
+    }
+
+    private static <K, V> boolean testMapEntryForNotNull( final Map.Entry<K, V> entry )
+    {
+        return entry != null && entry.getKey() != null && entry.getValue() != null;
     }
 }

+ 106 - 0
lib-util/src/main/java/password/pwm/util/java/CollectorUtil.java

@@ -0,0 +1,106 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.util.java;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+public class CollectorUtil
+{
+    public static <T, K, U> Collector<T, ?, Map<K, U>> toUnmodifiableLinkedMap(
+            final Function<? super T, ? extends K> keyMapper,
+            final Function<? super T, ? extends U> valueMapper
+    )
+    {
+        final Collector<T, ?, Map<K, U>> wrappedCollector = toLinkedMap( keyMapper, valueMapper );
+        return Collectors.collectingAndThen( wrappedCollector, Collections::unmodifiableMap );
+    }
+
+    public static <T, K, U> Collector<T, ?, Map<K, U>> toLinkedMap(
+            final Function<? super T, ? extends K> keyMapper,
+            final Function<? super T, ? extends U> valueMapper
+    )
+    {
+        return Collectors.toMap(
+                keyMapper,
+                valueMapper,
+                CollectorUtil::errorOnDuplicateMergeOperator,
+                LinkedHashMap::new );
+    }
+
+    public static <T, K, U> Collector<T, ?, SortedMap<K, U>> toUnmodifiableSortedMap(
+            final Function<? super T, ? extends K> keyMapper,
+            final Function<? super T, ? extends U> valueMapper,
+            final Comparator<K> comparator
+    )
+    {
+        final Collector<T, ?, SortedMap<K, U>> wrappedCollector = toSortedMap( keyMapper, valueMapper, comparator );
+        return Collectors.collectingAndThen( wrappedCollector, Collections::unmodifiableSortedMap );
+    }
+
+    public static <T, K, U> Collector<T, ?, SortedMap<K, U>> toSortedMap(
+            final Function<? super T, ? extends K> keyMapper,
+            final Function<? super T, ? extends U> valueMapper,
+            final Comparator<K> comparator
+    )
+    {
+        return Collectors.collectingAndThen( Collectors.toUnmodifiableMap(
+                        keyMapper,
+                        valueMapper ),
+                s -> new TreeMap<>( comparator ) );
+    }
+
+    public static <T, K extends Enum<K>, U> Collector<T, ?, Map<K, U>> toUnmodifiableEnumMap(
+            final Class<K> keyClass,
+            final Function<? super T, ? extends K> keyMapper,
+            final Function<? super T, ? extends U> valueMapper
+    )
+    {
+        final Collector<T, ?, Map<K, U>> wrappedCollector = toEnumMap( keyClass, keyMapper, valueMapper );
+        return Collectors.collectingAndThen( wrappedCollector, Collections::unmodifiableMap );
+    }
+
+    public static <T, K extends Enum<K>, U> Collector<T, ?, Map<K, U>> toEnumMap(
+            final Class<K> keyClass,
+            final Function<? super T, ? extends K> keyMapper,
+            final Function<? super T, ? extends U> valueMapper
+    )
+    {
+        return Collectors.toMap(
+                keyMapper,
+                valueMapper,
+                CollectorUtil::errorOnDuplicateMergeOperator,
+                () -> new EnumMap<>( keyClass ) );
+    }
+
+    static <V> V errorOnDuplicateMergeOperator( final V u, final V u2 )
+    {
+        throw new IllegalStateException( "Duplicate key " + u );
+    }
+}

+ 32 - 6
lib-util/src/test/java/password/pwm/util/java/CollectionUtilTest.java → lib-util/src/test/java/password/pwm/util/java/CollectorUtilTest.java

@@ -28,9 +28,8 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-public class CollectionUtilTest
+public class CollectorUtilTest
 {
-
     @Test
     public void collectorToLinkedMap()
     {
@@ -41,10 +40,10 @@ public class CollectionUtilTest
         map.put( "4", "4" );
         map.put( "5", "5" );
 
-        final Map<String, String> outputMap = map.entrySet().stream().collect( CollectionUtil.collectorToLinkedMap(
-                Map.Entry::getKey,
-                Map.Entry::getValue
-        ) );
+        final Map<String, String> outputMap = map.entrySet().stream()
+                .collect( CollectorUtil.toLinkedMap(
+                        Map.Entry::getKey,
+                        Map.Entry::getValue ) );
 
         final Iterator<String> iter = outputMap.values().iterator();
         Assert.assertEquals( "1", iter.next() );
@@ -54,5 +53,32 @@ public class CollectionUtilTest
         Assert.assertEquals( "5", iter.next() );
 
         Assert.assertEquals( "java.util.LinkedHashMap", outputMap.getClass().getName() );
+
+        outputMap.put( "testKey", "testValue" );
+    }
+
+    @Test
+    public void collectorToUnmodifiableLinkedMap()
+    {
+        final Map<String, String> map = new LinkedHashMap<>();
+        map.put( "1", "1" );
+        map.put( "2", "2" );
+        map.put( "3", "3" );
+        map.put( "4", "4" );
+        map.put( "5", "5" );
+
+        final Map<String, String> outputMap = map.entrySet().stream()
+                .collect( CollectorUtil.toUnmodifiableLinkedMap(
+                        Map.Entry::getKey,
+                        Map.Entry::getValue ) );
+
+        final Iterator<String> iter = outputMap.values().iterator();
+        Assert.assertEquals( "1", iter.next() );
+        Assert.assertEquals( "2", iter.next() );
+        Assert.assertEquals( "3", iter.next() );
+        Assert.assertEquals( "4", iter.next() );
+        Assert.assertEquals( "5", iter.next() );
+
+        Assert.assertThrows( UnsupportedOperationException.class, () -> outputMap.put( "testKey", "testValue" ) );
     }
 }

+ 6 - 13
server/src/main/java/password/pwm/PwmAboutProperty.java

@@ -20,13 +20,13 @@
 
 package password.pwm;
 
-import lombok.Value;
 import password.pwm.config.PwmSetting;
 import password.pwm.i18n.Display;
 import password.pwm.ldap.LdapDomainService;
 import password.pwm.svc.db.DatabaseService;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.CollectorUtil;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -125,23 +125,16 @@ public enum PwmAboutProperty
 
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmAboutProperty.class );
 
-    @Value
-    private static class Pair<K, V>
-    {
-        private final K key;
-        private final V value;
-    }
-
     public static Map<PwmAboutProperty, String> makeInfoBean(
             final PwmApplication pwmApplication
     )
     {
-        return Collections.unmodifiableMap( CollectionUtil.enumStream( PwmAboutProperty.class )
-                .map( aboutProp -> new Pair<>( aboutProp, readAboutValue( pwmApplication, aboutProp ) ) )
+        return CollectionUtil.enumStream( PwmAboutProperty.class )
+                .map( aboutProp -> Map.entry( aboutProp, readAboutValue( pwmApplication, aboutProp ) ) )
                 .filter( entry -> entry.getValue().isPresent() )
-                .collect( CollectionUtil.collectorToEnumMap( PwmAboutProperty.class,
-                        Pair::getKey,
-                        entry -> entry.getValue().get() ) ) );
+                .collect( CollectorUtil.toUnmodifiableEnumMap( PwmAboutProperty.class,
+                        Map.Entry::getKey,
+                        entry -> entry.getValue().get() ) );
 
     }
 

+ 8 - 7
server/src/main/java/password/pwm/PwmApplication.java

@@ -22,6 +22,7 @@ package password.pwm;
 
 import lombok.Value;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
@@ -474,7 +475,7 @@ public class PwmApplication
         return Optional.empty();
     }
 
-    public void writeLastLdapFailure( final DomainID domainID, final Map<String, ErrorInformation> errorInformationMap )
+    public void writeLastLdapFailure( final DomainID domainID, final Map<ProfileID, ErrorInformation> errorInformationMap )
     {
         try
         {
@@ -488,7 +489,7 @@ public class PwmApplication
         }
     }
 
-    public Map<String, ErrorInformation> readLastLdapFailure( final DomainID domainID )
+    public Map<ProfileID, ErrorInformation> readLastLdapFailure( final DomainID domainID )
     {
         return readLastLdapFailure().getRecords().getOrDefault( domainID, Collections.emptyMap() );
     }
@@ -518,14 +519,14 @@ public class PwmApplication
     @Value
     private static class StoredErrorRecords
     {
-        private final Map<DomainID, Map<String, ErrorInformation>> records;
+        private final Map<DomainID, Map<ProfileID, ErrorInformation>> records;
 
-        StoredErrorRecords( final Map<DomainID, Map<String, ErrorInformation>> records )
+        StoredErrorRecords( final Map<DomainID, Map<ProfileID, ErrorInformation>> records )
         {
             this.records = records == null ? Collections.emptyMap() : Map.copyOf( records );
         }
 
-        public Map<DomainID, Map<String, ErrorInformation>> getRecords()
+        public Map<DomainID, Map<ProfileID, ErrorInformation>> getRecords()
         {
             // required because json deserialization can still set records == null
             return records == null ? Collections.emptyMap() : records;
@@ -533,9 +534,9 @@ public class PwmApplication
 
         StoredErrorRecords addDomainErrorMap(
                 final DomainID domainID,
-                final Map<String, ErrorInformation> errorInformationMap )
+                final Map<ProfileID, ErrorInformation> errorInformationMap )
         {
-            final Map<DomainID, Map<String, ErrorInformation>> newRecords = new HashMap<>( getRecords() );
+            final Map<DomainID, Map<ProfileID, ErrorInformation>> newRecords = new HashMap<>( getRecords() );
             newRecords.put( domainID, Map.copyOf( errorInformationMap ) );
             return new StoredErrorRecords( newRecords );
         }

+ 3 - 2
server/src/main/java/password/pwm/PwmApplicationUtil.java

@@ -31,6 +31,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.PasswordData;
 import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
 import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.CollectorUtil;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
@@ -321,7 +322,7 @@ class PwmApplicationUtil
     static void outputNonDefaultPropertiesToLog( final PwmApplication pwmApplication )
     {
         final Map<String, String> data = pwmApplication.getConfig().readAllNonDefaultAppProperties().entrySet().stream()
-                .collect( CollectionUtil.collectorToLinkedMap(
+                .collect( CollectorUtil.toUnmodifiableLinkedMap(
                         entry -> "AppProperty: " + entry.getKey().getKey(),
                         Map.Entry::getValue ) );
 
@@ -331,7 +332,7 @@ class PwmApplicationUtil
     static void outputApplicationInfoToLog( final PwmApplication pwmApplication )
     {
         final Map<String, String> data = PwmAboutProperty.makeInfoBean( pwmApplication ).entrySet().stream()
-                .collect( CollectionUtil.collectorToLinkedMap(
+                .collect( CollectorUtil.toUnmodifiableLinkedMap(
                         entry -> "AboutProperty: " + entry.getKey().getLabel(),
                         Map.Entry::getValue ) );
 

+ 0 - 2
server/src/main/java/password/pwm/PwmConstants.java

@@ -92,8 +92,6 @@ public abstract class PwmConstants
             .getDefinedPackage( "password.pwm" );
 
     public static final String LDAP_AD_PASSWORD_POLICY_CONTROL_ASN = "1.2.840.113556.1.4.2066";
-    public static final String PROFILE_ID_ALL = "all";
-    public static final String PROFILE_ID_DEFAULT = "default";
 
     public static final String TOKEN_KEY_PWD_CHG_DATE = "_lastPwdChange";
 

+ 2 - 1
server/src/main/java/password/pwm/PwmDomain.java

@@ -24,6 +24,7 @@ import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
@@ -188,7 +189,7 @@ public class PwmDomain
         }
     }
 
-    public ChaiProvider getProxyChaiProvider( final SessionLabel sessionLabel, final String profileId )
+    public ChaiProvider getProxyChaiProvider( final SessionLabel sessionLabel, final ProfileID profileId )
             throws PwmUnrecoverableException
     {
         return getLdapService().getProxyChaiProvider( sessionLabel, profileId );

+ 41 - 16
server/src/main/java/password/pwm/bean/DomainID.java

@@ -20,23 +20,27 @@
 
 package password.pwm.bean;
 
-import password.pwm.config.PwmSetting;
+import password.pwm.PwmConstants;
 import password.pwm.config.PwmSettingScope;
-import password.pwm.config.value.StringValue;
 import password.pwm.util.java.MiscUtil;
 
 import java.io.Serializable;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Pattern;
 
 public final class DomainID implements Comparable<DomainID>, Serializable
 {
-    public static final List<String> DOMAIN_RESERVED_WORDS = List.of( "system", "private", "public", "pwm", "sspr", "domain", "profile", "password" );
-    public static final DomainID DOMAIN_ID_DEFAULT = create( "default" );
+    private static final Pattern REGEX_TEST = Pattern.compile( "^([a-z][a-z0-9]{2,10})$" );
+    private static final List<String> DOMAIN_RESERVED_WORDS = List.of( "system", "private", "public", "pwm", "sspr", "domain", "profile", "password" );
 
-    private static final String SYSTEM_ID = "system";
-    private static final DomainID SYSTEM_DOMAIN_ID = new DomainID( SYSTEM_ID );
+    public static final DomainID DOMAIN_ID_DEFAULT = new DomainID( "default" );
+    private static final DomainID SYSTEM_DOMAIN_ID = new DomainID( "system" );
+
+    private static final List<DomainID> BUILT_IN = List.of( SYSTEM_DOMAIN_ID, DOMAIN_ID_DEFAULT );
 
     // sort placing 'system' first then alphabetically.
     private static final Comparator<DomainID> COMPARATOR = Comparator.comparing( DomainID::isSystem )
@@ -52,14 +56,9 @@ public final class DomainID implements Comparable<DomainID>, Serializable
 
     public static DomainID create( final String domainID )
     {
-        Objects.requireNonNull( domainID );
-        
-        final List<String> errorMessages = StringValue.validateValue( PwmSetting.DOMAIN_LIST, domainID );
-        if ( !errorMessages.isEmpty() )
-        {
-            throw new IllegalArgumentException( "domainID value '" + domainID + "' does not match required syntax pattern for user defined domains: " + errorMessages.get( 0 ) );
-        }
-        return new DomainID( domainID );
+        return BUILT_IN.stream()
+                .filter( d -> d.domainID.equals( domainID ) )
+                .findFirst().orElse( new DomainID( domainID ) );
     }
 
     public boolean inScope( final PwmSettingScope scope )
@@ -97,7 +96,7 @@ public final class DomainID implements Comparable<DomainID>, Serializable
     @Override
     public int hashCode()
     {
-        return Objects.hash( domainID );
+        return Objects.hashCode( domainID );
     }
 
     @Override
@@ -124,6 +123,32 @@ public final class DomainID implements Comparable<DomainID>, Serializable
 
     public boolean isSystem()
     {
-        return SYSTEM_ID.equals( domainID );
+        return SYSTEM_DOMAIN_ID.domainID.equals( domainID );
+    }
+
+    public static Comparator<DomainID> comparator()
+    {
+        return COMPARATOR;
+    }
+
+    public static List<String> validateUserValue( final String value )
+    {
+        Objects.requireNonNull( value );
+        final String lCaseValue = value.toLowerCase( PwmConstants.DEFAULT_LOCALE );
+        final Optional<String> reservedWordMatch = DomainID.DOMAIN_RESERVED_WORDS.stream()
+                .map( String::toLowerCase )
+                .filter( lCaseValue::contains )
+                .findFirst();
+        if ( reservedWordMatch.isPresent() )
+        {
+            return Collections.singletonList( "contains reserved word '" + reservedWordMatch.get() + "'" );
+        }
+
+        if ( !REGEX_TEST.matcher( value ).matches() )
+        {
+            return Collections.singletonList( "pattern is invalid" );
+        }
+
+        return Collections.emptyList();
     }
 }

+ 139 - 0
server/src/main/java/password/pwm/bean/ProfileID.java

@@ -0,0 +1,139 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.bean;
+
+import org.jetbrains.annotations.NotNull;
+import password.pwm.PwmConstants;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+public final class ProfileID implements Serializable, Comparable<ProfileID>
+{
+    private static final Pattern REGEX_TEST = Pattern.compile( "^([a-zA-Z][a-zA-Z0-9-]{2,15})$" );
+
+    private static final List<String> PROFILE_RESERVED_WORDS = List.of( "all", "nmas" );
+
+    private static final Comparator<ProfileID> COMPARATOR = Comparator.nullsFirst( Comparator.comparing( k -> k.profileID ) );
+    private static final Comparator<String> STRING_COMPARATOR = Comparator.nullsFirst( Comparator.comparing( Function.identity() ) );
+
+    public static final ProfileID PROFILE_ID_DEFAULT = new ProfileID( "default" );
+    public static final ProfileID PROFILE_ID_ALL = new ProfileID( "all" );
+    public static final ProfileID PROFILE_ID_NMAS = new ProfileID( "nmas" );
+
+    private static final List<ProfileID> BUILT_IN_PROFILES = List.of( PROFILE_ID_DEFAULT, PROFILE_ID_ALL, PROFILE_ID_NMAS );
+    private final String profileID;
+
+    private ProfileID( final String profileID )
+    {
+        this.profileID = JavaHelper.requireNonEmpty( profileID );
+    }
+
+    public static ProfileID create( final String profileID )
+    {
+        return BUILT_IN_PROFILES.stream()
+                .filter( p -> p.profileID.equals( profileID ) )
+                .findFirst()
+                .orElse( new ProfileID( profileID ) );
+    }
+
+    public static Optional<ProfileID> createNullable( final String profileID )
+    {
+        return StringUtil.isEmpty( profileID ) ? Optional.empty() : Optional.of( create( profileID ) );
+    }
+
+    @Override
+    public boolean equals( final Object o )
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+        final ProfileID profileID1 = ( ProfileID ) o;
+        return Objects.equals( profileID, profileID1.profileID );
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return Objects.hashCode( profileID );
+    }
+
+    @Override
+    public String toString()
+    {
+        return profileID;
+    }
+
+    public String stringValue()
+    {
+        return profileID;
+    }
+
+    @Override
+    public int compareTo( @NotNull final ProfileID o )
+    {
+        return COMPARATOR.compare( this, o );
+    }
+
+    public static Comparator<ProfileID> comparator()
+    {
+        return COMPARATOR;
+    }
+
+    public static Comparator<String> stringComparator()
+    {
+        return STRING_COMPARATOR;
+    }
+
+    public static List<String> validateUserValue( final String value )
+    {
+        Objects.requireNonNull( value );
+        final String lCaseValue = value.toLowerCase( PwmConstants.DEFAULT_LOCALE );
+        final Optional<String> reservedWordMatch = PROFILE_RESERVED_WORDS.stream()
+                .map( String::toLowerCase )
+                .filter( lCaseValue::contains )
+                .findFirst();
+        if ( reservedWordMatch.isPresent() )
+        {
+            return Collections.singletonList( "contains reserved word '" + reservedWordMatch.get() + "'" );
+        }
+
+        if ( REGEX_TEST.matcher( value ).matches() )
+        {
+            return Collections.singletonList( "pattern is invalid" );
+        }
+
+        return Collections.emptyList();
+    }
+}

+ 11 - 12
server/src/main/java/password/pwm/bean/UserIdentity.java

@@ -45,7 +45,6 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
     private static final PwmLogger LOGGER = PwmLogger.forClass( UserIdentity.class );
     private static final long serialVersionUID = 1L;
 
-    private static final String CRYPO_HEADER = "ui_C-";
     private static final String DELIM_SEPARATOR = "|";
 
     private static final Comparator<UserIdentity> COMPARATOR = Comparator.comparing(
@@ -53,16 +52,16 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
             Comparator.nullsLast( Comparator.naturalOrder() ) )
             .thenComparing(
                     UserIdentity::getLdapProfileID,
-                    Comparator.nullsLast( Comparator.naturalOrder() ) )
+                    ProfileID.comparator()
+            )
             .thenComparing(
                     UserIdentity::getDomainID,
-                    Comparator.nullsLast( Comparator.naturalOrder() ) );
+                    DomainID.comparator() );
 
-    private transient String obfuscatedValue;
     private transient boolean canonical;
 
     private final String userDN;
-    private final String ldapProfile;
+    private final ProfileID ldapProfile;
     private final DomainID domainID;
 
     public enum Flag
@@ -70,14 +69,14 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
         PreCanonicalized,
     }
 
-    private UserIdentity( final String userDN, final String ldapProfile, final DomainID domainID )
+    private UserIdentity( final String userDN, final ProfileID ldapProfile, final DomainID domainID )
     {
         this.userDN = JavaHelper.requireNonEmpty( userDN, "UserIdentity: userDN value cannot be empty" );
-        this.ldapProfile = JavaHelper.requireNonEmpty( ldapProfile, "UserIdentity: ldapProfile value cannot be empty" );
+        this.ldapProfile = Objects.requireNonNull( ldapProfile, "UserIdentity: ldapProfile value cannot be empty" );
         this.domainID = Objects.requireNonNull( domainID );
     }
 
-    public UserIdentity( final String userDN, final String ldapProfile, final DomainID domainID, final boolean canonical )
+    public UserIdentity( final String userDN, final ProfileID ldapProfile, final DomainID domainID, final boolean canonical )
     {
         this( userDN, ldapProfile, domainID );
         this.canonical = canonical;
@@ -85,7 +84,7 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
 
     public static UserIdentity create(
             final String userDN,
-            final String ldapProfile,
+            final ProfileID ldapProfile,
             final DomainID domainID,
             final Flag... flags
     )
@@ -104,7 +103,7 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
         return domainID;
     }
 
-    public String getLdapProfileID( )
+    public ProfileID getLdapProfileID( )
     {
         return ldapProfile;
     }
@@ -134,7 +133,7 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
     {
         return "[" + this.getDomainID() + "]"
                 + " " + this.getUserDN()
-                + ( ( this.getLdapProfileID() != null && !this.getLdapProfileID().isEmpty() ) ? " (" + this.getLdapProfileID() + ")" : "" );
+                + " (" + this.getLdapProfileID().stringValue() + ")";
     }
 
     public static UserIdentity fromDelimitedKey( final SessionLabel sessionLabel, final String key )
@@ -179,7 +178,7 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
         {
             throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "too many string tokens while parsing delimited identity key" ) );
         }
-        final String profileID = st.nextToken();
+        final ProfileID profileID = ProfileID.create( st.nextToken() );
         final String userDN = st.nextToken();
         return create( userDN, profileID, domainID );
     }

+ 25 - 51
server/src/main/java/password/pwm/config/AppConfig.java

@@ -24,6 +24,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.PrivateKeyCertificate;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.option.CertificateMatchingMode;
 import password.pwm.config.option.DataStorageMethod;
@@ -41,6 +42,7 @@ import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.PasswordData;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.CollectorUtil;
 import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
@@ -50,21 +52,18 @@ import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.security.cert.X509Certificate;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumMap;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.TreeMap;
+import java.util.SortedMap;
 import java.util.TreeSet;
 import java.util.function.Function;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 
 public class AppConfig implements SettingReader
 {
@@ -108,14 +107,12 @@ public class AppConfig implements SettingReader
 
         this.localeFlagMap = makeLocaleFlagMap( this );
 
-        this.domainIDs = Collections.unmodifiableSet(  new TreeSet<>(
-                settingReader.readSettingAsStringArray( PwmSetting.DOMAIN_LIST ).stream()
-                .collect( Collectors.toUnmodifiableSet() ) ) );
+        this.domainIDs = Set.copyOf( new TreeSet<>( settingReader.readSettingAsStringArray( PwmSetting.DOMAIN_LIST ) ) );
 
-        this.domainConfigMap = Collections.unmodifiableMap(  domainIDs.stream()
-                .collect( CollectionUtil.collectorToLinkedMap(
+        this.domainConfigMap = domainIDs.stream()
+                .collect( CollectorUtil.toUnmodifiableLinkedMap(
                         DomainID::create,
-                        ( domainID ) -> new DomainConfig( this, DomainID.create( domainID ) ) ) ) );
+                        ( domainID ) -> new DomainConfig( this, DomainID.create( domainID ) ) ) );
     }
 
     public static AppConfig forStoredConfig( final StoredConfiguration storedConfiguration )
@@ -191,12 +188,11 @@ public class AppConfig implements SettingReader
 
     public Map<AppProperty, String> readAllAppProperties()
     {
-          return Collections.unmodifiableMap( CollectionUtil.enumStream( AppProperty.class )
-                  .collect( CollectionUtil.collectorToLinkedMap(
-                          Function.identity(),
-                          this::readAppProperty
-                  ) ) );
-
+        return CollectionUtil.enumStream( AppProperty.class )
+                .collect( CollectorUtil.toLinkedMap(
+                        Function.identity(),
+                        this::readAppProperty
+                ) );
     }
 
     public StoredConfiguration getStoredConfiguration()
@@ -306,7 +302,7 @@ public class AppConfig implements SettingReader
         return settingReader.readGenericStorageLocations( setting );
     }
 
-    public Map<String, EmailServerProfile> getEmailServerProfiles( )
+    public Map<ProfileID, EmailServerProfile> getEmailServerProfiles( )
     {
         return settingReader.getProfileMap( ProfileDefinition.EmailServers );
     }
@@ -349,12 +345,12 @@ public class AppConfig implements SettingReader
             AppProperty.forKey( stringEntry.getKey() )
                     .ifPresent( appProperty ->
                     {
-                       final String defaultValue = appProperty.getDefaultValue();
-                       final String value = stringEntry.getValue();
-                       if ( !Objects.equals( defaultValue, value ) )
-                       {
-                           appPropertyMap.put( appProperty, value );
-                       }
+                        final String defaultValue = appProperty.getDefaultValue();
+                        final String value = stringEntry.getValue();
+                        if ( !Objects.equals( defaultValue, value ) )
+                        {
+                            appPropertyMap.put( appProperty, value );
+                        }
                     } );
         }
 
@@ -422,38 +418,16 @@ public class AppConfig implements SettingReader
         }
     }
 
-    private static Map<Locale, String> makeLocaleFlagMap( final AppConfig appConfig )
+    private static SortedMap<Locale, String> makeLocaleFlagMap( final AppConfig appConfig )
     {
-        final String defaultLocaleAsString = PwmConstants.DEFAULT_LOCALE.toString();
-
         final List<String> inputList = appConfig.readSettingAsStringArray( PwmSetting.KNOWN_LOCALES );
         final Map<String, String> inputMap = StringUtil.convertStringListToNameValuePair( inputList, "::" );
 
-        final Map<String, String> sortedMap = new TreeMap<>( inputMap.keySet().stream()
-                .collect( Collectors.toMap(
-                        str -> LocaleHelper.parseLocaleString( str ).getDisplayName(),
-                        Function.identity()
-                ) ) );
-
-        final List<String> returnList = new ArrayList<>( sortedMap.size() + 1 );
-
-        //ensure default is first.
-        returnList.add( defaultLocaleAsString );
-        returnList.addAll( sortedMap.values().stream()
-                .filter( str -> !Objects.equals( defaultLocaleAsString, str ) )
-                .collect( Collectors.toList() ) );
-
-        final Map<Locale, String> localeFlagMap = new LinkedHashMap<>( returnList.size() );
-        for ( final String localeString : returnList )
-        {
-            final Locale loopLocale = LocaleHelper.parseLocaleString( localeString );
-            if ( loopLocale != null )
-            {
-                final String flagCode = inputMap.getOrDefault( localeString, loopLocale.getCountry() );
-                localeFlagMap.put( loopLocale, flagCode );
-            }
-        }
-        return Collections.unmodifiableMap( localeFlagMap );
+        return inputMap.keySet().stream()
+                .collect( CollectorUtil.toUnmodifiableSortedMap(
+                        LocaleHelper::parseLocaleString,
+                        s -> inputMap.getOrDefault( s, LocaleHelper.parseLocaleString( s ).getCountry() ),
+                        LocaleHelper.localeDisplayComparator() ) );
     }
 
     @Override

+ 62 - 40
server/src/main/java/password/pwm/config/DomainConfig.java

@@ -24,6 +24,7 @@ import password.pwm.AppProperty;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.PrivateKeyCertificate;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.option.TokenStorageMethod;
 import password.pwm.config.profile.ActivateUserProfile;
@@ -53,8 +54,8 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.CollectorUtil;
 import password.pwm.util.java.JavaHelper;
-import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.security.cert.X509Certificate;
@@ -74,15 +75,13 @@ import java.util.stream.Collectors;
  */
 public class DomainConfig implements SettingReader
 {
-    private static final PwmLogger LOGGER = PwmLogger.forClass( DomainConfig.class );
-
     private final StoredConfiguration storedConfiguration;
     private final AppConfig appConfig;
     private final DomainID domainID;
 
-    private final Map<String, PwmPasswordPolicy> cachedPasswordPolicy;
-    private final Map<String, Map<Locale, ChallengeProfile>> cachedChallengeProfiles;
-    private final Map<String, LdapProfile> ldapProfiles;
+    private final Map<ProfileID, PwmPasswordPolicy> cachedPasswordPolicy;
+    private final Map<ProfileID, Map<Locale, ChallengeProfile>> cachedChallengeProfiles;
+    private final Map<ProfileID, LdapProfile> ldapProfiles;
     private final StoredSettingReader settingReader;
     private final PwmSecurityKey domainSecurityKey;
 
@@ -93,22 +92,22 @@ public class DomainConfig implements SettingReader
         this.domainID = Objects.requireNonNull( domainID );
         this.settingReader = new StoredSettingReader( storedConfiguration, null, domainID );
 
-        this.cachedPasswordPolicy = Collections.unmodifiableMap( getPasswordProfileIDs().stream()
+        this.cachedPasswordPolicy = getPasswordProfileIDs().stream()
                 .map( profile -> PwmPasswordPolicy.createPwmPasswordPolicy( this, profile ) )
-                .collect( Collectors.toMap(
-                        PwmPasswordPolicy::getIdentifier,
+                .collect( CollectorUtil.toUnmodifiableLinkedMap(
+                        PwmPasswordPolicy::getId,
                         Function.identity()
-                ) ) );
+                ) );
 
-        this.cachedChallengeProfiles = Collections.unmodifiableMap( getChallengeProfileIDs().stream()
-                .collect( Collectors.toMap(
+        this.cachedChallengeProfiles = getChallengeProfileIDs().stream()
+                .collect( Collectors.toUnmodifiableMap(
                         Function.identity(),
-                        profileId -> Collections.unmodifiableMap( appConfig.getKnownLocales().stream()
-                                .collect( Collectors.toMap(
+                        profileId -> appConfig.getKnownLocales().stream()
+                                .collect( CollectorUtil.toUnmodifiableLinkedMap(
                                         Function.identity(),
                                         locale -> ChallengeProfile.readChallengeProfileFromConfig( domainID, profileId, locale, storedConfiguration )
-                                ) ) )
-                ) ) );
+                                ) )
+                ) );
 
         this.ldapProfiles = makeLdapProfileMap( this );
         this.domainSecurityKey = makeDomainSecurityKey( appConfig, settingReader.getValueHash() );
@@ -135,7 +134,7 @@ public class DomainConfig implements SettingReader
         return settingReader.readSettingAsUserPermission( setting );
     }
 
-    public Map<String, LdapProfile> getLdapProfiles( )
+    public Map<ProfileID, LdapProfile> getLdapProfiles( )
     {
         return ldapProfiles;
     }
@@ -192,12 +191,12 @@ public class DomainConfig implements SettingReader
         return settingReader.readLocalizedBundle( className, keyName );
     }
 
-    public List<String> getChallengeProfileIDs( )
+    public List<ProfileID> getChallengeProfileIDs( )
     {
         return StoredConfigurationUtil.profilesForSetting( this.getDomainID(), PwmSetting.CHALLENGE_PROFILE_LIST, storedConfiguration );
     }
 
-    public ChallengeProfile getChallengeProfile( final String profile, final Locale locale )
+    public ChallengeProfile getChallengeProfile( final ProfileID profile, final Locale locale )
     {
         final Map<Locale, ChallengeProfile> cachedLocaleMap = cachedChallengeProfiles.get( profile );
 
@@ -214,12 +213,12 @@ public class DomainConfig implements SettingReader
         return settingReader.readSettingAsLong( setting );
     }
 
-    public PwmPasswordPolicy getPasswordPolicy( final String profile )
+    public PwmPasswordPolicy getPasswordPolicy( final ProfileID profile )
     {
         return cachedPasswordPolicy.get( profile );
     }
 
-    public List<String> getPasswordProfileIDs( )
+    public List<ProfileID> getPasswordProfileIDs( )
     {
         return StoredConfigurationUtil.profilesForSetting( this.getDomainID(), PwmSetting.PASSWORD_PROFILE_LIST, storedConfiguration );
     }
@@ -290,52 +289,52 @@ public class DomainConfig implements SettingReader
     }
 
     /* generic profile stuff */
-    public Map<String, NewUserProfile> getNewUserProfiles( )
+    public Map<ProfileID, NewUserProfile> getNewUserProfiles( )
     {
         return this.getProfileMap( ProfileDefinition.NewUser );
     }
 
-    public Map<String, ActivateUserProfile> getUserActivationProfiles( )
+    public Map<ProfileID, ActivateUserProfile> getUserActivationProfiles( )
     {
         return this.getProfileMap( ProfileDefinition.ActivateUser );
     }
 
-    public Map<String, HelpdeskProfile> getHelpdeskProfiles( )
+    public Map<ProfileID, HelpdeskProfile> getHelpdeskProfiles( )
     {
         return this.getProfileMap( ProfileDefinition.Helpdesk );
     }
 
-    public Map<String, PeopleSearchProfile> getPeopleSearchProfiles( )
+    public Map<ProfileID, PeopleSearchProfile> getPeopleSearchProfiles( )
     {
         return this.getProfileMap( ProfileDefinition.PeopleSearch );
     }
 
-    public Map<String, SetupOtpProfile> getSetupOTPProfiles( )
+    public Map<ProfileID, SetupOtpProfile> getSetupOTPProfiles( )
     {
         return this.getProfileMap( ProfileDefinition.SetupOTPProfile );
     }
 
-    public Map<String, SetupResponsesProfile> getSetupResponseProfiles( )
+    public Map<ProfileID, SetupResponsesProfile> getSetupResponseProfiles( )
     {
         return this.getProfileMap( ProfileDefinition.SetupResponsesProfile );
     }
 
-    public Map<String, UpdateProfileProfile> getUpdateAttributesProfile( )
+    public Map<ProfileID, UpdateProfileProfile> getUpdateAttributesProfile( )
     {
         return this.getProfileMap( ProfileDefinition.UpdateAttributes );
     }
 
-    public Map<String, ChangePasswordProfile> getChangePasswordProfile( )
+    public Map<ProfileID, ChangePasswordProfile> getChangePasswordProfile( )
     {
         return this.getProfileMap( ProfileDefinition.ChangePassword );
     }
 
-    public Map<String, ForgottenPasswordProfile> getForgottenPasswordProfiles( )
+    public Map<ProfileID, ForgottenPasswordProfile> getForgottenPasswordProfiles( )
     {
         return this.getProfileMap( ProfileDefinition.ForgottenPassword );
     }
 
-    public <T extends Profile> Map<String, T> getProfileMap( final ProfileDefinition profileDefinition )
+    public <T extends Profile> Map<ProfileID, T> getProfileMap( final ProfileDefinition profileDefinition )
     {
         return settingReader.getProfileMap( profileDefinition );
     }
@@ -347,11 +346,14 @@ public class DomainConfig implements SettingReader
 
     public Optional<PeopleSearchProfile> getPublicPeopleSearchProfile()
     {
-        if ( readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC ) )
+        final Map<ProfileID, Profile> profileMap = settingReader.getProfileMap( ProfileDefinition.PeopleSearch );
+        if ( !CollectionUtil.isEmpty( profileMap ) && readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC ) )
         {
-            final String profileID = readSettingAsString( PwmSetting.PEOPLE_SEARCH_PUBLIC_PROFILE );
-            final Map<String, PeopleSearchProfile> profiles = settingReader.getProfileMap( ProfileDefinition.PeopleSearchPublic );
-            return Optional.ofNullable( profiles.get( profileID ) );
+            final Optional<ProfileID> profileID = profileForStringId( ProfileDefinition.PeopleSearch, readSettingAsString( PwmSetting.PEOPLE_SEARCH_PUBLIC_PROFILE ) );
+            if ( profileID.isPresent() )
+            {
+                return Optional.ofNullable( ( PeopleSearchProfile ) profileMap.get( profileID.get() ) );
+            }
         }
         return Optional.empty();
     }
@@ -397,16 +399,16 @@ public class DomainConfig implements SettingReader
     }
 
 
-    private static Map<String, LdapProfile> makeLdapProfileMap( final DomainConfig domainConfig )
+    private static Map<ProfileID, LdapProfile> makeLdapProfileMap( final DomainConfig domainConfig )
     {
-        final Map<String, LdapProfile> sourceMap = domainConfig.getProfileMap( ProfileDefinition.LdapProfile );
+        final Map<ProfileID, LdapProfile> sourceMap = domainConfig.getProfileMap( ProfileDefinition.LdapProfile );
 
-        return Collections.unmodifiableMap( sourceMap.entrySet()
+        return sourceMap.entrySet()
                 .stream()
                 .filter( entry -> entry.getValue().isEnabled() )
-                .collect( CollectionUtil.collectorToLinkedMap(
+                .collect( CollectorUtil.toUnmodifiableLinkedMap(
                         Map.Entry::getKey,
-                        Map.Entry::getValue ) ) );
+                        Map.Entry::getValue ) );
     }
 
     private static PwmSecurityKey makeDomainSecurityKey(
@@ -430,4 +432,24 @@ public class DomainConfig implements SettingReader
     {
         return settingReader.getValueHash();
     }
+
+    public Optional<ProfileID> ldapProfileForStringId( final String input )
+    {
+        return profileForStringId( ProfileDefinition.LdapProfile, input );
+    }
+
+    public Optional<ProfileID> profileForStringId( final ProfileDefinition profileDefinition, final String input )
+    {
+        final Map<ProfileID,  Profile> map = getProfileMap( profileDefinition );
+        if ( map != null )
+        {
+            return map.keySet().stream()
+                    .filter( profileID -> profileID.stringValue().equals( input ) )
+                    .findFirst();
+
+        }
+        return Optional.empty();
+
+    }
+
 }

+ 4 - 3
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -22,6 +22,7 @@ package password.pwm.config;
 
 import org.jrivard.xmlchai.XmlElement;
 import password.pwm.PwmConstants;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.ValueFactory;
@@ -1476,11 +1477,11 @@ public enum PwmSetting
     }
 
     public String toMenuLocationDebug(
-            final String profileID,
+            final ProfileID profileID,
             final Locale locale
     )
     {
-        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) && StringUtil.isEmpty( profileID ) )
+        if ( PwmConstants.DEFAULT_LOCALE.equals( locale ) && profileID == null )
         {
             return defaultMenuLocation.get();
         }
@@ -1547,7 +1548,7 @@ public enum PwmSetting
         return macroRequest.expandMacros( storedText );
     }
 
-    private static String readMenuLocationDebug( final PwmSetting pwmSetting, final String profileID, final Locale locale )
+    private static String readMenuLocationDebug( final PwmSetting pwmSetting, final ProfileID profileID, final Locale locale )
     {
         final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
         return pwmSetting.getCategory().toMenuLocationDebug( profileID, locale ) + separator + pwmSetting.getLabel( locale );

+ 3 - 2
server/src/main/java/password/pwm/config/PwmSettingCategory.java

@@ -22,6 +22,7 @@ package password.pwm.config;
 
 import org.jrivard.xmlchai.XmlElement;
 import password.pwm.PwmConstants;
+import password.pwm.bean.ProfileID;
 import password.pwm.i18n.Config;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
@@ -313,7 +314,7 @@ public enum PwmSettingCategory
     }
 
     public String toMenuLocationDebug(
-            final String profileID,
+            final ProfileID profileID,
             final Locale locale
     )
     {
@@ -322,7 +323,7 @@ public enum PwmSettingCategory
 
     private static String toMenuLocationDebugImpl(
             final PwmSettingCategory category,
-            final String profileID,
+            final ProfileID profileID,
             final Locale locale
     )
     {

+ 12 - 14
server/src/main/java/password/pwm/config/StoredSettingReader.java

@@ -24,6 +24,7 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.PrivateKeyCertificate;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.profile.Profile;
@@ -48,8 +49,8 @@ import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.PasswordData;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.CollectorUtil;
 import password.pwm.util.java.JavaHelper;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmHashAlgorithm;
 
@@ -58,7 +59,6 @@ import java.security.MessageDigest;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.EnumMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -73,13 +73,13 @@ public class StoredSettingReader implements SettingReader
     private static final PwmLogger LOGGER = PwmLogger.forClass( StoredSettingReader.class );
 
     private final StoredConfiguration storedConfiguration;
-    private final String profileID;
+    private final ProfileID profileID;
     private final DomainID domainID;
 
     private final Map<ProfileDefinition, Map> profileCache;
     private final String valueHash;
 
-    public StoredSettingReader( final StoredConfiguration storedConfiguration, final String profileID, final DomainID domainID )
+    public StoredSettingReader( final StoredConfiguration storedConfiguration, final ProfileID profileID, final DomainID domainID )
     {
         this.storedConfiguration = Objects.requireNonNull( storedConfiguration );
         this.profileID = profileID;
@@ -223,7 +223,7 @@ public class StoredSettingReader implements SettingReader
     }
 
 
-    public <T extends Profile> Map<String, T> getProfileMap( final ProfileDefinition profileDefinition )
+    public <T extends Profile> Map<ProfileID, T> getProfileMap( final ProfileDefinition profileDefinition )
     {
         if ( profileID != null )
         {
@@ -240,17 +240,15 @@ public class StoredSettingReader implements SettingReader
                 final DomainID domainID
         )
         {
-            final Map<ProfileDefinition, Map<String, Profile>> returnMap = new EnumMap<>( ProfileDefinition.class );
-            returnMap.putAll( CollectionUtil.enumStream( ProfileDefinition.class )
+            return CollectionUtil.enumStream( ProfileDefinition.class )
                     .filter( profileDefinition -> domainID.inScope( profileDefinition.getCategory().getScope() ) )
-                    .collect( CollectionUtil.collectorToLinkedMap(
+                    .collect( CollectorUtil.toUnmodifiableLinkedMap(
                             profileDefinition -> profileDefinition,
                             profileDefinition -> profileMap( profileDefinition, storedConfiguration, domainID )
-                    ) ) );
-            return Collections.unmodifiableMap( returnMap );
+                    ) );
         }
 
-        private static <T extends Profile> Map<String, T> profileMap(
+        private static <T extends Profile> Map<ProfileID, T> profileMap(
                 final ProfileDefinition profileDefinition,
                 final StoredConfiguration storedConfiguration,
                 final DomainID domainID
@@ -262,7 +260,7 @@ public class StoredSettingReader implements SettingReader
             }
 
             return ProfileUtility.profileIDsForCategory( storedConfiguration, domainID, profileDefinition.getCategory() ).stream()
-                    .collect( CollectionUtil.collectorToLinkedMap(
+                    .collect( CollectorUtil.toUnmodifiableLinkedMap(
                             Function.identity(),
                             profileID -> newProfileForID( profileDefinition, storedConfiguration, domainID, profileID )
                     ) );
@@ -272,7 +270,7 @@ public class StoredSettingReader implements SettingReader
                 final ProfileDefinition profileDefinition,
                 final StoredConfiguration storedConfiguration,
                 final DomainID domainID,
-                final String profileID
+                final ProfileID profileID
         )
         {
             Objects.requireNonNull( profileDefinition );
@@ -324,7 +322,7 @@ public class StoredSettingReader implements SettingReader
             LOGGER.warn( SessionLabel.SYSTEM_LABEL, () -> "attempt to read deprecated config setting: " + setting.toMenuLocationDebug( profileID, null ) );
         }
 
-        if ( StringUtil.isEmpty( profileID ) )
+        if ( profileID == null )
         {
             if ( setting.getCategory().hasProfiles() )
             {

+ 9 - 7
server/src/main/java/password/pwm/config/profile/AbstractProfile.java

@@ -21,6 +21,7 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredSettingReader;
 import password.pwm.config.option.IdentityVerificationMethod;
@@ -37,12 +38,13 @@ import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 
 public abstract class AbstractProfile implements Profile
 {
-    private final String identifier;
+    private final ProfileID profileID;
     private final StoredConfiguration storedConfiguration;
     private final StoredSettingReader settingReader;
 
@@ -53,23 +55,23 @@ public abstract class AbstractProfile implements Profile
         ATTRIBUTE,
     }
 
-    AbstractProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    AbstractProfile( final DomainID domainID, final ProfileID profileID, final StoredConfiguration storedConfiguration )
     {
-        this.identifier = identifier;
+        this.profileID = Objects.requireNonNull( profileID );
         this.storedConfiguration = storedConfiguration;
-        this.settingReader = new StoredSettingReader( storedConfiguration, identifier, domainID );
+        this.settingReader = new StoredSettingReader( storedConfiguration, profileID, domainID );
     }
 
     @Override
-    public String getIdentifier( )
+    public ProfileID getId( )
     {
-        return identifier;
+        return profileID;
     }
 
     @Override
     public String getDisplayName( final Locale locale )
     {
-        return getIdentifier();
+        return getId().stringValue();
     }
 
     public List<UserPermission> readSettingAsUserPermission( final PwmSetting setting )

+ 3 - 2
server/src/main/java/password/pwm/config/profile/AccountInformationProfile.java

@@ -21,13 +21,14 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class AccountInformationProfile extends AbstractProfile implements Profile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.AccountInformation;
 
-    protected AccountInformationProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    protected AccountInformationProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -41,7 +42,7 @@ public class AccountInformationProfile extends AbstractProfile implements Profil
     public static class AccountInformationProfileFactory implements Profile.ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new AccountInformationProfile( domainID, identifier, storedConfiguration );
         }

+ 3 - 2
server/src/main/java/password/pwm/config/profile/ActivateUserProfile.java

@@ -21,13 +21,14 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class ActivateUserProfile extends AbstractProfile implements Profile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.ActivateUser;
 
-    protected ActivateUserProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedValueMap )
+    protected ActivateUserProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedValueMap )
     {
         super( domainID, identifier, storedValueMap );
     }
@@ -41,7 +42,7 @@ public class ActivateUserProfile extends AbstractProfile implements Profile
     public static class UserActivationProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new ActivateUserProfile( domainID, identifier, storedConfiguration );
         }

+ 7 - 6
server/src/main/java/password/pwm/config/profile/ChallengeProfile.java

@@ -27,6 +27,7 @@ import com.novell.ldapchai.cr.ChallengeSet;
 import com.novell.ldapchai.exception.ChaiValidationException;
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredSettingReader;
 import password.pwm.config.stored.StoredConfiguration;
@@ -48,7 +49,7 @@ public class ChallengeProfile implements Profile, Serializable
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( ChallengeProfile.class );
 
-    private final String profileID;
+    private final ProfileID profileID;
     private final Locale locale;
     private final ChallengeSet challengeSet;
     private final ChallengeSet helpdeskChallengeSet;
@@ -57,7 +58,7 @@ public class ChallengeProfile implements Profile, Serializable
     private final List<UserPermission> userPermissions;
 
     private ChallengeProfile(
-            final String profileID,
+            final ProfileID profileID,
             final Locale locale,
             final ChallengeSet challengeSet,
             final ChallengeSet helpdeskChallengeSet,
@@ -77,7 +78,7 @@ public class ChallengeProfile implements Profile, Serializable
 
     public static ChallengeProfile readChallengeProfileFromConfig(
             final DomainID domainID,
-            final String profileID,
+            final ProfileID profileID,
             final Locale locale,
             final StoredConfiguration storedConfiguration
     )
@@ -127,7 +128,7 @@ public class ChallengeProfile implements Profile, Serializable
     }
 
     public static ChallengeProfile createChallengeProfile(
-            final String profileID,
+            final ProfileID profileID,
             final Locale locale,
             final ChallengeSet challengeSet,
             final ChallengeSet helpdeskChallengeSet,
@@ -139,7 +140,7 @@ public class ChallengeProfile implements Profile, Serializable
     }
 
     @Override
-    public String getIdentifier( )
+    public ProfileID getId( )
     {
         return profileID;
     }
@@ -147,7 +148,7 @@ public class ChallengeProfile implements Profile, Serializable
     @Override
     public String getDisplayName( final Locale locale )
     {
-        return getIdentifier();
+        return getId().stringValue();
     }
 
     public Locale getLocale( )

+ 3 - 2
server/src/main/java/password/pwm/config/profile/ChangePasswordProfile.java

@@ -21,13 +21,14 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class ChangePasswordProfile extends AbstractProfile implements Profile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.ChangePassword;
 
-    protected ChangePasswordProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    protected ChangePasswordProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -41,7 +42,7 @@ public class ChangePasswordProfile extends AbstractProfile implements Profile
     public static class ChangePasswordProfileFactory implements Profile.ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new ChangePasswordProfile( domainID, identifier, storedConfiguration );
         }

+ 3 - 2
server/src/main/java/password/pwm/config/profile/DeleteAccountProfile.java

@@ -21,13 +21,14 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class DeleteAccountProfile extends AbstractProfile implements Profile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.DeleteAccount;
 
-    protected DeleteAccountProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    protected DeleteAccountProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -41,7 +42,7 @@ public class DeleteAccountProfile extends AbstractProfile implements Profile
     public static class DeleteAccountProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new DeleteAccountProfile( domainID, identifier, storedConfiguration );
         }

+ 3 - 10
server/src/main/java/password/pwm/config/profile/EmailServerProfile.java

@@ -21,16 +21,15 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.stored.StoredConfiguration;
 
-import java.util.Locale;
-
 public class EmailServerProfile extends AbstractProfile
 {
 
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.EmailServers;
 
-    protected EmailServerProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    protected EmailServerProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -41,16 +40,10 @@ public class EmailServerProfile extends AbstractProfile
         return PROFILE_TYPE;
     }
 
-    @Override
-    public String getDisplayName( final Locale locale )
-    {
-        return this.getIdentifier();
-    }
-
     public static class EmailServerProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new EmailServerProfile( domainID, identifier, storedConfiguration );
         }

+ 3 - 2
server/src/main/java/password/pwm/config/profile/ForgottenPasswordProfile.java

@@ -21,6 +21,7 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.stored.StoredConfiguration;
@@ -36,7 +37,7 @@ public class ForgottenPasswordProfile extends AbstractProfile
     private Set<IdentityVerificationMethod> requiredRecoveryVerificationMethods;
     private Set<IdentityVerificationMethod> optionalRecoveryVerificationMethods;
 
-    public ForgottenPasswordProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    public ForgottenPasswordProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -80,7 +81,7 @@ public class ForgottenPasswordProfile extends AbstractProfile
     public static class ForgottenPasswordProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new ForgottenPasswordProfile( domainID, identifier, storedConfiguration );
         }

+ 3 - 2
server/src/main/java/password/pwm/config/profile/HelpdeskProfile.java

@@ -21,6 +21,7 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.stored.StoredConfiguration;
@@ -35,7 +36,7 @@ public class HelpdeskProfile extends AbstractProfile implements Profile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.Helpdesk;
 
-    protected HelpdeskProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    protected HelpdeskProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -62,7 +63,7 @@ public class HelpdeskProfile extends AbstractProfile implements Profile
     public static class HelpdeskProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new HelpdeskProfile( domainID, identifier, storedConfiguration );
         }

+ 61 - 25
server/src/main/java/password/pwm/config/profile/LdapProfile.java

@@ -27,6 +27,7 @@ import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
@@ -43,7 +44,6 @@ import password.pwm.util.logging.PwmLogger;
 
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -56,7 +56,10 @@ public class LdapProfile extends AbstractProfile implements Profile
 
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.LdapProfile;
 
-    protected LdapProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedValueMap )
+    private List<String> rootContextSupplier;
+    private Map<String, String> selectableContexts;
+
+    protected LdapProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedValueMap )
     {
         super( domainID, identifier, storedValueMap );
     }
@@ -67,17 +70,22 @@ public class LdapProfile extends AbstractProfile implements Profile
     )
             throws PwmUnrecoverableException
     {
-        final List<String> rawValues = readSettingAsStringArray( PwmSetting.LDAP_LOGIN_CONTEXTS );
-        final Map<String, String> configuredValues = StringUtil.convertStringListToNameValuePair( rawValues, ":::" );
-        final Map<String, String> canonicalValues = new LinkedHashMap<>( configuredValues.size() );
-        for ( final Map.Entry<String, String> entry : configuredValues.entrySet() )
+        if ( selectableContexts == null )
         {
-            final String dn = entry.getKey();
-            final String label = entry.getValue();
-            final String canonicalDN = readCanonicalDN( sessionLabel, pwmDomain, dn );
-            canonicalValues.put( canonicalDN, label );
+            final List<String> rawValues = readSettingAsStringArray( PwmSetting.LDAP_LOGIN_CONTEXTS );
+            final Map<String, String> configuredValues = StringUtil.convertStringListToNameValuePair( rawValues, ":::" );
+            final Map<String, String> canonicalValues = new LinkedHashMap<>( configuredValues.size() );
+            for ( final Map.Entry<String, String> entry : configuredValues.entrySet() )
+            {
+                final String dn = entry.getKey();
+                final String label = entry.getValue();
+                final String canonicalDN = readCanonicalDN( sessionLabel, pwmDomain, dn );
+                canonicalValues.put( canonicalDN, label );
+            }
+            selectableContexts = Map.copyOf( canonicalValues );
         }
-        return Collections.unmodifiableMap( canonicalValues );
+
+        return selectableContexts;
     }
 
     public List<String> getRootContexts(
@@ -86,14 +94,19 @@ public class LdapProfile extends AbstractProfile implements Profile
     )
             throws PwmUnrecoverableException
     {
-        final List<String> rawValues = readSettingAsStringArray( PwmSetting.LDAP_CONTEXTLESS_ROOT );
-        final List<String> canonicalValues = new ArrayList<>( rawValues.size() );
-        for ( final String dn : rawValues )
+        if ( rootContextSupplier == null )
         {
-            final String canonicalDN = readCanonicalDN( sessionLabel, pwmDomain, dn );
-            canonicalValues.add( canonicalDN );
+            final List<String> rawValues = readSettingAsStringArray( PwmSetting.LDAP_CONTEXTLESS_ROOT );
+            final List<String> canonicalValues = new ArrayList<>( rawValues.size() );
+            for ( final String dn : rawValues )
+            {
+                final String canonicalDN = readCanonicalDN( sessionLabel, pwmDomain, dn );
+                canonicalValues.add( canonicalDN );
+            }
+            rootContextSupplier = List.copyOf( canonicalValues );
         }
-        return Collections.unmodifiableList( canonicalValues );
+
+        return rootContextSupplier;
     }
 
     public List<String> getLdapUrls(
@@ -106,7 +119,7 @@ public class LdapProfile extends AbstractProfile implements Profile
     public String getDisplayName( final Locale locale )
     {
         final String displayName = readSettingAsLocalizedString( PwmSetting.LDAP_PROFILE_DISPLAY_NAME, locale );
-        return StringUtil.isTrimEmpty( displayName ) ? getIdentifier() : displayName;
+        return StringUtil.isTrimEmpty( displayName ) ? getId().stringValue() : displayName;
     }
 
     public String getUsernameAttribute( )
@@ -119,13 +132,13 @@ public class LdapProfile extends AbstractProfile implements Profile
     public ChaiProvider getProxyChaiProvider( final SessionLabel sessionLabel, final PwmDomain pwmDomain ) throws PwmUnrecoverableException
     {
         verifyIsEnabled();
-        return pwmDomain.getProxyChaiProvider( sessionLabel, this.getIdentifier() );
+        return pwmDomain.getProxyChaiProvider( sessionLabel, this.getId() );
     }
 
     @Override
     public ProfileDefinition profileType( )
     {
-        throw new UnsupportedOperationException();
+        return PROFILE_TYPE;
     }
 
     @Override
@@ -154,7 +167,7 @@ public class LdapProfile extends AbstractProfile implements Profile
         final boolean enableCanonicalCache = Boolean.parseBoolean( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_CACHE_CANONICAL_ENABLE ) );
 
         String canonicalValue = null;
-        final CacheKey cacheKey = CacheKey.newKey( LdapProfile.class, null, "canonicalDN-" + this.getIdentifier() + "-" + dnValue );
+        final CacheKey cacheKey = CacheKey.newKey( LdapProfile.class, null, "canonicalDN-" + this.getId() + "-" + dnValue );
         if ( enableCanonicalCache )
         {
             final String cachedDN = pwmDomain.getCacheService().get( cacheKey, String.class );
@@ -222,7 +235,7 @@ public class LdapProfile extends AbstractProfile implements Profile
 
         if ( StringUtil.notEmpty( testUserDN ) )
         {
-            return Optional.of( UserIdentity.create( testUserDN, this.getIdentifier(), pwmDomain.getDomainID() ).canonicalized( sessionLabel, pwmDomain.getPwmApplication() ) );
+            return Optional.of( UserIdentity.create( testUserDN, this.getId(), pwmDomain.getDomainID() ).canonicalized( sessionLabel, pwmDomain.getPwmApplication() ) );
         }
 
         return Optional.empty();
@@ -231,7 +244,7 @@ public class LdapProfile extends AbstractProfile implements Profile
     public static class LdapProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new LdapProfile( domainID, identifier, storedConfiguration );
         }
@@ -242,7 +255,7 @@ public class LdapProfile extends AbstractProfile implements Profile
     {
         if ( !isEnabled() )
         {
-            final String msg = "ldap profile '" + getIdentifier() + "' is not enabled";
+            final String msg = "ldap profile '" + getId() + "' is not enabled";
             throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, msg ) );
         }
     }
@@ -255,6 +268,29 @@ public class LdapProfile extends AbstractProfile implements Profile
     @Override
     public String toString()
     {
-        return "LDAPProfile:" + this.getIdentifier();
+        return "LDAPProfile:" + this.getId();
+    }
+
+    public void testIfDnIsContainedByRootContext( final SessionLabel sessionLabel, final PwmDomain pwmDomain, final String testDN )
+            throws PwmUnrecoverableException
+    {
+        if ( StringUtil.isEmpty( testDN ) )
+        {
+            return;
+        }
+
+        final List<String> rootContexts = getRootContexts( sessionLabel, pwmDomain );
+
+        final Optional<String> matchedDn = rootContexts.stream()
+                .filter( testDN::endsWith )
+                .findFirst();
+
+        if ( matchedDn.isPresent() )
+        {
+            return;
+        }
+
+        final String msg = "specified search context '" + testDN + "' is not contained by a configured root context";
+        throw new PwmUnrecoverableException( PwmError.CONFIG_FORMAT_ERROR, msg );
     }
 }

+ 15 - 12
server/src/main/java/password/pwm/config/profile/NewUserProfile.java

@@ -27,6 +27,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
@@ -44,6 +45,7 @@ import java.time.Instant;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 
 public class NewUserProfile extends AbstractProfile implements Profile
 {
@@ -54,7 +56,7 @@ public class NewUserProfile extends AbstractProfile implements Profile
     private Instant newUserPasswordPolicyCacheTime;
     private final Map<Locale, PwmPasswordPolicy> newUserPasswordPolicyCache = new HashMap<>();
 
-    protected NewUserProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    protected NewUserProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -69,7 +71,7 @@ public class NewUserProfile extends AbstractProfile implements Profile
     public String getDisplayName( final Locale locale )
     {
         final String value = this.readSettingAsLocalizedString( PwmSetting.NEWUSER_PROFILE_DISPLAY_NAME, locale );
-        return value != null && !value.isEmpty() ? value : this.getIdentifier();
+        return value != null && !value.isEmpty() ? value : this.getId().stringValue();
     }
 
     public PwmPasswordPolicy getNewUserPasswordPolicy( final PwmRequestContext pwmRequestContext )
@@ -101,7 +103,7 @@ public class NewUserProfile extends AbstractProfile implements Profile
         if ( StringUtil.isEmpty( configuredNewUserPasswordDN ) )
         {
             final String errorMsg = "the setting "
-                    + PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( this.getIdentifier(), PwmConstants.DEFAULT_LOCALE )
+                    + PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( this.getId(), PwmConstants.DEFAULT_LOCALE )
                     + " must have a value";
             throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, errorMsg ) );
         }
@@ -114,9 +116,9 @@ public class NewUserProfile extends AbstractProfile implements Profile
                 if ( StringUtil.isEmpty( lookupDN ) )
                 {
                     final String errorMsg = "setting "
-                            + PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE )
+                            + PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE )
                             + " must be configured since setting "
-                            + PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( this.getIdentifier(), PwmConstants.DEFAULT_LOCALE )
+                            + PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( this.getId(), PwmConstants.DEFAULT_LOCALE )
                             + " is set to " + TEST_USER_CONFIG_VALUE;
                     throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, errorMsg ) );
                 }
@@ -139,9 +141,9 @@ public class NewUserProfile extends AbstractProfile implements Profile
             {
                 try
                 {
-                    final ChaiProvider chaiProvider = pwmDomain.getProxyChaiProvider( sessionLabel, ldapProfile.getIdentifier() );
+                    final ChaiProvider chaiProvider = pwmDomain.getProxyChaiProvider( sessionLabel, ldapProfile.getId() );
                     final ChaiUser chaiUser = chaiProvider.getEntryFactory().newChaiUser( lookupDN );
-                    final UserIdentity userIdentity = UserIdentity.create( lookupDN, ldapProfile.getIdentifier(), pwmDomain.getDomainID() );
+                    final UserIdentity userIdentity = UserIdentity.create( lookupDN, ldapProfile.getId(), pwmDomain.getDomainID() );
                     thePolicy = PasswordUtility.readPasswordPolicyForUser( pwmDomain, null, userIdentity, chaiUser );
                 }
                 catch ( final ChaiUnavailableException e )
@@ -179,7 +181,7 @@ public class NewUserProfile extends AbstractProfile implements Profile
     public static class NewUserProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new NewUserProfile( domainID, identifier, storedConfiguration );
         }
@@ -188,16 +190,17 @@ public class NewUserProfile extends AbstractProfile implements Profile
     public LdapProfile getLdapProfile( final DomainConfig domainConfig )
             throws PwmUnrecoverableException
     {
-        final String configuredProfile = readSettingAsString( PwmSetting.NEWUSER_LDAP_PROFILE );
-        if ( StringUtil.notEmpty( configuredProfile ) )
+        final Optional<ProfileID> configuredProfile = domainConfig
+                .profileForStringId( ProfileDefinition.NewUser, readSettingAsString( PwmSetting.NEWUSER_LDAP_PROFILE ) );
+        if ( configuredProfile.isPresent() )
         {
-            final LdapProfile ldapProfile = domainConfig.getLdapProfiles().get( configuredProfile );
+            final LdapProfile ldapProfile = domainConfig.getLdapProfiles().get( configuredProfile.get() );
             if ( ldapProfile == null )
             {
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[]
                         {
                                 "configured ldap profile for new user profile is invalid.  check setting "
-                                        + PwmSetting.NEWUSER_LDAP_PROFILE.toMenuLocationDebug( this.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                                        + PwmSetting.NEWUSER_LDAP_PROFILE.toMenuLocationDebug( this.getId(), PwmConstants.DEFAULT_LOCALE ),
                         }
                 ) );
             }

+ 3 - 2
server/src/main/java/password/pwm/config/profile/PeopleSearchProfile.java

@@ -21,6 +21,7 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class PeopleSearchProfile extends AbstractProfile
@@ -28,7 +29,7 @@ public class PeopleSearchProfile extends AbstractProfile
 
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.PeopleSearch;
 
-    protected PeopleSearchProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    protected PeopleSearchProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -42,7 +43,7 @@ public class PeopleSearchProfile extends AbstractProfile
     public static class PeopleSearchProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new PeopleSearchProfile( domainID, identifier, storedConfiguration );
         }

+ 3 - 2
server/src/main/java/password/pwm/config/profile/Profile.java

@@ -21,6 +21,7 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.data.UserPermission;
 
@@ -31,7 +32,7 @@ public interface Profile
 {
     ProfileDefinition profileType( );
 
-    String getIdentifier( );
+    ProfileID getId( );
 
     String getDisplayName( Locale locale );
 
@@ -39,6 +40,6 @@ public interface Profile
 
     interface ProfileFactory
     {
-        Profile makeFromStoredConfiguration( StoredConfiguration storedConfiguration, DomainID domainID, String identifier );
+        Profile makeFromStoredConfiguration( StoredConfiguration storedConfiguration, DomainID domainID, ProfileID identifier );
     }
 }

+ 10 - 13
server/src/main/java/password/pwm/config/profile/ProfileUtility.java

@@ -20,9 +20,9 @@
 
 package password.pwm.config.profile;
 
-import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
@@ -38,9 +38,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequestContext;
 import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.util.java.StringUtil;
-import password.pwm.util.logging.PwmLogger;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -50,9 +48,7 @@ import java.util.stream.Collectors;
 
 public class ProfileUtility
 {
-    private static final PwmLogger LOGGER = PwmLogger.forClass( ProfileUtility.class );
-
-    public static Optional<String> discoverProfileIDForUser(
+    public static Optional<ProfileID> discoverProfileIDForUser(
             final PwmRequestContext pwmRequestContext,
             final UserIdentity userIdentity,
             final ProfileDefinition profileDefinition
@@ -70,7 +66,7 @@ public class ProfileUtility
     )
             throws PwmUnrecoverableException
     {
-        final Optional<String> profileID = discoverProfileIDForUser( pwmRequestContext, userIdentity, profileDefinition );
+        final Optional<ProfileID> profileID = discoverProfileIDForUser( pwmRequestContext, userIdentity, profileDefinition );
         if ( profileID.isEmpty() )
         {
             throw PwmUnrecoverableException.newException( PwmError.ERROR_NO_PROFILE_ASSIGNED, "profile of type " + profileDefinition + " is required but not assigned" );
@@ -79,7 +75,7 @@ public class ProfileUtility
     }
 
 
-    public static Optional<String> discoverProfileIDForUser(
+    public static Optional<ProfileID> discoverProfileIDForUser(
             final PwmDomain pwmDomain,
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity,
@@ -87,36 +83,37 @@ public class ProfileUtility
     )
             throws PwmUnrecoverableException
     {
-        final Map<String, Profile> profileMap = pwmDomain.getConfig().getProfileMap( profileDefinition );
+        final Map<ProfileID, Profile> profileMap = pwmDomain.getConfig().getProfileMap( profileDefinition );
         for ( final Profile profile : profileMap.values() )
         {
             final List<UserPermission> queryMatches = profile.profilePermissions();
             final boolean match = UserPermissionUtility.testUserPermission( pwmDomain, sessionLabel, userIdentity, queryMatches );
             if ( match )
             {
-                return Optional.of( profile.getIdentifier() );
+                return Optional.of( profile.getId() );
             }
         }
         return Optional.empty();
     }
 
-    public static List<String> profileIDsForCategory( final StoredConfiguration storedConfiguration, final DomainID domainID, final PwmSettingCategory pwmSettingCategory )
+    public static List<ProfileID> profileIDsForCategory( final StoredConfiguration storedConfiguration, final DomainID domainID, final PwmSettingCategory pwmSettingCategory )
     {
         final PwmSetting profileSetting = pwmSettingCategory.getProfileSetting().orElseThrow( IllegalStateException::new );
         final StoredConfigKey key = StoredConfigKey.forSetting( profileSetting, null, domainID );
         final StoredValue storedValue = StoredConfigurationUtil.getValueOrDefault( storedConfiguration, key );
         final Predicate<String> regexPredicate = syntaxFilterPredicateForProfileID( pwmSettingCategory );
 
-        final List<String> returnData = ValueTypeConverter.valueToStringArray( storedValue )
+        final List<ProfileID> returnData = ValueTypeConverter.valueToStringArray( storedValue )
                 .stream()
                 .distinct()
                 .filter( StringUtil::notEmpty )
                 .filter( regexPredicate )
+                .map( ProfileID::create )
                 .collect( Collectors.toUnmodifiableList() );
 
         if ( returnData.isEmpty() )
         {
-            return Collections.singletonList( PwmConstants.PROFILE_ID_DEFAULT );
+            return List.of( ProfileID.PROFILE_ID_DEFAULT );
         }
 
         return returnData;

+ 8 - 8
server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java

@@ -26,6 +26,7 @@ import lombok.Builder;
 import lombok.Value;
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredSettingReader;
@@ -36,7 +37,6 @@ import password.pwm.health.HealthRecord;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.LazySupplier;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.password.PasswordRuleReaderHelper;
@@ -129,7 +129,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
 
     public static PwmPasswordPolicy createPwmPasswordPolicy(
             final DomainConfig domainConfig,
-            final String profileID
+            final ProfileID profileID
     )
     {
         final StoredSettingReader settingReader = new StoredSettingReader( domainConfig.getStoredConfiguration(), profileID,  domainConfig.getDomainID() );
@@ -190,7 +190,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
     }
 
     @Override
-    public String getIdentifier( )
+    public ProfileID getId( )
     {
         return policyMetaData.getProfileID();
     }
@@ -198,7 +198,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
     @Override
     public String getDisplayName( final Locale locale )
     {
-        return getIdentifier();
+        return getId() == null ? "[no-profile]" : getId().stringValue();
     }
 
     public DomainID getDomainID()
@@ -369,7 +369,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
                         + maxRule.getLabel( locale, null ) + " (" + maxValue + ")";
                 returnList.add( HealthRecord.forMessage(
                         pwmPasswordPolicy.getDomainID(),
-                        HealthMessage.Config_PasswordPolicyProblem, policyMetaData.getProfileID(), detailMsg ) );
+                        HealthMessage.Config_PasswordPolicyProblem, policyMetaData.getProfileID().stringValue(), detailMsg ) );
             }
         }
 
@@ -385,7 +385,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
                         + PwmPasswordRule.CharGroupsMinMatch.getLabel( locale, null ) + " (" + maxValue + ")";
                 returnList.add( HealthRecord.forMessage(
                         pwmPasswordPolicy.getDomainID(),
-                        HealthMessage.Config_PasswordPolicyProblem, policyMetaData.getProfileID(), detailMsg ) );
+                        HealthMessage.Config_PasswordPolicyProblem, policyMetaData.getProfileID().stringValue(), detailMsg ) );
             }
         }
 
@@ -400,7 +400,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
     {
         private final DomainID domainID;
 
-        private final String profileID;
+        private final ProfileID profileID;
 
         @Builder.Default
         private final List<UserPermission> userPermissions = Collections.emptyList();
@@ -416,7 +416,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
                     .ruleText( CollectionUtil.isEmpty( ruleText ) ? otherPolicy.ruleText : ruleText )
                     .changePasswordText( CollectionUtil.isEmpty( changePasswordText ) ? otherPolicy.changePasswordText : changePasswordText )
                     .userPermissions( CollectionUtil.isEmpty( userPermissions ) ? otherPolicy.userPermissions : userPermissions )
-                    .profileID( StringUtil.isEmpty( profileID ) ? otherPolicy.profileID : profileID )
+                    .profileID( profileID == null ? otherPolicy.profileID : profileID )
                     .domainID( domainID == null ? otherPolicy.domainID : domainID )
                     .build();
         }

+ 3 - 2
server/src/main/java/password/pwm/config/profile/SetupOtpProfile.java

@@ -21,13 +21,14 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class SetupOtpProfile extends AbstractProfile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.SetupOTPProfile;
 
-    protected SetupOtpProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    protected SetupOtpProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -41,7 +42,7 @@ public class SetupOtpProfile extends AbstractProfile
     public static class SetupOtpProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID,  final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID,  final ProfileID identifier )
         {
             return new SetupOtpProfile( domainID, identifier, storedConfiguration );
         }

+ 3 - 2
server/src/main/java/password/pwm/config/profile/SetupResponsesProfile.java

@@ -21,13 +21,14 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.stored.StoredConfiguration;
 
 public class SetupResponsesProfile extends AbstractProfile
 {
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.SetupOTPProfile;
 
-    protected SetupResponsesProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    protected SetupResponsesProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -41,7 +42,7 @@ public class SetupResponsesProfile extends AbstractProfile
     public static class SetupResponseProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID,  final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID,  final ProfileID identifier )
         {
             return new SetupResponsesProfile( domainID, identifier, storedConfiguration );
         }

+ 3 - 2
server/src/main/java/password/pwm/config/profile/UpdateProfileProfile.java

@@ -21,6 +21,7 @@
 package password.pwm.config.profile;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfiguration;
@@ -31,7 +32,7 @@ public class UpdateProfileProfile extends AbstractProfile implements Profile
 
     private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.UpdateAttributes;
 
-    protected UpdateProfileProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    protected UpdateProfileProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedConfiguration )
     {
         super( domainID, identifier, storedConfiguration );
     }
@@ -67,7 +68,7 @@ public class UpdateProfileProfile extends AbstractProfile implements Profile
     public static class UpdateProfileProfileFactory implements ProfileFactory
     {
         @Override
-        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final String identifier )
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID, final ProfileID identifier )
         {
             return new UpdateProfileProfile( domainID, identifier, storedConfiguration );
         }

+ 15 - 10
server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java

@@ -22,6 +22,7 @@ package password.pwm.config.stored;
 
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
@@ -36,7 +37,6 @@ import password.pwm.config.value.ValueTypeConverter;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.PwmExceptionLoggingConsumer;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.LinkedHashSet;
@@ -97,7 +97,7 @@ public class ConfigurationCleaner
                 value = new StringValue( ADPolicyComplexity.NONE.toString() );
             }
 
-            final String profileID = key.getProfileID();
+            final Optional<ProfileID> profileID = key.getProfileID();
 
             LOGGER.info( () -> "converting deprecated non-default setting "
                     + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY.getKey() + "/" + profileID
@@ -108,7 +108,7 @@ public class ConfigurationCleaner
             final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null );
             try
             {
-                final StoredConfigKey writeKey = StoredConfigKey.forSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, key.getDomainID() );
+                final StoredConfigKey writeKey = StoredConfigKey.forSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID.orElse( null ), key.getDomainID() );
                 modifier.writeSetting( writeKey, value, userIdentity );
             }
             catch ( final PwmUnrecoverableException e )
@@ -127,7 +127,7 @@ public class ConfigurationCleaner
             final StoredConfiguration oldConfig = modifier.newStoredConfiguration();
             for ( final DomainID domainID : StoredConfigurationUtil.domainList( oldConfig ) )
             {
-                for ( final String profileID : StoredConfigurationUtil.profilesForSetting( domainID, PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, oldConfig ) )
+                for ( final ProfileID profileID : StoredConfigurationUtil.profilesForSetting( domainID, PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, oldConfig ) )
                 {
                     final StoredConfigKey key = StoredConfigKey.forSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID, domainID );
                     final Optional<StoredValue> oldValue = oldConfig.readStoredValue( key );
@@ -196,7 +196,7 @@ public class ConfigurationCleaner
             CollectionUtil.iteratorToStream( inputConfig.keys() )
                     .filter( ( key ) -> key.isRecordType( StoredConfigKey.RecordType.SETTING ) )
                     .filter( ( key ) -> key.toPwmSetting().getCategory().hasProfiles() )
-                    .filter( ( key ) -> StringUtil.isEmpty( key.getProfileID() ) )
+                    .filter( ( key ) -> key.getProfileID().isEmpty() )
                     .forEach( ( key ) -> convertSetting( inputConfig, modifier, key ) );
         }
 
@@ -207,11 +207,11 @@ public class ConfigurationCleaner
         {
             final PwmSetting pwmSetting = key.toPwmSetting();
 
-            final List<String> targetProfiles = StoredConfigurationUtil.profilesForSetting(  key.getDomainID(), pwmSetting, inputConfig );
+            final List<ProfileID> targetProfiles = StoredConfigurationUtil.profilesForSetting(  key.getDomainID(), pwmSetting, inputConfig );
             final StoredValue value = inputConfig.readStoredValue( key ).orElseThrow();
             final Optional<ValueMetaData> valueMetaData = inputConfig.readMetaData( key );
 
-            for ( final String destProfile : targetProfiles )
+            for ( final ProfileID destProfile : targetProfiles )
             {
                 LOGGER.info( () -> "moving setting " + key + " without profile attribute to profile \"" + destProfile + "\"." );
                 {
@@ -257,9 +257,14 @@ public class ConfigurationCleaner
         boolean verifyProfileIsValid( final StoredConfigKey key, final StoredConfiguration inputConfig )
         {
             final PwmSetting pwmSetting = key.toPwmSetting();
-            final String recordID = key.getProfileID();
-            final List<String> profiles = StoredConfigurationUtil.profilesForSetting( key.getDomainID(), pwmSetting, inputConfig );
-            return !profiles.contains( recordID );
+            final Optional<ProfileID> recordID = key.getProfileID();
+            if ( recordID.isPresent() )
+            {
+                final List<ProfileID> profiles = StoredConfigurationUtil.profilesForSetting( key.getDomainID(), pwmSetting, inputConfig );
+                return !profiles.contains( recordID.get() );
+            }
+
+            return false;
         }
 
         void removeSuperfluousProfile( final StoredConfigKey key, final StoredConfigurationModifier modifier )

+ 24 - 17
server/src/main/java/password/pwm/config/stored/StoredConfigKey.java

@@ -23,6 +23,7 @@ package password.pwm.config.stored;
 import org.jetbrains.annotations.NotNull;
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.i18n.Config;
@@ -35,10 +36,11 @@ import java.io.Serializable;
 import java.util.Comparator;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey>
+public final class StoredConfigKey implements Serializable, Comparable<StoredConfigKey>
 {
     private static final Comparator<StoredConfigKey> COMPARATOR = makeComparator();
 
@@ -97,14 +99,27 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
         return recordID;
     }
 
-    public String getProfileID()
+    public Optional<ProfileID> getProfileID()
     {
+        if ( !isRecordType( RecordType.SETTING ) )
+        {
+            throw new IllegalStateException( "can not read profileID for non-setting record type" );
+        }
+        return ProfileID.createNullable( profileID );
+    }
+
+    public String getLocaleKey()
+    {
+        if ( !isRecordType( RecordType.LOCALE_BUNDLE ) )
+        {
+            throw new IllegalStateException( "can not read profileID for non-locale record type" );
+        }
         return profileID;
     }
 
-    public static StoredConfigKey forSetting( final PwmSetting pwmSetting, final String profileID, final DomainID domainID )
+    public static StoredConfigKey forSetting( final PwmSetting pwmSetting, final ProfileID profileID, final DomainID domainID )
     {
-        return new StoredConfigKey( RecordType.SETTING, domainID, pwmSetting.getKey(), profileID );
+        return new StoredConfigKey( RecordType.SETTING, domainID, pwmSetting.getKey(), profileID == null ? null : profileID.stringValue() );
     }
 
     public static StoredConfigKey forLocaleBundle( final PwmLocaleBundle localeBundle, final String key, final DomainID domainID )
@@ -119,12 +134,12 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
 
     public StoredConfigKey withNewDomain( final DomainID domainID )
     {
-        return new StoredConfigKey( this.getRecordType(), domainID, this.getRecordID(), this.getProfileID() );
+        return new StoredConfigKey( this.recordType, domainID, this.recordID, this.profileID );
     }
 
     public boolean isRecordType( final RecordType recordType )
     {
-        return recordType != null && Objects.equals( getRecordType(), recordType );
+        return Objects.equals( this.recordType, recordType );
     }
 
     public boolean isValid()
@@ -190,7 +205,7 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
             case SETTING:
                 if ( toPwmSetting().getCategory().hasProfiles()  )
                 {
-                    return prefix + toPwmSetting().toMenuLocationDebug( profileID, locale );
+                    return prefix + toPwmSetting().toMenuLocationDebug( getProfileID().orElse( null ), locale );
                 }
                 else if ( StringUtil.notEmpty( profileID ) )
                 {
@@ -319,10 +334,6 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
                 Comparator.nullsLast( Comparator.naturalOrder() ) );
 
 
-        final Comparator<StoredConfigKey> domainComparator = Comparator.comparing( StoredConfigKey::getDomainID,
-                Comparator.nullsLast( Comparator.naturalOrder() ) );
-
-
         final Comparator<StoredConfigKey> recordComparator = ( o1, o2 ) ->
         {
             if ( Objects.equals( o1.getRecordType(), o2.getRecordType() )
@@ -337,14 +348,10 @@ public class StoredConfigKey implements Serializable, Comparable<StoredConfigKey
             }
         };
 
-        final Comparator<StoredConfigKey> profileComparator = Comparator.comparing(
-                StoredConfigKey::getProfileID,
-                Comparator.nullsLast( Comparator.naturalOrder() ) );
-
-        return domainComparator
+        return Comparator.comparing( StoredConfigKey::getDomainID, DomainID.comparator() )
                 .thenComparing( typeComparator )
                 .thenComparing( recordComparator )
-                .thenComparing( profileComparator );
+                .thenComparing( key -> key.profileID, ProfileID.stringComparator() );
     }
 
 }

+ 7 - 6
server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java

@@ -27,6 +27,7 @@ import org.jrivard.xmlchai.XmlElement;
 import org.jrivard.xmlchai.XmlFactory;
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
@@ -232,7 +233,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                     final PwmSetting pwmSetting = optionalPwmSetting.get();
                     final boolean defaultValueSaved = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_DEFAULT ).isPresent();
                     final DomainID domainID = readDomainIdForSetting( settingElement, pwmSetting );
-                    final StoredConfigKey key = StoredConfigKey.forSetting( pwmSetting, profileID.orElse( null ), domainID );
+                    final StoredConfigKey key = StoredConfigKey.forSetting( pwmSetting, profileID.map( ProfileID::create ).orElse( null ), domainID );
                     final ValueMetaData metaData = readMetaDataFromXmlElement( key, settingElement ).orElse( null );
 
                     final StoredValue storedValue = defaultValueSaved
@@ -561,7 +562,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
             Objects.requireNonNull( storedValue );
 
             final PwmSetting pwmSetting = key.toPwmSetting();
-            final String profileID = key.getProfileID();
+            final Optional<ProfileID> profileID = key.getProfileID();
 
             final XmlFactory xmlFactory = XmlChai.getFactory();
 
@@ -570,16 +571,16 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
 
             settingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY, pwmSetting.getKey() );
 
-            if ( StringUtil.notEmpty( profileID ) )
+            profileID.ifPresent( value ->
             {
-                settingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_PROFILE, profileID );
-            }
+                settingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_PROFILE, value.stringValue() );
+            } );
 
             settingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_SYNTAX, pwmSetting.getSyntax().name() );
 
             {
                 final XmlElement labelElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_LABEL );
-                labelElement.setText( pwmSetting.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) );
+                labelElement.setText( pwmSetting.toMenuLocationDebug( profileID.orElse( null ), PwmConstants.DEFAULT_LOCALE ) );
                 settingElement.attachElement( labelElement );
             }
 

+ 13 - 11
server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java

@@ -22,6 +22,7 @@ package password.pwm.config.stored;
 
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
@@ -75,7 +76,7 @@ public abstract class StoredConfigurationUtil
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationUtil.class );
 
-    public static List<String> profilesForSetting(
+    public static List<ProfileID> profilesForSetting(
             final DomainID domainID,
             final PwmSetting pwmSetting,
             final StoredConfiguration storedConfiguration
@@ -99,7 +100,7 @@ public abstract class StoredConfigurationUtil
         return profilesForProfileSetting( domainID, profileSetting, storedConfiguration );
     }
 
-    public static List<String> profilesForCategory(
+    public static List<ProfileID> profilesForCategory(
             final DomainID domainID,
             final PwmSettingCategory category,
             final StoredConfiguration storedConfiguration
@@ -109,7 +110,7 @@ public abstract class StoredConfigurationUtil
         return profilesForProfileSetting( domainID, profileSetting, storedConfiguration );
     }
 
-    private static List<String> profilesForProfileSetting(
+    private static List<ProfileID> profilesForProfileSetting(
             final DomainID domainID,
             final PwmSetting profileSetting,
             final StoredConfiguration storedConfiguration
@@ -120,6 +121,7 @@ public abstract class StoredConfigurationUtil
         final List<String> settingValues = ValueTypeConverter.valueToStringArray( storedValue );
         return settingValues.stream()
                 .filter( StringUtil::notEmpty )
+                .map( ProfileID::create )
                 .collect( Collectors.toUnmodifiableList() );
     }
 
@@ -165,7 +167,7 @@ public abstract class StoredConfigurationUtil
         final Function<StoredConfigKey, Stream<String>> validateSettingFunction = storedConfigItemKey ->
         {
             final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting();
-            final String profileID = storedConfigItemKey.getProfileID();
+            final Optional<ProfileID> profileID = storedConfigItemKey.getProfileID();
             final Optional<StoredValue> loopValue = storedConfiguration.readStoredValue( storedConfigItemKey );
 
             if ( loopValue.isPresent() )
@@ -175,13 +177,13 @@ public abstract class StoredConfigurationUtil
                     final List<String> errors = loopValue.get().validateValue( pwmSetting );
                     for ( final String loopError : errors )
                     {
-                        return Stream.of( pwmSetting.toMenuLocationDebug( storedConfigItemKey.getProfileID(), PwmConstants.DEFAULT_LOCALE ) + " - " + loopError );
+                        return Stream.of( pwmSetting.toMenuLocationDebug( profileID.orElse( null ), PwmConstants.DEFAULT_LOCALE ) + " - " + loopError );
                     }
                 }
                 catch ( final Exception e )
                 {
                     LOGGER.error( () -> "unexpected error during validate value for "
-                            + pwmSetting.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + ", error: "
+                            + pwmSetting.toMenuLocationDebug( profileID.orElse( null ), PwmConstants.DEFAULT_LOCALE ) + ", error: "
                             + e.getMessage(), e );
                 }
             }
@@ -379,8 +381,8 @@ public abstract class StoredConfigurationUtil
             final StoredConfiguration oldStoredConfiguration,
             final DomainID domainID,
             final PwmSettingCategory category,
-            final String sourceID,
-            final String destinationID,
+            final ProfileID sourceID,
+            final ProfileID destinationID,
             final UserIdentity userIdentity
     )
             throws PwmUnrecoverableException
@@ -393,7 +395,7 @@ public abstract class StoredConfigurationUtil
         }
 
         final PwmSetting profileSetting = category.getProfileSetting().orElseThrow( IllegalStateException::new );
-        final List<String> existingProfiles = StoredConfigurationUtil.profilesForSetting( domainID, profileSetting, oldStoredConfiguration );
+        final List<ProfileID> existingProfiles = StoredConfigurationUtil.profilesForSetting( domainID, profileSetting, oldStoredConfiguration );
         if ( !existingProfiles.contains( sourceID ) )
         {
             throw PwmUnrecoverableException.newException(
@@ -424,10 +426,10 @@ public abstract class StoredConfigurationUtil
         }
 
         {
-            final List<String> newProfileIDList = new ArrayList<>( existingProfiles );
+            final List<ProfileID> newProfileIDList = new ArrayList<>( existingProfiles );
             newProfileIDList.add( destinationID );
             final StoredConfigKey key = StoredConfigKey.forSetting( profileSetting, null, domainID );
-            final StoredValue value = StringArrayValue.create( newProfileIDList );
+            final StoredValue value = StringArrayValue.create( CollectionUtil.convertListType( newProfileIDList, ProfileID::toString ) );
             modifier.writeSetting( key, value, userIdentity );
         }
 

+ 14 - 9
server/src/main/java/password/pwm/config/value/StringValue.java

@@ -22,14 +22,15 @@ package password.pwm.config.value;
 
 import org.jrivard.xmlchai.XmlChai;
 import org.jrivard.xmlchai.XmlElement;
-import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingFlag;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.stored.StoredConfigXmlConstants;
 import password.pwm.config.stored.XmlOutputProcessData;
 import password.pwm.config.value.data.FormConfiguration;
+import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.secure.PwmSecurityKey;
@@ -122,15 +123,19 @@ public class StringValue extends AbstractValue implements StoredValue
 
         if ( StringUtil.notEmpty( value ) && pwmSetting.getSyntax() == PwmSettingSyntax.DOMAIN )
         {
-            final String lCaseValue = value.toLowerCase( PwmConstants.DEFAULT_LOCALE );
-            final List<String> reservedWords = DomainID.DOMAIN_RESERVED_WORDS;
-            final Optional<String> reservedWordMatch = reservedWords.stream()
-                    .map( String::toLowerCase )
-                    .filter( lCaseValue::contains )
-                    .findFirst();
-            if ( reservedWordMatch.isPresent() )
+            final List<String> errorStrings = DomainID.validateUserValue( value );
+            if ( !CollectionUtil.isEmpty( errorStrings ) )
             {
-                return Collections.singletonList( "contains reserved word '" + reservedWordMatch.get() + "'" );
+                return List.copyOf( errorStrings );
+            }
+        }
+
+        if ( StringUtil.notEmpty( value ) && pwmSetting.getSyntax() == PwmSettingSyntax.PROFILE )
+        {
+            final List<String> errorStrings = ProfileID.validateUserValue( value );
+            if ( !CollectionUtil.isEmpty( errorStrings ) )
+            {
+                return List.copyOf( errorStrings );
             }
         }
 

+ 15 - 18
server/src/main/java/password/pwm/config/value/ValueTypeConverter.java

@@ -32,11 +32,10 @@ import password.pwm.config.value.data.UserPermission;
 import password.pwm.util.PasswordData;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.CollectorUtil;
 import password.pwm.util.java.JavaHelper;
-import password.pwm.util.java.StringUtil;
 
 import java.security.cert.X509Certificate;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
@@ -166,7 +165,7 @@ public final class ValueTypeConverter
 
         if ( value instanceof CustomLinkValue )
         {
-            return ( List<FormConfiguration> ) value.toNativeObject();
+            return List.copyOf( ( List<FormConfiguration> ) value.toNativeObject() );
         }
 
         if ( ( !( value instanceof FormValue ) ) )
@@ -174,7 +173,7 @@ public final class ValueTypeConverter
             throw new IllegalArgumentException( "setting value is not readable as form" );
         }
 
-        return ( List<FormConfiguration> ) value.toNativeObject();
+        return List.copyOf( ( List<FormConfiguration> ) value.toNativeObject() );
     }
 
     public static List<String> valueToStringArray( final StoredValue value )
@@ -184,9 +183,8 @@ public final class ValueTypeConverter
             throw new IllegalArgumentException( "setting value is not readable as string array" );
         }
 
-        final List<String> results = new ArrayList<>( ( List<String> ) value.toNativeObject() );
-        results.removeIf( StringUtil::isEmpty );
-        return List.copyOf( results );
+        final List<String> results = ( List<String> ) value.toNativeObject();
+        return List.copyOf( CollectionUtil.stripNulls( results ) );
     }
 
     public static List<UserPermission> valueToUserPermissions( final StoredValue value )
@@ -198,9 +196,8 @@ public final class ValueTypeConverter
             throw new IllegalArgumentException( "setting value is not readable as string array" );
         }
 
-        final List<UserPermission> results = new ArrayList<>( ( List<UserPermission> ) value.toNativeObject() );
-        results.removeIf( Objects::isNull );
-        return List.copyOf( results );
+        final List<UserPermission> results = ( List<UserPermission> ) value.toNativeObject();
+        return List.copyOf( CollectionUtil.stripNulls( results ) );
     }
 
     public static Map<String, List<ChallengeItemConfiguration>> valueToChallengeItems( final StoredValue value )
@@ -212,7 +209,7 @@ public final class ValueTypeConverter
             throw new IllegalArgumentException( "setting value is not readable as challenge items" );
         }
 
-        return ( Map<String, List<ChallengeItemConfiguration>> ) value.toNativeObject();
+        return Map.copyOf( CollectionUtil.stripNulls( ( Map<String, List<ChallengeItemConfiguration>> ) value.toNativeObject() ) );
     }
 
     public static boolean valueToBoolean( final StoredValue value )
@@ -234,7 +231,7 @@ public final class ValueTypeConverter
 
         final Map<String, String> availableValues = ( Map<String, String> ) value.toNativeObject();
         final Map<Locale, String> availableLocaleMap = Collections.unmodifiableMap( availableValues.entrySet().stream()
-                .collect( CollectionUtil.collectorToLinkedMap(
+                .collect( CollectorUtil.toLinkedMap(
                     entry -> LocaleHelper.parseLocaleString( entry.getKey() ),
                         Map.Entry::getValue ) ) );
 
@@ -251,14 +248,14 @@ public final class ValueTypeConverter
         }
         final Map<String, List<String>> storedValues = ( Map<String, List<String>> ) value.toNativeObject();
 
-        final Map<Locale, List<String>> availableLocaleMap = Collections.unmodifiableMap( storedValues.entrySet().stream()
-                .collect( CollectionUtil.collectorToLinkedMap(
+        final Map<Locale, List<String>> availableLocaleMap = storedValues.entrySet().stream()
+                .collect( CollectorUtil.toLinkedMap(
                         entry -> LocaleHelper.parseLocaleString( entry.getKey() ),
-                        Map.Entry::getValue ) ) );
+                        Map.Entry::getValue ) );
 
         final Locale matchedLocale = LocaleHelper.localeResolver( locale, availableLocaleMap.keySet() );
 
-        return availableLocaleMap.get( matchedLocale );
+        return List.copyOf( availableLocaleMap.get( matchedLocale ) );
     }
 
     public static <E extends Enum<E>> E valueToEnum( final PwmSetting setting, final StoredValue value, final Class<E> enumClass )
@@ -281,9 +278,9 @@ public final class ValueTypeConverter
 
         final Map<String, EmailItemBean> storedValues =  ( Map<String, EmailItemBean> ) storedValue.toNativeObject();
 
-        return Collections.unmodifiableMap( storedValues.entrySet().stream().collect( CollectionUtil.collectorToLinkedMap(
+        return storedValues.entrySet().stream().collect( CollectorUtil.toUnmodifiableLinkedMap(
                 entry -> LocaleHelper.parseLocaleString( entry.getKey() ),
-                Map.Entry::getValue ) ) );
+                Map.Entry::getValue ) );
     }
 
     public static Map<FileValue.FileInformation, FileValue.FileContent> valueToFile( final PwmSetting setting, final StoredValue storedValue )

+ 5 - 3
server/src/main/java/password/pwm/config/value/data/UserPermission.java

@@ -23,6 +23,7 @@ package password.pwm.config.value.data;
 import lombok.Builder;
 import lombok.Value;
 import org.jetbrains.annotations.NotNull;
+import password.pwm.bean.ProfileID;
 import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.util.java.StringUtil;
 
@@ -39,7 +40,7 @@ public class UserPermission implements Serializable, Comparable<UserPermission>
     @Builder.Default
     private UserPermissionType type = UserPermissionType.ldapQuery;
 
-    private String ldapProfileID;
+    private ProfileID ldapProfileID;
     private String ldapQuery;
     private String ldapBase;
 
@@ -48,7 +49,7 @@ public class UserPermission implements Serializable, Comparable<UserPermission>
             Comparator.nullsLast( Comparator.naturalOrder() ) )
             .thenComparing(
                     UserPermission::getLdapProfileID,
-                    Comparator.nullsLast( Comparator.naturalOrder() ) )
+                    ProfileID.comparator() )
             .thenComparing(
                     UserPermission::getLdapBase,
                     Comparator.nullsLast( Comparator.naturalOrder() ) )
@@ -56,6 +57,7 @@ public class UserPermission implements Serializable, Comparable<UserPermission>
                     UserPermission::getLdapQuery,
                     Comparator.nullsLast( Comparator.naturalOrder() ) );
 
+
     public UserPermissionType getType( )
     {
         return type == null ? UserPermissionType.ldapQuery : type;
@@ -65,7 +67,7 @@ public class UserPermission implements Serializable, Comparable<UserPermission>
     {
         return getType().getLabel()
                 +  ": [Profile: "
-                + ( StringUtil.isEmpty( getLdapProfileID() ) ?  "All" : '\'' + this.getLdapProfileID() + '\'' )
+                + ( getLdapProfileID() == null ?  "All" : '\'' + this.getLdapProfileID().stringValue() + '\'' )
                 + ( StringUtil.isEmpty( getLdapBase() ) ?  "" : " " + getType().getBaseLabel() + ": " + this.getLdapBase() )
                 + ( StringUtil.isEmpty( getLdapQuery() ) ?  "" : " Filter: " + this.getLdapQuery() )
                 + "]";

+ 1 - 1
server/src/main/java/password/pwm/health/CertificateChecker.java

@@ -155,7 +155,7 @@ public class CertificateChecker implements HealthSupplier
                     final HealthRecord record = HealthRecord.forMessage(
                             storedConfigKey.getDomainID(),
                             HealthMessage.Config_Certificate,
-                            storedConfigKey.toPwmSetting().toMenuLocationDebug( storedConfigKey.getProfileID(), PwmConstants.DEFAULT_LOCALE ),
+                            storedConfigKey.toPwmSetting().toMenuLocationDebug( storedConfigKey.getProfileID().orElse( null ), PwmConstants.DEFAULT_LOCALE ),
                             errorDetail
                     );
                     returnList.add( record );

+ 33 - 27
server/src/main/java/password/pwm/health/ConfigurationChecker.java

@@ -28,6 +28,7 @@ import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmEnvironment;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.AppConfig;
 import password.pwm.config.DomainConfig;
@@ -41,6 +42,7 @@ import password.pwm.config.profile.ForgottenPasswordProfile;
 import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.NewUserProfile;
+import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfiguration;
@@ -202,7 +204,7 @@ public class ConfigurationChecker implements HealthSupplier
                         records.add( HealthRecord.forMessage(
                                 config.getDomainID(),
                                 HealthMessage.NewUser_PwTemplateBad,
-                                PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( newUserProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                                PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( newUserProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
                                 e.getMessage() ) );
                     }
                 }
@@ -286,7 +288,7 @@ public class ConfigurationChecker implements HealthSupplier
                     records.add( HealthRecord.forMessage(
                             config.getDomainID(),
                             HealthMessage.Config_AddTestUser,
-                            PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), locale )
+                            PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), locale )
                     ) );
                 }
             }
@@ -307,7 +309,7 @@ public class ConfigurationChecker implements HealthSupplier
                                 records.add( HealthRecord.forMessage(
                                         config.getDomainID(),
                                         HealthMessage.Config_LDAPUnsecure,
-                                        PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug( ldapProfile.getIdentifier(), locale )
+                                        PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug( ldapProfile.getId(), locale )
                                 ) );
                             }
                         }
@@ -317,7 +319,7 @@ public class ConfigurationChecker implements HealthSupplier
                                     config.getDomainID(),
                                     HealthMessage.Config_ParseError,
                                     e.getMessage(),
-                                    PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug( ldapProfile.getIdentifier(), locale ),
+                                    PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug( ldapProfile.getId(), locale ),
                                     urlStringValue
                             ) );
                         }
@@ -377,7 +379,7 @@ public class ConfigurationChecker implements HealthSupplier
                         return Optional.of( HealthRecord.forMessage(
                                 domainHealthCheckRequest.getDomainConfig().getDomainID(),
                                 HealthMessage.Config_WeakPassword,
-                                pwmSetting.toMenuLocationDebug( key.getProfileID(), domainHealthCheckRequest.getLocale() ), String.valueOf( strength ) ) );
+                                pwmSetting.toMenuLocationDebug( key.getProfileID().orElse( null ), domainHealthCheckRequest.getLocale() ), String.valueOf( strength ) ) );
                     }
                 }
             }
@@ -413,7 +415,7 @@ public class ConfigurationChecker implements HealthSupplier
                                     config.getDomainID(),
                                     HealthMessage.Config_MissingLDAPResponseAttr,
                                     loopSetting.toMenuLocationDebug( null, locale ),
-                                    PwmSetting.CHALLENGE_USER_ATTRIBUTE.toMenuLocationDebug( ldapProfile.getIdentifier(), locale )
+                                    PwmSetting.CHALLENGE_USER_ATTRIBUTE.toMenuLocationDebug( ldapProfile.getId(), locale )
                             ) );
                         }
                     }
@@ -531,7 +533,7 @@ public class ConfigurationChecker implements HealthSupplier
             final Locale locale = domainHealthCheckRequest.getLocale();
 
             final List<HealthRecord> records = new ArrayList<>();
-            for ( final String profileID : config.getPasswordProfileIDs() )
+            for ( final ProfileID profileID : config.getPasswordProfileIDs() )
             {
                 try
                 {
@@ -558,17 +560,18 @@ public class ConfigurationChecker implements HealthSupplier
             final List<HealthRecord> records = new ArrayList<>();
             for ( final NewUserProfile newUserProfile : config.getNewUserProfiles().values() )
             {
-                final String configuredProfile = newUserProfile.readSettingAsString( PwmSetting.NEWUSER_LDAP_PROFILE );
-                if ( StringUtil.notEmpty( configuredProfile ) )
+                final Optional<ProfileID> configuredProfile = config.profileForStringId( ProfileDefinition.NewUser,
+                        newUserProfile.readSettingAsString( PwmSetting.NEWUSER_LDAP_PROFILE ) );
+                if ( configuredProfile.isPresent() )
                 {
-                    final LdapProfile ldapProfile = config.getLdapProfiles().get( configuredProfile );
+                    final LdapProfile ldapProfile = config.getLdapProfiles().get( configuredProfile.get() );
 
                     if ( ldapProfile == null )
                     {
                         records.add( HealthRecord.forMessage(
                                 config.getDomainID(),
                                 HealthMessage.Config_InvalidLdapProfile,
-                                PwmSetting.NEWUSER_LDAP_PROFILE.toMenuLocationDebug( newUserProfile.getIdentifier(), locale ) ) );
+                                PwmSetting.NEWUSER_LDAP_PROFILE.toMenuLocationDebug( newUserProfile.getId(), locale ) ) );
                     }
                 }
             }
@@ -603,7 +606,7 @@ public class ConfigurationChecker implements HealthSupplier
                         records.add( HealthRecord.forMessage(
                                 config.getDomainID(),
                                 HealthMessage.Config_DeprecatedJSForm,
-                                loopSetting.toMenuLocationDebug( key.getProfileID(), locale ),
+                                loopSetting.toMenuLocationDebug( key.getProfileID().orElse( null ), locale ),
                                 PwmSetting.DISPLAY_CUSTOM_JAVASCRIPT.toMenuLocationDebug( null, locale )
                         ) );
                     }
@@ -638,7 +641,7 @@ public class ConfigurationChecker implements HealthSupplier
                             config.getDomainID(),
                             HealthMessage.Config_InvalidSendMethod,
                             method.toString(),
-                            PwmSetting.ACTIVATE_TOKEN_SEND_METHOD.toMenuLocationDebug( activationProfile.getIdentifier(), locale )
+                            PwmSetting.ACTIVATE_TOKEN_SEND_METHOD.toMenuLocationDebug( activationProfile.getId(), locale )
                     ) );
                 }
             }
@@ -666,7 +669,7 @@ public class ConfigurationChecker implements HealthSupplier
                             config.getDomainID(),
                             HealthMessage.Config_InvalidSendMethod,
                             method.toString(),
-                            PwmSetting.HELPDESK_TOKEN_SEND_METHOD.toMenuLocationDebug( helpdeskProfile.getIdentifier(), locale )
+                            PwmSetting.HELPDESK_TOKEN_SEND_METHOD.toMenuLocationDebug( helpdeskProfile.getId(), locale )
                     ) );
                 }
             }
@@ -682,7 +685,7 @@ public class ConfigurationChecker implements HealthSupplier
                                 config.getDomainID(),
                                 HealthMessage.Config_InvalidSendMethod,
                                 method.toString(),
-                                PwmSetting.RECOVERY_SENDNEWPW_METHOD.toMenuLocationDebug( forgottenPasswordProfile.getIdentifier(), locale )
+                                PwmSetting.RECOVERY_SENDNEWPW_METHOD.toMenuLocationDebug( forgottenPasswordProfile.getId(), locale )
                         ) );
                     }
                 }
@@ -695,7 +698,7 @@ public class ConfigurationChecker implements HealthSupplier
                                 config.getDomainID(),
                                 HealthMessage.Config_InvalidSendMethod,
                                 method.toString(),
-                                PwmSetting.RECOVERY_TOKEN_SEND_METHOD.toMenuLocationDebug( forgottenPasswordProfile.getIdentifier(), locale )
+                                PwmSetting.RECOVERY_TOKEN_SEND_METHOD.toMenuLocationDebug( forgottenPasswordProfile.getId(), locale )
                         ) );
                     }
                 }
@@ -721,7 +724,7 @@ public class ConfigurationChecker implements HealthSupplier
                 final long maxValue = changePasswordProfile.readSettingAsLong( PwmSetting.PASSWORD_SYNC_MAX_WAIT_TIME );
                 if ( maxValue > 0 && minValue > maxValue )
                 {
-                    final String profileID = changePasswordProfile.getIdentifier();
+                    final ProfileID profileID = changePasswordProfile.getId();
                     final String detailMsg = " (" + minValue + ")"
                             + " > "
                             + " (" + maxValue + ")";
@@ -771,7 +774,7 @@ public class ConfigurationChecker implements HealthSupplier
                         records.add( HealthRecord.forMessage(
                                 config.getDomainID(),
                                 HealthMessage.Config_SettingIssue,
-                                pwmSetting.toMenuLocationDebug( configItemKey.getProfileID(), locale ),
+                                pwmSetting.toMenuLocationDebug( configItemKey.getProfileID().orElse( null ), locale ),
                                 e.getMessage() ) );
                     }
 
@@ -789,21 +792,24 @@ public class ConfigurationChecker implements HealthSupplier
                 final UserPermission permission
         )
         {
-            final List<LdapProfile> ldapProfiles = ldapProfilesForLdapProfileSetting( domainConfig, permission.getLdapProfileID() );
-            if ( ldapProfiles.isEmpty()  )
+            if ( permission.getLdapProfileID() != null )
             {
-                final PwmSetting pwmSetting = storedConfigKey.toPwmSetting();
-                return Collections.singletonList( HealthRecord.forMessage(
-                        domainConfig.getDomainID(),
-                        HealthMessage.Config_ProfileValueValidity,
-                        pwmSetting.toMenuLocationDebug( storedConfigKey.getProfileID(), locale ),
-                        permission.getLdapProfileID() ) );
+                final List<LdapProfile> ldapProfiles = ldapProfilesForLdapProfileSetting( domainConfig, permission.getLdapProfileID() );
+                if ( ldapProfiles.isEmpty() )
+                {
+                    final PwmSetting pwmSetting = storedConfigKey.toPwmSetting();
+                    return Collections.singletonList( HealthRecord.forMessage(
+                            domainConfig.getDomainID(),
+                            HealthMessage.Config_ProfileValueValidity,
+                            pwmSetting.toMenuLocationDebug( storedConfigKey.getProfileID().orElse( null ), locale ),
+                            permission.getLdapProfileID().stringValue() ) );
+                }
             }
 
             return Collections.emptyList();
         }
 
-        public static List<LdapProfile> ldapProfilesForLdapProfileSetting( final DomainConfig domainConfig, final String profileID )
+        public static List<LdapProfile> ldapProfilesForLdapProfileSetting( final DomainConfig domainConfig, final ProfileID profileID )
         {
             if ( UserPermissionUtility.isAllProfiles( profileID ) )
             {

+ 41 - 41
server/src/main/java/password/pwm/health/LDAPHealthChecker.java

@@ -36,6 +36,7 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.PasswordStatus;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
@@ -103,11 +104,11 @@ public class LDAPHealthChecker implements HealthSupplier
     {
         final DomainConfig config = pwmDomain.getConfig();
         final List<HealthRecord> returnRecords = new ArrayList<>();
-        final Map<String, LdapProfile> ldapProfiles = pwmDomain.getConfig().getLdapProfiles();
+        final Map<ProfileID, LdapProfile> ldapProfiles = pwmDomain.getConfig().getLdapProfiles();
 
-        for ( final Map.Entry<String, LdapProfile> entry : ldapProfiles.entrySet() )
+        for ( final Map.Entry<ProfileID, LdapProfile> entry : ldapProfiles.entrySet() )
         {
-            final String profileID = entry.getKey();
+            final ProfileID profileID = entry.getKey();
             final List<HealthRecord> profileRecords = new ArrayList<>(
                     checkBasicLdapConnectivity( sessionLabel, pwmDomain, config, entry.getValue(), true )
             );
@@ -125,7 +126,7 @@ public class LDAPHealthChecker implements HealthSupplier
             returnRecords.addAll( profileRecords );
         }
 
-        for ( final Map.Entry<String, ErrorInformation> entry : pwmDomain.getLdapService().getLastLdapFailure().entrySet() )
+        for ( final Map.Entry<ProfileID, ErrorInformation> entry : pwmDomain.getLdapService().getLastLdapFailure().entrySet() )
         {
             final ErrorInformation errorInfo = entry.getValue();
             final LdapProfile ldapProfile = pwmDomain.getConfig().getLdapProfiles().get( entry.getKey() );
@@ -164,7 +165,7 @@ public class LDAPHealthChecker implements HealthSupplier
 
                 returnRecords.addAll( checkNewUserPasswordTemplateSetting( sessionLabel, pwmDomain, config ) );
 
-     //           returnRecords.addAll( checkUserSearching( pwmApplication ) );
+                //           returnRecords.addAll( checkUserSearching( pwmApplication ) );
             }
         }
 
@@ -201,7 +202,7 @@ public class LDAPHealthChecker implements HealthSupplier
             LOGGER.trace( sessionLabel, () -> "unexpected error while testing test user (during object creation): message="
                     + msgString + " debug info: " + JavaHelper.readHostileExceptionMessage( e ) );
             returnRecords.add( HealthRecord.forMessage( pwmDomain.getDomainID(), HealthMessage.LDAP_TestUserUnexpected,
-                    PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                    PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
                     msgString
             ) );
             return returnRecords;
@@ -212,8 +213,8 @@ public class LDAPHealthChecker implements HealthSupplier
             returnRecords.add( HealthRecord.forMessage(
                     pwmDomain.getDomainID(),
                     HealthMessage.LDAP_ProxyTestSameUser,
-                    PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
-                    PwmSetting.LDAP_PROXY_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE )
+                    PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
+                    PwmSetting.LDAP_PROXY_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE )
             ) );
             return returnRecords;
         }
@@ -243,7 +244,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 returnRecords.add( HealthRecord.forMessage(
                         pwmDomain.getDomainID(),
                         HealthMessage.LDAP_TestUserUnavailable,
-                        PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                        PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
                         e.getMessage()
                 ) );
                 return returnRecords;
@@ -259,7 +260,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 returnRecords.add( HealthRecord.forMessage(
                         pwmDomain.getDomainID(),
                         HealthMessage.LDAP_TestUserUnexpected,
-                        PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                        PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
                         msgString
                 ) );
                 return returnRecords;
@@ -274,7 +275,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 returnRecords.add( HealthRecord.forMessage(
                         pwmDomain.getDomainID(),
                         HealthMessage.LDAP_TestUserError,
-                        PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                        PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
                         e.getMessage()
                 ) );
                 return returnRecords;
@@ -283,7 +284,7 @@ public class LDAPHealthChecker implements HealthSupplier
             LOGGER.trace(
                     sessionLabel,
                     () -> "beginning process to check ldap test user password read/write operations for profile "
-                            + ldapProfile.getIdentifier()
+                            + ldapProfile.getId()
             );
             try
             {
@@ -303,7 +304,7 @@ public class LDAPHealthChecker implements HealthSupplier
                                 pwmDomain.getDomainID(),
                                 HealthMessage.LDAP_TestUserReadPwError,
                                 PwmSetting.EDIRECTORY_READ_USER_PWD.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ),
-                                PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                                PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
                                 e.getMessage()
                         ) );
                         return returnRecords;
@@ -312,7 +313,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 else
                 {
                     final Locale locale = PwmConstants.DEFAULT_LOCALE;
-                    final UserIdentity userIdentity = UserIdentity.create( testUserDN, ldapProfile.getIdentifier(), pwmDomain.getDomainID() );
+                    final UserIdentity userIdentity = UserIdentity.create( testUserDN, ldapProfile.getId(), pwmDomain.getDomainID() );
 
                     final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser(
                             pwmDomain, sessionLabel, userIdentity, theUser );
@@ -368,7 +369,7 @@ public class LDAPHealthChecker implements HealthSupplier
                             returnRecords.add( HealthRecord.forMessage(
                                     pwmDomain.getDomainID(),
                                     HealthMessage.LDAP_TestUserWritePwError,
-                                    PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                                    PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
                                     e.getMessage()
                             ) );
                             return returnRecords;
@@ -384,7 +385,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 returnRecords.add( HealthRecord.forMessage(
                         pwmDomain.getDomainID(),
                         HealthMessage.LDAP_TestUserUnexpected,
-                        PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                        PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
                         msg
                 ) );
                 return returnRecords;
@@ -392,7 +393,7 @@ public class LDAPHealthChecker implements HealthSupplier
 
             try
             {
-                final UserIdentity userIdentity = UserIdentity.create( theUser.getEntryDN(), ldapProfile.getIdentifier(), pwmDomain.getDomainID() );
+                final UserIdentity userIdentity = UserIdentity.create( theUser.getEntryDN(), ldapProfile.getId(), pwmDomain.getDomainID() );
                 final UserInfo userInfo = UserInfoFactory.newUserInfo(
                         pwmDomain.getPwmApplication(),
                         sessionLabel,
@@ -417,7 +418,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 returnRecords.add( HealthRecord.forMessage(
                         pwmDomain.getDomainID(),
                         HealthMessage.LDAP_TestUserError,
-                        PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                        PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
                         "unable to read test user data: " + e.getMessage() ) );
                 return returnRecords;
             }
@@ -521,20 +522,20 @@ public class LDAPHealthChecker implements HealthSupplier
                 final PasswordData proxyPW = ldapProfile.readSettingAsPassword( PwmSetting.LDAP_PROXY_USER_PASSWORD );
                 if ( proxyDN == null || proxyDN.length() < 1 )
                 {
-                    final String menuLocationStr = PwmSetting.LDAP_PROXY_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE );
+                    final String menuLocationStr = PwmSetting.LDAP_PROXY_USER_DN.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE );
                     return Collections.singletonList( HealthRecord.forMessage(
                             pwmDomain.getDomainID(),
                             HealthMessage.LDAP_No_Connection,
-                             ldapProfile.getIdentifier(),
-                             "Missing Proxy User DN: " + menuLocationStr ) );
+                            ldapProfile.getId().stringValue(),
+                            "Missing Proxy User DN: " + menuLocationStr ) );
                 }
                 if ( proxyPW == null )
                 {
-                    final String menuLocationStr = PwmSetting.LDAP_PROXY_USER_PASSWORD.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE );
+                    final String menuLocationStr = PwmSetting.LDAP_PROXY_USER_PASSWORD.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE );
                     return Collections.singletonList( HealthRecord.forMessage(
                             pwmDomain.getDomainID(),
                             HealthMessage.LDAP_No_Connection,
-                            ldapProfile.getIdentifier(),
+                            ldapProfile.getId().stringValue(),
                             "Missing Proxy User Password: " + menuLocationStr ) );
                 }
                 chaiProvider = LdapOperationsHelper.createChaiProvider( pwmDomain, sessionLabel, ldapProfile, config, proxyDN, proxyPW );
@@ -566,7 +567,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 final ChaiError chaiError = ChaiErrors.getErrorForMessage( e.getMessage() );
                 final PwmError pwmError = PwmError.forChaiError( chaiError ).orElse( PwmError.ERROR_INTERNAL );
                 final StringBuilder errorString = new StringBuilder();
-                final String profileName = ldapProfile.getIdentifier();
+                final ProfileID profileName = ldapProfile.getId();
                 errorString.append( "error connecting to ldap directory (" ).append( profileName ).append( "), error: " ).append( e.getMessage() );
                 if ( chaiError != null && chaiError != ChaiError.UNKNOWN )
                 {
@@ -582,7 +583,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 returnRecords.add( HealthRecord.forMessage(
                         pwmDomain.getDomainID(),
                         HealthMessage.LDAP_No_Connection,
-                        ldapProfile.getIdentifier(),
+                        ldapProfile.getId().stringValue(),
                         errorString.toString() ) );
 
                 pwmDomain.getLdapService().setLastLdapFailure( ldapProfile,
@@ -618,24 +619,24 @@ public class LDAPHealthChecker implements HealthSupplier
                         if ( objectClasses == null || objectClasses.isEmpty() )
                         {
                             final String errorString = "ldap context setting '"
-                                    + PwmSetting.LDAP_CONTEXTLESS_ROOT.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE )
+                                    + PwmSetting.LDAP_CONTEXTLESS_ROOT.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE )
                                     + "' value '" + loopContext + "' is not valid";
                             returnRecords.add( HealthRecord.forMessage(
                                     pwmDomain.getDomainID(),
                                     HealthMessage.LDAP_No_Connection,
-                                    ldapProfile.getIdentifier(),
+                                    ldapProfile.getId().stringValue(),
                                     errorString ) );
                         }
                     }
                     catch ( final Exception e )
                     {
                         final String errorString = "ldap context setting '"
-                                + PwmSetting.LDAP_CONTEXTLESS_ROOT.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE )
+                                + PwmSetting.LDAP_CONTEXTLESS_ROOT.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE )
                                 + "' value '" + loopContext + "' is not valid: " + e.getMessage();
                         returnRecords.add( HealthRecord.forMessage(
                                 pwmDomain.getDomainID(),
                                 HealthMessage.LDAP_No_Connection,
-                                ldapProfile.getIdentifier(),
+                                ldapProfile.getId().stringValue(),
                                 errorString ) );
                     }
                 }
@@ -697,7 +698,7 @@ public class LDAPHealthChecker implements HealthSupplier
                         pwmDomain.getDomainID(),
                         HealthMessage.Config_ParseError,
                         e.getMessage(),
-                        PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
+                        PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug( ldapProfile.getId(), PwmConstants.DEFAULT_LOCALE ),
                         loopURL
                 ) );
             }
@@ -906,7 +907,7 @@ public class LDAPHealthChecker implements HealthSupplier
                         && pwmSetting.getFlags().contains( PwmSettingFlag.ldapDnSyntax )
                 )
                 {
-                    for ( final String profile : config.getLdapProfiles().keySet() )
+                    for ( final ProfileID profile : config.getLdapProfiles().keySet() )
                     {
                         if ( pwmSetting.getSyntax() == PwmSettingSyntax.STRING )
                         {
@@ -971,7 +972,7 @@ public class LDAPHealthChecker implements HealthSupplier
                         HealthRecord.forMessage(
                                 pwmDomain.getDomainID(),
                                 HealthMessage.NewUser_PwTemplateBad,
-                                PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( newUserProfile.getIdentifier(), locale ),
+                                PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( newUserProfile.getId(), locale ),
                                 "Value missing"
                         )
                 );
@@ -989,7 +990,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 }
                 else
                 {
-                    final UserIdentity newUserTemplateIdentity = UserIdentity.create( policyUserStr, ldapProfile.getIdentifier(), pwmDomain.getDomainID() );
+                    final UserIdentity newUserTemplateIdentity = UserIdentity.create( policyUserStr, ldapProfile.getId(), pwmDomain.getDomainID() );
                     final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( sessionLabel, newUserTemplateIdentity );
 
                     try
@@ -1000,7 +1001,7 @@ public class LDAPHealthChecker implements HealthSupplier
                                     HealthRecord.forMessage(
                                             pwmDomain.getDomainID(),
                                             HealthMessage.NewUser_PwTemplateBad,
-                                            PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( newUserProfile.getIdentifier(), locale ),
+                                            PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( newUserProfile.getId(), locale ),
                                             "userDN value is not valid"
                                     )
                             );
@@ -1081,12 +1082,11 @@ public class LDAPHealthChecker implements HealthSupplier
         final String settingDebugName = pwmSetting.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
         final List<HealthRecord> returnList = new ArrayList<>();
         final DomainConfig config = pwmDomain.getConfig();
-        final List<String> ldapProfilesToCheck = new ArrayList<>();
+        final List<ProfileID> ldapProfilesToCheck = new ArrayList<>();
         {
-            final String configuredLdapProfileID = userPermission.getLdapProfileID();
+            final ProfileID configuredLdapProfileID = userPermission.getLdapProfileID();
             if ( configuredLdapProfileID == null
-                    || configuredLdapProfileID.isEmpty()
-                    || PwmConstants.PROFILE_ID_ALL.equals( configuredLdapProfileID ) )
+                    || ProfileID.PROFILE_ID_ALL.equals( configuredLdapProfileID ) )
             {
                 ldapProfilesToCheck.addAll( config.getLdapProfiles().keySet() );
             }
@@ -1109,7 +1109,7 @@ public class LDAPHealthChecker implements HealthSupplier
             }
         }
 
-        for ( final String ldapProfileID : ldapProfilesToCheck )
+        for ( final ProfileID ldapProfileID : ldapProfilesToCheck )
         {
             switch ( userPermission.getType() )
             {
@@ -1171,7 +1171,7 @@ public class LDAPHealthChecker implements HealthSupplier
             final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final String dnValue,
-            final String ldapProfileID
+            final ProfileID ldapProfileID
     )
             throws PwmUnrecoverableException
     {
@@ -1239,7 +1239,7 @@ public class LDAPHealthChecker implements HealthSupplier
             final PwmDomain pwmDomain,
             final DomainConfig config,
             final Locale locale,
-            final String profileID,
+            final ProfileID profileID,
             final boolean testContextless,
             final boolean fullTest
 

+ 4 - 3
server/src/main/java/password/pwm/http/ClientConnectionHolder.java

@@ -26,6 +26,7 @@ import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.PwmApplication;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.AppConfig;
@@ -59,7 +60,7 @@ public class ClientConnectionHolder
     private final AuthenticationType authenticationType;
 
     private ChaiProvider actorChaiProvider;
-    private final Map<DomainID, Map<String, ChaiProvider>> proxyChaiProviders = new HashMap<>();
+    private final Map<DomainID, Map<ProfileID, ChaiProvider>> proxyChaiProviders = new HashMap<>();
     private final Map<PwmHttpClientConfiguration, PwmHttpClient> httpClients = new HashMap<>();
 
     private ClientConnectionHolder(
@@ -121,10 +122,10 @@ public class ClientConnectionHolder
     public ChaiProvider getProxyChaiProvider( final LdapProfile ldapProfile )
             throws PwmUnrecoverableException
     {
-        return getProxyChaiProvider( ldapProfile.getIdentifier() );
+        return getProxyChaiProvider( ldapProfile.getId() );
     }
 
-    public ChaiProvider getProxyChaiProvider( final String ldapProfileID )
+    public ChaiProvider getProxyChaiProvider( final ProfileID ldapProfileID )
             throws PwmUnrecoverableException
     {
         final PwmDomain pwmDomain = pwmApplication.domains().get( domainID );

+ 1 - 1
server/src/main/java/password/pwm/http/ContextManager.java

@@ -737,7 +737,7 @@ public class ContextManager implements Serializable
                             }
                             final StoredValue storedValue = X509CertificateValue.fromX509( certs );
 
-                            final StoredConfigKey key = StoredConfigKey.forSetting( PwmSetting.LDAP_SERVER_CERTS, ldapProfile.getIdentifier(), domainConfig.getDomainID() );
+                            final StoredConfigKey key = StoredConfigKey.forSetting( PwmSetting.LDAP_SERVER_CERTS, ldapProfile.getId(), domainConfig.getDomainID() );
                             modifiedConfig.writeSetting( key, storedValue, null );
                         }
                     }

+ 7 - 7
server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java

@@ -26,6 +26,7 @@ import password.pwm.Permission;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.HelpdeskProfile;
@@ -35,7 +36,6 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.user.UserInfo;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
@@ -83,7 +83,7 @@ public class IdleTimeoutCalculator
                     if ( peopleSearchIdleTimeout > 0 )
                     {
                         results.add( new MaxIdleTimeoutResult(
-                                MaxIdleTimeoutResult.reasonFor( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS, publicProfile.getIdentifier() ),
+                                MaxIdleTimeoutResult.reasonFor( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS, publicProfile.getId() ),
                                 TimeDuration.of( peopleSearchIdleTimeout, TimeDuration.Unit.SECONDS ) ) );
                     }
                 }
@@ -117,8 +117,8 @@ public class IdleTimeoutCalculator
 
         if ( domainConfig.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE ) )
         {
-            final String helpdeskProfileID = userInfo.getProfileIDs().get( ProfileDefinition.Helpdesk );
-            if ( StringUtil.notEmpty( helpdeskProfileID ) )
+            final ProfileID helpdeskProfileID = userInfo.getProfileIDs().get( ProfileDefinition.Helpdesk );
+            if ( helpdeskProfileID != null )
             {
                 final HelpdeskProfile helpdeskProfile = domainConfig.getHelpdeskProfiles().get( helpdeskProfileID );
                 final long helpdeskIdleTimeout = helpdeskProfile.readSettingAsLong( PwmSetting.HELPDESK_IDLE_TIMEOUT_SECONDS );
@@ -130,8 +130,8 @@ public class IdleTimeoutCalculator
 
         if ( domainConfig.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE ) )
         {
-            final String peopleSearchID = userInfo.getProfileIDs().get( ProfileDefinition.PeopleSearch );
-            if ( StringUtil.notEmpty( peopleSearchID ) )
+            final ProfileID peopleSearchID = userInfo.getProfileIDs().get( ProfileDefinition.PeopleSearch );
+            if ( peopleSearchID != null )
             {
                 final PeopleSearchProfile peopleSearchProfile = domainConfig.getPeopleSearchProfiles().get( peopleSearchID );
                 final long peopleSearchIdleTimeout = peopleSearchProfile.readSettingAsLong( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS );
@@ -167,7 +167,7 @@ public class IdleTimeoutCalculator
             return this.idleTimeout.compareTo( o.getIdleTimeout() );
         }
 
-        static Supplier<String> reasonFor( final PwmSetting pwmSetting, final String profileID )
+        static Supplier<String> reasonFor( final PwmSetting pwmSetting, final ProfileID profileID )
         {
             return () -> "Setting " + pwmSetting.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE );
         }

+ 2 - 1
server/src/main/java/password/pwm/http/PwmRequest.java

@@ -31,6 +31,7 @@ import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.AppConfig;
@@ -578,7 +579,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
             throw new IllegalStateException( "can not read authenticated profile while session is unauthenticated" );
         }
 
-        final String profileID = getPwmSession().getUserInfo().getProfileIDs().get( profileDefinition );
+        final ProfileID profileID = getPwmSession().getUserInfo().getProfileIDs().get( profileDefinition );
         if ( profileID != null )
         {
             return pwmDomain.getConfig().getProfileMap( profileDefinition ).get( profileID );

+ 8 - 11
server/src/main/java/password/pwm/http/PwmRequestUtil.java

@@ -26,7 +26,6 @@ import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import org.apache.commons.validator.routines.InetAddressValidator;
 import password.pwm.Permission;
 import password.pwm.PwmDomain;
-import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.AppConfig;
@@ -106,12 +105,17 @@ public class PwmRequestUtil
     {
         final SessionLabel.SessionLabelBuilder builder = SessionLabel.builder();
 
+        builder.sourceAddress( pwmRequest.getSrcAddress().orElse( null ) );
+        builder.sourceHostname( pwmRequest.getSrcHostname().orElse( null ) );
+        builder.requestID( pwmRequest.getPwmRequestID() );
+        builder.domain( pwmRequest.getDomainID().stringValue() );
+
         if ( pwmRequest.hasSession() )
         {
             final PwmSession pwmSession = pwmRequest.getPwmSession();
-            final LocalSessionStateBean ssBean = pwmSession.getSessionStateBean();
+            builder.sessionID( pwmSession.getSessionStateBean().getSessionID() );
 
-            if ( pwmSession.isAuthenticated() )
+            if ( pwmRequest.isAuthenticated() )
             {
                 try
                 {
@@ -119,22 +123,15 @@ public class PwmRequestUtil
                     final UserIdentity userIdentity = userInfo.getUserIdentity();
 
                     builder.username( userInfo.getUsername() );
-                    builder.profile( userIdentity == null ? null : userIdentity.getLdapProfileID() );
+                    builder.profile( userIdentity == null ? null : userIdentity.getLdapProfileID().stringValue() );
                 }
                 catch ( final PwmUnrecoverableException e )
                 {
                     LOGGER.error( () -> "unexpected error reading username: " + e.getMessage(), e );
                 }
             }
-
-            builder.sessionID( ssBean.getSessionID() );
         }
 
-        builder.sourceAddress( pwmRequest.getSrcAddress().orElse( null ) );
-        builder.sourceHostname( pwmRequest.getSrcHostname().orElse( null ) );
-        builder.requestID( pwmRequest.getPwmRequestID() );
-        builder.domain( pwmRequest.getDomainID().stringValue() );
-
         return builder.build();
     }
 

+ 2 - 1
server/src/main/java/password/pwm/http/bean/ActivateUserBean.java

@@ -23,6 +23,7 @@ package password.pwm.http.bean;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.option.SessionBeanMode;
@@ -53,7 +54,7 @@ public class ActivateUserBean extends PwmSessionBean
     private TokenDestinationItem tokenDestination;
 
     @SerializedName( "p" )
-    private String profileID;
+    private ProfileID profileID;
 
 
     @Override

+ 4 - 91
server/src/main/java/password/pwm/http/bean/ChangePasswordBean.java

@@ -21,6 +21,8 @@
 package password.pwm.http.bean;
 
 import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.ldap.PasswordChangeProgressChecker;
 
@@ -32,9 +34,10 @@ import java.util.Set;
 /**
  * @author Jason D. Rivard
  */
+@Data
+@EqualsAndHashCode( callSuper = false )
 public class ChangePasswordBean extends PwmSessionBean
 {
-
     @SerializedName( "ap" )
     private boolean agreementPassed;
 
@@ -62,96 +65,6 @@ public class ChangePasswordBean extends PwmSessionBean
     @SerializedName( "mc" )
     private Instant changePasswordMaxCompletion;
 
-    public boolean isAgreementPassed( )
-    {
-        return agreementPassed;
-    }
-
-    public void setAgreementPassed( final boolean agreementPassed )
-    {
-        this.agreementPassed = agreementPassed;
-    }
-
-    public boolean isCurrentPasswordRequired( )
-    {
-        return currentPasswordRequired;
-    }
-
-    public void setCurrentPasswordRequired( final boolean currentPasswordRequired )
-    {
-        this.currentPasswordRequired = currentPasswordRequired;
-    }
-
-    public boolean isCurrentPasswordPassed( )
-    {
-        return currentPasswordPassed;
-    }
-
-    public void setCurrentPasswordPassed( final boolean currentPasswordPassed )
-    {
-        this.currentPasswordPassed = currentPasswordPassed;
-    }
-
-    public boolean isFormPassed( )
-    {
-        return formPassed;
-    }
-
-    public void setFormPassed( final boolean formPassed )
-    {
-        this.formPassed = formPassed;
-    }
-
-    public boolean isAllChecksPassed( )
-    {
-        return allChecksPassed;
-    }
-
-    public void setAllChecksPassed( final boolean allChecksPassed )
-    {
-        this.allChecksPassed = allChecksPassed;
-    }
-
-    public PasswordChangeProgressChecker.ProgressTracker getChangeProgressTracker( )
-    {
-        return changeProgressTracker;
-    }
-
-    public void setChangeProgressTracker( final PasswordChangeProgressChecker.ProgressTracker changeProgressTracker )
-    {
-        this.changeProgressTracker = changeProgressTracker;
-    }
-
-    public Instant getChangePasswordMaxCompletion( )
-    {
-        return changePasswordMaxCompletion;
-    }
-
-    public void setChangePasswordMaxCompletion( final Instant changePasswordMaxCompletion )
-    {
-        this.changePasswordMaxCompletion = changePasswordMaxCompletion;
-    }
-
-    public boolean isNextAllowedTimePassed( )
-    {
-        return nextAllowedTimePassed;
-    }
-
-    public void setNextAllowedTimePassed( final boolean nextAllowedTimePassed )
-    {
-        this.nextAllowedTimePassed = nextAllowedTimePassed;
-    }
-
-    public boolean isWarnPassed( )
-    {
-        return warnPassed;
-    }
-
-    public void setWarnPassed( final boolean warnPassed )
-    {
-        this.warnPassed = warnPassed;
-    }
-
     @Override
     public BeanType getBeanType( )
     {

+ 4 - 11
server/src/main/java/password/pwm/http/bean/DeleteAccountBean.java

@@ -20,27 +20,20 @@
 
 package password.pwm.http.bean;
 
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 import password.pwm.config.option.SessionBeanMode;
 
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.Set;
 
+@Data
+@EqualsAndHashCode( callSuper = false )
 public class DeleteAccountBean extends PwmSessionBean
 {
-
     private boolean agreementPassed;
 
-    public boolean isAgreementPassed( )
-    {
-        return agreementPassed;
-    }
-
-    public void setAgreementPassed( final boolean agreementPassed )
-    {
-        this.agreementPassed = agreementPassed;
-    }
-
     @Override
     public BeanType getBeanType( )
     {

+ 3 - 2
server/src/main/java/password/pwm/http/bean/ForgottenPasswordBean.java

@@ -27,6 +27,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.Value;
 import password.pwm.VerificationMethodSystem;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.option.IdentityVerificationMethod;
@@ -52,7 +53,7 @@ public class ForgottenPasswordBean extends PwmSessionBean
     private static final long serialVersionUID = 1L;
 
     @SerializedName( "pr" )
-    private String profile;
+    private ProfileID profile;
 
     @SerializedName( "u" )
     private UserIdentity userIdentity;
@@ -79,7 +80,7 @@ public class ForgottenPasswordBean extends PwmSessionBean
     private boolean agreementPassed;
 
     @SerializedName( "fp" )
-    private String forgottenPasswordProfileID;
+    private ProfileID forgottenPasswordProfileID;
 
     @SerializedName( "lf" )
     private Map<String, String> userSearchValues;

+ 4 - 10
server/src/main/java/password/pwm/http/bean/LoginServletBean.java

@@ -21,27 +21,21 @@
 package password.pwm.http.bean;
 
 import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 import password.pwm.config.option.SessionBeanMode;
 
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.Set;
 
+@Data
+@EqualsAndHashCode( callSuper = false )
 public class LoginServletBean extends PwmSessionBean
 {
     @SerializedName( "n" )
     private String nextUrl;
 
-    public String getNextUrl( )
-    {
-        return nextUrl;
-    }
-
-    public void setNextUrl( final String nextUrl )
-    {
-        this.nextUrl = nextUrl;
-    }
-
     @Override
     public BeanType getBeanType( )
     {

+ 2 - 1
server/src/main/java/password/pwm/http/bean/NewUserBean.java

@@ -25,6 +25,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 import password.pwm.VerificationMethodSystem;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.http.servlet.newuser.NewUserForm;
 
@@ -44,7 +45,7 @@ public class NewUserBean extends PwmSessionBean
     private static final long serialVersionUID = 1L;
 
     @SerializedName( "p" )
-    private String profileID;
+    private ProfileID profileID;
 
     @SerializedName( "f" )
     private NewUserForm newUserForm = new NewUserForm( new HashMap<>(), null, null );

+ 5 - 94
server/src/main/java/password/pwm/http/bean/SetupOtpBean.java

@@ -20,22 +20,21 @@
 
 package password.pwm.http.bean;
 
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 import password.pwm.config.option.SessionBeanMode;
-import password.pwm.util.logging.PwmLogger;
 import password.pwm.svc.otp.OTPUserRecord;
+import password.pwm.util.logging.PwmLogger;
 
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
 import java.util.Collections;
-import java.util.Date;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Set;
 
+@Data
+@EqualsAndHashCode( callSuper = false )
 public class SetupOtpBean extends PwmSessionBean
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( SetupOtpBean.class );
 
     private OTPUserRecord otpUserRecord;
@@ -48,94 +47,6 @@ public class SetupOtpBean extends PwmSessionBean
     private Long challenge;
     private boolean hasPreExistingOtp;
 
-    public SetupOtpBean( )
-    {
-    }
-
-    public OTPUserRecord getOtpUserRecord( )
-    {
-        return otpUserRecord;
-    }
-
-    public boolean isHasPreExistingOtp( )
-    {
-        return hasPreExistingOtp;
-    }
-
-    public void setHasPreExistingOtp( final boolean hasPreExistingOtp )
-    {
-        this.hasPreExistingOtp = hasPreExistingOtp;
-    }
-
-    public void setOtpUserRecord( final OTPUserRecord otp )
-    {
-        this.otpUserRecord = otp;
-    }
-
-    public boolean isConfirmed( )
-    {
-        return confirmed;
-    }
-
-    public void setConfirmed( final boolean confirmed )
-    {
-        this.confirmed = confirmed;
-    }
-
-    public Long getChallenge( )
-    {
-        if ( challenge == null )
-        {
-            SecureRandom random;
-            try
-            {
-                random = SecureRandom.getInstance( "SHA1PRNG", "SUN" );
-            }
-            catch ( final NoSuchAlgorithmException | NoSuchProviderException ex )
-            {
-                random = new SecureRandom();
-                LOGGER.error( ex::getMessage, ex );
-            }
-            random.setSeed( ( new Date() ).getTime() );
-            challenge = random.nextLong() % ( 1_000_000 );
-        }
-        return challenge;
-    }
-
-    public void setChallenge( final Long challenge )
-    {
-        this.challenge = challenge;
-    }
-
-    public List<String> getRecoveryCodes( )
-    {
-        return recoveryCodes;
-    }
-
-    public void setRecoveryCodes( final List<String> recoveryCodes )
-    {
-        this.recoveryCodes = recoveryCodes;
-    }
-
-    public boolean isCodeSeen( )
-    {
-        return codeSeen;
-    }
-
-    public void setCodeSeen( final boolean codeSeen )
-    {
-        this.codeSeen = codeSeen;
-    }
-
-    public boolean isWritten( )
-    {
-        return written;
-    }
-
-    public void setWritten( final boolean written )
-    {
-        this.written = written;
-    }
 
     @Override
     public BeanType getBeanType( )

+ 7 - 6
server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java

@@ -27,6 +27,7 @@ import password.pwm.Permission;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.SelectableContextMode;
@@ -353,8 +354,8 @@ public class ClientApiServlet extends ControlledPwmServlet
 
         if ( pwmRequest.isAuthenticated() )
         {
-            final String profileID = pwmSession.getUserInfo().getProfileIDs().get( ProfileDefinition.ChangePassword );
-            if ( StringUtil.notEmpty( profileID ) )
+            final ProfileID profileID = pwmSession.getUserInfo().getProfileIDs().get( ProfileDefinition.ChangePassword );
+            if ( profileID != null )
             {
                 final ChangePasswordProfile changePasswordProfile = pwmRequest.getDomainConfig().getChangePasswordProfile().get( profileID );
                 final String configuredGuideText = changePasswordProfile.readSettingAsLocalizedString(
@@ -402,12 +403,12 @@ public class ClientApiServlet extends ControlledPwmServlet
 
         if ( pwmDomain.getConfig().readSettingAsEnum( PwmSetting.LDAP_SELECTABLE_CONTEXT_MODE, SelectableContextMode.class ) != SelectableContextMode.NONE )
         {
-            final Map<String, LdapProfile> configuredProfiles = pwmDomain.getConfig().getLdapProfiles();
+            final Map<ProfileID, LdapProfile> configuredProfiles = pwmDomain.getConfig().getLdapProfiles();
 
-            final Map<String, Map<String, String>> ldapProfiles = new LinkedHashMap<>( configuredProfiles.size() );
-            for ( final Map.Entry<String, LdapProfile> entry : configuredProfiles.entrySet() )
+            final Map<ProfileID, Map<String, String>> ldapProfiles = new LinkedHashMap<>( configuredProfiles.size() );
+            for ( final Map.Entry<ProfileID, LdapProfile> entry : configuredProfiles.entrySet() )
             {
-                final String ldapProfile = entry.getKey();
+                final ProfileID ldapProfile = entry.getKey();
                 final Map<String, String> contexts = entry.getValue().getSelectableContexts( pwmRequest.getLabel(), pwmDomain );
                 ldapProfiles.put( ldapProfile, contexts );
             }

+ 5 - 3
server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java

@@ -24,6 +24,7 @@ import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LocalSessionStateBean;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
@@ -39,7 +40,6 @@ import password.pwm.http.JspUrl;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
-import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchService;
@@ -47,6 +47,7 @@ import password.pwm.svc.intruder.IntruderServiceClient;
 import password.pwm.svc.sms.SmsQueueService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
+import password.pwm.user.UserInfo;
 import password.pwm.util.CaptchaUtility;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.JavaHelper;
@@ -148,7 +149,8 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
         }
 
         final String contextParam = pwmRequest.readParameterAsString( PwmConstants.PARAM_CONTEXT );
-        final String ldapProfile = pwmRequest.readParameterAsString( PwmConstants.PARAM_LDAP_PROFILE );
+        final Optional<ProfileID> ldapProfile = pwmDomain.getConfig()
+                .ldapProfileForStringId( pwmRequest.readParameterAsString( PwmConstants.PARAM_LDAP_PROFILE ) );
 
         final List<FormConfiguration> forgottenUsernameForm = pwmDomain.getConfig().readSettingAsForm( PwmSetting.FORGOTTEN_USERNAME_FORM );
 
@@ -185,7 +187,7 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
                 final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                         .filter( searchFilter )
                         .formValues( formValues )
-                        .ldapProfile( ldapProfile )
+                        .ldapProfile( ldapProfile.orElse( null ) )
                         .contexts( Collections.singletonList( contextParam ) )
                         .build();
                 userIdentity = userSearchService.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );

+ 7 - 2
server/src/main/java/password/pwm/http/servlet/LoginServlet.java

@@ -22,6 +22,7 @@ package password.pwm.http.servlet;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.PwmConstants;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
@@ -53,6 +54,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 
 /**
  * User interaction servlet for form-based authentication.   Depending on how PWM is deployed,
@@ -219,7 +221,10 @@ public class LoginServlet extends ControlledPwmServlet
                 ? new PasswordData( passwordStr )
                 : null;
         final String context = valueMap.get( PwmConstants.PARAM_CONTEXT );
-        final String ldapProfile = valueMap.get( PwmConstants.PARAM_LDAP_PROFILE );
+
+        final Optional<ProfileID> ldapProfile = pwmRequest.getPwmDomain().getConfig()
+                .ldapProfileForStringId( valueMap.get( PwmConstants.PARAM_LDAP_PROFILE ) );
+
         final String recaptchaResponse = valueMap.get( CaptchaUtility.PARAM_RECAPTCHA_FORM_NAME );
 
 
@@ -254,7 +259,7 @@ public class LoginServlet extends ControlledPwmServlet
         }
         else
         {
-            sessionAuthenticator.searchAndAuthenticateUser( username, password, context, ldapProfile );
+            sessionAuthenticator.searchAndAuthenticateUser( username, password, context, ldapProfile.orElse( null ) );
         }
 
         // if here then login was successful

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java

@@ -526,7 +526,7 @@ public class SetupOtpServlet extends ControlledPwmServlet
             if ( policy == ForceSetupPolicy.FORCE_ALLOW_SKIP )
             {
                 LOGGER.trace( pwmRequest, () -> "allowing setup skipping due to setting "
-                        + PwmSetting.OTP_FORCE_SETUP.toMenuLocationDebug( setupOtpProfile.getIdentifier(), pwmRequest.getLocale() ) );
+                        + PwmSetting.OTP_FORCE_SETUP.toMenuLocationDebug( setupOtpProfile.getId(), pwmRequest.getLocale() ) );
                 return true;
             }
 

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java

@@ -40,7 +40,7 @@ import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
-import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.CollectorUtil;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -201,7 +201,7 @@ public class ShortcutServlet extends ControlledPwmServlet
 
         final Map<String, ShortcutItem> visibleItems = Collections.unmodifiableMap( configuredItems.stream()
                 .filter( item -> checkItemMatch( pwmRequest, labelsFromHeader, item ) )
-                .collect( CollectionUtil.collectorToLinkedMap(
+                .collect( CollectorUtil.toLinkedMap(
                         ShortcutItem::getLabel,
                         Function.identity() ) ) );
 

+ 6 - 4
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java

@@ -25,6 +25,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.LocalSessionStateBean;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
@@ -47,7 +48,6 @@ import password.pwm.http.bean.ActivateUserBean;
 import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.i18n.Message;
-import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchService;
@@ -60,6 +60,7 @@ import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenType;
 import password.pwm.svc.token.TokenUtil;
+import password.pwm.user.UserInfo;
 import password.pwm.util.CaptchaUtility;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.MiscUtil;
@@ -181,7 +182,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
             throws PwmUnrecoverableException
     {
         final ActivateUserBean activateUserBean = activateUserBean( pwmRequest );
-        final String profileID = activateUserBean.getProfileID();
+        final ProfileID profileID = activateUserBean.getProfileID();
         final ActivateUserProfile activateUserProfile = pwmRequest.getDomainConfig().getUserActivationProfiles().get( profileID );
         if ( activateUserProfile == null )
         {
@@ -250,7 +251,8 @@ public class ActivateUserServlet extends ControlledPwmServlet
             final String contextParam = pwmRequest.readParameterAsString( PwmConstants.PARAM_CONTEXT );
 
             // read the profile attr
-            final String ldapProfile = pwmRequest.readParameterAsString( PwmConstants.PARAM_LDAP_PROFILE );
+            final Optional<ProfileID> ldapProfile = pwmDomain.getConfig()
+                    .ldapProfileForStringId( pwmRequest.readParameterAsString( PwmConstants.PARAM_LDAP_PROFILE ) );
 
             // see if the values meet the configured form requirements.
             FormUtility.validateFormValues( config, formValues, ssBean.getLocale() );
@@ -265,7 +267,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
                         .contexts( Collections.singletonList( contextParam ) )
                         .filter( searchFilter )
                         .formValues( formValues )
-                        .ldapProfile( ldapProfile )
+                        .ldapProfile( ldapProfile.orElse( null ) )
                         .build();
 
                 userIdentity = userSearchService.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );

+ 3 - 2
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java

@@ -28,6 +28,7 @@ import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.PwmDomain;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LoginInfoBean;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
@@ -345,9 +346,9 @@ class ActivateUserUtils
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final ActivateUserBean activateUserBean = pwmDomain.getSessionStateService().getBean( pwmRequest, ActivateUserBean.class );
 
-        final Optional<String> profileID = ProfileUtility.discoverProfileIDForUser( pwmRequest.getPwmRequestContext(), userIdentity, ProfileDefinition.ActivateUser );
+        final Optional<ProfileID> profileID = ProfileUtility.discoverProfileIDForUser( pwmRequest.getPwmRequestContext(), userIdentity, ProfileDefinition.ActivateUser );
 
-        if ( !profileID.isPresent() || !pwmDomain.getConfig().getUserActivationProfiles().containsKey( profileID.get() ) )
+        if ( !profileID.isPresent() )
         {
             throw PwmUnrecoverableException.newException( PwmError.ERROR_ACTIVATE_NO_PERMISSION, "no matching user activation profile for user" );
         }

+ 2 - 1
server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataBean.java

@@ -23,6 +23,7 @@ package password.pwm.http.servlet.admin;
 import lombok.Builder;
 import lombok.Value;
 import password.pwm.Permission;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.pub.PublicUserInfoBean;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.PwmPasswordPolicy;
@@ -46,7 +47,7 @@ public class UserDebugDataBean
 
     private final PwmPasswordPolicy ldapPasswordPolicy;
     private final PwmPasswordPolicy configuredPasswordPolicy;
-    private final Map<ProfileDefinition, String> profiles;
+    private final Map<ProfileDefinition, ProfileID> profiles;
 
     private final PwNotifyUserStatus pwNotifyUserStatus;
 }

+ 7 - 6
server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java

@@ -23,6 +23,7 @@ package password.pwm.http.servlet.admin;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.Permission;
 import password.pwm.PwmDomain;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
@@ -32,12 +33,12 @@ import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapOperationsHelper;
-import password.pwm.user.UserInfoBean;
-import password.pwm.ldap.permission.UserPermissionUtility;
-import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.pwnotify.PwNotifyUserStatus;
+import password.pwm.user.UserInfo;
+import password.pwm.user.UserInfoBean;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.password.PasswordUtility;
@@ -68,7 +69,7 @@ public class UserDebugDataReader
 
         final Map<Permission, String> permissions = UserDebugDataReader.permissionMap( pwmDomain, sessionLabel, userIdentity );
 
-        final Map<ProfileDefinition, String> profiles = UserDebugDataReader.profileMap( pwmDomain, sessionLabel, userIdentity );
+        final Map<ProfileDefinition, ProfileID> profiles = UserDebugDataReader.profileMap( pwmDomain, sessionLabel, userIdentity );
 
         final PwmPasswordPolicy ldapPasswordPolicy = PasswordUtility.readLdapPasswordPolicy( pwmDomain, pwmDomain.getProxiedChaiUser( sessionLabel, userIdentity ) );
 
@@ -134,14 +135,14 @@ public class UserDebugDataReader
         return Collections.unmodifiableMap( results );
     }
 
-    private static Map<ProfileDefinition, String> profileMap(
+    private static Map<ProfileDefinition, ProfileID> profileMap(
             final PwmDomain pwmDomain,
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity
     )
         throws PwmUnrecoverableException
     {
-        final Map<ProfileDefinition, String> results = new TreeMap<>( Comparator.comparing( Enum::name ) );
+        final Map<ProfileDefinition, ProfileID> results = new TreeMap<>( Comparator.comparing( Enum::name ) );
         for ( final ProfileDefinition profileDefinition : ProfileDefinition.values() )
         {
             if ( profileDefinition.getQueryMatch().isPresent() && profileDefinition.getProfileFactoryClass().isPresent() )

+ 14 - 13
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -24,6 +24,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.EmailItemBean;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.AppConfig;
@@ -250,7 +251,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final PwmSetting pwmSetting = PwmSetting.forKey( requestMap.get( "setting" ) )
                 .orElseThrow( () -> new IllegalStateException( "invalid setting parameter value" ) );
         final String functionName = requestMap.get( "function" );
-        final String profileID = pwmSetting.getCategory().hasProfiles() ? pwmRequest.readParameterAsString( REQ_PARAM_PROFILE ) : null;
+        final ProfileID profileID = pwmSetting.getCategory().hasProfiles() ? ProfileID.create( pwmRequest.readParameterAsString( REQ_PARAM_PROFILE ) ) : null;
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainID( pwmSetting );
         final String extraData = requestMap.get( "extraData" );
 
@@ -327,7 +328,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             final Map<String, String> outputMap = new LinkedHashMap<>( valueMap );
 
             final PwmLocaleBundle pwmLocaleBundle = key.toLocaleBundle();
-            final String keyName = key.getProfileID();
+            final String keyName = key.getLocaleKey();
             modifier.writeLocaleBundleMap( key.getDomainID(), pwmLocaleBundle, keyName, outputMap );
             readSettingResponse = ConfigEditorServletUtils.handleLocaleBundleReadSetting( pwmRequest, modifier.newStoredConfiguration(), key );
         }
@@ -366,7 +367,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         if ( key.getRecordType() == StoredConfigKey.RecordType.LOCALE_BUNDLE )
         {
             final PwmLocaleBundle pwmLocaleBundle = key.toLocaleBundle();
-            final String keyName = key.getProfileID();
+            final String keyName = key.getLocaleKey();
             final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainIDForLocaleBundle();
             modifier.resetLocaleBundleMap( pwmLocaleBundle, keyName, domainID );
         }
@@ -573,10 +574,11 @@ public class ConfigEditorServlet extends ControlledPwmServlet
     )
             throws IOException, PwmUnrecoverableException
     {
+        LOGGER.debug( pwmRequest, () -> "beginning restLdapHealthCheck" );
+
         final Instant startTime = Instant.now();
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        LOGGER.debug( pwmRequest, () -> "beginning restLdapHealthCheck" );
-        final String profileID = pwmRequest.readParameterAsString( REQ_PARAM_PROFILE );
+        final ProfileID profileID = ProfileID.create( pwmRequest.readParameterAsString( REQ_PARAM_PROFILE ) );
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainID( PwmSetting.LDAP_SERVER_URLS );
         final DomainConfig config = AppConfig.forStoredConfig( configManagerBean.getStoredConfiguration() ).getDomainConfigs().get( domainID );
         final PublicHealthData healthData = LDAPHealthChecker.healthForNewConfiguration(
@@ -587,6 +589,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                 profileID,
                 true,
                 true );
+
         final RestResultBean restResultBean = RestResultBean.withData( healthData, PublicHealthData.class );
 
         pwmRequest.outputJsonResult( restResultBean );
@@ -667,7 +670,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
     {
         final Instant startTime = Instant.now();
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        final String profileID = pwmRequest.readParameterAsString( REQ_PARAM_PROFILE );
+        final ProfileID profileID = ProfileID.create( pwmRequest.readParameterAsString( REQ_PARAM_PROFILE ) );
 
         LOGGER.debug( pwmRequest, () -> "beginning restEmailHealthCheck" );
 
@@ -839,14 +842,12 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration();
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainIDForDomainSetting(  );
 
-        final String profile;
+        final ProfileID profile;
         {
-            final String selectedProfile = inputMap.get( LdapBrowser.PARAM_PROFILE );
             final AppConfig appConfig = AppConfig.forStoredConfig( storedConfiguration );
             final DomainConfig domainConfig = appConfig.getDomainConfigs().getOrDefault( domainID, AppConfig.defaultConfig().getAdminDomain() );
-            profile = domainConfig.getLdapProfiles().containsKey( selectedProfile )
-                    ? selectedProfile
-                    : domainConfig.getLdapProfiles().keySet().iterator().next();
+            final Optional<ProfileID> selectedProfile = domainConfig.ldapProfileForStringId( inputMap.get( LdapBrowser.PARAM_PROFILE ) );
+            profile = selectedProfile.orElse( domainConfig.getLdapProfiles().keySet().iterator().next() );
         }
         final String dn = inputMap.getOrDefault( LdapBrowser.PARAM_DN, "" );
 
@@ -895,8 +896,8 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final PwmSettingCategory category = PwmSettingCategory.forProfileSetting( setting )
                 .orElseThrow( () -> new IllegalStateException( "specified key does not associated with a profile-enabled category" ) );
 
-        final String sourceID = inputMap.get( "sourceID" );
-        final String destinationID = inputMap.get( "destinationID" );
+        final ProfileID sourceID = ProfileID.create( inputMap.get( "sourceID" ) );
+        final ProfileID destinationID = ProfileID.create( inputMap.get( "destinationID" ) );
 
         try
         {

+ 3 - 2
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java

@@ -24,6 +24,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;
@@ -176,7 +177,7 @@ public class ConfigEditorServletUtils
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainIDForLocaleBundle();
         final ReadSettingResponse.ReadSettingResponseBuilder builder = ReadSettingResponse.builder();
         final PwmLocaleBundle pwmLocaleBundle = key.toLocaleBundle();
-        final String keyName = key.getProfileID();
+        final String keyName = key.getProfileID().toString();
         final Map<String, String> bundleMap = storedConfig.readLocaleBundleMap( pwmLocaleBundle, keyName, domainID );
         if ( bundleMap == null || bundleMap.isEmpty() )
         {
@@ -346,6 +347,6 @@ public class ConfigEditorServletUtils
                 .orElseThrow( () -> new IllegalStateException( "invalid StoredConfigKey setting key" ) );
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainID( setting );
         final String profileID = setting.getCategory().hasProfiles() ? pwmRequest.readParameterAsString( ConfigEditorServlet.REQ_PARAM_PROFILE ) : null;
-        return StoredConfigKey.forSetting( setting, profileID, domainID );
+        return StoredConfigKey.forSetting( setting, profileID == null ? null : ProfileID.create( profileID ), domainID );
     }
 }

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/configeditor/SearchResultItem.java

@@ -50,9 +50,9 @@ class SearchResultItem implements Serializable
         return new SearchResultItem(
                 setting.getCategory().toString(),
                 storedConfiguration.readStoredValue( key ).orElseThrow().toDebugString( locale ),
-                setting.getCategory().toMenuLocationDebug( key.getProfileID(), locale ),
+                setting.getCategory().toMenuLocationDebug( key.getProfileID().orElse( null ), locale ),
                 StoredConfigurationUtil.isDefaultValue( storedConfiguration, key ),
-                key.getProfileID()
+                key.getProfileID().map( v -> v.stringValue() ).orElse( null )
         );
     }
 }

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/configeditor/data/CategoryInfo.java

@@ -53,7 +53,7 @@ public class CategoryInfo implements Serializable
                 .hidden( category.isHidden() )
                 .parent( category.getParent() != null ? category.getParent().getKey() : null )
                 .profiles( category.hasProfiles() )
-                .menuLocation( category.toMenuLocationDebug( "PROFILE", locale ) )
+                .menuLocation( category.toMenuLocationDebug( null, locale ) )
                 .build();
     }
 }

+ 9 - 8
server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java

@@ -22,6 +22,7 @@ package password.pwm.http.servlet.configeditor.data;
 
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
@@ -232,7 +233,7 @@ public class NavTreeDataMaker
             return List.of( navTreeItemForCategory( loopCategory, locale, null ) );
         }
 
-        final List<String> profiles = StoredConfigurationUtil.profilesForCategory( domainId, loopCategory, storedConfiguration );
+        final List<ProfileID> profiles = StoredConfigurationUtil.profilesForCategory( domainId, loopCategory, storedConfiguration );
         if ( loopCategory.isTopLevelProfile() )
         {
             final List<NavTreeItem> navigationData = new ArrayList<>( profiles.size() );
@@ -254,14 +255,14 @@ public class NavTreeDataMaker
                 navigationData.add( profileEditorInfo );
             }
 
-            for ( final String profileId : profiles )
+            for ( final ProfileID profileId : profiles )
             {
                 final NavTreeItem.NavItemType type = !loopCategory.hasChildren()
                         ? NavTreeItem.NavItemType.category
                         : NavTreeItem.NavItemType.navigation;
 
                 final NavTreeItem profileInfo = navTreeItemForCategory( loopCategory, locale, profileId ).toBuilder()
-                        .name( profileId.isEmpty() ? "Default" : profileId )
+                        .name( profileId == null ? "Default" : profileId.stringValue() )
                         .id( "profile-" + loopCategory.getKey() + "-" + profileId )
                         .parent( loopCategory.getKey() )
                         .type( type )
@@ -274,7 +275,7 @@ public class NavTreeDataMaker
         }
 
         final List<NavTreeItem> navigationData = new ArrayList<>();
-        for ( final String profileId : profiles )
+        for ( final ProfileID profileId : profiles )
         {
             if ( categoryMatcher( domainId, loopCategory, profileId, storedConfiguration, navTreeSettings ) )
             {
@@ -288,7 +289,7 @@ public class NavTreeDataMaker
     private static NavTreeItem navTreeItemForCategory(
             final PwmSettingCategory category,
             final Locale locale,
-            final String profileId
+            final ProfileID profileId
     )
     {
         final String parent = category.getParent() != null
@@ -305,7 +306,7 @@ public class NavTreeDataMaker
                 .category( category.getKey() )
                 .parent( parent )
                 .type( type )
-                .profile( profileId )
+                .profile( profileId == null ? null : profileId.stringValue() )
                 .menuLocation( category.toMenuLocationDebug( profileId, locale ) )
                 .build();
     }
@@ -313,7 +314,7 @@ public class NavTreeDataMaker
     private static boolean categoryMatcher(
             final DomainID domainID,
             final PwmSettingCategory category,
-            final String profile,
+            final ProfileID profile,
             final StoredConfiguration storedConfiguration,
             final NavTreeSettings navTreeSettings
     )
@@ -354,7 +355,7 @@ public class NavTreeDataMaker
             final DomainID domainID,
             final StoredConfiguration storedConfiguration,
             final PwmSetting setting,
-            final String profileID,
+            final ProfileID profileID,
             final NavTreeSettings navTreeSettings
     )
     {

+ 6 - 2
server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java

@@ -21,6 +21,7 @@
 package password.pwm.http.servlet.configeditor.data;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
@@ -30,6 +31,7 @@ import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.PwmLocaleBundle;
+import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
@@ -37,6 +39,7 @@ import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
@@ -62,7 +65,7 @@ public class SettingDataMaker
         {
             final Set<PwmSetting> interestedSets = StoredConfigurationUtil.allPossibleSettingKeysForConfiguration( storedConfiguration ).stream()
                     .filter( k -> k.isRecordType( StoredConfigKey.RecordType.SETTING ) )
-                    .filter( k -> NavTreeDataMaker.settingMatcher( domainID, storedConfiguration, k.toPwmSetting(), k.getProfileID(), navTreeSettings ) )
+                    .filter( k -> NavTreeDataMaker.settingMatcher( domainID, storedConfiguration, k.toPwmSetting(), k.getProfileID().orElse( null ), navTreeSettings ) )
                     .map( StoredConfigKey::toPwmSetting )
                     .collect( Collectors.toSet() );
 
@@ -92,8 +95,9 @@ public class SettingDataMaker
                         ( u, v ) -> v,
                         LinkedHashMap::new ) ) );
 
+        final List<ProfileID> profileIDList = StoredConfigurationUtil.profilesForSetting( domainID, PwmSetting.LDAP_PROFILE_LIST, storedConfiguration );
         final VarData varMap = VarData.builder()
-                .ldapProfileIds( StoredConfigurationUtil.profilesForSetting( domainID, PwmSetting.LDAP_PROFILE_LIST, storedConfiguration ) )
+                .ldapProfileIds( CollectionUtil.convertListType( profileIDList, ProfileID::toString ) )
                 .domainIds( StoredConfigurationUtil.domainList( storedConfiguration ).stream()
                         .map( DomainID::stringValue ).sorted().collect( Collectors.toList() ) )
                 .currentTemplate( templateSet )

+ 23 - 0
server/src/main/java/password/pwm/http/servlet/configeditor/function/AbstractUriCertImportFunction.java

@@ -20,6 +20,8 @@
 
 package password.pwm.http.servlet.configeditor.function;
 
+import org.jetbrains.annotations.NotNull;
+import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.AppConfig;
 import password.pwm.config.stored.StoredConfigKey;
@@ -110,5 +112,26 @@ abstract class AbstractUriCertImportFunction implements SettingUIFunction
         modifier.writeSetting( key, X509CertificateValue.fromX509( certs ), userIdentity );
     }
 
+    @NotNull
+    protected static String validateUriStringSetting( final String uriString, final StoredConfigKey storedConfigKey )
+            throws PwmOperationalException
+    {
 
+        final String menuDebugLocation = storedConfigKey.toPwmSetting().toMenuLocationDebug( storedConfigKey.getProfileID().orElse( null ), PwmConstants.DEFAULT_LOCALE );
+        if ( uriString.isEmpty() )
+        {
+            final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "Setting " + menuDebugLocation + " must first be configured" );
+            throw new PwmOperationalException( errorInformation );
+        }
+        try
+        {
+            URI.create( uriString );
+        }
+        catch ( final IllegalArgumentException e )
+        {
+            final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "Setting " + menuDebugLocation + " has an invalid URL syntax" );
+            throw new PwmOperationalException( errorInformation );
+        }
+        return uriString;
+    }
 }

+ 1 - 25
server/src/main/java/password/pwm/http/servlet/configeditor/function/ActionCertImportFunction.java

@@ -21,7 +21,6 @@
 package password.pwm.http.servlet.configeditor.function;
 
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.stored.StoredConfigurationUtil;
@@ -29,13 +28,10 @@ import password.pwm.config.value.ActionValue;
 import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.ValueTypeConverter;
 import password.pwm.config.value.data.ActionConfiguration;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.json.JsonFactory;
 
-import java.net.URI;
 import java.security.cert.X509Certificate;
 import java.util.List;
 import java.util.Map;
@@ -54,7 +50,6 @@ public class ActionCertImportFunction extends AbstractUriCertImportFunction
             throws PwmOperationalException, PwmUnrecoverableException
     {
         final Map<String, Integer> extraDataMap = JsonFactory.get().deserializeMap( extraData, String.class, Integer.class );
-        final PwmSetting pwmSetting = key.toPwmSetting();
 
         final StoredValue actionValue = StoredConfigurationUtil.getValueOrDefault( modifier.newStoredConfiguration(), key );
         final List<ActionConfiguration> actionConfigurations = ValueTypeConverter.valueToAction( key.toPwmSetting(), actionValue );
@@ -63,26 +58,7 @@ public class ActionCertImportFunction extends AbstractUriCertImportFunction
 
         final String uriString = webAction.getUrl();
 
-        if ( uriString == null || uriString.isEmpty() )
-        {
-            final ErrorInformation errorInformation = new ErrorInformation(
-                    PwmError.CONFIG_FORMAT_ERROR,
-                    "Setting " + pwmSetting.toMenuLocationDebug( key.getProfileID(), null )
-                            + " action URL must first be configured" );
-            throw new PwmOperationalException( errorInformation );
-        }
-        try
-        {
-            URI.create( uriString );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            final ErrorInformation errorInformation = new ErrorInformation(
-                    PwmError.CONFIG_FORMAT_ERROR, "Setting "
-                    + pwmSetting.toMenuLocationDebug( key.getProfileID(), null ) + " action URL has an invalid URL syntax" );
-            throw new PwmOperationalException( errorInformation );
-        }
-        return uriString;
+        return validateUriStringSetting( uriString, key );
     }
 
     @Override

+ 2 - 24
server/src/main/java/password/pwm/http/servlet/configeditor/function/OAuthCertImportFunction.java

@@ -20,22 +20,15 @@
 
 package password.pwm.http.servlet.configeditor.function;
 
-import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfigurationModifier;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.MiscUtil;
 
-import java.net.URI;
-
 public class OAuthCertImportFunction extends AbstractUriCertImportFunction
 {
-
-
     @Override
     String getUri( final StoredConfigurationModifier modifier, final StoredConfigKey key, final String extraData )
             throws PwmOperationalException, PwmUnrecoverableException
@@ -61,24 +54,9 @@ public class OAuthCertImportFunction extends AbstractUriCertImportFunction
                 return null;
         }
 
-        final StoredConfigKey oauthCertKey = StoredConfigKey.forSetting( urlCertSetting, key.getProfileID(), key.getDomainID() );
+        final StoredConfigKey oauthCertKey = StoredConfigKey.forSetting( urlCertSetting, key.getProfileID().orElse( null ), key.getDomainID() );
         uriString = ( String ) modifier.newStoredConfiguration().readStoredValue( oauthCertKey ).orElseThrow().toNativeObject();
-        menuDebugLocation = urlCertSetting.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
 
-        if ( uriString.isEmpty() )
-        {
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "Setting " + menuDebugLocation + " must first be configured" );
-            throw new PwmOperationalException( errorInformation );
-        }
-        try
-        {
-            URI.create( uriString );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "Setting " + menuDebugLocation + " has an invalid URL syntax" );
-            throw new PwmOperationalException( errorInformation );
-        }
-        return uriString;
+        return validateUriStringSetting( uriString, oauthCertKey );
     }
 }

+ 1 - 24
server/src/main/java/password/pwm/http/servlet/configeditor/function/RemoteWebServiceCertImportFunction.java

@@ -21,18 +21,14 @@
 package password.pwm.http.servlet.configeditor.function;
 
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfigurationModifier;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.RemoteWebServiceValue;
 import password.pwm.config.value.data.RemoteWebServiceConfiguration;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 
-import java.net.URI;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.List;
@@ -44,31 +40,12 @@ public class RemoteWebServiceCertImportFunction extends AbstractUriCertImportFun
     String getUri( final StoredConfigurationModifier modifier, final StoredConfigKey key, final String extraData )
             throws PwmOperationalException, PwmUnrecoverableException
     {
-        final PwmSetting pwmSetting = key.toPwmSetting();
-        final String profile = key.getProfileID();
-
         final RemoteWebServiceValue actionValue = ( RemoteWebServiceValue ) StoredConfigurationUtil.getValueOrDefault( modifier.newStoredConfiguration(), key );
         final String serviceName = actionNameFromExtraData( extraData );
         final RemoteWebServiceConfiguration action = actionValue.forName( serviceName );
         final String uriString = action.getUrl();
 
-        if ( uriString == null || uriString.isEmpty() )
-        {
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR,
-                    "Setting " + pwmSetting.toMenuLocationDebug( profile, null ) + " action " + serviceName + " must first be configured" );
-            throw new PwmOperationalException( errorInformation );
-        }
-        try
-        {
-            URI.create( uriString );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR,
-                    "Setting " + pwmSetting.toMenuLocationDebug( profile, null ) + " action " + serviceName + " has an invalid URL syntax" );
-            throw new PwmOperationalException( errorInformation );
-        }
-        return uriString;
+        return validateUriStringSetting( uriString, key );
     }
 
     private String actionNameFromExtraData( final String extraData )

+ 3 - 23
server/src/main/java/password/pwm/http/servlet/configeditor/function/SMSGatewayCertImportFunction.java

@@ -20,17 +20,12 @@
 
 package password.pwm.http.servlet.configeditor.function;
 
-import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfigurationModifier;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 
-import java.net.URI;
-
 public class SMSGatewayCertImportFunction extends AbstractUriCertImportFunction
 {
     @Override
@@ -38,26 +33,11 @@ public class SMSGatewayCertImportFunction extends AbstractUriCertImportFunction
             throws PwmOperationalException, PwmUnrecoverableException
     {
         final String uriString;
-        final String menuDebugLocation;
 
-        final var urlSettingKey = StoredConfigKey.forSetting( PwmSetting.SMS_GATEWAY_URL, key.getProfileID(), key.getDomainID() );
+
+        final var urlSettingKey = StoredConfigKey.forSetting( PwmSetting.SMS_GATEWAY_URL, key.getProfileID().orElse( null ), key.getDomainID() );
         uriString = ( String ) modifier.newStoredConfiguration().readStoredValue( urlSettingKey ).orElseThrow().toNativeObject();
-        menuDebugLocation = PwmSetting.SMS_GATEWAY_URL.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
 
-        if ( uriString.isEmpty() )
-        {
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "Setting " + menuDebugLocation + " must first be configured" );
-            throw new PwmOperationalException( errorInformation );
-        }
-        try
-        {
-            URI.create( uriString );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "Setting " + menuDebugLocation + " has an invalid URL syntax" );
-            throw new PwmOperationalException( errorInformation );
-        }
-        return uriString;
+        return validateUriStringSetting( uriString, urlSettingKey );
     }
 }

+ 2 - 1
server/src/main/java/password/pwm/http/servlet/configeditor/function/SmtpCertImportFunction.java

@@ -20,6 +20,7 @@
 
 package password.pwm.http.servlet.configeditor.function;
 
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfigurationModifier;
@@ -46,7 +47,7 @@ public class SmtpCertImportFunction implements SettingUIFunction
             throws PwmUnrecoverableException
     {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final String profile = key.getProfileID();
+        final ProfileID profile = key.getProfileID().orElse( null );
 
         final List<X509Certificate> certs = EmailServerUtil.readCertificates( pwmRequest.getAppConfig(), profile, pwmRequest.getLabel() );
         if ( !CollectionUtil.isEmpty( certs ) )

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/configeditor/function/SyslogCertImportFunction.java

@@ -64,7 +64,7 @@ public class SyslogCertImportFunction implements SettingUIFunction
 
         final Set<X509Certificate> resultCertificates = new LinkedHashSet<>();
 
-        final var syslogServerSetting = StoredConfigKey.forSetting( PwmSetting.AUDIT_SYSLOG_SERVERS, key.getProfileID(), key.getDomainID() );
+        final var syslogServerSetting = StoredConfigKey.forSetting( PwmSetting.AUDIT_SYSLOG_SERVERS, key.getProfileID().orElse( null ), key.getDomainID() );
         final List<String> syslogConfigStrs = ValueTypeConverter.valueToStringArray( modifier.newStoredConfiguration().readStoredValue( syslogServerSetting ).orElseThrow() );
         if ( !CollectionUtil.isEmpty( syslogConfigStrs ) )
         {

+ 4 - 3
server/src/main/java/password/pwm/http/servlet/configeditor/function/UserMatchViewerFunction.java

@@ -28,6 +28,7 @@ import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmDomain;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.AppConfig;
@@ -156,11 +157,11 @@ public class UserMatchViewerFunction implements SettingUIFunction
             final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final String baseDN,
-            final String profileID
+            final ProfileID profileID
     )
             throws PwmOperationalException, PwmUnrecoverableException
     {
-        final Set<String> profileIDsToTest = new LinkedHashSet<>();
+        final Set<ProfileID> profileIDsToTest = new LinkedHashSet<>();
 
         if ( UserPermissionUtility.isAllProfiles( profileID ) )
         {
@@ -176,7 +177,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
             throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_NO_PROFILE_ASSIGNED, "invalid ldap profile" ) );
         }
 
-        for ( final String loopID : profileIDsToTest )
+        for ( final ProfileID loopID : profileIDsToTest )
         {
             ChaiEntry chaiEntry = null;
             try

+ 10 - 4
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java

@@ -21,6 +21,7 @@
 package password.pwm.http.servlet.configguide;
 
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.stored.StoredConfigKey;
@@ -55,7 +56,7 @@ public class ConfigGuideForm
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( ConfigGuideForm.class );
 
-    static final String LDAP_PROFILE_NAME = "default";
+    static final ProfileID LDAP_PROFILE_NAME = ProfileID.PROFILE_ID_DEFAULT;
     public static final DomainID DOMAIN_ID = DomainID.DOMAIN_ID_DEFAULT;
 
     public static Map<ConfigGuideFormField, String> defaultForm( )
@@ -125,7 +126,7 @@ public class ConfigGuideForm
         // establish a default ldap profile
 
         modifySetting( modifier, PwmSetting.LDAP_PROFILE_LIST, null, StringArrayValue.create(
-                Collections.singletonList( LDAP_PROFILE_NAME )
+                Collections.singletonList( LDAP_PROFILE_NAME.stringValue() )
         ) );
 
         {
@@ -220,7 +221,7 @@ public class ConfigGuideForm
         {
             final String stringValue = formData.get( ConfigGuideFormField.CHALLENGE_RESPONSE_DATA );
             final StoredValue challengeValue = ChallengeValue.factory().fromJson( PwmSetting.CHALLENGE_RANDOM_CHALLENGES, stringValue );
-            modifySetting( modifier, PwmSetting.CHALLENGE_RANDOM_CHALLENGES, "default", challengeValue );
+            modifySetting( modifier, PwmSetting.CHALLENGE_RANDOM_CHALLENGES, LDAP_PROFILE_NAME, challengeValue );
         }
 
         // set site url
@@ -232,7 +233,12 @@ public class ConfigGuideForm
         return modifier.newStoredConfiguration();
     }
 
-    private static void modifySetting( final StoredConfigurationModifier modifier, final PwmSetting pwmSetting, final String profile, final StoredValue storedValue )
+    private static void modifySetting(
+            final StoredConfigurationModifier modifier,
+                                       final PwmSetting pwmSetting,
+            final ProfileID profile,
+            final StoredValue storedValue
+    )
             throws PwmUnrecoverableException
     {
         final StoredConfigKey key = StoredConfigKey.forSetting( pwmSetting, profile, DOMAIN_ID );

+ 2 - 1
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java

@@ -27,6 +27,7 @@ import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
@@ -105,7 +106,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
 
     private static final PwmLogger LOGGER = PwmLogger.getLogger( ConfigGuideServlet.class.getName() );
 
-    private static final String LDAP_PROFILE_KEY = PwmConstants.PROFILE_ID_DEFAULT;
+    private static final ProfileID LDAP_PROFILE_KEY = ProfileID.PROFILE_ID_DEFAULT;
     public static final String PARAM_STEP = "step";
     public static final String PARAM_KEY = "key";
 

+ 5 - 4
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java

@@ -23,6 +23,7 @@ package password.pwm.http.servlet.configmanager;
 import lombok.Builder;
 import lombok.Value;
 import password.pwm.PwmConstants;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;
@@ -127,7 +128,7 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
             {
                 final StoredValue storedValue = storedConfiguration.readStoredValue( key ).orElseThrow();
                 final List<X509Certificate> certificates = ValueTypeConverter.valueToX509Certificates( pwmSetting, storedValue );
-                certificateDebugDataItems.addAll( makeItems( pwmSetting, key.getProfileID(), certificates ) );
+                certificateDebugDataItems.addAll( makeItems( pwmSetting, key.getProfileID().orElseThrow(), certificates ) );
             }
             else if ( pwmSetting.getSyntax() == PwmSettingSyntax.ACTION )
             {
@@ -138,7 +139,7 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
                     for ( final ActionConfiguration.WebAction webAction : actionConfiguration.getWebActions() )
                     {
                         final List<X509Certificate> certificates = webAction.getCertificates();
-                        certificateDebugDataItems.addAll( makeItems( pwmSetting, key.getProfileID(), certificates ) );
+                        certificateDebugDataItems.addAll( makeItems( pwmSetting, key.getProfileID().orElseThrow(), certificates ) );
                     }
                 }
             }
@@ -150,7 +151,7 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
 
     Collection<CertificateDebugDataItem> makeItems(
             final PwmSetting setting,
-            final String profileId,
+            final ProfileID profileId,
             final List<X509Certificate> certificates
     )
             throws PwmUnrecoverableException
@@ -171,7 +172,7 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
 
     CertificateDebugDataItem makeItem(
             final PwmSetting setting,
-            final String profileId,
+            final ProfileID profileId,
             final X509Certificate certificate
     )
     {

+ 28 - 13
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -32,6 +32,7 @@ import password.pwm.PwmDomain;
 import password.pwm.VerificationMethodSystem;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.PasswordStatus;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
@@ -63,35 +64,35 @@ import password.pwm.http.servlet.oauth.OAuthMachine;
 import password.pwm.http.servlet.oauth.OAuthSettings;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapOperationsHelper;
-import password.pwm.user.UserInfo;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.AuthenticationUtility;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchService;
+import password.pwm.svc.cr.NMASCrOperator;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.intruder.IntruderServiceClient;
+import password.pwm.svc.otp.OTPUserRecord;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenType;
 import password.pwm.svc.token.TokenUtil;
+import password.pwm.user.UserInfo;
 import password.pwm.util.CaptchaUtility;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.MiscUtil;
-import password.pwm.util.json.JsonFactory;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
+import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
-import password.pwm.svc.cr.NMASCrOperator;
-import password.pwm.svc.otp.OTPUserRecord;
 import password.pwm.util.password.PasswordUtility;
 import password.pwm.ws.server.RestResultBean;
 
@@ -235,7 +236,10 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             throws PwmUnrecoverableException, ServletException, IOException, ChaiUnavailableException
     {
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean( pwmRequest );
-        final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmRequest.getPwmDomain(), forgottenPasswordBean );
+        final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile(
+                pwmRequest.getPwmDomain(),
+                pwmRequest.getLabel(),
+                forgottenPasswordBean );
 
         final boolean resendEnabled = forgottenPasswordProfile.readSettingAsBoolean( PwmSetting.TOKEN_RESEND_ENABLE );
 
@@ -398,7 +402,8 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
 
         final String contextParam = pwmRequest.readParameterAsString( PwmConstants.PARAM_CONTEXT );
-        final String ldapProfile = pwmRequest.readParameterAsString( PwmConstants.PARAM_LDAP_PROFILE );
+        final Optional<ProfileID> ldapProfile = pwmDomain.getConfig()
+                .ldapProfileForStringId( pwmRequest.readParameterAsString( PwmConstants.PARAM_LDAP_PROFILE ) );
 
         final boolean bogusUserModeEnabled = pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.RECOVERY_BOGUS_USER_ENABLE );
 
@@ -454,7 +459,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                         .filter( searchFilter )
                         .formValues( formValues )
                         .contexts( Collections.singletonList( contextParam ) )
-                        .ldapProfile( ldapProfile )
+                        .ldapProfile( ldapProfile.orElse( null ) )
                         .build();
 
                 userIdentity = userSearchService.performSingleUserSearch( searchConfiguration, pwmRequest.getLabel() );
@@ -801,7 +806,10 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean( pwmRequest );
 
         {
-            final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmRequest.getPwmDomain(), forgottenPasswordBean );
+            final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile(
+                    pwmRequest.getPwmDomain(),
+                    pwmRequest.getLabel(),
+                    forgottenPasswordBean );
             final boolean resendEnabled = forgottenPasswordProfile.readSettingAsBoolean( PwmSetting.TOKEN_RESEND_ENABLE );
             if ( !resendEnabled )
             {
@@ -974,11 +982,14 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             return;
         }
 
-        final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmRequest.getPwmDomain(), forgottenPasswordBean );
+        final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile(
+                pwmRequest.getPwmDomain(),
+                pwmRequest.getLabel(),
+                forgottenPasswordBean );
         {
-            final Map<String, ForgottenPasswordProfile> profiles = pwmRequest.getDomainConfig().getForgottenPasswordProfiles();
+            final Map<ProfileID, ForgottenPasswordProfile> profiles = pwmRequest.getDomainConfig().getForgottenPasswordProfiles();
             final String profileDebugMsg = forgottenPasswordProfile != null && profiles != null && profiles.size() > 1
-                    ? " profile=" + forgottenPasswordProfile.getIdentifier() + ", "
+                    ? " profile=" + forgottenPasswordProfile.getId() + ", "
                     : "";
             LOGGER.trace( pwmRequest, () -> "entering forgotten password progress engine: "
                     + profileDebugMsg
@@ -1446,12 +1457,15 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
 
             case OAUTH:
                 forgottenPasswordBean.getProgress().setInProgressVerificationMethod( IdentityVerificationMethod.OAUTH );
-                final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmRequest.getPwmDomain(), forgottenPasswordBean );
+                final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile(
+                        pwmRequest.getPwmDomain(),
+                        pwmRequest.getLabel(),
+                        forgottenPasswordBean );
                 final OAuthSettings oAuthSettings = OAuthSettings.forForgottenPassword( forgottenPasswordProfile );
                 final OAuthMachine oAuthMachine = new OAuthMachine( pwmRequest.getLabel(), oAuthSettings );
                 pwmRequest.getPwmDomain().getSessionStateService().saveSessionBeans( pwmRequest );
                 final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
-                oAuthMachine.redirectUserToOAuthServer( pwmRequest, null, userIdentity, forgottenPasswordProfile.getIdentifier() );
+                oAuthMachine.redirectUserToOAuthServer( pwmRequest, null, userIdentity, forgottenPasswordProfile.getId() );
                 break;
 
 
@@ -1482,6 +1496,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean( pwmRequest );
         final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile(
                 pwmRequest.getPwmDomain(),
+                pwmRequest.getLabel(),
                 forgottenPasswordBean
         );
 

+ 6 - 5
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStageProcessor.java

@@ -22,6 +22,7 @@ package password.pwm.http.servlet.forgottenpw;
 
 import password.pwm.PwmDomain;
 import password.pwm.bean.PasswordStatus;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
@@ -144,11 +145,11 @@ class ForgottenPasswordStageProcessor
                 return Optional.of( ForgottenPasswordStage.VERIFICATION );
             }
 
-            final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmDomain, forgottenPasswordBean );
+            final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmDomain, sessionLabel, forgottenPasswordBean );
             {
-                final Map<String, ForgottenPasswordProfile> profileIDList = config.getForgottenPasswordProfiles();
+                final Map<ProfileID, ForgottenPasswordProfile> profileIDList = config.getForgottenPasswordProfiles();
                 final String profileDebugMsg = forgottenPasswordProfile != null && profileIDList != null && profileIDList.size() > 1
-                        ? " profile=" + forgottenPasswordProfile.getIdentifier() + ", "
+                        ? " profile=" + forgottenPasswordProfile.getId() + ", "
                         : "";
                 LOGGER.trace( sessionLabel, () -> "entering forgotten password progress engine: "
                         + profileDebugMsg
@@ -332,7 +333,7 @@ class ForgottenPasswordStageProcessor
                             PwmError.ERROR_INTERNAL, "unable to load userInfo while processing forgotten password controller 6" ) );
 
             // check if user's pw is within min lifetime window
-            final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmDomain, forgottenPasswordBean );
+            final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmDomain, sessionLabel, forgottenPasswordBean );
             final RecoveryMinLifetimeOption minLifetimeOption = forgottenPasswordProfile.readSettingAsEnum(
                     PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS,
                     RecoveryMinLifetimeOption.class
@@ -369,7 +370,7 @@ class ForgottenPasswordStageProcessor
                     .orElseThrow( () -> PwmUnrecoverableException.newException(
                             PwmError.ERROR_INTERNAL, "unable to load userInfo while processing forgotten password controller 7" ) );
 
-            final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmDomain, forgottenPasswordBean );
+            final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmDomain, sessionLabel, forgottenPasswordBean );
 
             final RecoveryMinLifetimeOption minLifetimeOption = forgottenPasswordProfile.readSettingAsEnum(
                     PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS,

+ 12 - 10
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java

@@ -30,6 +30,7 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
@@ -38,7 +39,6 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.option.RecoveryAction;
 import password.pwm.config.option.SelectableContextMode;
-import password.pwm.config.profile.AbstractProfile;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordRule;
@@ -55,7 +55,6 @@ import password.pwm.http.bean.ForgottenPasswordStage;
 import password.pwm.http.tag.PasswordRequirementsTag;
 import password.pwm.i18n.Display;
 import password.pwm.i18n.Message;
-import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.auth.AuthenticationUtility;
 import password.pwm.ldap.auth.SessionAuthenticator;
@@ -69,6 +68,7 @@ import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenType;
 import password.pwm.svc.token.TokenUtil;
+import password.pwm.user.UserInfo;
 import password.pwm.util.PasswordData;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.i18n.LocaleHelper;
@@ -925,7 +925,7 @@ public class ForgottenPasswordStateMachine
                 throws PwmUnrecoverableException
         {
             final PwmRequestContext pwmRequestContext = forgottenPasswordStateMachine.getRequestContext();
-            final String profile = forgottenPasswordStateMachine.getForgottenPasswordBean().getProfile();
+            final ProfileID profile = forgottenPasswordStateMachine.getForgottenPasswordBean().getProfile();
             final List<FormConfiguration> formFields = new ArrayList<>( makeSelectableContextValues( pwmRequestContext, profile ) );
             formFields.addAll( pwmRequestContext.getDomainConfig().readSettingAsForm( PwmSetting.FORGOTTEN_PASSWORD_SEARCH_FORM ) );
 
@@ -954,11 +954,13 @@ public class ForgottenPasswordStateMachine
 
             // process input profile
             {
-                final String inputProfile = values.get( PwmConstants.PARAM_LDAP_PROFILE );
-                if ( StringUtil.notEmpty( inputProfile ) && pwmRequestContext.getDomainConfig().getLdapProfiles().containsKey( inputProfile ) )
+                ProfileID.createNullable( values.get( PwmConstants.PARAM_LDAP_PROFILE ) ).ifPresent( inputProfile ->
                 {
-                    forgottenPasswordStateMachine.getForgottenPasswordBean().setProfile( inputProfile );
-                }
+                    if ( pwmRequestContext.getDomainConfig().getLdapProfiles().containsKey( inputProfile ) )
+                    {
+                        forgottenPasswordStateMachine.getForgottenPasswordBean().setProfile( inputProfile );
+                    }
+                } );
             }
 
             final LdapProfile ldapProfile = pwmRequestContext.getDomainConfig().getLdapProfiles().getOrDefault(
@@ -1006,7 +1008,7 @@ public class ForgottenPasswordStateMachine
                             .filter( searchFilter )
                             .formValues( formValues )
                             .contexts( Collections.singletonList( contextParam ) )
-                            .ldapProfile( ldapProfile.getIdentifier() )
+                            .ldapProfile( ldapProfile.getId() )
                             .build();
 
                     userIdentity = userSearchService.performSingleUserSearch( searchConfiguration, pwmRequestContext.getSessionLabel() );
@@ -1050,7 +1052,7 @@ public class ForgottenPasswordStateMachine
             forgottenPasswordStateMachine.getForgottenPasswordBean().setUserSearchValues( FormUtility.asStringMap( formValues ) );
         }
 
-        private List<FormConfiguration> makeSelectableContextValues( final PwmRequestContext pwmRequestContext, final String profile )
+        private List<FormConfiguration> makeSelectableContextValues( final PwmRequestContext pwmRequestContext, final ProfileID profile )
                 throws PwmUnrecoverableException
         {
             final SelectableContextMode selectableContextMode = pwmRequestContext.getDomainConfig().readSettingAsEnum(
@@ -1069,7 +1071,7 @@ public class ForgottenPasswordStateMachine
 
                 final Map<String, String> profileSelectValues = pwmRequestContext.getDomainConfig().getLdapProfiles().values().stream()
                         .collect( Collectors.toUnmodifiableMap(
-                                AbstractProfile::getIdentifier,
+                                ldapProfile -> ldapProfile.getId().stringValue(),
                                 ldapProfile -> ldapProfile.getDisplayName( pwmRequestContext.getLocale() ) ) );
 
                 final Map<String, String> labelLocaleMap = LocaleHelper.localeMapToStringMap(

+ 19 - 12
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -35,6 +35,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.EmailItemBean;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
@@ -59,7 +60,6 @@ import password.pwm.http.PwmSession;
 import password.pwm.http.auth.HttpAuthRecord;
 import password.pwm.http.bean.ForgottenPasswordBean;
 import password.pwm.i18n.Message;
-import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
@@ -69,9 +69,9 @@ import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.token.TokenType;
 import password.pwm.svc.token.TokenUtil;
+import password.pwm.user.UserInfo;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.CollectionUtil;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.password.PasswordUtility;
@@ -249,7 +249,7 @@ public class ForgottenPasswordUtil
     )
             throws PwmUnrecoverableException
     {
-        final String profileID = forgottenPasswordBean.getForgottenPasswordProfileID();
+        final ProfileID profileID = forgottenPasswordBean.getForgottenPasswordProfileID();
         final ForgottenPasswordProfile forgottenPasswordProfile = pwmRequestContext.getDomainConfig().getForgottenPasswordProfiles().get( profileID );
         final MessageSendMethod tokenSendMethod = forgottenPasswordProfile.readSettingAsEnum( PwmSetting.RECOVERY_TOKEN_SEND_METHOD, MessageSendMethod.class );
         final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean ).orElseThrow();
@@ -413,7 +413,7 @@ public class ForgottenPasswordUtil
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final ForgottenPasswordBean forgottenPasswordBean = ForgottenPasswordServlet.forgottenPasswordBean( pwmRequest );
-        final ForgottenPasswordProfile forgottenPasswordProfile = forgottenPasswordProfile( pwmRequest.getPwmDomain(), forgottenPasswordBean );
+        final ForgottenPasswordProfile forgottenPasswordProfile = forgottenPasswordProfile( pwmRequest.getPwmDomain(), pwmRequest.getLabel(), forgottenPasswordBean );
         final RecoveryAction recoveryAction = ForgottenPasswordUtil.getRecoveryAction( pwmDomain.getConfig(), forgottenPasswordBean );
 
         LOGGER.trace( pwmRequest, () -> "beginning process to send new password to user" );
@@ -540,7 +540,7 @@ public class ForgottenPasswordUtil
 
         final List<Challenge> challengeList;
         {
-            final String firstProfile = pwmRequestContext.getDomainConfig().getChallengeProfileIDs().get( 0 );
+            final ProfileID firstProfile = pwmRequestContext.getDomainConfig().getChallengeProfileIDs().get( 0 );
             final ChallengeSet challengeSet = pwmRequestContext.getDomainConfig().getChallengeProfile( firstProfile, PwmConstants.DEFAULT_LOCALE ).getChallengeSet()
                     .orElseThrow( () -> new PwmUnrecoverableException( PwmError.ERROR_NO_CHALLENGES.toInfo() ) );
             challengeList = new ArrayList<>( challengeSet.getRequiredChallenges() );
@@ -569,7 +569,7 @@ public class ForgottenPasswordUtil
         forgottenPasswordBean.setAttributeForm( formData );
         forgottenPasswordBean.setBogusUser( true );
         {
-            final String profileID = pwmRequestContext.getDomainConfig().getForgottenPasswordProfiles().keySet().iterator().next();
+            final ProfileID profileID = pwmRequestContext.getDomainConfig().getForgottenPasswordProfiles().keySet().iterator().next();
             forgottenPasswordBean.setForgottenPasswordProfileID( profileID  );
         }
 
@@ -624,7 +624,7 @@ public class ForgottenPasswordUtil
     )
             throws PwmUnrecoverableException
     {
-        final Optional<String> profileID = ProfileUtility.discoverProfileIDForUser(
+        final Optional<ProfileID> profileID = ProfileUtility.discoverProfileIDForUser(
                 pwmDomain,
                 sessionLabel,
                 userIdentity,
@@ -642,15 +642,22 @@ public class ForgottenPasswordUtil
 
     static ForgottenPasswordProfile forgottenPasswordProfile(
             final PwmDomain pwmDomain,
+            final SessionLabel sessionLabel,
             final ForgottenPasswordBean forgottenPasswordBean
     )
     {
-        final String forgottenProfileID = forgottenPasswordBean.getForgottenPasswordProfileID();
-        if ( StringUtil.isEmpty( forgottenProfileID ) )
+        final ProfileID forgottenProfileID = forgottenPasswordBean.getForgottenPasswordProfileID();
+        if ( forgottenProfileID == null )
         {
             throw new IllegalStateException( "cannot load forgotten profile without ID registered in bean" );
         }
-        return pwmDomain.getConfig().getForgottenPasswordProfiles().get( forgottenProfileID );
+        final ForgottenPasswordProfile profile = pwmDomain.getConfig().getForgottenPasswordProfiles().get( forgottenProfileID );
+        if ( profile == null )
+        {
+            LOGGER.trace( sessionLabel, () -> "forgotten password bean references an invalid profile, clearing value in bean" );
+            forgottenPasswordBean.setForgottenPasswordProfileID( null );
+        }
+        return profile;
     }
 
 
@@ -675,7 +682,7 @@ public class ForgottenPasswordUtil
                 pwmRequestContext.getSessionLabel(),
                 userIdentity
         );
-        final String forgottenProfileID = forgottenPasswordProfile.getIdentifier();
+        final ProfileID forgottenProfileID = forgottenPasswordProfile.getId();
         forgottenPasswordBean.setForgottenPasswordProfileID( forgottenProfileID );
 
         final ForgottenPasswordBean.RecoveryFlags recoveryFlags = calculateRecoveryFlags(
@@ -801,7 +808,7 @@ public class ForgottenPasswordUtil
 
     static ForgottenPasswordBean.RecoveryFlags calculateRecoveryFlags(
             final PwmDomain pwmDomain,
-            final String forgottenPasswordProfileID
+            final ProfileID forgottenPasswordProfileID
     )
     {
         final DomainConfig config = pwmDomain.getConfig();

+ 5 - 5
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java

@@ -43,14 +43,14 @@ import password.pwm.http.bean.DisplayElement;
 import password.pwm.http.servlet.accountinfo.AccountInformationBean;
 import password.pwm.http.tag.PasswordRequirementsTag;
 import password.pwm.i18n.Display;
-import password.pwm.user.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.ViewableUserInfoDisplayReader;
-import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.user.UserInfo;
 import password.pwm.util.form.FormUtility;
+import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
-import password.pwm.util.json.JsonFactory;
 import password.pwm.util.java.TimeDuration;
+import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 
@@ -177,9 +177,9 @@ public class HelpdeskDetailInfoBean implements Serializable
         }
 
         if ( ( userInfo.getPasswordPolicy() != null )
-                && userInfo.getPasswordPolicy().getIdentifier() != null )
+                && userInfo.getPasswordPolicy().getId() != null )
         {
-            builder.passwordPolicyID( userInfo.getPasswordPolicy().getIdentifier() );
+            builder.passwordPolicyID( userInfo.getPasswordPolicy().getId().toString() );
         }
         else
         {

+ 6 - 6
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -1149,9 +1149,9 @@ public class HelpdeskServlet extends ControlledPwmServlet
             if ( !buttonEnabled && ( mode == HelpdeskClearResponseMode.no ) )
             {
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting "
-                        + PwmSetting.HELPDESK_CLEAR_RESPONSES_BUTTON.toMenuLocationDebug( helpdeskProfile.getIdentifier(), pwmRequest.getLocale() )
+                        + PwmSetting.HELPDESK_CLEAR_RESPONSES_BUTTON.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() )
                         + " must be enabled or setting "
-                        + PwmSetting.HELPDESK_CLEAR_RESPONSES.toMenuLocationDebug( helpdeskProfile.getIdentifier(), pwmRequest.getLocale() )
+                        + PwmSetting.HELPDESK_CLEAR_RESPONSES.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() )
                         + "must be set to yes or ask" ) );
             }
         }
@@ -1213,7 +1213,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             if ( mode == HelpdeskUIMode.none )
             {
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting "
-                        + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getIdentifier(), pwmRequest.getLocale() )
+                        + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() )
                         + " must not be set to none" ) );
             }
         }
@@ -1259,7 +1259,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
         if ( mode == HelpdeskUIMode.none )
         {
             throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting "
-                    + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getIdentifier(), pwmRequest.getLocale() )
+                    + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() )
                     + " must not be set to none" ) );
         }
 
@@ -1270,7 +1270,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             if ( mode != HelpdeskUIMode.random )
             {
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting "
-                        + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getIdentifier(), pwmRequest.getLocale() )
+                        + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() )
                         + " is set to " + mode + " and no password is included in request" ) );
             }
             final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser(
@@ -1289,7 +1289,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             if ( mode == HelpdeskUIMode.random )
             {
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, "setting "
-                        + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getIdentifier(), pwmRequest.getLocale() )
+                        + PwmSetting.HELPDESK_SET_PASSWORD_MODE.toMenuLocationDebug( helpdeskProfile.getId(), pwmRequest.getLocale() )
                         + " is set to autogen yet a password is included in request" ) );
             }
 

+ 14 - 11
server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -24,6 +24,7 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.VerificationMethodSystem;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
@@ -205,7 +206,7 @@ public class NewUserServlet extends ControlledPwmServlet
 
         if ( newUserBean.getProfileID() == null )
         {
-            final Set<String> newUserProfileIDs = pwmDomain.getConfig().getNewUserProfiles().keySet();
+            final Set<ProfileID> newUserProfileIDs = pwmDomain.getConfig().getNewUserProfiles().keySet();
             if ( newUserProfileIDs.isEmpty() )
             {
                 pwmRequest.respondWithError( new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, "no new user profiles are defined" ) );
@@ -216,7 +217,7 @@ public class NewUserServlet extends ControlledPwmServlet
 
             if ( visibleProfiles.size() == 1 )
             {
-                final String singleID = newUserProfileIDs.iterator().next();
+                final ProfileID singleID = newUserProfileIDs.iterator().next();
                 LOGGER.trace( pwmRequest, () -> "only one new user profile is defined, auto-selecting profile " + singleID );
                 newUserBean.setProfileID( singleID );
             }
@@ -352,8 +353,8 @@ public class NewUserServlet extends ControlledPwmServlet
             final List<String> urlSegments = PwmURL.splitPathString( urlRemainder );
             if ( urlSegments.size() == 2 && profileUrlSegment.equals( urlSegments.get( 0 ) ) )
             {
-                final String requestedProfile = urlSegments.get( 1 );
-                final Collection<String> profileIDs = pwmRequest.getDomainConfig().getNewUserProfiles().keySet();
+                final ProfileID requestedProfile = ProfileID.create( urlSegments.get( 1 ) );
+                final Collection<ProfileID> profileIDs = pwmRequest.getDomainConfig().getNewUserProfiles().keySet();
                 if ( profileIDs.contains( requestedProfile ) )
                 {
                     LOGGER.debug( pwmRequest, () -> "detected profile on request uri: " + requestedProfile );
@@ -612,20 +613,22 @@ public class NewUserServlet extends ControlledPwmServlet
     public ProcessStatus handleProfileChoiceRequest( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
     {
-        final Set<String> profileIDs = pwmRequest.getDomainConfig().getNewUserProfiles().keySet();
-        final String requestedProfileID = pwmRequest.readParameterAsString( "profile" );
+        final Set<ProfileID> profileIDs = pwmRequest.getDomainConfig().getNewUserProfiles().keySet();
+        final Optional<ProfileID> requestedProfileID = ProfileID.createNullable( pwmRequest.readParameterAsString( "profile" ) );
 
         final NewUserBean newUserBean = getNewUserBean( pwmRequest );
 
-        if ( requestedProfileID == null || requestedProfileID.isEmpty() )
+        if ( requestedProfileID.isPresent() && profileIDs.contains( requestedProfileID.get() ) )
         {
-            newUserBean.setProfileID( null );
+            newUserBean.setProfileID( requestedProfileID.get() );
         }
-        if ( profileIDs.contains( requestedProfileID ) )
+        else
         {
-            newUserBean.setProfileID( requestedProfileID );
+            newUserBean.setProfileID( null );
         }
 
+
+
         return ProcessStatus.Continue;
     }
 
@@ -807,7 +810,7 @@ public class NewUserServlet extends ControlledPwmServlet
 
     public static NewUserProfile getNewUserProfile( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
     {
-        final String profileID = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, NewUserBean.class ).getProfileID();
+        final ProfileID profileID = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, NewUserBean.class ).getProfileID();
         if ( profileID == null )
         {
             throw new IllegalStateException( "can not read new user profile until profile is selected" );

+ 2 - 1
server/src/main/java/password/pwm/http/servlet/newuser/NewUserTokenData.java

@@ -22,6 +22,7 @@ package password.pwm.http.servlet.newuser;
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
+import password.pwm.bean.ProfileID;
 
 import java.io.Serializable;
 import java.util.HashSet;
@@ -33,7 +34,7 @@ class NewUserTokenData implements Serializable
 {
 
     @SerializedName( "id" )
-    private String profileID;
+    private ProfileID profileID;
 
     @SerializedName( "f" )
     private NewUserForm formData;

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -313,7 +313,7 @@ class NewUserUtils
         remoteWriteFormData( pwmRequest, newUserForm );
 
         // authenticate the user to pwm
-        final UserIdentity userIdentity = UserIdentity.create( newUserDN, newUserProfile.getLdapProfile( pwmDomain.getConfig() ).getIdentifier(), pwmRequest.getDomainID() );
+        final UserIdentity userIdentity = UserIdentity.create( newUserDN, newUserProfile.getLdapProfile( pwmDomain.getConfig() ).getId(), pwmRequest.getDomainID() );
         final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator( pwmDomain, pwmRequest, PwmAuthenticationSource.NEW_USER_REGISTRATION );
         sessionAuthenticator.authenticateUser( userIdentity, userPassword );
 
@@ -533,7 +533,7 @@ class NewUserUtils
             final boolean visible = newUserProfile.readSettingAsBoolean( PwmSetting.NEWUSER_PROFILE_DISPLAY_VISIBLE );
             if ( visible )
             {
-                returnMap.put( newUserProfile.getIdentifier(), newUserProfile.getDisplayName( pwmRequest.getLocale() ) );
+                returnMap.put( newUserProfile.getId().stringValue(), newUserProfile.getDisplayName( pwmRequest.getLocale() ) );
             }
         }
         return Collections.unmodifiableMap( returnMap );

+ 2 - 1
server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java

@@ -23,6 +23,7 @@ package password.pwm.http.servlet.oauth;
 import password.pwm.AppProperty;
 import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.profile.ForgottenPasswordProfile;
@@ -318,7 +319,7 @@ public class OAuthConsumerServlet extends AbstractPwmServlet
                 return OAuthSettings.forSSOAuthentication( pwmRequest.getDomainConfig() );
 
             case ForgottenPassword:
-                final String profileId = oAuthState.getForgottenProfileId();
+                final ProfileID profileId = oAuthState.getForgottenProfileId();
                 final ForgottenPasswordProfile profile = pwmRequest.getDomainConfig().getForgottenPasswordProfiles().get( profileId );
                 return OAuthSettings.forForgottenPassword( profile );
 

+ 3 - 2
server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java

@@ -23,6 +23,7 @@ package password.pwm.http.servlet.oauth;
 import org.apache.http.HttpStatus;
 import password.pwm.AppProperty;
 import password.pwm.bean.LoginInfoBean;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
@@ -104,7 +105,7 @@ public class OAuthMachine
             final PwmRequest pwmRequest,
             final String nextUrl,
             final UserIdentity userIdentity,
-            final String forgottenPasswordProfile
+            final ProfileID forgottenPasswordProfile
     )
             throws PwmUnrecoverableException, IOException
     {
@@ -402,7 +403,7 @@ public class OAuthMachine
     private String makeStateStringForRequest(
             final PwmRequest pwmRequest,
             final String nextUrl,
-            final String forgottenPasswordProfileID
+            final ProfileID forgottenPasswordProfileID
     )
             throws PwmUnrecoverableException
     {

+ 3 - 2
server/src/main/java/password/pwm/http/servlet/oauth/OAuthState.java

@@ -23,6 +23,7 @@ package password.pwm.http.servlet.oauth;
 import com.google.gson.annotations.SerializedName;
 import lombok.Builder;
 import lombok.Value;
+import password.pwm.bean.ProfileID;
 import password.pwm.util.java.AtomicLoopIntIncrementer;
 
 import java.io.Serializable;
@@ -57,7 +58,7 @@ class OAuthState implements Serializable
     private OAuthUseCase useCase;
 
     @SerializedName( "f" )
-    private String forgottenProfileId;
+    private ProfileID forgottenProfileId;
 
     @SerializedName( "v" )
     private int version = 1;
@@ -71,7 +72,7 @@ class OAuthState implements Serializable
                 .build();
     }
 
-    static OAuthState newForgottenPasswordState( final String sessionID, final String forgottenProfileId )
+    static OAuthState newForgottenPasswordState( final String sessionID, final ProfileID forgottenProfileId )
     {
         return OAuthState.builder()
                 .sessionID( sessionID )

+ 28 - 11
server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java

@@ -27,8 +27,11 @@ import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmEnvironment;
 import password.pwm.bean.PasswordStatus;
+import password.pwm.bean.ProfileID;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.option.SelectableContextMode;
 import password.pwm.config.profile.ChangePasswordProfile;
+import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.PeopleSearchProfile;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.error.PwmUnrecoverableException;
@@ -37,12 +40,13 @@ import password.pwm.health.HealthStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestFlag;
 import password.pwm.http.servlet.resource.TextFileResource;
-import password.pwm.user.UserInfo;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.otp.OTPUserRecord;
+import password.pwm.user.UserInfo;
 import password.pwm.util.java.StringUtil;
 
 import java.util.Collections;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -419,7 +423,6 @@ public enum PwmIfTest
 
     private static class ActorHasProfileTest implements Test
     {
-
         private final ProfileDefinition profileDefinition;
 
         ActorHasProfileTest( final ProfileDefinition profileDefinition )
@@ -430,8 +433,7 @@ public enum PwmIfTest
         @Override
         public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) throws ChaiUnavailableException, PwmUnrecoverableException
         {
-            final String profileID = pwmRequest.getPwmSession().getUserInfo().getProfileIDs().get( profileDefinition );
-            return StringUtil.notEmpty( profileID );
+            return pwmRequest.getPwmSession().getUserInfo().getProfileIDs().containsKey( profileDefinition );
         }
     }
 
@@ -550,13 +552,8 @@ public enum PwmIfTest
                 }
             }
 
-            final String profileID = pwmRequest.getPwmSession().getUserInfo().getProfileIDs().get( profileDefinition );
-            if ( StringUtil.isEmpty( profileID ) )
-            {
-                return false;
-            }
-
-            return true;
+            final ProfileID profileID = pwmRequest.getPwmSession().getUserInfo().getProfileIDs().get( profileDefinition );
+            return profileID != null;
         }
     }
 
@@ -616,4 +613,24 @@ public enum PwmIfTest
         }
     }
 
+    private static class ShowSelectableContexts implements Test
+    {
+        @Override
+        public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options )
+                throws ChaiUnavailableException, PwmUnrecoverableException
+        {
+            final SelectableContextMode selectableContextMode = pwmRequest.getDomainConfig()
+                    .readSettingAsEnum( PwmSetting.LDAP_SELECTABLE_CONTEXT_MODE, SelectableContextMode.class );
+
+            final String selectedProfileStr = pwmRequest.readParameterAsString( PwmConstants.PARAM_LDAP_PROFILE );
+
+            final ProfileID selectedProfileID = pwmRequest.getPwmDomain().getConfig().ldapProfileForStringId( selectedProfileStr )
+                    .orElse( pwmRequest.getDomainConfig().getDefaultLdapProfile( ).getId() );
+
+            final LdapProfile selectedProfile = pwmRequest.getDomainConfig().getLdapProfiles().get( selectedProfileID );
+
+            final Map<String, String> selectableContexts = selectedProfile.getSelectableContexts( pwmRequest.getLabel(), pwmRequest.getPwmDomain() );
+            return selectableContextMode == SelectableContextMode.SHOW_CONTEXTS && selectableContexts.size() > 0;
+        }
+    }
 }

+ 12 - 11
server/src/main/java/password/pwm/ldap/LdapBrowser.java

@@ -33,6 +33,7 @@ import lombok.Builder;
 import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.ProfileID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.config.AppConfig;
 import password.pwm.config.DomainConfig;
@@ -41,7 +42,7 @@ import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.CollectorUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
@@ -68,7 +69,7 @@ public class LdapBrowser
 
     private final SessionLabel sessionLabel;
     private final ChaiProviderFactory chaiProviderFactory;
-    private final Map<String, ChaiProvider> providerCache = new HashMap<>();
+    private final Map<ProfileID, ChaiProvider> providerCache = new HashMap<>();
 
     private enum DnType
     {
@@ -89,7 +90,7 @@ public class LdapBrowser
 
     public LdapBrowseResult doBrowse(
             final DomainID domainID,
-            final String profile,
+            final ProfileID profile,
             final String dn
     )
             throws PwmUnrecoverableException
@@ -119,7 +120,7 @@ public class LdapBrowser
 
     private LdapBrowseResult doBrowseImpl(
             final DomainID domainID,
-            final String profileID,
+            final ProfileID profileID,
             final String dn
     )
             throws PwmUnrecoverableException, ChaiUnavailableException, ChaiOperationException
@@ -160,7 +161,7 @@ public class LdapBrowser
 
     private void updateBrowseResultChildren(
             final DomainID domainID,
-            final String profileID,
+            final ProfileID profileID,
             final String dn,
             final LdapBrowseResult.LdapBrowseResultBuilder result
     )
@@ -189,7 +190,7 @@ public class LdapBrowser
         result.maxResults( childDNs.size() >= getMaxSizeLimit() );
     }
 
-    private ChaiProvider getChaiProvider( final DomainID domainID, final String profile ) throws PwmUnrecoverableException
+    private ChaiProvider getChaiProvider( final DomainID domainID, final ProfileID profile ) throws PwmUnrecoverableException
     {
         if ( !providerCache.containsKey( profile ) )
         {
@@ -209,7 +210,7 @@ public class LdapBrowser
 
     private Map<String, DnType> getChildEntries(
             final DomainID domainID,
-            final String profile,
+            final ProfileID profile,
             final String dn
     )
             throws ChaiUnavailableException, PwmUnrecoverableException, ChaiOperationException
@@ -219,7 +220,7 @@ public class LdapBrowser
 
         if ( StringUtil.isEmpty( dn ) && chaiProvider.getDirectoryVendor() == DirectoryVendor.ACTIVE_DIRECTORY )
         {
-            return Collections.unmodifiableMap( adRootDNList( domainID, profile ).stream().collect( CollectionUtil.collectorToLinkedMap(
+            return Collections.unmodifiableMap( adRootDNList( domainID, profile ).stream().collect( CollectorUtil.toLinkedMap(
                     Function.identity(),
                     rootDN -> DnType.navigable
             ) ) );
@@ -288,7 +289,7 @@ public class LdapBrowser
         return chaiProvider.searchMultiValues( dn, searchHelper );
     }
 
-    private Set<String> adRootDNList( final DomainID domainID, final String profile )
+    private Set<String> adRootDNList( final DomainID domainID, final ProfileID profile )
             throws ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException
     {
         final ChaiProvider chaiProvider = getChaiProvider( domainID, profile );
@@ -319,9 +320,9 @@ public class LdapBrowser
     public static class LdapBrowseResult implements Serializable
     {
         private String dn;
-        private String profileID;
+        private ProfileID profileID;
         private String parentDN;
-        private List<String> profileList;
+        private List<ProfileID> profileList;
         private boolean maxResults;
 
         private List<DNInformation> navigableDNlist;

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません