diff --git a/build/checkstyle.xml b/build/checkstyle.xml index 4e9d67b10..50718572b 100644 --- a/build/checkstyle.xml +++ b/build/checkstyle.xml @@ -269,12 +269,16 @@ - + + + - + + + diff --git a/client/package.json b/client/package.json index b4b90fd6c..d16e0b725 100644 --- a/client/package.json +++ b/client/package.json @@ -21,9 +21,9 @@ "@microfocus/ng-ias": "1.0.1", "@microfocus/ux-ias": "1.1.2", "@uirouter/angularjs": "1.0.15", - "angular": "1.7.8", + "angular": "^1.7.9", "angular-aria": "1.7.8", - "angular-sanitize": "1.7.8", + "angular-sanitize": "1.7.9", "angular-translate": "2.18.1", "core-js": "3.2.1", "textangular": "1.5.16" @@ -38,7 +38,7 @@ "angular-mocks": "1.6.9", "autoprefixer": "8.1.0", "copy-webpack-plugin": "4.5.1", - "css-loader": "^3.2.0", + "css-loader": "^3.2.1", "file-loader": "1.1.11", "html-loader": "0.5.5", "html-webpack-plugin": "3.0.6", @@ -46,10 +46,10 @@ "imports-loader": "0.8.0", "jasmine": "3.2.0", "jasmine-core": "3.2.1", - "jshint": "^2.10.2", + "jshint": "^2.10.3", "jshint-loader": "0.8.4", "json-loader": "0.5.7", - "karma": "^4.3.0", + "karma": "^4.4.1", "karma-chrome-launcher": "2.2.0", "karma-jasmine": "1.1.2", "karma-jasmine-html-reporter": "1.3.1", @@ -59,7 +59,7 @@ "karma-webpack": "3.0.5", "moment": "2.21.0", "ngtemplate-loader": "2.0.1", - "node-sass": "^4.12.0", + "node-sass": "^4.13.0", "phantomjs": "2.1.7", "phantomjs-prebuilt": "2.1.16", "postcss-loader": "2.1.1", @@ -76,7 +76,7 @@ "uglifyjs-webpack-plugin": "1.2.3", "url-loader": "1.0.1", "webpack": "4.1.1", - "webpack-cli": "^3.3.8", + "webpack-cli": "^3.3.10", "webpack-dev-server": "3.1.14", "webpack-merge": "4.1.2", "write-file-webpack-plugin": "4.2.0" diff --git a/data-service/src/main/java/password/pwm/receiver/FtpDataIngestor.java b/data-service/src/main/java/password/pwm/receiver/FtpDataIngestor.java index 0d36eaf37..5675e3e6c 100644 --- a/data-service/src/main/java/password/pwm/receiver/FtpDataIngestor.java +++ b/data-service/src/main/java/password/pwm/receiver/FtpDataIngestor.java @@ -74,7 +74,7 @@ void readData( final Storage storage ) { readFile( ftpClient, fileName, storage ); } - catch ( Exception e ) + catch ( final Exception e ) { app.getStatus().setLastFtpIngest( Instant.now() ); final String msg = "error while reading ftp file '" + fileName + "': " + e.getMessage(); @@ -93,7 +93,7 @@ void readData( final Storage storage ) app.getStatus().setLastFtpIngest( Instant.now() ); app.getStatus().setLastFtpFilesRead( files.size() ); } - catch ( Exception e ) + catch ( final Exception e ) { app.getStatus().setLastFtpIngest( Instant.now() ); app.getStatus().setLastFtpStatus( "error during ftp scan: " + e.getMessage() ); @@ -130,7 +130,7 @@ private void readZippedByteStream( final InputStream inputStream, final String f storage.store( bean ); } } - catch ( Exception e ) + catch ( final Exception e ) { final String msg = "error reading ftp file '" + fileName + "', error: " + e.getMessage(); LOGGER.info( msg ); diff --git a/data-service/src/main/java/password/pwm/receiver/PwmReceiverApp.java b/data-service/src/main/java/password/pwm/receiver/PwmReceiverApp.java index 2c52b5a0e..9a9833d09 100644 --- a/data-service/src/main/java/password/pwm/receiver/PwmReceiverApp.java +++ b/data-service/src/main/java/password/pwm/receiver/PwmReceiverApp.java @@ -53,7 +53,7 @@ public PwmReceiverApp( ) { settings = Settings.readFromFile( propsFile ); } - catch ( IOException e ) + catch ( final IOException e ) { final String errorMsg = "can't read configuration: " + JavaHelper.readHostileExceptionMessage( e ); status.setErrorState( errorMsg ); @@ -65,7 +65,7 @@ public PwmReceiverApp( ) { storage = new Storage( settings ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "can't start storage system: " + JavaHelper.readHostileExceptionMessage( e ); status.setErrorState( errorMsg ); diff --git a/data-service/src/main/java/password/pwm/receiver/Storage.java b/data-service/src/main/java/password/pwm/receiver/Storage.java index 8e548972a..978bd5bce 100644 --- a/data-service/src/main/java/password/pwm/receiver/Storage.java +++ b/data-service/src/main/java/password/pwm/receiver/Storage.java @@ -173,7 +173,7 @@ private void doNext( ) } nextValue = string; } - catch ( Exception e ) + catch ( final Exception e ) { e.printStackTrace(); throw e; diff --git a/data-service/src/main/java/password/pwm/receiver/TelemetryRestReceiver.java b/data-service/src/main/java/password/pwm/receiver/TelemetryRestReceiver.java index 00d6e6450..c5ab0facc 100644 --- a/data-service/src/main/java/password/pwm/receiver/TelemetryRestReceiver.java +++ b/data-service/src/main/java/password/pwm/receiver/TelemetryRestReceiver.java @@ -58,11 +58,11 @@ protected void doPost( final HttpServletRequest req, final HttpServletResponse r stoage.store( telemetryPublishBean ); resp.getWriter().print( RestResultBean.forSuccessMessage( null, null, null, Message.Success_Unknown ).toJson() ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { resp.getWriter().print( RestResultBean.fromError( e.getErrorInformation() ).toJson() ); } - catch ( Exception e ) + catch ( final Exception e ) { final RestResultBean restResultBean = RestResultBean.fromError( new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ) ); resp.getWriter().print( restResultBean.toJson() ); diff --git a/onejar/pom.xml b/onejar/pom.xml index e269d0852..e62bc2349 100644 --- a/onejar/pom.xml +++ b/onejar/pom.xml @@ -17,7 +17,7 @@ ${project.basedir}/.. - 9.0.27 + 9.0.29 diff --git a/onejar/src/main/java/password/pwm/onejar/ArgumentParser.java b/onejar/src/main/java/password/pwm/onejar/ArgumentParser.java index 6973f9353..cf22cca21 100644 --- a/onejar/src/main/java/password/pwm/onejar/ArgumentParser.java +++ b/onejar/src/main/java/password/pwm/onejar/ArgumentParser.java @@ -58,7 +58,7 @@ public OnejarConfig parseArguments( final String[] args ) { commandLine = new DefaultParser().parse( Argument.asOptions(), args ); } - catch ( ParseException e ) + catch ( final ParseException e ) { throw new ArgumentParserException( "unable to parse command line: " + e.getMessage() ); } @@ -100,7 +100,7 @@ else if ( commandLine.hasOption( Argument.help.name() ) ) { onejarConfig = makeTomcatConfig( argumentMap ); } - catch ( IOException e ) + catch ( final IOException e ) { throw new ArgumentParserException( "error while reading input: " + e.getMessage() ); } @@ -118,7 +118,7 @@ private Map mapFromProperties( final String filename ) throws { props.load( is ); } - catch ( IOException e ) + catch ( final IOException e ) { throw new ArgumentParserException( "unable to read properties input file: " + e.getMessage() ); } @@ -204,7 +204,7 @@ private OnejarConfig makeTomcatConfig( final Map argumentMap ) port = Integer.parseInt( argumentMap.get( Argument.port ) ); onejarConfig.port( port ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { final String msg = Argument.port.name() + " argument must be numeric"; System.out.println( msg ); @@ -228,7 +228,7 @@ private OnejarConfig makeTomcatConfig( final Map argumentMap ) final ServerSocket socket = new ServerSocket( port, 100, InetAddress.getByName( localAddress ) ); socket.close(); } - catch ( Exception e ) + catch ( final Exception e ) { throw new ArgumentParserException( "port or address conflict: " + e.getMessage() ); } diff --git a/onejar/src/main/java/password/pwm/onejar/OnejarMain.java b/onejar/src/main/java/password/pwm/onejar/OnejarMain.java index 010185801..baba2b205 100644 --- a/onejar/src/main/java/password/pwm/onejar/OnejarMain.java +++ b/onejar/src/main/java/password/pwm/onejar/OnejarMain.java @@ -52,7 +52,7 @@ public static void main( final String[] args ) { onejarConfig = argumentParser.parseArguments( args ); } - catch ( ArgumentParserException | OnejarException e ) + catch ( final ArgumentParserException | OnejarException e ) { output( "error parsing command line: " + e.getMessage() ); } @@ -91,7 +91,7 @@ private void execCommand( final OnejarConfig onejarConfig ) mainMethod.invoke( null, ( Object ) arguments ); } - catch ( Exception e ) + catch ( final Exception e ) { e.printStackTrace( ); } @@ -111,7 +111,7 @@ void deployWebApp( final OnejarConfig onejarConfig ) runner.startTomcat( onejarConfig ); } - catch ( OnejarException | ServletException | IOException e ) + catch ( final OnejarException | ServletException | IOException e ) { out( "error starting tomcat: " + e.getMessage() ); } diff --git a/onejar/src/main/java/password/pwm/onejar/TomcatOnejarRunner.java b/onejar/src/main/java/password/pwm/onejar/TomcatOnejarRunner.java index 34812f622..a72f51316 100644 --- a/onejar/src/main/java/password/pwm/onejar/TomcatOnejarRunner.java +++ b/onejar/src/main/java/password/pwm/onejar/TomcatOnejarRunner.java @@ -70,7 +70,7 @@ void startTomcat( final OnejarConfig onejarConfig ) tlsProperties = this.executeOnejarHelper( onejarConfig ); out( "keystore generated" ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new OnejarException( "error generating keystore: " + e.getMessage() ); } @@ -113,7 +113,7 @@ void startTomcat( final OnejarConfig onejarConfig ) tomcat.start(); out( "tomcat started in " + Duration.between( Instant.now(), startTime ).toString() ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new OnejarException( "unable to start tomcat: " + e.getMessage() ); } @@ -199,7 +199,7 @@ static String getVersion( ) throws OnejarException return attr.getValue( "Implementation-Version-Display" ) + " [" + ServerInfo.getServerInfo() + "]"; } - catch ( IOException e ) + catch ( final IOException e ) { throw new OnejarException( "error reading internal version info: " + e.getMessage() ); } diff --git a/pom.xml b/pom.xml index 7eb2ffeae..af0fc6ceb 100644 --- a/pom.xml +++ b/pom.xml @@ -166,7 +166,7 @@ com.puppycrawl.tools checkstyle - 8.25 + 8.27 @@ -308,13 +308,13 @@ org.mockito mockito-core - 3.1.0 + 3.2.0 test org.assertj assertj-core - 3.13.2 + 3.14.0 test @@ -332,13 +332,13 @@ org.openjdk.jmh jmh-core - 1.21 + 1.22 test org.openjdk.jmh jmh-generator-annprocess - 1.21 + 1.22 test diff --git a/pwm-cr/src/main/java/password/pwm/cr/ChaiXmlResponseSetSerializer.java b/pwm-cr/src/main/java/password/pwm/cr/ChaiXmlResponseSetSerializer.java index d4616e5c7..22118cb79 100644 --- a/pwm-cr/src/main/java/password/pwm/cr/ChaiXmlResponseSetSerializer.java +++ b/pwm-cr/src/main/java/password/pwm/cr/ChaiXmlResponseSetSerializer.java @@ -142,7 +142,7 @@ public Map read( final Reader input ) { timestamp = CrUtils.parseDateString( timeStr ); } - catch ( ParseException e ) + catch ( final ParseException e ) { throw new IllegalArgumentException( "unexpected error attempting to parse timestamp: " + e.getMessage() ); } @@ -173,14 +173,14 @@ public Map read( final Reader input ) break; default: - throw new IllegalStateException( "unknown response type '" + type + "'" ); + throw new IllegalStateException( "unknown response type '" + type + '\'' ); } } } } } - catch ( JDOMException | IOException | NullPointerException e ) + catch ( final JDOMException | IOException | NullPointerException e ) { throw new IllegalArgumentException( "error parsing stored response record: " + e.getMessage() ); } @@ -228,7 +228,7 @@ private static String elementNameForType( final Type type ) return XML_NODE_HELPDESK_RESPONSE; default: - throw new IllegalArgumentException( "unknown type '" + type + "'" ); + throw new IllegalArgumentException( "unknown type '" + type + '\'' ); } } @@ -279,7 +279,7 @@ private static StoredResponseItem parseAnswerElement( final Element element ) { saltCount = Integer.parseInt( hashCount ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { /* noop */ } @@ -306,7 +306,7 @@ private static String makeId( final byte[] hashedBytes = md.digest( questionText.getBytes( StandardCharsets.UTF_8 ) ); return net.iharder.Base64.encodeBytes( hashedBytes, Base64.URL_SAFE ); } - catch ( NoSuchAlgorithmException | IOException e ) + catch ( final NoSuchAlgorithmException | IOException e ) { throw new IllegalStateException( "unable to load SHA1 message digest algorithm: " + e.getMessage() ); } diff --git a/pwm-cr/src/main/java/password/pwm/cr/CrUtils.java b/pwm-cr/src/main/java/password/pwm/cr/CrUtils.java index 50e4ecc50..df639d660 100644 --- a/pwm-cr/src/main/java/password/pwm/cr/CrUtils.java +++ b/pwm-cr/src/main/java/password/pwm/cr/CrUtils.java @@ -26,6 +26,7 @@ import java.time.Instant; import java.util.TimeZone; +@SuppressWarnings( "checkstyle:MultipleStringLiterals" ) public class CrUtils { static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss Z"; diff --git a/pwm-cr/src/main/java/password/pwm/cr/hash/HashFactory.java b/pwm-cr/src/main/java/password/pwm/cr/hash/HashFactory.java index 27ce92cd8..677e33ea2 100644 --- a/pwm-cr/src/main/java/password/pwm/cr/hash/HashFactory.java +++ b/pwm-cr/src/main/java/password/pwm/cr/hash/HashFactory.java @@ -54,7 +54,7 @@ private static ResponseHashMachine machineForStoredResponse( final StoredRespons { alg = ResponseHashAlgorithm.valueOf( algName ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { throw new IllegalArgumentException( "unknown format type '" + algName + "'" ); } @@ -64,7 +64,7 @@ private static ResponseHashMachine machineForStoredResponse( final StoredRespons { responseHashMachine = ( ResponseHashMachineSpi ) algClass.newInstance(); } - catch ( Exception e ) + catch ( final Exception e ) { throw new IllegalStateException( "unexpected error instantiating response hash machine spi class: " + e.getMessage() ); } diff --git a/pwm-cr/src/main/java/password/pwm/cr/hash/PBKDF2HashMachine.java b/pwm-cr/src/main/java/password/pwm/cr/hash/PBKDF2HashMachine.java index 8ff6f1c49..398205b20 100644 --- a/pwm-cr/src/main/java/password/pwm/cr/hash/PBKDF2HashMachine.java +++ b/pwm-cr/src/main/java/password/pwm/cr/hash/PBKDF2HashMachine.java @@ -115,7 +115,7 @@ private String hashValue( final String input, final int iterations, final String final byte[] hash = skf.generateSecret( spec ).getEncoded(); return Base64.encodeBytes( hash ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new IllegalStateException( "unable to perform PBKDF2 hashing operation: " + e.getMessage() ); } diff --git a/pwm-cr/src/main/java/password/pwm/cr/hash/TypicalHashMachine.java b/pwm-cr/src/main/java/password/pwm/cr/hash/TypicalHashMachine.java index 219c80ec1..a44f18b73 100644 --- a/pwm-cr/src/main/java/password/pwm/cr/hash/TypicalHashMachine.java +++ b/pwm-cr/src/main/java/password/pwm/cr/hash/TypicalHashMachine.java @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.Map; +@SuppressWarnings( "checkstyle:MultipleStringLiterals" ) public class TypicalHashMachine extends AbstractHashMachine implements ResponseHashMachineSpi { @@ -106,7 +107,7 @@ static String doHash( { md = MessageDigest.getInstance( algorithm ); } - catch ( NoSuchAlgorithmException e ) + catch ( final NoSuchAlgorithmException e ) { throw new IllegalStateException( "unable to load " + algorithm + " message digest algorithm: " + e.getMessage() ); } @@ -117,7 +118,7 @@ static String doHash( { hashedBytes = input.getBytes( "UTF-8" ); } - catch ( UnsupportedEncodingException e ) + catch ( final UnsupportedEncodingException e ) { throw new IllegalStateException( "unsupported UTF8 byte encoding: " + e.getMessage() ); } diff --git a/pwm-cr/src/test/java/password/pwm/cr/ChaiXmlResponseSet1Test.java b/pwm-cr/src/test/java/password/pwm/cr/ChaiXmlResponseSet1Test.java index bde0f6043..7550cc35d 100644 --- a/pwm-cr/src/test/java/password/pwm/cr/ChaiXmlResponseSet1Test.java +++ b/pwm-cr/src/test/java/password/pwm/cr/ChaiXmlResponseSet1Test.java @@ -33,6 +33,7 @@ import java.io.Reader; import java.nio.charset.Charset; +@SuppressWarnings( "checkstyle:MultipleStringLiterals" ) public class ChaiXmlResponseSet1Test { diff --git a/pwm-cr/src/test/java/password/pwm/cr/ChaiXmlResponseSetReaderTest.java b/pwm-cr/src/test/java/password/pwm/cr/ChaiXmlResponseSetReaderTest.java index a5d8d6d7a..48ff29d9c 100644 --- a/pwm-cr/src/test/java/password/pwm/cr/ChaiXmlResponseSetReaderTest.java +++ b/pwm-cr/src/test/java/password/pwm/cr/ChaiXmlResponseSetReaderTest.java @@ -25,7 +25,7 @@ import password.pwm.cr.api.QuestionSource; import password.pwm.cr.api.ResponseLevel; - +@SuppressWarnings( "checkstyle:MultipleStringLiterals" ) public class ChaiXmlResponseSetReaderTest { diff --git a/rest-test-service/src/main/java/password/pwm/resttest/RestTestUtilities.java b/rest-test-service/src/main/java/password/pwm/resttest/RestTestUtilities.java index b4f36052d..529cff964 100644 --- a/rest-test-service/src/main/java/password/pwm/resttest/RestTestUtilities.java +++ b/rest-test-service/src/main/java/password/pwm/resttest/RestTestUtilities.java @@ -44,7 +44,7 @@ public static String readRequestBodyAsString( final HttpServletRequest req ) { IOUtils.copy( readerStream, stringWriter ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error reading request body stream: " + e.getMessage(); throw new IOException( errorMsg ); diff --git a/server/pom.xml b/server/pom.xml index c86822561..566a86559 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -52,6 +52,7 @@ test + false ${skipTests} **/ExtendedTest*.java diff --git a/server/src/main/java/password/pwm/PwmAboutProperty.java b/server/src/main/java/password/pwm/PwmAboutProperty.java index 103681781..f725dc9d1 100644 --- a/server/src/main/java/password/pwm/PwmAboutProperty.java +++ b/server/src/main/java/password/pwm/PwmAboutProperty.java @@ -28,8 +28,10 @@ import password.pwm.util.java.StringUtil; import password.pwm.util.logging.PwmLogger; +import javax.net.ssl.SSLContext; import java.lang.management.ManagementFactory; import java.nio.charset.Charset; +import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.util.Collections; import java.util.Date; @@ -69,6 +71,7 @@ public enum PwmAboutProperty app_secureBlockAlgorithm( null, pwmApplication -> pwmApplication.getSecureService().getDefaultBlockAlgorithm().getLabel() ), app_secureHashAlgorithm( null, pwmApplication -> pwmApplication.getSecureService().getDefaultHashAlgorithm().toString() ), app_ldapProfileCount( null, pwmApplication -> Integer.toString( pwmApplication.getConfig().getLdapProfiles().size() ) ), + app_ldapConnectionCount( null, pwmApplication -> Integer.toString( pwmApplication.getLdapConnectionService().connectionCount() ) ), build_Time( "Build Time", pwmApplication -> PwmConstants.BUILD_TIME ), build_Number( "Build Number", pwmApplication -> PwmConstants.BUILD_NUMBER ), @@ -91,9 +94,10 @@ public enum PwmAboutProperty java_osName( "Operating System Name", pwmApplication -> System.getProperty( "os.name" ) ), java_osVersion( "Operating System Version", pwmApplication -> System.getProperty( "os.version" ) ), java_osArch( "Operating System Architecture", pwmApplication -> System.getProperty( "os.arch" ) ), - java_randomAlgorithm( null, pwmApplication -> pwmApplication.getSecureService().pwmRandom().getAlgorithm() ), - java_defaultCharset( null, pwmApplication -> Charset.defaultCharset().name() ), + java_randomAlgorithm( "Random Algorithm", pwmApplication -> pwmApplication.getSecureService().pwmRandom().getAlgorithm() ), + java_defaultCharset( "Default Character Set", pwmApplication -> Charset.defaultCharset().name() ), java_appServerInfo( "Java AppServer Info", pwmApplication -> pwmApplication.getPwmEnvironment().getContextManager().getServerInfo() ), + java_sslVersions( "Java SSL Versions", pwmApplication -> readSslVersions() ), database_driverName( null, pwmApplication -> pwmApplication.getDatabaseService().getConnectionDebugProperties().get( DatabaseService.DatabaseAboutProperty.driverName ) ), @@ -136,7 +140,7 @@ public static Map makeInfoBean( final String value = valueProvider.value( pwmApplication ); aboutMap.put( pwmAboutProperty.name(), value == null ? "" : value ); } - catch ( Throwable t ) + catch ( final Throwable t ) { aboutMap.put( pwmAboutProperty.name(), LocaleHelper.getLocalizedMessage( null, Display.Value_NotApplicable, null ) ); LOGGER.trace( () -> "error generating about value for '" + pwmAboutProperty.name() + "', error: " + t.getMessage() ); @@ -188,4 +192,16 @@ public static Map toStringMap( final Map "unable to detect if configuration has been modified since previous startup: " + e.getMessage() ); + LOGGER.error( "error outputting log to debug: " + e.getMessage() ); } + if ( this.getConfig() != null ) { final Map nonDefaultProperties = getConfig().readAllNonDefaultAppProperties(); @@ -360,7 +344,7 @@ private void postInitTasks( ) ); getAuditManager().submit( auditRecord ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.warn( "unable to submit start alert event " + e.getMessage() ); } @@ -370,7 +354,7 @@ private void postInitTasks( ) final Map infoMap = PwmAboutProperty.makeInfoBean( this ); LOGGER.trace( () -> "application info: " + JsonUtil.serializeMap( infoMap ) ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error generating about application bean: " + e.getMessage(), e ); } @@ -379,7 +363,7 @@ private void postInitTasks( ) { this.getIntruderManager().clear( RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "error while clearing configmanager-intruder-username from intruder table: " + e.getMessage() ); } @@ -390,7 +374,7 @@ private void postInitTasks( ) { outputKeystore( this ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "error while generating keystore output: " + e.getMessage() ); } @@ -399,7 +383,7 @@ private void postInitTasks( ) { outputTomcatConf( this ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "error while generating tomcat conf output: " + e.getMessage() ); } @@ -411,7 +395,7 @@ private void postInitTasks( ) { UserAgentUtils.initializeCache(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "error initializing UserAgentUtils: " + e.getMessage() ); } @@ -511,6 +495,31 @@ private static void outputTomcatConf( final PwmApplication pwmApplication ) thro } } + private static void outputConfigurationToLog( final PwmApplication pwmApplication ) + throws PwmUnrecoverableException + { + if ( !LOGGER.isEnabled( PwmLogLevel.TRACE ) ) + { + return; + } + + final StoredConfiguration storedConfiguration = pwmApplication.getConfig().getStoredConfiguration(); + final Map debugStrings = StoredConfigurationUtil.makeDebugMap( storedConfiguration, storedConfiguration.modifiedItems(), PwmConstants.DEFAULT_LOCALE ); + final List> outputStrings = new ArrayList<>(); + + for ( final Map.Entry entry : debugStrings.entrySet() ) + { + final String spacedValue = entry.getValue().replace( "\n", "\n " ); + final String output = " " + entry.getKey() + "\n " + spacedValue + "\n"; + outputStrings.add( () -> output ); + } + + LOGGER.trace( () -> "--begin current configuration output--" ); + outputStrings.forEach( LOGGER::trace ); + LOGGER.trace( () -> "--end current configuration output--" ); + } + + public String getInstanceID( ) { return instanceID; @@ -534,7 +543,7 @@ public ChaiUser getProxiedChaiUser( final UserIdentity userIdentity ) final ChaiProvider proxiedProvider = getProxyChaiProvider( userIdentity.getLdapProfileID() ); return proxiedProvider.getEntryFactory().newChaiUser( userIdentity.getUserDN() ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -689,7 +698,7 @@ private Instant fetchInstallDate( final Instant startupTime ) return Instant.ofEpochMilli( Long.parseLong( storedDateStr ) ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error retrieving installation date from localDB: " + e.getMessage() ); } @@ -790,7 +799,7 @@ public void sendSmsUsingQueue( { smsQueue.addSmsToQueue( smsItemBean ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( "unable to add sms to queue: " + e.getMessage() ); } @@ -814,7 +823,7 @@ public void shutdown( ) getAuditManager().submit( auditRecord ); } } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.warn( "unable to submit shutdown alert event " + e.getMessage() ); } @@ -830,7 +839,7 @@ public void shutdown( ) { localDBLogger.close(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error closing localDBLogger: " + e.getMessage(), e ); } @@ -844,7 +853,7 @@ public void shutdown( ) LOGGER.trace( () -> "beginning close of LocalDB" ); localDB.close(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.fatal( "error closing localDB: " + e, e ); } @@ -883,7 +892,7 @@ public static LocalDB initializeLocalDB( final PwmApplication pwmApplication ) t final String localDBLocationSetting = pwmApplication.getConfig().readAppProperty( AppProperty.LOCALDB_LOCATION ); databaseDirectory = FileSystemUtility.figureFilepath( localDBLocationSetting, pwmApplication.pwmEnvironment.getApplicationPath() ); } - catch ( Exception e ) + catch ( final Exception e ) { pwmApplication.lastLocalDBFailure = new ErrorInformation( PwmError.ERROR_LOCALDB_UNAVAILABLE, "error locating configured LocalDB directory: " + e.getMessage() ); LOGGER.warn( pwmApplication.lastLocalDBFailure.toDebugStr() ); @@ -898,7 +907,7 @@ public static LocalDB initializeLocalDB( final PwmApplication pwmApplication ) t final boolean readOnly = pwmApplication.getApplicationMode() == PwmApplicationMode.READ_ONLY; return LocalDBFactory.getInstance( databaseDirectory, readOnly, pwmApplication, pwmApplication.getConfig() ); } - catch ( Exception e ) + catch ( final Exception e ) { pwmApplication.lastLocalDBFailure = new ErrorInformation( PwmError.ERROR_LOCALDB_UNAVAILABLE, "unable to initialize LocalDB: " + e.getMessage() ); LOGGER.warn( pwmApplication.lastLocalDBFailure.toDebugStr() ); @@ -935,7 +944,7 @@ public T readAppAttribute( final AppAttribute appAttrib final String strValue = localDB.get( LocalDB.DB.PWM_META, appAttribute.getKey() ); return JsonUtil.deserialize( strValue, returnClass ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error retrieving key '" + appAttribute.getKey() + "' value from localDB: " + e.getMessage() ); } @@ -967,14 +976,14 @@ public void writeAppAttribute( final AppAttribute appAttribute, final Serializab localDB.put( LocalDB.DB.PWM_META, appAttribute.getKey(), jsonValue ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error retrieving key '" + appAttribute.getKey() + "' installation date from localDB: " + e.getMessage() ); try { localDB.remove( LocalDB.DB.PWM_META, appAttribute.getKey() ); } - catch ( Exception e2 ) + catch ( final Exception e2 ) { LOGGER.error( "error removing bogus appAttribute value for key " + appAttribute.getKey() + ", error: " + localDB ); } diff --git a/server/src/main/java/password/pwm/PwmApplicationMode.java b/server/src/main/java/password/pwm/PwmApplicationMode.java index ea62005a3..bd5380f07 100644 --- a/server/src/main/java/password/pwm/PwmApplicationMode.java +++ b/server/src/main/java/password/pwm/PwmApplicationMode.java @@ -39,7 +39,7 @@ public static PwmApplicationMode determineMode( final HttpServletRequest httpSer { contextManager = ContextManager.getContextManager( httpServletRequest.getServletContext() ); } - catch ( Throwable t ) + catch ( final Throwable t ) { return ERROR; } @@ -49,7 +49,7 @@ public static PwmApplicationMode determineMode( final HttpServletRequest httpSer { pwmApplication = contextManager.getPwmApplication(); } - catch ( Throwable t ) + catch ( final Throwable t ) { return ERROR; } diff --git a/server/src/main/java/password/pwm/PwmConstants.java b/server/src/main/java/password/pwm/PwmConstants.java index dff09aa9a..33ad9865b 100644 --- a/server/src/main/java/password/pwm/PwmConstants.java +++ b/server/src/main/java/password/pwm/PwmConstants.java @@ -23,7 +23,6 @@ import org.apache.commons.csv.CSVFormat; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.StringUtil; -import password.pwm.util.secure.PwmHashAlgorithm; import java.io.InputStream; import java.net.URL; @@ -101,6 +100,7 @@ public abstract class PwmConstants 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"; @@ -118,9 +118,6 @@ public abstract class PwmConstants public static final String REQUEST_ATTR_FORGOTTEN_PW_AVAIL_TOKEN_DEST_CACHE = "ForgottenPw-AvailableTokenDestCache"; public static final String REQUEST_ATTR_PWM_APPLICATION = "PwmApplication"; - public static final PwmHashAlgorithm SETTING_CHECKSUM_HASH_METHOD = PwmHashAlgorithm.SHA256; - - public static final String LOG_REMOVED_VALUE_REPLACEMENT = readPwmConstantsBundle( "log.removedValue" ); public static final Collection INCLUDED_LOCALES; @@ -272,14 +269,14 @@ private static String readBuildInfoBundle( final String key, final String defaul } } } - catch ( Throwable t ) + catch ( final Throwable t ) { System.out.println( t ); } } } } - catch ( Throwable t ) + catch ( final Throwable t ) { System.out.println( t ); } diff --git a/server/src/main/java/password/pwm/PwmEnvironment.java b/server/src/main/java/password/pwm/PwmEnvironment.java index cd8c6f7f5..318e69427 100644 --- a/server/src/main/java/password/pwm/PwmEnvironment.java +++ b/server/src/main/java/password/pwm/PwmEnvironment.java @@ -74,7 +74,8 @@ public enum ApplicationParameter AppliancePort, ApplianceHostnameFile, ApplianceTokenFile, - InstanceID,; + InstanceID, + InitConsoleLogLevel,; public static ApplicationParameter forString( final String input ) { @@ -317,7 +318,7 @@ public static Map readApplicationParmsFromSystem( final String rawValue = readValueFromSystem( EnvironmentParameter.applicationParamFile, contextName ); if ( rawValue != null ) { - return parseApplicationParamValueParameter( rawValue ); + return readAppParametersFromPath( rawValue ); } return Collections.emptyMap(); } @@ -372,7 +373,7 @@ public static Collection parseApplicationFlagValueParameter( fi } return Collections.unmodifiableList( returnFlags ); } - catch ( Exception e ) + catch ( final Exception e ) { // } @@ -393,7 +394,7 @@ public static Collection parseApplicationFlagValueParameter( fi return returnFlags; } - public static Map parseApplicationParamValueParameter( final String input ) + public static Map readAppParametersFromPath( final String input ) { if ( input == null ) { @@ -405,7 +406,7 @@ public static Map parseApplicationParamValueParame { propValues.load( fileInputStream ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "error reading properties file '" + input + "' specified by environment setting " + EnvironmentParameter.applicationParamFile.toString() + ", error: " + e.getMessage() ); @@ -429,7 +430,7 @@ public static Map parseApplicationParamValueParame } return Collections.unmodifiableMap( returnParams ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "unable to parse jason value of " + EnvironmentParameter.applicationParamFile.toString() + ", error: " + e.getMessage() ); } @@ -625,7 +626,7 @@ public void attemptFileLock( ) LOGGER.debug( () -> "unable to obtain file lock on file " + lockfile.getAbsolutePath() ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unable to obtain file lock on file " + lockfile.getAbsolutePath() + " due to error: " + e.getMessage() ); } @@ -645,7 +646,7 @@ void writeLockFileContents( final RandomAccessFile file ) props.store( stringWriter, comment ); file.write( stringWriter.getBuffer().toString().getBytes( PwmConstants.DEFAULT_CHARSET ) ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.error( "unable to write contents of application lock file: " + e.getMessage() ); } @@ -660,7 +661,7 @@ public void releaseFileLock( ) { lock.release(); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.error( "error releasing file lock: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/bean/PrivateKeyCertificate.java b/server/src/main/java/password/pwm/bean/PrivateKeyCertificate.java index 1a6c77f93..318b8746f 100644 --- a/server/src/main/java/password/pwm/bean/PrivateKeyCertificate.java +++ b/server/src/main/java/password/pwm/bean/PrivateKeyCertificate.java @@ -20,30 +20,16 @@ package password.pwm.bean; +import lombok.Value; + import java.io.Serializable; import java.security.PrivateKey; import java.security.cert.X509Certificate; -import java.util.Collections; import java.util.List; +@Value public class PrivateKeyCertificate implements Serializable { - private final List certificates; - private final PrivateKey key; - - public PrivateKeyCertificate( final List certificates, final PrivateKey key ) - { - this.certificates = Collections.unmodifiableList( certificates ); - this.key = key; - } - - public List getCertificates( ) - { - return Collections.unmodifiableList( certificates ); - } - - public PrivateKey getKey( ) - { - return key; - } + private List certificates; + private PrivateKey key; } diff --git a/server/src/main/java/password/pwm/bean/UserIdentity.java b/server/src/main/java/password/pwm/bean/UserIdentity.java index 6a40ecf85..2ae7de2c0 100644 --- a/server/src/main/java/password/pwm/bean/UserIdentity.java +++ b/server/src/main/java/password/pwm/bean/UserIdentity.java @@ -112,7 +112,7 @@ public String toObfuscatedKey( final PwmApplication pwmApplication ) cacheService.put( cacheKey, CachePolicy.makePolicyWithExpiration( TimeDuration.DAY ), localValue ); return localValue; } - catch ( Exception e ) + catch ( final Exception e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "unexpected error making obfuscated user key: " + e.getMessage() ) ); } @@ -146,7 +146,7 @@ public static UserIdentity fromObfuscatedKey( final String key, final PwmApplica final String jsonValue = pwmApplication.getSecureService().decryptStringValue( input ); return JsonUtil.deserialize( jsonValue, UserIdentity.class ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "unexpected error reversing obfuscated user key: " + e.getMessage() ) ); } @@ -260,7 +260,7 @@ public UserIdentity canonicalized( final PwmApplication pwmApplication ) { userDN = chaiUser.readCanonicalDN(); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } diff --git a/server/src/main/java/password/pwm/config/Configuration.java b/server/src/main/java/password/pwm/config/Configuration.java index d78385349..c063ddcb9 100644 --- a/server/src/main/java/password/pwm/config/Configuration.java +++ b/server/src/main/java/password/pwm/config/Configuration.java @@ -45,8 +45,8 @@ import password.pwm.config.profile.PwmPasswordRule; import password.pwm.config.profile.SetupOtpProfile; import password.pwm.config.profile.UpdateProfileProfile; -import password.pwm.config.stored.ConfigurationProperty; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigItemKey; +import password.pwm.config.stored.StoredConfiguration; import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.config.value.BooleanValue; import password.pwm.config.value.CustomLinkValue; @@ -70,6 +70,7 @@ import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmUnrecoverableException; +import password.pwm.i18n.PwmLocaleBundle; import password.pwm.util.PasswordData; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.StringUtil; @@ -77,8 +78,8 @@ import password.pwm.util.logging.PwmLogger; import password.pwm.util.secure.PwmRandom; import password.pwm.util.secure.PwmSecurityKey; +import password.pwm.util.secure.SecureService; -import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -95,7 +96,6 @@ import java.util.Optional; import java.util.Set; import java.util.TreeMap; -import java.util.function.Supplier; /** * @author Jason D. Rivard @@ -104,13 +104,11 @@ public class Configuration implements SettingReader { private static final PwmLogger LOGGER = PwmLogger.forClass( Configuration.class ); - private final StoredConfigurationImpl storedConfiguration; + private final StoredConfiguration storedConfiguration; private DataCache dataCache = new DataCache(); - private String cashedConfigurationHash; - - public Configuration( final StoredConfigurationImpl storedConfiguration ) + public Configuration( final StoredConfiguration storedConfiguration ) { this.storedConfiguration = storedConfiguration; } @@ -125,28 +123,6 @@ public static void deprecatedSettingException( final PwmSetting pwmSetting, fina } } - public void outputToLog( ) - { - if ( !LOGGER.isEnabled( PwmLogLevel.TRACE ) ) - { - return; - } - - final Map debugStrings = storedConfiguration.getModifiedSettingDebugValues( PwmConstants.DEFAULT_LOCALE, true ); - final List> outputStrings = new ArrayList<>(); - - for ( final Map.Entry entry : debugStrings.entrySet() ) - { - final String spacedValue = entry.getValue().replace( "\n", "\n " ); - final String output = " " + entry.getKey() + "\n " + spacedValue + "\n"; - outputStrings.add( () -> output ); - } - - LOGGER.trace( () -> "--begin current configuration output--" ); - outputStrings.forEach( LOGGER::trace ); - LOGGER.trace( () -> "--end current configuration output--" ); - } - public List readSettingAsForm( final PwmSetting setting ) { final StoredValue value = readStoredValue( setting ); @@ -468,14 +444,14 @@ public static > E valueToEnum( final PwmSetting setting, final { return ( E ) enumClass.getMethod( "valueOf", String.class ).invoke( null, strValue ); } - catch ( InvocationTargetException e1 ) + catch ( final InvocationTargetException e1 ) { if ( e1.getCause() instanceof IllegalArgumentException ) { LOGGER.error( "illegal setting value for option '" + strValue + "' for setting key '" + setting.getKey() + "' is not recognized, will use default" ); } } - catch ( Exception e1 ) + catch ( final Exception e1 ) { LOGGER.error( "unexpected error", e1 ); } @@ -499,14 +475,14 @@ public static > Set valueToOptionList( final PwmSetting set { returnSet.add( ( E ) enumClass.getMethod( "valueOf", String.class ).invoke( null, strValue ) ); } - catch ( InvocationTargetException e1 ) + catch ( final InvocationTargetException e1 ) { if ( e1.getCause() instanceof IllegalArgumentException ) { LOGGER.error( "illegal setting value for option '" + strValue + "' is not recognized, will use default" ); } } - catch ( Exception e1 ) + catch ( final Exception e1 ) { LOGGER.error( "unexpected error", e1 ); } @@ -516,7 +492,7 @@ public static > Set valueToOptionList( final PwmSetting set } } - public Map readLocalizedBundle( final String className, final String keyName ) + public Map readLocalizedBundle( final PwmLocaleBundle className, final String keyName ) { final String key = className + "-" + keyName; if ( dataCache.customText.containsKey( key ) ) @@ -643,7 +619,7 @@ protected PwmPasswordPolicy initPasswordPolicy( final String profile, final Loca // set case sensitivity final String caseSensitivitySetting = JavaTypeConverter.valueToString( storedConfiguration.readSetting( - PwmSetting.PASSWORD_POLICY_CASE_SENSITIVITY ) ); + PwmSetting.PASSWORD_POLICY_CASE_SENSITIVITY, null ) ); if ( !"read".equals( caseSensitivitySetting ) ) { passwordPolicySettings.put( PwmPasswordRule.CaseSensitive.getKey(), caseSensitivitySetting ); @@ -674,7 +650,7 @@ public String readSettingAsLocalizedString( final PwmSetting setting, final Loca public boolean isDefaultValue( final PwmSetting pwmSetting ) { - return storedConfiguration.isDefaultValue( pwmSetting ); + return storedConfiguration.isDefaultValue( pwmSetting, null ); } public Collection localesForSetting( final PwmSetting setting ) @@ -705,11 +681,6 @@ public Collection localesForSetting( final PwmSetting setting ) return returnCollection; } - public String readProperty( final ConfigurationProperty key ) - { - return storedConfiguration.readConfigProperty( key ); - } - public boolean readSettingAsBoolean( final PwmSetting setting ) { return JavaTypeConverter.valueToBoolean( readStoredValue( setting ) ); @@ -717,7 +688,7 @@ public boolean readSettingAsBoolean( final PwmSetting setting ) public Map readSettingAsFile( final PwmSetting setting ) { - final FileValue fileValue = ( FileValue ) storedConfiguration.readSetting( setting ); + final FileValue fileValue = ( FileValue ) storedConfiguration.readSetting( setting, null ); return ( Map ) fileValue.toNativeObject(); } @@ -748,48 +719,50 @@ public PrivateKeyCertificate readSettingAsPrivateKey( final PwmSetting setting ) return ( PrivateKeyCertificate ) readStoredValue( setting ).toNativeObject(); } - public String getNotes( ) - { - return storedConfiguration.readConfigProperty( ConfigurationProperty.NOTES ); - } - private PwmSecurityKey tempInstanceKey = null; public PwmSecurityKey getSecurityKey( ) throws PwmUnrecoverableException { - final PasswordData configValue = readSettingAsPassword( PwmSetting.PWM_SECURITY_KEY ); - - if ( configValue == null || configValue.getStringValue().isEmpty() ) + if ( dataCache.pwmSecurityKey == null ) { - final String errorMsg = "Security Key value is not configured,will generate temp value for use by runtime instance"; - final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg ); - LOGGER.warn( errorInfo.toDebugStr() ); - if ( tempInstanceKey == null ) + final PasswordData configValue = readSettingAsPassword( PwmSetting.PWM_SECURITY_KEY ); + + if ( configValue == null || configValue.getStringValue().isEmpty() ) { - tempInstanceKey = new PwmSecurityKey( PwmRandom.getInstance().alphaNumericString( 256 ) ); + final String errorMsg = "Security Key value is not configured, will generate temp value for use by runtime instance"; + final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg ); + LOGGER.warn( errorInfo.toDebugStr() ); + if ( tempInstanceKey == null ) + { + tempInstanceKey = new PwmSecurityKey( PwmRandom.getInstance().alphaNumericString( 1024 ) ); + } + dataCache.pwmSecurityKey = tempInstanceKey; } - return tempInstanceKey; - } + else + { + final int minSecurityKeyLength = Integer.parseInt( readAppProperty( AppProperty.SECURITY_CONFIG_MIN_SECURITY_KEY_LENGTH ) ); + if ( configValue.getStringValue().length() < minSecurityKeyLength ) + { + final String errorMsg = "Security Key must be greater than 32 characters in length"; + final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg ); + throw new PwmUnrecoverableException( errorInfo ); + } - final int minSecurityKeyLength = Integer.parseInt( readAppProperty( AppProperty.SECURITY_CONFIG_MIN_SECURITY_KEY_LENGTH ) ); - if ( configValue.getStringValue().length() < minSecurityKeyLength ) - { - final String errorMsg = "Security Key must be greater than 32 characters in length"; - final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg ); - throw new PwmUnrecoverableException( errorInfo ); + try + { + dataCache.pwmSecurityKey = new PwmSecurityKey( configValue.getStringValue() ); + } + catch ( final Exception e ) + { + final String errorMsg = "unexpected error generating Security Key crypto: " + e.getMessage(); + final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg ); + LOGGER.error( errorInfo.toDebugStr(), e ); + throw new PwmUnrecoverableException( errorInfo ); + } + } } - try - { - return new PwmSecurityKey( configValue.getStringValue() ); - } - catch ( Exception e ) - { - final String errorMsg = "unexpected error generating Security Key crypto: " + e.getMessage(); - final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg ); - LOGGER.error( errorInfo.toDebugStr(), e ); - throw new PwmUnrecoverableException( errorInfo ); - } + return dataCache.pwmSecurityKey; } public List getResponseStorageLocations( final PwmSetting setting ) @@ -813,7 +786,7 @@ private List getGenericStorageLocations( final PwmSetting set { storageMethods.add( DataStorageMethod.valueOf( rawValue ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { LOGGER.error( "unknown STORAGE_METHOD found: " + rawValue ); } @@ -902,7 +875,7 @@ public TokenStorageMethod getTokenStorageMethod( ) { return TokenStorageMethod.valueOf( readSettingAsString( PwmSetting.TOKEN_STORAGEMETHOD ) ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unknown storage method specified: " + readSettingAsString( PwmSetting.TOKEN_STORAGEMETHOD ); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, errorMsg ); @@ -1012,12 +985,12 @@ private StoredValue readStoredValue( final PwmSetting setting ) return dataCache.settings.get( setting ); } - final StoredValue readValue = storedConfiguration.readSetting( setting ); + final StoredValue readValue = storedConfiguration.readSetting( setting, null ); dataCache.settings.put( setting, readValue ); return readValue; } - private static class DataCache implements Serializable + private static class DataCache { private final Map> cachedPasswordPolicy = new LinkedHashMap<>(); private Map localeFlagMap = null; @@ -1025,6 +998,7 @@ private static class DataCache implements Serializable private final Map> customText = new LinkedHashMap<>(); private final Map profileCache = new LinkedHashMap<>(); private Map appPropertyOverrides = null; + private PwmSecurityKey pwmSecurityKey; } public Map readAllNonDefaultAppProperties( ) @@ -1118,18 +1092,16 @@ private Profile newProfileForID( final ProfileDefinition profileDefinition, fina { profileFactory = profileFactoryClass.getDeclaredConstructor().newInstance(); } - catch ( InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e ) + catch ( final InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e ) { throw new IllegalStateException( "unable to create profile instance for " + profileDefinition ); } return profileFactory.makeFromStoredConfiguration( storedConfiguration, profileID ); } - public StoredConfigurationImpl getStoredConfiguration( ) throws PwmUnrecoverableException + public StoredConfiguration getStoredConfiguration( ) { - final StoredConfigurationImpl copiedStoredConfiguration = StoredConfigurationImpl.copy( storedConfiguration ); - copiedStoredConfiguration.lock(); - return copiedStoredConfiguration; + return this.storedConfiguration; } public boolean isDevDebugMode( ) @@ -1137,22 +1109,21 @@ public boolean isDevDebugMode( ) return Boolean.parseBoolean( readAppProperty( AppProperty.LOGGING_DEV_OUTPUT ) ); } - public String configurationHash( ) + public String configurationHash( final SecureService secureService ) throws PwmUnrecoverableException { - if ( this.cashedConfigurationHash == null ) - { - this.cashedConfigurationHash = storedConfiguration.settingChecksum(); - } - return cashedConfigurationHash; + return storedConfiguration.valueHash(); } public Set nonDefaultSettings( ) { - final Set returnSet = new LinkedHashSet(); - for ( final StoredConfigurationImpl.SettingValueRecord valueRecord : this.storedConfiguration.modifiedSettings() ) + final Set returnSet = new LinkedHashSet<>(); + for ( final StoredConfigItemKey key : this.storedConfiguration.modifiedItems() ) { - returnSet.add( valueRecord.getSetting() ); + if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) + { + returnSet.add( key.toPwmSetting() ); + } } return returnSet; } @@ -1163,7 +1134,6 @@ public CertificateMatchingMode readCertificateMatchingMode() return mode == null ? CertificateMatchingMode.CA_ONLY : mode; - } public Optional getPublicPeopleSearchProfile() diff --git a/server/src/main/java/password/pwm/config/PwmSetting.java b/server/src/main/java/password/pwm/config/PwmSetting.java index ef4d42888..f42c6c912 100644 --- a/server/src/main/java/password/pwm/config/PwmSetting.java +++ b/server/src/main/java/password/pwm/config/PwmSetting.java @@ -20,11 +20,13 @@ package password.pwm.config; +import lombok.Value; import password.pwm.config.value.PasswordValue; import password.pwm.config.value.ValueFactory; import password.pwm.i18n.Config; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.JavaHelper; +import password.pwm.util.java.LazySupplier; import password.pwm.util.java.StringUtil; import password.pwm.util.java.XmlElement; import password.pwm.util.logging.PwmLogger; @@ -36,10 +38,13 @@ import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.TreeMap; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -1269,9 +1274,6 @@ public enum PwmSetting "helpdesk.otp.verify", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_BASE ),; - - - private static final PwmLogger LOGGER = PwmLogger.forClass( PwmSetting.class ); private final String key; @@ -1279,16 +1281,16 @@ public enum PwmSetting private final PwmSettingCategory category; // cached values read from XML file - private transient Supplier> defaultValues; - private transient Supplier> examples; - private transient Supplier> options; - private transient Supplier> flags; - private transient Supplier> properties; - private transient Supplier> ldapPermissionInfo; - private transient Supplier required; - private transient Supplier hidden; - private transient Supplier level; - private transient Supplier pattern; + private final Supplier> defaultValues = new LazySupplier<>( () -> PwmSettingReader.readDefaultValue( PwmSetting.this ) ); + private final Supplier> examples = new LazySupplier<>( () -> PwmSettingReader.readExamples( PwmSetting.this ) ); + private final Supplier> options = new LazySupplier<>( () -> PwmSettingReader.readOptions( PwmSetting.this ) ); + private final Supplier> flags = new LazySupplier<>( () -> PwmSettingReader.readFlags( PwmSetting.this ) ); + private final Supplier> properties = new LazySupplier<>( () -> PwmSettingReader.readProperties( PwmSetting.this ) ); + private final Supplier> ldapPermissionInfo = new LazySupplier<>( () -> PwmSettingReader.readLdapPermissionInfo( PwmSetting.this ) ); + private final Supplier required = new LazySupplier<>( () -> PwmSettingReader.readRequired( PwmSetting.this ) ); + private final Supplier hidden = new LazySupplier<>( () -> PwmSettingReader.readHidden( PwmSetting.this ) ); + private final Supplier level = new LazySupplier<>( () -> PwmSettingReader.readLevel( PwmSetting.this ) ); + private final Supplier pattern = new LazySupplier<>( () -> PwmSettingReader.readPattern( PwmSetting.this ) ); PwmSetting( final String key, @@ -1323,31 +1325,6 @@ public PwmSettingSyntax getSyntax( ) private List getDefaultValue() { - if ( defaultValues == null ) - { - final List returnObj = new ArrayList<>(); - final XmlElement settingElement = PwmSettingXml.readSettingXml( this ); - final List defaultElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_DEFAULT ); - if ( this.getSyntax() == PwmSettingSyntax.PASSWORD ) - { - returnObj.add( new TemplateSetAssociation( new PasswordValue( null ), Collections.emptySet() ) ); - } - else - { - for ( final XmlElement defaultElement : defaultElements ) - { - final Set definedTemplates = PwmSettingXml.parseTemplateAttribute( defaultElement ); - final StoredValue storedValue = ValueFactory.fromXmlValues( this, defaultElement, null ); - returnObj.add( new TemplateSetAssociation( storedValue, definedTemplates ) ); - } - } - if ( returnObj.isEmpty() ) - { - throw new IllegalStateException( "no default value for setting " + this.getKey() ); - } - final List finalObj = Collections.unmodifiableList( returnObj ); - defaultValues = ( ) -> finalObj; - } return defaultValues.get(); } @@ -1370,132 +1347,22 @@ public Map getDefaultValueDebugStrings( final Locale locale ) return Collections.unmodifiableMap( returnObj ); } - public Map getOptions( ) - { - if ( options == null ) - { - final Map returnList = new LinkedHashMap<>(); - final XmlElement settingElement = PwmSettingXml.readSettingXml( this ); - final XmlElement optionsElement = settingElement.getChild( "options" ); - if ( optionsElement != null ) - { - final List optionElements = optionsElement.getChildren( "option" ); - if ( optionElements != null ) - { - for ( final XmlElement optionElement : optionElements ) - { - if ( optionElement.getAttributeValue( "value" ) == null ) - { - throw new IllegalStateException( "option element is missing 'value' attribute for key " + this.getKey() ); - } - returnList.put( optionElement.getAttributeValue( "value" ), optionElement.getText() ); - } - } - } - final Map finalList = Collections.unmodifiableMap( returnList ); - options = ( ) -> Collections.unmodifiableMap( finalList ); - } - - return options.get( ); - } - public Map getProperties( ) { - if ( properties == null ) - { - final Map newProps = new LinkedHashMap<>(); - final XmlElement settingElement = PwmSettingXml.readSettingXml( this ); - final XmlElement propertiesElement = settingElement.getChild( "properties" ); - if ( propertiesElement != null ) - { - final List propertyElements = propertiesElement.getChildren( "property" ); - if ( propertyElements != null ) - { - for ( final XmlElement propertyElement : propertyElements ) - { - if ( propertyElement.getAttributeValue( "key" ) == null ) - { - throw new IllegalStateException( "property element is missing 'key' attribute for value " + this.getKey() ); - } - final PwmSettingProperty property = JavaHelper.readEnumFromString( PwmSettingProperty.class, null, propertyElement.getAttributeValue( "key" ) ); - if ( property == null ) - { - throw new IllegalStateException( "property element has unknown 'key' attribute for value " + this.getKey() ); - } - newProps.put( property, propertyElement.getText() ); - } - } - } - final Map finalProps = Collections.unmodifiableMap( newProps ); - properties = ( ) -> finalProps; - } - return properties.get(); } public Collection getFlags( ) { - if ( flags == null ) - { - final Collection returnObj = new ArrayList<>(); - final XmlElement settingElement = PwmSettingXml.readSettingXml( this ); - final List flagElements = settingElement.getChildren( "flag" ); - for ( final XmlElement flagElement : flagElements ) - { - final String value = flagElement.getTextTrim(); - - try - { - final PwmSettingFlag flag = PwmSettingFlag.valueOf( value ); - returnObj.add( flag ); - } - catch ( IllegalArgumentException e ) - { - LOGGER.error( "unknown flag for setting " + this.getKey() + ", error: unknown flag value: " + value ); - } - - } - final Collection finalObj = Collections.unmodifiableCollection( returnObj ); - flags = ( ) -> finalObj; - } return flags.get(); } - public Collection getLDAPPermissionInfo( ) + public Map getOptions() { - if ( ldapPermissionInfo == null ) - { - final XmlElement settingElement = PwmSettingXml.readSettingXml( this ); - final List permissionElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_LDAP_PERMISSION ); - final List returnObj = new ArrayList<>(); - if ( permissionElements != null ) - { - for ( final XmlElement permissionElement : permissionElements ) - { - final LDAPPermissionInfo.Actor actor = JavaHelper.readEnumFromString( - LDAPPermissionInfo.Actor.class, - null, - permissionElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACTOR ) - ); - final LDAPPermissionInfo.Access type = JavaHelper.readEnumFromString( - LDAPPermissionInfo.Access.class, - null, - permissionElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACCESS ) - ); - if ( actor != null && type != null ) - { - final LDAPPermissionInfo permissionInfo = new LDAPPermissionInfo( type, actor ); - returnObj.add( permissionInfo ); - } - } - } - final List finalObj = Collections.unmodifiableList( returnObj ); - ldapPermissionInfo = ( ) -> finalObj; - } - - return ldapPermissionInfo.get(); + return options.get(); } + public String getLabel( final Locale locale ) { final String propertyKey = password.pwm.i18n.PwmSetting.SETTING_LABEL_PREFIX + this.getKey(); @@ -1512,94 +1379,27 @@ public String getDescription( final Locale locale ) public String getExample( final PwmSettingTemplateSet template ) { - if ( examples == null ) - { - final List returnObj = new ArrayList<>(); - final MacroMachine macroMachine = MacroMachine.forStatic(); - final XmlElement settingElement = PwmSettingXml.readSettingXml( this ); - final List exampleElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_EXAMPLE ); - for ( final XmlElement exampleElement : exampleElements ) - { - final Set definedTemplates = PwmSettingXml.parseTemplateAttribute( exampleElement ); - final String exampleString = macroMachine.expandMacros( exampleElement.getText() ); - returnObj.add( new TemplateSetAssociation( exampleString, Collections.unmodifiableSet( definedTemplates ) ) ); - } - if ( returnObj.isEmpty() ) - { - returnObj.add( new TemplateSetAssociation( "", Collections.emptySet() ) ); - } - final List exampleOutput = Collections.unmodifiableList( returnObj ); - examples = ( ) -> exampleOutput; - } - return ( String ) associationForTempleSet( examples.get(), template ).getObject(); } public boolean isRequired( ) { - if ( required == null ) - { - final XmlElement settingElement = PwmSettingXml.readSettingXml( this ); - final String requiredAttribute = settingElement.getAttributeValue( "required" ); - final boolean requiredOutput = requiredAttribute != null && "true".equalsIgnoreCase( requiredAttribute ); - required = ( ) -> requiredOutput; - } return required.get(); } public boolean isHidden( ) { - if ( hidden == null ) - { - final XmlElement settingElement = PwmSettingXml.readSettingXml( this ); - final String requiredAttribute = settingElement.getAttributeValue( "hidden" ); - final boolean outputHidden = requiredAttribute != null && "true".equalsIgnoreCase( requiredAttribute ) || this.getCategory().isHidden(); - hidden = ( ) -> outputHidden; - } return hidden.get(); } public int getLevel( ) { - if ( level == null ) - { - final XmlElement settingElement = PwmSettingXml.readSettingXml( this ); - final String levelAttribute = settingElement.getAttributeValue( "level" ); - final int outputLevel = levelAttribute != null ? Integer.parseInt( levelAttribute ) : 0; - level = ( ) -> outputLevel; - } return level.get(); } public Pattern getRegExPattern( ) { - if ( pattern == null ) - { - final XmlElement settingNode = PwmSettingXml.readSettingXml( this ); - final XmlElement regexNode = settingNode.getChild( "regex" ); - if ( regexNode != null ) - { - try - { - final Pattern output = Pattern.compile( regexNode.getText() ); - pattern = ( ) -> output; - } - catch ( PatternSyntaxException e ) - { - final String errorMsg = "error compiling regex constraints for setting " + this.toString() + ", error: " + e.getMessage(); - LOGGER.error( errorMsg, e ); - throw new IllegalStateException( errorMsg, e ); - } - } - if ( pattern == null ) - { - final Pattern output = Pattern.compile( ".*", Pattern.DOTALL ); - pattern = ( ) -> output; - } - } - return pattern.get(); - } public static PwmSetting forKey( final String key ) @@ -1619,6 +1419,11 @@ public String toMenuLocationDebug( return this.getCategory().toMenuLocationDebug( profileID, locale ) + separator + this.getLabel( locale ); } + public Collection getLDAPPermissionInfo() + { + return ldapPermissionInfo.get(); + } + public enum SettingStat { Total, @@ -1658,26 +1463,21 @@ public static Map getStats( ) return returnObj; } + @Value public static class TemplateSetAssociation { private final Object object; private final Set settingTemplates; + } - TemplateSetAssociation( final Object association, final Set settingTemplates ) - { - this.object = association; - this.settingTemplates = settingTemplates; - } - - public Object getObject( ) - { - return object; - } - - Set getSettingTemplates( ) + public static Set sortedByMenuLocation( final Locale locale ) + { + final TreeMap treeMap = new TreeMap<>(); + for ( final PwmSetting pwmSetting : PwmSetting.values() ) { - return settingTemplates; + treeMap.put( pwmSetting.toMenuLocationDebug( null, locale ), pwmSetting ); } + return Collections.unmodifiableSet( new LinkedHashSet<>( treeMap.values() ) ); } private static TemplateSetAssociation associationForTempleSet( @@ -1711,5 +1511,201 @@ private static TemplateSetAssociation associationForTempleSet( return associationSets.iterator().next(); } -} + static class PwmSettingReader + { + + private static Collection readFlags( final PwmSetting pwmSetting ) + { + final Collection returnObj = new ArrayList<>(); + final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting ); + final List flagElements = settingElement.getChildren( "flag" ); + for ( final XmlElement flagElement : flagElements ) + { + final String value = flagElement.getTextTrim(); + + try + { + final PwmSettingFlag flag = PwmSettingFlag.valueOf( value ); + returnObj.add( flag ); + } + catch ( final IllegalArgumentException e ) + { + LOGGER.error( "unknown flag for setting " + pwmSetting.getKey() + ", error: unknown flag value: " + value ); + } + + } + return Collections.unmodifiableCollection( returnObj ); + } + + private static Map readOptions( final PwmSetting pwmSetting ) + { + final Map returnList = new LinkedHashMap<>(); + final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting ); + final Optional optionsElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_OPTIONS ); + if ( optionsElement.isPresent() ) + { + final List optionElements = optionsElement.get().getChildren( PwmSettingXml.XML_ELEMENT_OPTION ); + if ( optionElements != null ) + { + for ( final XmlElement optionElement : optionElements ) + { + if ( optionElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_VALUE ) == null ) + { + throw new IllegalStateException( "option element is missing 'value' attribute for key " + pwmSetting.getKey() ); + } + returnList.put( optionElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_VALUE ), optionElement.getText() ); + } + } + } + final Map finalList = Collections.unmodifiableMap( returnList ); + return Collections.unmodifiableMap( finalList ); + } + + private static Collection readLdapPermissionInfo( final PwmSetting pwmSetting ) + { + final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting ); + final List permissionElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_LDAP_PERMISSION ); + final List returnObj = new ArrayList<>(); + if ( permissionElements != null ) + { + for ( final XmlElement permissionElement : permissionElements ) + { + final LDAPPermissionInfo.Actor actor = JavaHelper.readEnumFromString( + LDAPPermissionInfo.Actor.class, + null, + permissionElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACTOR ) + ); + final LDAPPermissionInfo.Access type = JavaHelper.readEnumFromString( + LDAPPermissionInfo.Access.class, + null, + permissionElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_PERMISSION_ACCESS ) + ); + if ( actor != null && type != null ) + { + final LDAPPermissionInfo permissionInfo = new LDAPPermissionInfo( type, actor ); + returnObj.add( permissionInfo ); + } + } + } + return Collections.unmodifiableList( returnObj ); + } + + private static List readExamples( final PwmSetting pwmSetting ) + { + final List returnObj = new ArrayList<>(); + final MacroMachine macroMachine = MacroMachine.forStatic(); + final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting ); + final List exampleElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_EXAMPLE ); + for ( final XmlElement exampleElement : exampleElements ) + { + final Set definedTemplates = PwmSettingXml.parseTemplateAttribute( exampleElement ); + final String exampleString = macroMachine.expandMacros( exampleElement.getText() ); + returnObj.add( new TemplateSetAssociation( exampleString, Collections.unmodifiableSet( definedTemplates ) ) ); + } + if ( returnObj.isEmpty() ) + { + returnObj.add( new TemplateSetAssociation( "", Collections.emptySet() ) ); + } + return Collections.unmodifiableList( returnObj ); + } + + private static Map readProperties( final PwmSetting pwmSetting ) + { + final Map newProps = new LinkedHashMap<>(); + final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting ); + final Optional propertiesElement = settingElement.getChild( PwmSettingXml.XML_ELEMENT_PROPERTIES ); + if ( propertiesElement.isPresent() ) + { + final List propertyElements = propertiesElement.get().getChildren( PwmSettingXml.XML_ELEMENT_PROPERTY ); + if ( propertyElements != null ) + { + for ( final XmlElement propertyElement : propertyElements ) + { + if ( propertyElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_KEY ) == null ) + { + throw new IllegalStateException( "property element is missing 'key' attribute for value " + pwmSetting.getKey() ); + } + final PwmSettingProperty property = JavaHelper.readEnumFromString( + PwmSettingProperty.class, + null, + propertyElement.getAttributeValue( PwmSettingXml.XML_ATTRIBUTE_KEY ) ); + if ( property == null ) + { + throw new IllegalStateException( "property element has unknown 'key' attribute for value " + pwmSetting.getKey() ); + } + newProps.put( property, propertyElement.getText() ); + } + } + } + return Collections.unmodifiableMap( newProps ); + } + + private static List readDefaultValue( final PwmSetting pwmSetting ) + { + final List returnObj = new ArrayList<>(); + final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting ); + final List defaultElements = settingElement.getChildren( PwmSettingXml.XML_ELEMENT_DEFAULT ); + if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD ) + { + returnObj.add( new TemplateSetAssociation( new PasswordValue( null ), Collections.emptySet() ) ); + } + else + { + for ( final XmlElement defaultElement : defaultElements ) + { + final Set definedTemplates = PwmSettingXml.parseTemplateAttribute( defaultElement ); + final StoredValue storedValue = ValueFactory.fromXmlValues( pwmSetting, defaultElement, null ); + returnObj.add( new TemplateSetAssociation( storedValue, definedTemplates ) ); + } + } + if ( returnObj.isEmpty() ) + { + throw new IllegalStateException( "no default value for setting " + pwmSetting.getKey() ); + } + return Collections.unmodifiableList( returnObj ); + } + + + private static boolean readRequired( final PwmSetting pwmSetting ) + { + final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting ); + final String requiredAttribute = settingElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_REQUIRED ); + return "true".equalsIgnoreCase( requiredAttribute ); + } + + private static boolean readHidden( final PwmSetting pwmSetting ) + { + final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting ); + final String requiredAttribute = settingElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_HIDDEN ); + return "true".equalsIgnoreCase( requiredAttribute ) || pwmSetting.getCategory().isHidden(); + } + + private static int readLevel( final PwmSetting pwmSetting ) + { + final XmlElement settingElement = PwmSettingXml.readSettingXml( pwmSetting ); + final String levelAttribute = settingElement.getAttributeValue( PwmSettingXml.XML_ELEMENT_LEVEL ); + return JavaHelper.silentParseInt( levelAttribute, 0 ); + } + + private static Pattern readPattern( final PwmSetting pwmSetting ) + { + final XmlElement settingNode = PwmSettingXml.readSettingXml( pwmSetting ); + final Optional regexNode = settingNode.getChild( PwmSettingXml.XML_ELEMENT_REGEX ); + if ( regexNode.isPresent() ) + { + try + { + return Pattern.compile( regexNode.get().getText() ); + } + catch ( final PatternSyntaxException e ) + { + final String errorMsg = "error compiling regex constraints for setting " + pwmSetting.toString() + ", error: " + e.getMessage(); + LOGGER.error( errorMsg, e ); + throw new IllegalStateException( errorMsg, e ); + } + } + return Pattern.compile( ".*", Pattern.DOTALL ); + } + } +} diff --git a/server/src/main/java/password/pwm/config/PwmSettingCategory.java b/server/src/main/java/password/pwm/config/PwmSettingCategory.java index e536583fa..65596bfea 100644 --- a/server/src/main/java/password/pwm/config/PwmSettingCategory.java +++ b/server/src/main/java/password/pwm/config/PwmSettingCategory.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.TreeMap; import java.util.function.Supplier; @@ -322,10 +323,10 @@ private password.pwm.config.PwmSetting readProfileSettingFromXml( final boolean while ( nextCategory != null ) { final XmlElement categoryElement = PwmSettingXml.readCategoryXml( nextCategory ); - final XmlElement profileElement = categoryElement.getChild( "profile" ); - if ( profileElement != null ) + final Optional profileElement = categoryElement.getChild( "profile" ); + if ( profileElement.isPresent() ) { - final String settingKey = profileElement.getAttributeValue( "setting" ); + final String settingKey = profileElement.get().getAttributeValue( "setting" ); if ( settingKey != null ) { return password.pwm.config.PwmSetting.forKey( settingKey ); diff --git a/server/src/main/java/password/pwm/config/PwmSettingFlag.java b/server/src/main/java/password/pwm/config/PwmSettingFlag.java index 110356951..9f7525b41 100644 --- a/server/src/main/java/password/pwm/config/PwmSettingFlag.java +++ b/server/src/main/java/password/pwm/config/PwmSettingFlag.java @@ -54,4 +54,5 @@ public enum PwmSettingFlag WebService_NoBody, + Deprecated, } diff --git a/server/src/main/java/password/pwm/config/PwmSettingProperty.java b/server/src/main/java/password/pwm/config/PwmSettingProperty.java index f9b4068ab..6dc571db2 100644 --- a/server/src/main/java/password/pwm/config/PwmSettingProperty.java +++ b/server/src/main/java/password/pwm/config/PwmSettingProperty.java @@ -22,7 +22,6 @@ public enum PwmSettingProperty { - ModificationWarning, Minimum, @@ -36,4 +35,6 @@ public enum PwmSettingProperty Cert_ImportHandler, MethodType, + + Restart_Requirements, } diff --git a/server/src/main/java/password/pwm/config/PwmSettingSyntax.java b/server/src/main/java/password/pwm/config/PwmSettingSyntax.java index 24d6902ca..8087ca5cb 100644 --- a/server/src/main/java/password/pwm/config/PwmSettingSyntax.java +++ b/server/src/main/java/password/pwm/config/PwmSettingSyntax.java @@ -83,7 +83,7 @@ public enum PwmSettingSyntax this.storedValueImpl = storedValueImpl; } - public StoredValue.StoredValueFactory getStoredValueImpl( ) + public StoredValue.StoredValueFactory getFactory( ) { return storedValueImpl; } diff --git a/server/src/main/java/password/pwm/config/PwmSettingXml.java b/server/src/main/java/password/pwm/config/PwmSettingXml.java index a81dd19ea..a4044f7fd 100644 --- a/server/src/main/java/password/pwm/config/PwmSettingXml.java +++ b/server/src/main/java/password/pwm/config/PwmSettingXml.java @@ -21,6 +21,8 @@ package password.pwm.config; import password.pwm.error.PwmUnrecoverableException; +import password.pwm.util.java.JavaHelper; +import password.pwm.util.java.LazySoftReference; import password.pwm.util.java.TimeDuration; import password.pwm.util.java.XmlDocument; import password.pwm.util.java.XmlElement; @@ -34,7 +36,6 @@ import javax.xml.validation.Validator; import java.io.IOException; import java.io.InputStream; -import java.lang.ref.WeakReference; import java.time.Instant; import java.util.Collections; import java.util.LinkedHashSet; @@ -46,41 +47,44 @@ public class PwmSettingXml public static final String SETTING_XML_FILENAME = ( PwmSetting.class.getPackage().getName() + "." + PwmSetting.class.getSimpleName() ).replace( ".", "/" ) + ".xml"; - public static final String XML_ELEMENT_LDAP_PERMISSION = "ldapPermission"; - public static final String XML_ELEMENT_EXAMPLE = "example"; - public static final String XML_ELEMENT_DEFAULT = "default"; + static final String XML_ELEMENT_LDAP_PERMISSION = "ldapPermission"; + static final String XML_ELEMENT_EXAMPLE = "example"; + static final String XML_ELEMENT_DEFAULT = "default"; + + static final String XML_ATTRIBUTE_PERMISSION_ACTOR = "actor"; + static final String XML_ATTRIBUTE_PERMISSION_ACCESS = "access"; + static final String XML_ATTRIBUTE_TEMPLATE = "template"; + static final String XML_ELEMENT_REGEX = "regex"; + static final String XML_ELEMENT_HIDDEN = "hidden"; + static final String XML_ELEMENT_REQUIRED = "required"; + static final String XML_ELEMENT_LEVEL = "level"; + static final String XML_ELEMENT_PROPERTIES = "properties"; + static final String XML_ELEMENT_PROPERTY = "property"; + static final String XML_ATTRIBUTE_KEY = "key"; + static final String XML_ELEMENT_VALUE = "value"; + static final String XML_ELEMENT_OPTION = "option"; + static final String XML_ELEMENT_OPTIONS = "options"; - public static final String XML_ATTRIBUTE_PERMISSION_ACTOR = "actor"; - public static final String XML_ATTRIBUTE_PERMISSION_ACCESS = "access"; - public static final String XML_ATTRIBUTE_TEMPLATE = "template"; private static final PwmLogger LOGGER = PwmLogger.forClass( PwmSettingXml.class ); - private static WeakReference xmlDocCache = new WeakReference<>( null ); + private static LazySoftReference xmlDocCache = new LazySoftReference<>( () -> readXml() ); private static final AtomicInteger LOAD_COUNTER = new AtomicInteger( 0 ); private static XmlDocument readXml( ) { - final XmlDocument docRefCopy = xmlDocCache.get(); - if ( docRefCopy == null ) + try ( InputStream inputStream = PwmSetting.class.getClassLoader().getResourceAsStream( SETTING_XML_FILENAME ) ) { - try ( InputStream inputStream = PwmSetting.class.getClassLoader().getResourceAsStream( SETTING_XML_FILENAME ) ) - { - final Instant startTime = Instant.now(); - final XmlDocument newDoc = XmlFactory.getFactory().parseXml( inputStream ); - final TimeDuration parseDuration = TimeDuration.fromCurrent( startTime ); - LOGGER.trace( () -> "parsed PwmSettingXml in " + parseDuration.asCompactString() + ", loads=" + LOAD_COUNTER.getAndIncrement() ); - - xmlDocCache = new WeakReference<>( newDoc ); - - return newDoc; - } - catch ( IOException | PwmUnrecoverableException e ) - { - throw new IllegalStateException( "error parsing " + SETTING_XML_FILENAME + ": " + e.getMessage() ); - } + final Instant startTime = Instant.now(); + final XmlDocument newDoc = XmlFactory.getFactory().parseXml( inputStream ); + final TimeDuration parseDuration = TimeDuration.fromCurrent( startTime ); + LOGGER.trace( () -> "parsed PwmSettingXml in " + parseDuration.asCompactString() + ", loads=" + LOAD_COUNTER.getAndIncrement() ); + return newDoc; + } + catch ( final IOException | PwmUnrecoverableException e ) + { + throw new IllegalStateException( "error parsing " + SETTING_XML_FILENAME + ": " + e.getMessage() ); } - return docRefCopy; } private static void validateXmlSchema( ) @@ -94,7 +98,7 @@ private static void validateXmlSchema( ) final Validator validator = schema.newValidator(); validator.validate( new StreamSource( xmlInputStream ) ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new IllegalStateException( "error validating PwmSetting.xml schema using PwmSetting.xsd definition: " + e.getMessage() ); } @@ -103,19 +107,22 @@ private static void validateXmlSchema( ) static XmlElement readSettingXml( final PwmSetting setting ) { final String expression = "/settings/setting[@key=\"" + setting.getKey() + "\"]"; - return readXml().evaluateXpathToElement( expression ); + return xmlDocCache.get().evaluateXpathToElement( expression ) + .orElseThrow( () -> new IllegalStateException( "PwmSetting.xml is missing setting for key '" + setting.getKey() + "'" ) ); } static XmlElement readCategoryXml( final PwmSettingCategory category ) { final String expression = "/settings/category[@key=\"" + category.toString() + "\"]"; - return readXml().evaluateXpathToElement( expression ); + return xmlDocCache.get().evaluateXpathToElement( expression ) + .orElseThrow( () -> new IllegalStateException( "PwmSetting.xml is missing category for key '" + category.getKey() + "'" ) ); } static XmlElement readTemplateXml( final PwmSettingTemplate template ) { final String expression = "/settings/template[@key=\"" + template.toString() + "\"]"; - return readXml().evaluateXpathToElement( expression ); + return xmlDocCache.get().evaluateXpathToElement( expression ) + .orElseThrow( () -> new IllegalStateException( "PwmSetting.xml is missing template for key '" + template.toString() + "'" ) ); } static Set parseTemplateAttribute( final XmlElement element ) @@ -124,14 +131,14 @@ static Set parseTemplateAttribute( final XmlElement element { return Collections.emptySet(); } - final String templateStrValues = element.getAttributeValue( "template" ); + final String templateStrValues = element.getAttributeValue( XML_ATTRIBUTE_TEMPLATE ); final String[] templateSplitValues = templateStrValues == null ? new String[ 0 ] : templateStrValues.split( "," ); final Set definedTemplates = new LinkedHashSet<>(); for ( final String templateStrValue : templateSplitValues ) { - final PwmSettingTemplate template = PwmSettingTemplate.valueOf( templateStrValue ); + final PwmSettingTemplate template = JavaHelper.readEnumFromString( PwmSettingTemplate.class, null, templateStrValue ); if ( template != null ) { definedTemplates.add( template ); diff --git a/server/src/main/java/password/pwm/config/SettingUIFunction.java b/server/src/main/java/password/pwm/config/SettingUIFunction.java index 5fc6fab4b..b7b3fb6b7 100644 --- a/server/src/main/java/password/pwm/config/SettingUIFunction.java +++ b/server/src/main/java/password/pwm/config/SettingUIFunction.java @@ -20,7 +20,7 @@ package password.pwm.config; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.http.PwmRequest; import java.io.Serializable; @@ -29,7 +29,7 @@ public interface SettingUIFunction { Serializable provideFunction( PwmRequest pwmRequest, - StoredConfigurationImpl storedConfiguration, + StoredConfigurationModifier modifier, PwmSetting setting, String profile, String extraData diff --git a/server/src/main/java/password/pwm/config/StoredValue.java b/server/src/main/java/password/pwm/config/StoredValue.java index 81316be6e..02db0029a 100644 --- a/server/src/main/java/password/pwm/config/StoredValue.java +++ b/server/src/main/java/password/pwm/config/StoredValue.java @@ -20,8 +20,8 @@ package password.pwm.config; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.error.PwmException; -import password.pwm.error.PwmUnrecoverableException; import password.pwm.util.java.XmlElement; import password.pwm.util.secure.PwmSecurityKey; @@ -31,7 +31,7 @@ public interface StoredValue extends Serializable { - List toXmlValues( String valueElementName, PwmSecurityKey pwmSecurityKey ); + List toXmlValues( String valueElementName, XmlOutputProcessData xmlOutputProcessData ); Object toNativeObject( ); @@ -41,8 +41,6 @@ public interface StoredValue extends Serializable String toDebugString( Locale locale ); - boolean requiresStoredUpdate( ); - int currentSyntaxVersion( ); interface StoredValueFactory @@ -53,5 +51,5 @@ StoredValue fromXmlElement( PwmSetting pwmSetting, XmlElement settingElement, Pw throws PwmException; } - String valueHash( ) throws PwmUnrecoverableException; + String valueHash(); } diff --git a/server/src/main/java/password/pwm/config/function/AbstractUriCertImportFunction.java b/server/src/main/java/password/pwm/config/function/AbstractUriCertImportFunction.java index 3991a7e36..c46a39e44 100644 --- a/server/src/main/java/password/pwm/config/function/AbstractUriCertImportFunction.java +++ b/server/src/main/java/password/pwm/config/function/AbstractUriCertImportFunction.java @@ -24,7 +24,7 @@ import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.SettingUIFunction; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.config.value.X509CertificateValue; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; @@ -45,7 +45,7 @@ abstract class AbstractUriCertImportFunction implements SettingUIFunction @Override public String provideFunction( final PwmRequest pwmRequest, - final StoredConfigurationImpl storedConfiguration, + final StoredConfigurationModifier modifier, final PwmSetting setting, final String profile, final String extraData ) @@ -54,21 +54,21 @@ public String provideFunction( final PwmSession pwmSession = pwmRequest.getPwmSession(); final List certs; - final String urlString = getUri( storedConfiguration, setting, profile, extraData ); + final String urlString = getUri( modifier, setting, profile, extraData ); try { final URI uri = URI.create( urlString ); if ( "https".equalsIgnoreCase( uri.getScheme() ) ) { - certs = X509Utils.readRemoteHttpCertificates( pwmRequest.getPwmApplication(), pwmSession.getLabel(), uri, new Configuration( storedConfiguration ) ); + certs = X509Utils.readRemoteHttpCertificates( pwmRequest.getPwmApplication(), pwmSession.getLabel(), uri, new Configuration( modifier.newStoredConfiguration() ) ); } else { - final Configuration configuration = new Configuration( storedConfiguration ); + final Configuration configuration = new Configuration( modifier.newStoredConfiguration() ); certs = X509Utils.readRemoteCertificates( URI.create( urlString ), configuration ); } } - catch ( Exception e ) + catch ( final Exception e ) { if ( e instanceof PwmException ) { @@ -80,7 +80,7 @@ public String provideFunction( final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null; - store( certs, storedConfiguration, setting, profile, extraData, userIdentity ); + store( certs, modifier, setting, profile, extraData, userIdentity ); final StringBuffer returnStr = new StringBuffer(); for ( final X509Certificate loopCert : certs ) @@ -91,12 +91,18 @@ public String provideFunction( return returnStr.toString(); } - abstract String getUri( StoredConfigurationImpl storedConfiguration, PwmSetting pwmSetting, String profile, String extraData ) throws PwmOperationalException; + abstract String getUri( + StoredConfigurationModifier modifier, + PwmSetting pwmSetting, + String profile, + String extraData + ) + throws PwmOperationalException, PwmUnrecoverableException; void store( final List certs, - final StoredConfigurationImpl storedConfiguration, + final StoredConfigurationModifier storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData, diff --git a/server/src/main/java/password/pwm/config/function/ActionCertImportFunction.java b/server/src/main/java/password/pwm/config/function/ActionCertImportFunction.java index 622c9b581..696474b57 100644 --- a/server/src/main/java/password/pwm/config/function/ActionCertImportFunction.java +++ b/server/src/main/java/password/pwm/config/function/ActionCertImportFunction.java @@ -23,7 +23,7 @@ import com.google.gson.reflect.TypeToken; import password.pwm.bean.UserIdentity; import password.pwm.config.PwmSetting; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.config.value.ActionValue; import password.pwm.config.value.data.ActionConfiguration; import password.pwm.error.ErrorInformation; @@ -42,14 +42,20 @@ public class ActionCertImportFunction extends AbstractUriCertImportFunction private static final String KEY_ITERATION = "iteration"; private static final String KEY_WEB_ACTION_ITERATION = "webActionIter"; - @Override - String getUri( final StoredConfigurationImpl storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData ) throws PwmOperationalException + @Override + String getUri( + final StoredConfigurationModifier modifier, + final PwmSetting pwmSetting, + final String profile, + final String extraData + ) + throws PwmOperationalException, PwmUnrecoverableException { final Map extraDataMap = JsonUtil.deserialize( extraData, new TypeToken>() { } ); - final ActionValue actionValue = ( ActionValue ) storedConfiguration.readSetting( pwmSetting, profile ); + final ActionValue actionValue = ( ActionValue ) modifier.newStoredConfiguration().readSetting( pwmSetting, profile ); final ActionConfiguration action = ( actionValue.toNativeObject() ).get( extraDataMap.get( KEY_ITERATION ) ); final ActionConfiguration.WebAction webAction = action.getWebActions().get( extraDataMap.get( KEY_WEB_ACTION_ITERATION ) ); @@ -67,7 +73,7 @@ String getUri( final StoredConfigurationImpl storedConfiguration, final PwmSetti { URI.create( uriString ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "Setting " @@ -79,7 +85,7 @@ String getUri( final StoredConfigurationImpl storedConfiguration, final PwmSetti void store( final List certs, - final StoredConfigurationImpl storedConfiguration, + final StoredConfigurationModifier storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData, @@ -91,7 +97,7 @@ void store( { } ); - final ActionValue actionValue = ( ActionValue ) storedConfiguration.readSetting( pwmSetting, profile ); + final ActionValue actionValue = ( ActionValue ) storedConfiguration.newStoredConfiguration().readSetting( pwmSetting, profile ); final List actionConfigurations = actionValue.toNativeObject(); final ActionConfiguration action = actionConfigurations.get( extraDataMap.get( KEY_ITERATION ) ); final ActionConfiguration.WebAction webAction = action.getWebActions().get( extraDataMap.get( KEY_WEB_ACTION_ITERATION ) ); diff --git a/server/src/main/java/password/pwm/config/function/LdapCertImportFunction.java b/server/src/main/java/password/pwm/config/function/LdapCertImportFunction.java index 44237e5a1..000a0a1e6 100644 --- a/server/src/main/java/password/pwm/config/function/LdapCertImportFunction.java +++ b/server/src/main/java/password/pwm/config/function/LdapCertImportFunction.java @@ -24,7 +24,7 @@ import password.pwm.bean.UserIdentity; import password.pwm.config.PwmSetting; import password.pwm.config.SettingUIFunction; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.config.value.StringArrayValue; import password.pwm.config.value.X509CertificateValue; import password.pwm.error.PwmUnrecoverableException; @@ -43,7 +43,7 @@ public class LdapCertImportFunction implements SettingUIFunction @Override public String provideFunction( final PwmRequest pwmRequest, - final StoredConfigurationImpl storedConfiguration, + final StoredConfigurationModifier modifier, final PwmSetting setting, final String profile, final String extraData @@ -53,7 +53,7 @@ public String provideFunction( final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final PwmSession pwmSession = pwmRequest.getPwmSession(); - final StringArrayValue ldapUrlsValue = ( StringArrayValue ) storedConfiguration.readSetting( PwmSetting.LDAP_SERVER_URLS, profile ); + final StringArrayValue ldapUrlsValue = ( StringArrayValue ) modifier.newStoredConfiguration().readSetting( PwmSetting.LDAP_SERVER_URLS, profile ); final Set resultCertificates = new LinkedHashSet<>(); if ( ldapUrlsValue != null && ldapUrlsValue.toNativeObject() != null ) { @@ -62,7 +62,7 @@ public String provideFunction( } final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null; - storedConfiguration.writeSetting( setting, profile, new X509CertificateValue( resultCertificates ), userIdentity ); + modifier.writeSetting( setting, profile, new X509CertificateValue( resultCertificates ), userIdentity ); return Message.getLocalizedMessage( pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmApplication.getConfig() ); } diff --git a/server/src/main/java/password/pwm/config/function/OAuthCertImportFunction.java b/server/src/main/java/password/pwm/config/function/OAuthCertImportFunction.java index 6470da225..1380fe3d2 100644 --- a/server/src/main/java/password/pwm/config/function/OAuthCertImportFunction.java +++ b/server/src/main/java/password/pwm/config/function/OAuthCertImportFunction.java @@ -22,10 +22,11 @@ import password.pwm.PwmConstants; import password.pwm.config.PwmSetting; -import password.pwm.config.stored.StoredConfigurationImpl; +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.JavaHelper; import java.net.URI; @@ -35,7 +36,8 @@ public class OAuthCertImportFunction extends AbstractUriCertImportFunction @Override - String getUri( final StoredConfigurationImpl storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData ) throws PwmOperationalException + String getUri( final StoredConfigurationModifier modifier, final PwmSetting pwmSetting, final String profile, final String extraData ) + throws PwmOperationalException, PwmUnrecoverableException { final String uriString; @@ -44,12 +46,12 @@ String getUri( final StoredConfigurationImpl storedConfiguration, final PwmSetti switch ( pwmSetting ) { case OAUTH_ID_CERTIFICATE: - uriString = ( String ) storedConfiguration.readSetting( PwmSetting.OAUTH_ID_CODERESOLVE_URL ).toNativeObject(); + uriString = ( String ) modifier.newStoredConfiguration().readSetting( PwmSetting.OAUTH_ID_CODERESOLVE_URL, null ).toNativeObject(); menuDebugLocation = PwmSetting.OAUTH_ID_CODERESOLVE_URL.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ); break; case RECOVERY_OAUTH_ID_CERTIFICATE: - uriString = ( String ) storedConfiguration.readSetting( PwmSetting.RECOVERY_OAUTH_ID_CODERESOLVE_URL, profile ).toNativeObject(); + uriString = ( String ) modifier.newStoredConfiguration().readSetting( PwmSetting.RECOVERY_OAUTH_ID_CODERESOLVE_URL, profile ).toNativeObject(); menuDebugLocation = PwmSetting.RECOVERY_OAUTH_ID_CERTIFICATE.toMenuLocationDebug( profile, PwmConstants.DEFAULT_LOCALE ); break; @@ -68,7 +70,7 @@ String getUri( final StoredConfigurationImpl storedConfiguration, final PwmSetti { URI.create( uriString ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "Setting " + menuDebugLocation + " has an invalid URL syntax" ); throw new PwmOperationalException( errorInformation ); diff --git a/server/src/main/java/password/pwm/config/function/RemoteWebServiceCertImportFunction.java b/server/src/main/java/password/pwm/config/function/RemoteWebServiceCertImportFunction.java index a031b31d5..d3f7d4081 100644 --- a/server/src/main/java/password/pwm/config/function/RemoteWebServiceCertImportFunction.java +++ b/server/src/main/java/password/pwm/config/function/RemoteWebServiceCertImportFunction.java @@ -22,14 +22,13 @@ import password.pwm.bean.UserIdentity; import password.pwm.config.PwmSetting; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigurationModifier; 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 password.pwm.util.java.JsonUtil; import java.net.URI; import java.security.cert.X509Certificate; @@ -40,9 +39,10 @@ public class RemoteWebServiceCertImportFunction extends AbstractUriCertImportFun { @Override - String getUri( final StoredConfigurationImpl storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData ) throws PwmOperationalException + String getUri( final StoredConfigurationModifier modifier, final PwmSetting pwmSetting, final String profile, final String extraData ) + throws PwmOperationalException, PwmUnrecoverableException { - final RemoteWebServiceValue actionValue = ( RemoteWebServiceValue ) storedConfiguration.readSetting( pwmSetting, profile ); + final RemoteWebServiceValue actionValue = ( RemoteWebServiceValue ) modifier.newStoredConfiguration().readSetting( pwmSetting, profile ); final String serviceName = actionNameFromExtraData( extraData ); final RemoteWebServiceConfiguration action = actionValue.forName( serviceName ); final String uriString = action.getUrl(); @@ -57,7 +57,7 @@ String getUri( final StoredConfigurationImpl storedConfiguration, final PwmSetti { URI.create( uriString ); } - catch ( IllegalArgumentException e ) + 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" ); @@ -73,7 +73,7 @@ private String actionNameFromExtraData( final String extraData ) void store( final List certs, - final StoredConfigurationImpl storedConfiguration, + final StoredConfigurationModifier modifier, final PwmSetting pwmSetting, final String profile, final String extraData, @@ -81,24 +81,25 @@ void store( ) throws PwmOperationalException, PwmUnrecoverableException { - final RemoteWebServiceValue actionValue = ( RemoteWebServiceValue ) storedConfiguration.readSetting( pwmSetting, profile ); + final RemoteWebServiceValue actionValue = ( RemoteWebServiceValue ) modifier.newStoredConfiguration().readSetting( pwmSetting, profile ); final String actionName = actionNameFromExtraData( extraData ); final List newList = new ArrayList<>(); for ( final RemoteWebServiceConfiguration loopConfiguration : actionValue.toNativeObject() ) { if ( actionName.equals( loopConfiguration.getName() ) ) { - final RemoteWebServiceConfiguration newConfig = JsonUtil.cloneUsingJson( loopConfiguration, RemoteWebServiceConfiguration.class ); - newConfig.setCertificates( certs ); + final RemoteWebServiceConfiguration newConfig = loopConfiguration.toBuilder() + .certificates( certs ) + .build(); newList.add( newConfig ); } else { - newList.add( JsonUtil.cloneUsingJson( loopConfiguration, RemoteWebServiceConfiguration.class ) ); + newList.add( loopConfiguration ); } } final RemoteWebServiceValue newActionValue = new RemoteWebServiceValue( newList ); - storedConfiguration.writeSetting( pwmSetting, profile, newActionValue, userIdentity ); + modifier.writeSetting( pwmSetting, profile, newActionValue, userIdentity ); } } diff --git a/server/src/main/java/password/pwm/config/function/SMSGatewayCertImportFunction.java b/server/src/main/java/password/pwm/config/function/SMSGatewayCertImportFunction.java index 849ff67d0..fd8a7933f 100644 --- a/server/src/main/java/password/pwm/config/function/SMSGatewayCertImportFunction.java +++ b/server/src/main/java/password/pwm/config/function/SMSGatewayCertImportFunction.java @@ -22,26 +22,24 @@ import password.pwm.PwmConstants; import password.pwm.config.PwmSetting; -import password.pwm.config.stored.StoredConfigurationImpl; +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 - String getUri( final StoredConfigurationImpl storedConfiguration, final PwmSetting pwmSetting, final String profile, final String extraData ) throws PwmOperationalException + String getUri( final StoredConfigurationModifier modifier, final PwmSetting pwmSetting, final String profile, final String extraData ) + throws PwmOperationalException, PwmUnrecoverableException { - - final String uriString; final String menuDebugLocation; - uriString = ( String ) storedConfiguration.readSetting( PwmSetting.SMS_GATEWAY_URL ).toNativeObject(); + uriString = ( String ) modifier.newStoredConfiguration().readSetting( PwmSetting.SMS_GATEWAY_URL, null ).toNativeObject(); menuDebugLocation = PwmSetting.SMS_GATEWAY_URL.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ); if ( uriString.isEmpty() ) @@ -53,7 +51,7 @@ String getUri( final StoredConfigurationImpl storedConfiguration, final PwmSetti { URI.create( uriString ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "Setting " + menuDebugLocation + " has an invalid URL syntax" ); throw new PwmOperationalException( errorInformation ); diff --git a/server/src/main/java/password/pwm/config/function/SmtpCertImportFunction.java b/server/src/main/java/password/pwm/config/function/SmtpCertImportFunction.java index 42890e061..42ef748a7 100644 --- a/server/src/main/java/password/pwm/config/function/SmtpCertImportFunction.java +++ b/server/src/main/java/password/pwm/config/function/SmtpCertImportFunction.java @@ -24,7 +24,7 @@ import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.SettingUIFunction; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.config.value.X509CertificateValue; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.PwmRequest; @@ -41,7 +41,7 @@ public class SmtpCertImportFunction implements SettingUIFunction @Override public String provideFunction( final PwmRequest pwmRequest, - final StoredConfigurationImpl storedConfiguration, + final StoredConfigurationModifier modifier, final PwmSetting setting, final String profile, final String extraData @@ -50,12 +50,12 @@ public String provideFunction( { final PwmSession pwmSession = pwmRequest.getPwmSession(); - final Configuration configuration = new Configuration( storedConfiguration ); + final Configuration configuration = new Configuration( modifier.newStoredConfiguration() ); final List certs = EmailServerUtil.readCertificates( configuration, profile ); if ( !JavaHelper.isEmpty( certs ) ) { final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null; - storedConfiguration.writeSetting( PwmSetting.EMAIL_SERVER_CERTS, profile, new X509CertificateValue( certs ), userIdentity ); + modifier.writeSetting( PwmSetting.EMAIL_SERVER_CERTS, profile, new X509CertificateValue( certs ), userIdentity ); } return Message.getLocalizedMessage( pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmRequest.getConfig() ); diff --git a/server/src/main/java/password/pwm/config/function/SyslogCertImportFunction.java b/server/src/main/java/password/pwm/config/function/SyslogCertImportFunction.java index 69428c28d..1f133acb2 100644 --- a/server/src/main/java/password/pwm/config/function/SyslogCertImportFunction.java +++ b/server/src/main/java/password/pwm/config/function/SyslogCertImportFunction.java @@ -25,7 +25,7 @@ import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.SettingUIFunction; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.config.value.X509CertificateValue; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; @@ -49,7 +49,7 @@ public class SyslogCertImportFunction implements SettingUIFunction @Override public String provideFunction( final PwmRequest pwmRequest, - final StoredConfigurationImpl storedConfiguration, + final StoredConfigurationModifier modifier, final PwmSetting setting, final String profile, final String extraData ) @@ -62,10 +62,10 @@ public String provideFunction( final Set resultCertificates = new LinkedHashSet<>(); - final List syslogConfigStrs = ( List ) storedConfiguration.readSetting( PwmSetting.AUDIT_SYSLOG_SERVERS ).toNativeObject(); + final List syslogConfigStrs = ( List ) modifier.newStoredConfiguration().readSetting( PwmSetting.AUDIT_SYSLOG_SERVERS, null ).toNativeObject(); if ( syslogConfigStrs != null && !syslogConfigStrs.isEmpty() ) { - for ( String entry : syslogConfigStrs ) + for ( final String entry : syslogConfigStrs ) { if ( entry.toUpperCase().startsWith( "TLS" ) ) { @@ -77,7 +77,7 @@ public String provideFunction( final List certs = X509Utils.readRemoteCertificates( syslogConfig.getHost(), syslogConfig.getPort(), - new Configuration( storedConfiguration ) + new Configuration( modifier.newStoredConfiguration() ) ); if ( certs != null ) { @@ -85,7 +85,7 @@ public String provideFunction( error = false; } } - catch ( Exception e ) + catch ( final Exception e ) { error = true; exeception = e; @@ -98,7 +98,7 @@ public String provideFunction( if ( !error ) { final UserIdentity userIdentity = pwmSession.isAuthenticated() ? pwmSession.getUserInfo().getUserIdentity() : null; - storedConfiguration.writeSetting( setting, new X509CertificateValue( resultCertificates ), userIdentity ); + modifier.writeSetting( setting, null, new X509CertificateValue( resultCertificates ), userIdentity ); return Message.getLocalizedMessage( pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmApplication.getConfig() ); } else diff --git a/server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java b/server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java index d6c8aafc7..6cd76e6a3 100644 --- a/server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java +++ b/server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java @@ -31,7 +31,8 @@ import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.SettingUIFunction; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.config.value.data.UserPermission; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; @@ -58,7 +59,7 @@ public class UserMatchViewerFunction implements SettingUIFunction @Override public Serializable provideFunction( final PwmRequest pwmRequest, - final StoredConfigurationImpl storedConfiguration, + final StoredConfigurationModifier storedConfiguration, final PwmSetting setting, final String profile, final String extraData ) @@ -68,7 +69,7 @@ public Serializable provideFunction( final Instant startSearchTime = Instant.now(); final int maxResultSize = Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT ) ); - final Collection users = discoverMatchingUsers( pwmApplication, maxResultSize, storedConfiguration, setting, profile ); + final Collection users = discoverMatchingUsers( pwmApplication, maxResultSize, storedConfiguration.newStoredConfiguration(), setting, profile ); final TimeDuration searchDuration = TimeDuration.fromCurrent( startSearchTime ); final UserMatchViewerResults userMatchViewerResults = new UserMatchViewerResults(); @@ -88,7 +89,7 @@ public Serializable provideFunction( public Collection discoverMatchingUsers( final PwmApplication pwmApplication, final int maxResultSize, - final StoredConfigurationImpl storedConfiguration, + final StoredConfiguration storedConfiguration, final PwmSetting setting, final String profile ) @@ -141,7 +142,7 @@ else if ( profileID.equals( PwmConstants.PROFILE_ID_ALL ) ) final ChaiProvider proxiedProvider = pwmApplication.getProxyChaiProvider( loopID ); chaiEntry = proxiedProvider.getEntryFactory().newChaiEntry( baseDN ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error while testing entry DN for profile '" + profileID + "', error:" + profileID ); } @@ -153,7 +154,7 @@ else if ( profileID.equals( PwmConstants.PROFILE_ID_ALL ) ) throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_LDAP_DATA_ERROR, errorMsg ) ); } } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } diff --git a/server/src/main/java/password/pwm/config/profile/ChallengeProfile.java b/server/src/main/java/password/pwm/config/profile/ChallengeProfile.java index c15ed829e..0672d994c 100644 --- a/server/src/main/java/password/pwm/config/profile/ChallengeProfile.java +++ b/server/src/main/java/password/pwm/config/profile/ChallengeProfile.java @@ -98,7 +98,7 @@ public static ChallengeProfile readChallengeProfileFromConfig( minRandomRequired ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.trace( () -> "configured challengeSet for profile '" + profileID + "' is not valid: " + e.getMessage() ); } @@ -115,7 +115,7 @@ public static ChallengeProfile readChallengeProfileFromConfig( 1 ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.trace( () -> "discarding configured helpdesk challengeSet for profile '" + profileID + "' issue: " + e.getMessage() ); } @@ -256,7 +256,7 @@ private static ChallengeSet readChallengeSet( { return new ChaiChallengeSet( challenges, randoms, locale, PwmConstants.PWM_APP_NAME + "-defined " + PwmConstants.SERVLET_VERSION ); } - catch ( ChaiValidationException e ) + catch ( final ChaiValidationException e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "invalid challenge set configuration: " + e.getMessage() ) ); } diff --git a/server/src/main/java/password/pwm/config/profile/LdapProfile.java b/server/src/main/java/password/pwm/config/profile/LdapProfile.java index 9db351980..e8dc594de 100644 --- a/server/src/main/java/password/pwm/config/profile/LdapProfile.java +++ b/server/src/main/java/password/pwm/config/profile/LdapProfile.java @@ -173,7 +173,7 @@ public String readCanonicalDN( LOGGER.trace( () -> "read and cached canonical ldap DN value for input '" + dnValue + "' as '" + finalCanonical + "'" ); } } - catch ( ChaiUnavailableException | ChaiOperationException e ) + catch ( final ChaiUnavailableException | ChaiOperationException e ) { LOGGER.error( "error while reading canonicalDN for dn value '" + dnValue + "', error: " + e.getMessage() ); return dnValue; diff --git a/server/src/main/java/password/pwm/config/profile/NewUserProfile.java b/server/src/main/java/password/pwm/config/profile/NewUserProfile.java index 232f815f8..7afa58706 100644 --- a/server/src/main/java/password/pwm/config/profile/NewUserProfile.java +++ b/server/src/main/java/password/pwm/config/profile/NewUserProfile.java @@ -135,7 +135,7 @@ public PwmPasswordPolicy getNewUserPasswordPolicy( final PwmApplication pwmAppli final UserIdentity userIdentity = new UserIdentity( lookupDN, defaultLdapProfile.getIdentifier() ); thePolicy = PasswordUtility.readPasswordPolicyForUser( pwmApplication, null, userIdentity, chaiUser, userLocale ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) ); } diff --git a/server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java b/server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java index d44adb78e..9dc11bd3d 100644 --- a/server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java +++ b/server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java @@ -59,7 +59,7 @@ public class PwmPasswordPolicy implements Profile, Serializable private static final PwmPasswordPolicy DEFAULT_POLICY; - private final Map policyMap = new HashMap<>(); + private final Map policyMap; private final transient ChaiPasswordPolicy chaiPasswordPolicy; @@ -111,7 +111,7 @@ public String getDisplayName( final Locale locale ) } newDefaultPolicy = createPwmPasswordPolicy( defaultPolicyMap, null ); } - catch ( Throwable t ) + catch ( final Throwable t ) { LOGGER.fatal( "error initializing PwmPasswordPolicy class: " + t.getMessage(), t ); } @@ -130,19 +130,20 @@ private PwmPasswordPolicy( final PolicyMetaData policyMetaData ) { + final Map effectivePolicyMap = new HashMap<>(); if ( policyMap != null ) { - this.policyMap.putAll( policyMap ); + effectivePolicyMap.putAll( policyMap ); } if ( chaiPasswordPolicy != null ) { if ( Boolean.parseBoolean( chaiPasswordPolicy.getValue( ChaiPasswordRule.ADComplexity ) ) ) { - this.policyMap.put( PwmPasswordRule.ADComplexityLevel.getKey(), ADPolicyComplexity.AD2003.toString() ); + effectivePolicyMap.put( PwmPasswordRule.ADComplexityLevel.getKey(), ADPolicyComplexity.AD2003.toString() ); } else if ( Boolean.parseBoolean( chaiPasswordPolicy.getValue( ChaiPasswordRule.ADComplexity2008 ) ) ) { - this.policyMap.put( PwmPasswordRule.ADComplexityLevel.getKey(), ADPolicyComplexity.AD2008.toString() ); + effectivePolicyMap.put( PwmPasswordRule.ADComplexityLevel.getKey(), ADPolicyComplexity.AD2008.toString() ); } } this.chaiPasswordPolicy = chaiPasswordPolicy; @@ -152,6 +153,8 @@ else if ( Boolean.parseBoolean( chaiPasswordPolicy.getValue( ChaiPasswordRule.AD this.userPermissions = policyMetaData.getUserPermissions(); this.profileID = policyMetaData.getProfileID(); } + + this.policyMap = Collections.unmodifiableMap( effectivePolicyMap ); } @Override diff --git a/server/src/main/java/password/pwm/config/profile/PwmPasswordRule.java b/server/src/main/java/password/pwm/config/profile/PwmPasswordRule.java index e1a977f72..935e62991 100644 --- a/server/src/main/java/password/pwm/config/profile/PwmPasswordRule.java +++ b/server/src/main/java/password/pwm/config/profile/PwmPasswordRule.java @@ -403,7 +403,7 @@ public enum PwmPasswordRule } assert keys.size() == PwmSetting.values().length; } - catch ( Throwable t ) + catch ( final Throwable t ) { LOGGER.fatal( "error initializing PwmPasswordRule class: " + t.getMessage(), t ); } @@ -515,7 +515,7 @@ public String getLabel( final Locale locale, final Configuration config ) { return LocaleHelper.getLocalizedMessage( locale, key, config, Message.class ); } - catch ( MissingResourceException e ) + catch ( final MissingResourceException e ) { return "MissingKey-" + key; } diff --git a/server/src/main/java/password/pwm/config/stored/ConfigChangeLogImpl.java b/server/src/main/java/password/pwm/config/stored/ConfigChangeLogImpl.java deleted file mode 100644 index d29aed4e8..000000000 --- a/server/src/main/java/password/pwm/config/stored/ConfigChangeLogImpl.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2019 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.config.stored; - -import password.pwm.config.PwmSetting; -import password.pwm.config.StoredValue; -import password.pwm.util.java.StringUtil; - -import java.io.Serializable; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; -import java.util.TreeMap; - -public class ConfigChangeLogImpl implements Serializable, ConfigChangeLog -{ - private final Map changeLog = new LinkedHashMap<>(); - private final Map originalValue = new LinkedHashMap<>(); - private final transient StorageEngine storedConfiguration; - - public ConfigChangeLogImpl( final StorageEngine storageEngine ) - { - this.storedConfiguration = storageEngine; - } - - @Override - public boolean isModified( ) - { - return !changeLog.isEmpty(); - } - - @Override - public String changeLogAsDebugString( final Locale locale, final boolean asHtml ) - { - final Map outputMap = new TreeMap<>(); - - for ( final StoredConfigReference configReference : changeLog.keySet() ) - { - switch ( configReference.getRecordType() ) - { - case SETTING: - { - final PwmSetting pwmSetting = PwmSetting.forKey( configReference.getRecordID() ); - final StoredValue currentValue = storedConfiguration.read( configReference ); - final String keyName = pwmSetting.toMenuLocationDebug( configReference.getProfileID(), locale ); - final String debugValue = currentValue.toDebugString( locale ); - outputMap.put( keyName, debugValue ); - } - break; - - /* - case LOCALE_BUNDLE: { - final String SEPARATOR = LocaleHelper.getLocalizedMessage(locale, Config.Display_SettingNavigationSeparator, null); - final String key = (String) configReference.recordID; - final String bundleName = key.split("!")[0]; - final String keys = key.split("!")[1]; - final Map currentValue = readLocaleBundleMap(bundleName,keys); - final String debugValue = JsonUtil.serializeMap(currentValue, JsonUtil.Flag.PrettyPrint); - outputMap.put("LocaleBundle" + SEPARATOR + bundleName + " " + keys,debugValue); - } - break; - */ - - default: - //continue - break; - } - } - final StringBuilder output = new StringBuilder(); - if ( outputMap.isEmpty() ) - { - output.append( "No setting changes." ); - } - else - { - for ( final Map.Entry entry : outputMap.entrySet() ) - { - final String keyName = entry.getKey(); - final String value = entry.getValue(); - if ( asHtml ) - { - output.append( "
" ); - output.append( keyName ); - output.append( "
" ); - output.append( StringUtil.escapeHtml( value ) ); - output.append( "
" ); - } - else - { - output.append( keyName ); - output.append( "\n" ); - output.append( " Value: " ); - output.append( value ); - output.append( "\n" ); - } - } - } - return output.toString(); - } - - @Override - public void updateChangeLog( final StoredConfigReference reference, final StoredValue newValue ) - { - changeLog.put( reference, newValue ); - originalValue.put( reference, null ); - } - - @Override - public void updateChangeLog( final StoredConfigReference reference, final StoredValue currentValue, final StoredValue newValue ) - { - if ( originalValue.containsKey( reference ) ) - { - if ( newValue.equals( originalValue.get( reference ) ) ) - { - originalValue.remove( reference ); - changeLog.remove( reference ); - } - } - else - { - originalValue.put( reference, currentValue ); - changeLog.put( reference, newValue ); - } - } - - @Override - public Collection changedValues( ) - { - return changeLog.keySet(); - } -} diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigReference.java b/server/src/main/java/password/pwm/config/stored/ConfigRestartRequirement.java similarity index 72% rename from server/src/main/java/password/pwm/config/stored/StoredConfigReference.java rename to server/src/main/java/password/pwm/config/stored/ConfigRestartRequirement.java index b7b750548..0de077a23 100644 --- a/server/src/main/java/password/pwm/config/stored/StoredConfigReference.java +++ b/server/src/main/java/password/pwm/config/stored/ConfigRestartRequirement.java @@ -20,20 +20,7 @@ package password.pwm.config.stored; -import java.io.Serializable; - -public interface StoredConfigReference extends Serializable, Comparable +public enum ConfigRestartRequirement { - RecordType getRecordType( ); - - String getRecordID( ); - - String getProfileID( ); - - enum RecordType - { - SETTING, - LOCALE_BUNDLE, - PROPERTY, - } + Application } diff --git a/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java b/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java index a9995d69a..7a0b71109 100644 --- a/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java +++ b/server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java @@ -22,106 +22,130 @@ import password.pwm.PwmConstants; import password.pwm.bean.UserIdentity; +import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; import password.pwm.config.option.ADPolicyComplexity; +import password.pwm.config.option.RecoveryMinLifetimeOption; import password.pwm.config.option.WebServiceUsage; import password.pwm.config.value.OptionListValue; +import password.pwm.config.value.StoredValueEncoder; import password.pwm.config.value.StringArrayValue; import password.pwm.config.value.StringValue; import password.pwm.error.PwmUnrecoverableException; +import password.pwm.util.java.JavaHelper; +import password.pwm.util.java.PwmExceptionLoggingConsumer; +import password.pwm.util.java.StringUtil; import password.pwm.util.java.XmlDocument; import password.pwm.util.java.XmlElement; +import password.pwm.util.java.XmlFactory; import password.pwm.util.logging.PwmLogger; +import password.pwm.util.secure.PwmSecurityKey; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; +import java.util.Optional; import java.util.Set; class ConfigurationCleaner { private static final PwmLogger LOGGER = PwmLogger.forClass( ConfigurationCleaner.class ); - private static final String NEW_PROFILE_NAME = "default"; - private final StoredConfigurationImpl storedConfiguration; - private final XmlDocument document; - ConfigurationCleaner( - final StoredConfigurationImpl storedConfiguration, final XmlDocument document - ) - { - this.storedConfiguration = storedConfiguration; - this.document = document; - } + private static final List> XML_PRE_PROCESSORS = Collections.unmodifiableList( Arrays.asList( + new MigratePreValueXmlElements(), + new MigrateOldPropertyFormat(), + new AppPropertyOverrideMigration(), + new ProfileNonProfiledSettings(), + new MigrateDeprecatedProperties(), + new UpdatePropertiesWithoutType() + ) ); + private static final List> STORED_CONFIG_POST_PROCESSORS = Collections.unmodifiableList( Arrays.asList( + new UpdateDeprecatedAdComplexitySettings(), + new UpdateDeprecatedMinPwdLifetimeSetting(), + new UpdateDeprecatedPublicHealthSetting() + ) ); - static void cleanup( - final StoredConfigurationImpl storedConfiguration, final XmlDocument document - ) - throws PwmUnrecoverableException - { - new ConfigurationCleaner( storedConfiguration, document ).cleanupImpl(); - } - static void updateMandatoryElements( - final StoredConfigurationImpl storedConfiguration, + static void preProcessXml( final XmlDocument document ) { - new ConfigurationCleaner( storedConfiguration, document ).updateMandatoryElementsImpl(); + XML_PRE_PROCESSORS.forEach( ( c ) -> PwmExceptionLoggingConsumer.wrapConsumer( c ).accept( document ) ); } - private void cleanupImpl( + static void postProcessStoredConfig( + final StoredConfigurationModifier storedConfiguration ) - throws PwmUnrecoverableException { - updateProperitiesWithoutType( ); - updateMandatoryElementsImpl(); - profilizeNonProfiledSettings( ); - stripOrphanedProfileSettings( ); - migrateAppProperties( ); - updateDeprecatedSettings( ); - migrateDeprecatedProperties( ); + STORED_CONFIG_POST_PROCESSORS.forEach( aClass -> PwmExceptionLoggingConsumer.wrapConsumer( aClass ).accept( storedConfiguration ) ); } - - private void updateMandatoryElementsImpl( ) + private static class MigratePreValueXmlElements implements PwmExceptionLoggingConsumer { - final XmlElement rootElement = document.getRootElement(); - rootElement.setComment( Collections.singletonList( generateCommentText() ) ); + @Override + public void accept( final XmlDocument xmlDocument ) + { + if ( readDocVersion( xmlDocument ) >= 4 ) + { + return; + } - rootElement.setAttribute( "pwmVersion", PwmConstants.BUILD_VERSION ); - rootElement.setAttribute( "pwmBuild", PwmConstants.BUILD_NUMBER ); - rootElement.setAttribute( "xmlVersion", StoredConfigurationImpl.XML_FORMAT_VERSION ); + final List settingElements = xmlDocument.evaluateXpathToElements( "//" + + StoredConfigXmlConstants.XML_ELEMENT_SETTING ); + for ( final XmlElement settingElement : settingElements ) + { + final Optional valueElement = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); + final Optional defaultElement = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_DEFAULT ); + if ( valueElement.isPresent() && defaultElement.isPresent() ) + { + final String textValue = settingElement.getTextTrim(); + if ( !StringUtil.isEmpty( textValue ) ) + { + final XmlElement newValueElement = XmlFactory.getFactory().newElement( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); + newValueElement.addText( textValue ); + settingElement.addContent( newValueElement ); + final String key = settingElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY ); + LOGGER.info( () -> "migrating pre-xml 'value' tag format to use value element for key: " + key ); + } + } + } + } + } - // migrate old properties + private static class MigrateOldPropertyFormat implements PwmExceptionLoggingConsumer + { + @Override + public void accept( final XmlDocument xmlDocument ) { - // read correct (new) //properties[@type="config"] - final String configPropertiesXpath = "//" + StoredConfigurationImpl.XML_ELEMENT_PROPERTIES - + "[@" + StoredConfigurationImpl.XML_ATTRIBUTE_TYPE + "=\"" + StoredConfigurationImpl.XML_ATTRIBUTE_VALUE_CONFIG + "\"]"; - final XmlElement configPropertiesElement = document.evaluateXpathToElement( configPropertiesXpath ); + final String configPropertiesXpath = "//" + StoredConfigXmlConstants.XML_ELEMENT_PROPERTIES + + "[@" + StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE + "=\"" + StoredConfigXmlConstants.XML_ATTRIBUTE_VALUE_CONFIG + "\"]"; + final Optional configPropertiesElement = xmlDocument.evaluateXpathToElement( configPropertiesXpath ); // read list of old //properties[not (@type)]/property - final String nonAttributedPropertyXpath = "//" + StoredConfigurationImpl.XML_ELEMENT_PROPERTIES - + "[not (@" + StoredConfigurationImpl.XML_ATTRIBUTE_TYPE + ")]/" + StoredConfigurationImpl.XML_ELEMENT_PROPERTY; - final List nonAttributedProperties = document.evaluateXpathToElements( nonAttributedPropertyXpath ); + final String nonAttributedPropertyXpath = "//" + StoredConfigXmlConstants.XML_ELEMENT_PROPERTIES + + "[not (@" + StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE + ")]/" + StoredConfigXmlConstants.XML_ELEMENT_PROPERTY; + final List nonAttributedProperties = xmlDocument.evaluateXpathToElements( nonAttributedPropertyXpath ); - if ( configPropertiesElement != null && nonAttributedProperties != null ) + if ( configPropertiesElement.isPresent() && nonAttributedProperties != null ) { for ( final XmlElement element : nonAttributedProperties ) { element.detach(); - configPropertiesElement.addContent( element ); + configPropertiesElement.get().addContent( element ); } } // remove old //properties[not (@type] element - final String oldPropertiesXpath = "//" + StoredConfigurationImpl.XML_ELEMENT_PROPERTIES + "[not (@" + StoredConfigurationImpl.XML_ATTRIBUTE_TYPE + ")]"; - final List oldPropertiesElements = document.evaluateXpathToElements( oldPropertiesXpath ); + final String oldPropertiesXpath = "//" + StoredConfigXmlConstants.XML_ELEMENT_PROPERTIES + + "[not (@" + StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE + ")]"; + final List oldPropertiesElements = xmlDocument.evaluateXpathToElements( oldPropertiesXpath ); if ( oldPropertiesElements != null ) { for ( final XmlElement element : oldPropertiesElements ) @@ -132,226 +156,260 @@ private void updateMandatoryElementsImpl( ) } } - private String generateCommentText( ) + static class ProfileNonProfiledSettings implements PwmExceptionLoggingConsumer { - final StringBuilder commentText = new StringBuilder(); - commentText.append( "\t\t" ).append( " " ).append( "\n" ); - commentText.append( "\t\t" ).append( "This configuration file has been auto-generated by the " ).append( PwmConstants.PWM_APP_NAME ) - .append( " password self service application." ).append( "\n" ); - commentText.append( "\t\t" ).append( "" ).append( "\n" ); - commentText.append( "\t\t" ).append( "WARNING: This configuration file contains sensitive security information, please handle with care!" ).append( "\n" ); - commentText.append( "\t\t" ).append( "" ).append( "\n" ); - commentText.append( "\t\t" ).append( "WARNING: If a server is currently running using this configuration file, it will be restarted" ).append( "\n" ); - commentText.append( "\t\t" ).append( " and the configuration updated immediately when it is modified." ).append( "\n" ); - commentText.append( "\t\t" ).append( "" ).append( "\n" ); - commentText.append( "\t\t" ).append( "NOTICE: This file is encoded as UTF-8. Do not save or edit this file with an editor that does not" ).append( "\n" ); - commentText.append( "\t\t" ).append( " support UTF-8 encoding." ).append( "\n" ); - commentText.append( "\t\t" ).append( "" ).append( "\n" ); - commentText.append( "\t\t" ).append( "If unable to edit using the application ConfigurationEditor web UI, the following options are available." ).append( "\n" ); - commentText.append( "\t\t" ).append( " or 1. Edit this file directly by hand." ).append( "\n" ); - commentText.append( "\t\t" ).append( " or 2. Remove restrictions of the configuration by setting the property 'configIsEditable' to 'true' in this file. This will " ) - .append( "\n" ); - commentText.append( "\t\t" ).append( " allow access to the ConfigurationEditor web UI without having to authenticate to an LDAP server first." ).append( "\n" ); - commentText.append( "\t\t" ).append( " or 3. Remove restrictions of the configuration by using the the command line utility. " ).append( "\n" ); - commentText.append( "\t\t" ).append( "" ).append( "\n" ); - return commentText.toString(); - } - - - private void profilizeNonProfiledSettings() - throws PwmUnrecoverableException - { - for ( final PwmSetting setting : PwmSetting.values() ) + @Override + public void accept( final XmlDocument xmlDocument ) { - if ( setting.getCategory().hasProfiles() ) + final StoredConfigurationFactory.XmlInputDocumentReader reader = new StoredConfigurationFactory.XmlInputDocumentReader( xmlDocument ); + for ( final PwmSetting setting : PwmSetting.values() ) { - - final XmlElement settingElement = storedConfiguration.getXmlHelper().xpathForSetting( setting, null ); - if ( settingElement != null ) + if ( setting.getCategory().hasProfiles() ) { - settingElement.detach(); - - final PwmSetting profileSetting = setting.getCategory().getProfileSetting(); - final List profileStringDefinitions = new ArrayList<>(); + reader.xpathForSetting( setting, null ).ifPresent( existingSettingElement -> { - final StringArrayValue profileDefinitions = ( StringArrayValue ) storedConfiguration.readSetting( profileSetting ); - if ( profileDefinitions != null ) + final List profileStringDefinitions = new ArrayList<>(); { - if ( profileDefinitions.toNativeObject() != null ) + final List configuredProfiles = reader.profilesForSetting( setting ); + if ( !JavaHelper.isEmpty( configuredProfiles ) ) { - profileStringDefinitions.addAll( profileDefinitions.toNativeObject() ); + profileStringDefinitions.addAll( configuredProfiles ); } } - } - if ( profileStringDefinitions.isEmpty() ) - { - profileStringDefinitions.add( NEW_PROFILE_NAME ); - } - - final UserIdentity userIdentity = settingElement.getAttributeValue( StoredConfigurationImpl.XML_ATTRIBUTE_MODIFY_USER ) != null - ? UserIdentity.fromDelimitedKey( settingElement.getAttributeValue( StoredConfigurationImpl.XML_ATTRIBUTE_MODIFY_USER ) ) - : null; + if ( profileStringDefinitions.isEmpty() ) + { + profileStringDefinitions.add( PwmConstants.PROFILE_ID_DEFAULT ); + } - for ( final String destProfile : profileStringDefinitions ) - { - LOGGER.info( () -> "moving setting " + setting.getKey() + " without profile attribute to profile \"" + destProfile + "\"." ); + for ( final String destProfile : profileStringDefinitions ) { - storedConfiguration.writeSetting( profileSetting, new StringArrayValue( profileStringDefinitions ), userIdentity ); + LOGGER.info( () -> "moving setting " + setting.getKey() + " without profile attribute to profile \"" + destProfile + "\"." ); + { + //existingSettingElement.detach(); + final XmlElement newSettingElement = existingSettingElement.copy(); + newSettingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_PROFILE, destProfile ); + + final XmlElement settingsElement = reader.xpathForSettings(); + settingsElement.addContent( newSettingElement ); + } } - } + } ); } } } } - private void migrateDeprecatedProperties( - ) - throws PwmUnrecoverableException + private static class MigrateDeprecatedProperties implements PwmExceptionLoggingConsumer { + @Override + public void accept( final XmlDocument xmlDocument ) throws PwmUnrecoverableException { - final String xpathString = "//property[@key=\"" + ConfigurationProperty.LDAP_TEMPLATE.getKey() + "\"]"; - final List propertyElement = document.evaluateXpathToElements( xpathString ); - if ( propertyElement != null && !propertyElement.isEmpty() ) { - final String value = propertyElement.get( 0 ).getText(); - storedConfiguration.writeSetting( PwmSetting.TEMPLATE_LDAP, new StringValue( value ), null ); - propertyElement.get( 0 ).detach(); + final String xpathString = "//property[@key=\"" + ConfigurationProperty.LDAP_TEMPLATE.getKey() + "\"]"; + final List propertyElement = xmlDocument.evaluateXpathToElements( xpathString ); + if ( propertyElement != null && !propertyElement.isEmpty() ) + { + final String value = propertyElement.get( 0 ).getText(); + propertyElement.get( 0 ).detach(); + attachStringSettingElement( xmlDocument, PwmSetting.TEMPLATE_LDAP, value ); + + } } - } - { - final String xpathString = "//property[@key=\"" + ConfigurationProperty.NOTES.getKey() + "\"]"; - final List propertyElement = document.evaluateXpathToElements( xpathString ); - if ( propertyElement != null && !propertyElement.isEmpty() ) { - final String value = propertyElement.get( 0 ).getText(); - storedConfiguration.writeSetting( PwmSetting.NOTES, new StringValue( value ), null ); - propertyElement.get( 0 ).detach(); + final String xpathString = "//property[@key=\"" + ConfigurationProperty.NOTES.getKey() + "\"]"; + final List propertyElement = xmlDocument.evaluateXpathToElements( xpathString ); + if ( propertyElement != null && !propertyElement.isEmpty() ) + { + final String value = propertyElement.get( 0 ).getText(); + propertyElement.get( 0 ).detach(); + attachStringSettingElement( xmlDocument, PwmSetting.NOTES, value ); + } } } + + private static void attachStringSettingElement( + final XmlDocument xmlDocument, + final PwmSetting pwmSetting, + final String stringValue + ) + throws PwmUnrecoverableException + { + final StoredConfigurationFactory.XmlInputDocumentReader inputDocumentReader = new StoredConfigurationFactory.XmlInputDocumentReader( xmlDocument ); + + final PwmSecurityKey pwmSecurityKey = inputDocumentReader.getKey(); + + final XmlElement settingElement = StoredConfigurationFactory.XmlOutputHandler.makeSettingXmlElement( + null, + pwmSetting, + null, + new StringValue( stringValue ), + XmlOutputProcessData.builder().storedValueEncoderMode( StoredValueEncoder.Mode.PLAIN ).pwmSecurityKey( pwmSecurityKey ).build() ); + final Optional settingsElement = xmlDocument.getRootElement().getChild( StoredConfigXmlConstants.XML_ELEMENT_SETTING ); + settingsElement.ifPresent( xmlElement -> xmlElement.addContent( settingElement ) ); + } } - private void updateProperitiesWithoutType() + private static class UpdatePropertiesWithoutType implements PwmExceptionLoggingConsumer { - final String xpathString = "//properties[not(@type)]"; - final List propertiesElements = document.evaluateXpathToElements( xpathString ); - for ( final XmlElement propertiesElement : propertiesElements ) + @Override + public void accept( final XmlDocument xmlDocument ) { - propertiesElement.setAttribute( StoredConfigurationImpl.XML_ATTRIBUTE_TYPE, StoredConfigurationImpl.XML_ATTRIBUTE_VALUE_CONFIG ); + final String xpathString = "//properties[not(@type)]"; + final List propertiesElements = xmlDocument.evaluateXpathToElements( xpathString ); + for ( final XmlElement propertiesElement : propertiesElements ) + { + propertiesElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE, StoredConfigXmlConstants.XML_ATTRIBUTE_VALUE_CONFIG ); + } } } - private void stripOrphanedProfileSettings() + private static class AppPropertyOverrideMigration implements PwmExceptionLoggingConsumer { - for ( final PwmSetting setting : PwmSetting.values() ) + @Override + public void accept( final XmlDocument xmlDocument ) throws PwmUnrecoverableException { - if ( setting.getCategory().hasProfiles() ) + final StoredConfigurationFactory.XmlInputDocumentReader documentReader = new StoredConfigurationFactory.XmlInputDocumentReader( xmlDocument ); + final List appPropertiesElements = documentReader.xpathForAppProperties(); + for ( final XmlElement element : appPropertiesElements ) { - final List validProfiles = storedConfiguration.profilesForSetting( setting ); - final String xpathString = "//setting[@key=\"" + setting.getKey() + "\"]"; - final List settingElements = document.evaluateXpathToElements( xpathString ); - for ( final XmlElement settingElement : settingElements ) + final List properties = element.getChildren(); + for ( final XmlElement property : properties ) { - final String profileID = settingElement.getAttributeValue( StoredConfigurationImpl.XML_ATTRIBUTE_PROFILE ); - if ( profileID != null ) + final String key = property.getAttributeValue( "key" ); + final String value = property.getText(); + if ( key != null && !key.isEmpty() && value != null && !value.isEmpty() ) { - if ( !validProfiles.contains( profileID ) ) + LOGGER.info( () -> "migrating app-property config element '" + key + "' to setting " + PwmSetting.APP_PROPERTY_OVERRIDES.getKey() ); + final String newValue = key + "=" + value; + + final List existingValues = new ArrayList<>(); { - LOGGER.info( () -> "removing setting " + setting.getKey() + " with profile \"" + profileID + "\", profile is not a valid profile" ); - settingElement.detach(); + final Optional valueAndMetaTuple = documentReader.readSetting( PwmSetting.APP_PROPERTY_OVERRIDES, null ); + valueAndMetaTuple.ifPresent( ( t ) -> existingValues.addAll( ( List ) t.getValue().toNativeObject() ) ); } + existingValues.add( newValue ); + rewriteAppPropertySettingElement( xmlDocument, existingValues ); } } + element.detach(); + } + } + + private static void rewriteAppPropertySettingElement( final XmlDocument xmlDocument, final List newValues ) + throws PwmUnrecoverableException + { + final StoredConfigurationFactory.XmlInputDocumentReader inputDocumentReader = new StoredConfigurationFactory.XmlInputDocumentReader( xmlDocument ); + + { + final Optional existingAppPropertySetting = inputDocumentReader.xpathForSetting( PwmSetting.APP_PROPERTY_OVERRIDES, null ); + existingAppPropertySetting.ifPresent( XmlElement::detach ); } + + final PwmSecurityKey pwmSecurityKey = inputDocumentReader.getKey(); + + final XmlElement settingElement = StoredConfigurationFactory.XmlOutputHandler.makeSettingXmlElement( + null, + PwmSetting.APP_PROPERTY_OVERRIDES, + null, + new StringArrayValue( newValues ), + XmlOutputProcessData.builder().storedValueEncoderMode( StoredValueEncoder.Mode.PLAIN ).pwmSecurityKey( pwmSecurityKey ).build() ); + final Optional settingsElement = xmlDocument.getRootElement().getChild( StoredConfigXmlConstants.XML_ELEMENT_SETTING ); + settingsElement.ifPresent( ( s ) -> s.addContent( settingElement ) ); } } - private void migrateAppProperties( - ) - throws PwmUnrecoverableException + private static class UpdateDeprecatedAdComplexitySettings implements PwmExceptionLoggingConsumer { - final List appPropertiesElements = storedConfiguration.getXmlHelper().xpathForAppProperties(); - for ( final XmlElement element : appPropertiesElements ) + @Override + public void accept( final StoredConfigurationModifier modifier ) + throws PwmUnrecoverableException { - final List properties = element.getChildren(); - for ( final XmlElement property : properties ) + final StoredConfiguration oldConfig = modifier.newStoredConfiguration(); + final Configuration configuration = new Configuration( oldConfig ); + for ( final String profileID : configuration.getPasswordProfileIDs() ) { - final String key = property.getAttributeValue( "key" ); - final String value = property.getText(); - if ( key != null && !key.isEmpty() && value != null && !value.isEmpty() ) + if ( !oldConfig.isDefaultValue( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) ) { - LOGGER.info( () -> "migrating app-property config element '" + key + "' to setting " + PwmSetting.APP_PROPERTY_OVERRIDES.getKey() ); - final String newValue = key + "=" + value; - List existingValues = ( List ) storedConfiguration.readSetting( PwmSetting.APP_PROPERTY_OVERRIDES ).toNativeObject(); - if ( existingValues == null ) + final boolean ad2003Enabled = ( boolean ) oldConfig.readSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ).toNativeObject(); + final StoredValue value; + if ( ad2003Enabled ) + { + value = new StringValue( ADPolicyComplexity.AD2003.toString() ); + } + else { - existingValues = new ArrayList<>(); + value = new StringValue( ADPolicyComplexity.NONE.toString() ); } - existingValues = new ArrayList<>( existingValues ); - existingValues.add( newValue ); - storedConfiguration.writeSetting( PwmSetting.APP_PROPERTY_OVERRIDES, new StringArrayValue( existingValues ), null ); + LOGGER.info( () -> "converting deprecated non-default setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY.getKey() + "/" + profileID + + " to replacement setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL + ", value=" + value.toNativeObject().toString() ); + final Optional valueMetaData = oldConfig.readMetaData( + StoredConfigItemKey.fromSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) ); + final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null ); + modifier.writeSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, value, userIdentity ); } } - element.detach(); } } - private void updateDeprecatedSettings( ) throws PwmUnrecoverableException + private static class UpdateDeprecatedMinPwdLifetimeSetting implements PwmExceptionLoggingConsumer { - final UserIdentity actor = new UserIdentity( "UpgradeProcessor", null ); - for ( final String profileID : storedConfiguration.profilesForSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY ) ) + @Override + public void accept( final StoredConfigurationModifier modifier ) + throws PwmUnrecoverableException { - if ( !storedConfiguration.isDefaultValue( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) ) + final StoredConfiguration oldConfig = modifier.newStoredConfiguration(); + for ( final String profileID : oldConfig.profilesForSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME ) ) { - final boolean ad2003Enabled = ( boolean ) storedConfiguration.readSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ).toNativeObject(); - final StoredValue value; - if ( ad2003Enabled ) + if ( !oldConfig.isDefaultValue( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ) ) { - value = new StringValue( ADPolicyComplexity.AD2003.toString() ); - } - else - { - value = new StringValue( ADPolicyComplexity.NONE.toString() ); + final boolean enforceEnabled = ( boolean ) oldConfig.readSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ).toNativeObject(); + final StoredValue value = enforceEnabled + ? new StringValue( RecoveryMinLifetimeOption.NONE.name() ) + : new StringValue( RecoveryMinLifetimeOption.ALLOW.name() ); + final ValueMetaData existingData = oldConfig.readSettingMetadata( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ); + final UserIdentity newActor = existingData != null && existingData.getUserIdentity() != null + ? existingData.getUserIdentity() + : null; + LOGGER.info( () -> "converting deprecated non-default setting " + + PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + "/" + profileID + + " to replacement setting " + PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + + ", value=" + value.toNativeObject().toString() ); + modifier.writeSetting( PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS, profileID, value, newActor ); } - LOGGER.warn( "converting deprecated non-default setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY.getKey() + "/" + profileID - + " to replacement setting " + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL + ", value=" + value.toNativeObject().toString() ); - storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL, profileID, value, actor ); - storedConfiguration.resetSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID, actor ); } } + } - for ( final String profileID : storedConfiguration.profilesForSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME ) ) + private static class UpdateDeprecatedPublicHealthSetting implements PwmExceptionLoggingConsumer + { + @Override + public void accept( final StoredConfigurationModifier modifier ) + throws PwmUnrecoverableException { - if ( !storedConfiguration.isDefaultValue( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ) ) + final StoredConfiguration oldConfig = modifier.newStoredConfiguration(); + if ( !oldConfig.isDefaultValue( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES, null ) ) { - final boolean enforceEnabled = ( boolean ) storedConfiguration.readSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ).toNativeObject(); - final StoredValue value = enforceEnabled - ? new StringValue( "NONE" ) - : new StringValue( "ALLOW" ); - final ValueMetaData existingData = storedConfiguration.readSettingMetadata( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID ); - LOGGER.warn( "converting deprecated non-default setting " - + PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + "/" + profileID - + " to replacement setting " + PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) - + ", value=" + value.toNativeObject().toString() ); - final UserIdentity newActor = existingData != null && existingData.getUserIdentity() != null - ? existingData.getUserIdentity() - : actor; - storedConfiguration.writeSetting( PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS, profileID, value, newActor ); - storedConfiguration.resetSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID, actor ); + LOGGER.info( () -> "converting deprecated non-default setting " + + PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) + + " to replacement setting " + PwmSetting.WEBSERVICES_PUBLIC_ENABLE.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) ); + final Set existingValues = ( Set ) oldConfig.readSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, null ).toNativeObject(); + final Set newValues = new LinkedHashSet<>( existingValues ); + newValues.add( WebServiceUsage.Health.name() ); + newValues.add( WebServiceUsage.Statistics.name() ); + + final Optional valueMetaData = oldConfig.readMetaData( + StoredConfigItemKey.fromSetting( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES, null ) ); + final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null ); + + modifier.writeSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, null, new OptionListValue( newValues ), userIdentity ); } } + } - if ( !storedConfiguration.isDefaultValue( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES ) ) - { - LOGGER.warn( "converting deprecated non-default setting " - + PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) - + " to replacement setting " + PwmSetting.WEBSERVICES_PUBLIC_ENABLE.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) ); - final Set existingValues = (Set) storedConfiguration.readSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE ).toNativeObject(); - final Set newValues = new LinkedHashSet<>( existingValues ); - newValues.add( WebServiceUsage.Health.name() ); - newValues.add( WebServiceUsage.Statistics.name() ); - storedConfiguration.writeSetting( PwmSetting.WEBSERVICES_PUBLIC_ENABLE, null, new OptionListValue( newValues ), actor ); - storedConfiguration.resetSetting( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES, null, actor ); - } + private static int readDocVersion( final XmlDocument xmlDocument ) + { + final String xmlVersionStr = xmlDocument.getRootElement().getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_XML_VERSION ); + return JavaHelper.silentParseInt( xmlVersionStr, 0 ); } } diff --git a/server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java b/server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java index 835a9f771..1c606de25 100644 --- a/server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java +++ b/server/src/main/java/password/pwm/config/stored/ConfigurationProperty.java @@ -27,8 +27,8 @@ public enum ConfigurationProperty LDAP_TEMPLATE( "configTemplate" ), NOTES( "notes" ), PASSWORD_HASH( "configPasswordHash" ), - CONFIG_ON_START( "saveConfigOnStart" ), - MODIFIFICATION_TIMESTAMP( "modificationTimestamp" ), + STORE_PLAINTEXT_VALUES( "storePlaintextValues" ), + MODIFICATION_TIMESTAMP( "modificationTimestamp" ), IMPORT_LDAP_CERTIFICATES( "importLdapCertificates" ),; private final String key; diff --git a/server/src/main/java/password/pwm/config/stored/ConfigurationReader.java b/server/src/main/java/password/pwm/config/stored/ConfigurationReader.java index 53ea43f78..fcea22432 100644 --- a/server/src/main/java/password/pwm/config/stored/ConfigurationReader.java +++ b/server/src/main/java/password/pwm/config/stored/ConfigurationReader.java @@ -25,13 +25,17 @@ import password.pwm.PwmApplicationMode; import password.pwm.PwmConstants; import password.pwm.bean.SessionLabel; +import password.pwm.bean.UserIdentity; import password.pwm.config.Configuration; +import password.pwm.config.StoredValue; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; +import password.pwm.svc.event.AuditEvent; +import password.pwm.svc.event.AuditRecordFactory; import password.pwm.util.java.FileSystemUtility; -import password.pwm.util.java.JsonUtil; +import password.pwm.util.java.JavaHelper; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; @@ -45,6 +49,8 @@ import java.nio.file.StandardCopyOption; import java.time.Instant; import java.util.List; +import java.util.Optional; +import java.util.Set; /** * Read the PWM configuration. @@ -58,9 +64,10 @@ public class ConfigurationReader private final File configFile; private final String configFileChecksum; private Configuration configuration; - private StoredConfigurationImpl storedConfiguration; + private StoredConfiguration storedConfiguration; private ErrorInformation configFileError; + private PwmApplicationMode configMode = PwmApplicationMode.NEW; private volatile boolean saveInProgress; @@ -75,7 +82,7 @@ public ConfigurationReader( final File configFile ) throws PwmUnrecoverableExcep this.storedConfiguration = readStoredConfig(); this.configFileError = null; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { this.configFileError = e.getErrorInformation(); LOGGER.warn( "error reading configuration file: " + e.getMessage() ); @@ -83,7 +90,7 @@ public ConfigurationReader( final File configFile ) throws PwmUnrecoverableExcep if ( storedConfiguration == null ) { - this.storedConfiguration = StoredConfigurationImpl.newStoredConfiguration(); + this.storedConfiguration = StoredConfigurationFactory.newConfig(); } LOGGER.debug( () -> "configuration mode: " + configMode ); @@ -94,7 +101,7 @@ public PwmApplicationMode getConfigMode( ) return configMode; } - public StoredConfigurationImpl getStoredConfiguration( ) + public StoredConfiguration getStoredConfiguration( ) { return storedConfiguration; } @@ -103,19 +110,15 @@ public Configuration getConfiguration( ) throws PwmUnrecoverableException { if ( configuration == null ) { - final StoredConfigurationImpl newStoredConfig = this.storedConfiguration == null - ? StoredConfigurationImpl.newStoredConfiguration() + final StoredConfiguration newStoredConfig = this.storedConfiguration == null + ? StoredConfigurationFactory.newConfig() : this.storedConfiguration; configuration = new Configuration( newStoredConfig ); - if ( storedConfiguration != null ) - { - storedConfiguration.lock(); - } } return configuration; } - private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableException + private StoredConfiguration readStoredConfig( ) throws PwmUnrecoverableException { LOGGER.debug( () -> "loading configuration file: " + configFile ); @@ -126,12 +129,34 @@ private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableExcep } final Instant startTime = Instant.now(); + + /* + try + { + final InputStream theFileData = Files.newInputStream( configFile.toPath() ); + final StoredConfiguration storedConfiguration = StoredConfigurationFactory.fromXml( theFileData ); + + System.out.println( TimeDuration.compactFromCurrent( startTime ) ); + + + //final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final FileOutputStream fos = new FileOutputStream( new File( "/tmp/NEWCFG" ) ); + StoredConfigurationFactory.toXml( storedConfiguration, fos ); + + //System.out.println( new String( baos.toByteArray(), "UTF-8" ) ); + } + catch ( final Exception e ) + { + e.printStackTrace( ); + } + */ + final InputStream theFileData; try { theFileData = Files.newInputStream( configFile.toPath() ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unable to read configuration file: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] @@ -143,13 +168,12 @@ private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableExcep throw new PwmUnrecoverableException( errorInformation ); } - final StoredConfigurationImpl storedConfiguration; + final StoredConfiguration storedConfiguration; try { - storedConfiguration = StoredConfigurationImpl.fromXml( theFileData ); - //restoredConfiguration = (new NGStoredConfigurationFactory()).fromXml(theFileData); + storedConfiguration = StoredConfigurationFactory.fromXml( theFileData ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "unable to parse configuration file: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] @@ -162,8 +186,8 @@ private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableExcep throw new PwmUnrecoverableException( errorInformation ); } - final List validationErrorMsgs = storedConfiguration.validateValues(); - if ( validationErrorMsgs != null && !validationErrorMsgs.isEmpty() ) + final List validationErrorMsgs = StoredConfigurationUtil.validateValues( storedConfiguration ); + if ( !JavaHelper.isEmpty( validationErrorMsgs ) ) { final String errorMsg = "value error in config file, please investigate: " + validationErrorMsgs.get( 0 ); final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] @@ -175,8 +199,8 @@ private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableExcep throw new PwmUnrecoverableException( errorInformation ); } - final String configIsEditable = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE ); - if ( PwmConstants.TRIAL_MODE || ( configIsEditable != null && "true".equalsIgnoreCase( configIsEditable ) ) ) + final Optional configIsEditable = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE ); + if ( PwmConstants.TRIAL_MODE || ( configIsEditable.isPresent() && "true".equalsIgnoreCase( configIsEditable.get() ) ) ) { this.configMode = PwmApplicationMode.CONFIGURATION; } @@ -189,11 +213,13 @@ private StoredConfigurationImpl readStoredConfig( ) throws PwmUnrecoverableExcep final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime ); LOGGER.debug( () -> "configuration reading/parsing of " + fileSize + " complete in " + timeDuration.asLongString() ); + + return storedConfiguration; } public void saveConfiguration( - final StoredConfigurationImpl storedConfiguration, + final StoredConfiguration storedConfiguration, final PwmApplication pwmApplication, final SessionLabel sessionLabel ) @@ -216,18 +242,21 @@ public void saveConfiguration( { // increment the config epoch - String epochStrValue = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_EPOCH ); + String newEpochStrValue = "0"; try { - final BigInteger epochValue = epochStrValue == null || epochStrValue.length() < 0 ? BigInteger.ZERO : new BigInteger( epochStrValue ); - epochStrValue = epochValue.add( BigInteger.ONE ).toString(); + final Optional storedEpochStrValue = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_EPOCH ); + final BigInteger epochValue = storedEpochStrValue.map( BigInteger::new ).orElse( BigInteger.ZERO ); + newEpochStrValue = epochValue.add( BigInteger.ONE ).toString(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( sessionLabel, "error trying to parse previous config epoch property: " + e.getMessage() ); - epochStrValue = "0"; } - storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_EPOCH, epochStrValue ); + + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); + modifier.writeConfigProperty( ConfigurationProperty.CONFIG_EPOCH, newEpochStrValue ); + this.storedConfiguration = modifier.newStoredConfiguration(); } if ( backupDirectory != null && !backupDirectory.exists() ) @@ -239,51 +268,97 @@ public void saveConfiguration( } } - try + if ( pwmApplication != null && pwmApplication.getAuditManager() != null ) { - final File tempWriteFile = new File( configFile.getAbsoluteFile() + ".new" ); - LOGGER.info( sessionLabel, () -> "beginning write to configuration file " + tempWriteFile ); - saveInProgress = true; - - try ( FileOutputStream fileOutputStream = new FileOutputStream( tempWriteFile, false ) ) - { - storedConfiguration.toXml( fileOutputStream ); - } + auditModifiedSettings( pwmApplication, storedConfiguration, sessionLabel ); + } - LOGGER.info( () -> "saved configuration " + JsonUtil.serialize( storedConfiguration.toJsonDebugObject() ) ); - if ( pwmApplication != null ) - { - final String actualChecksum = storedConfiguration.settingChecksum(); - pwmApplication.writeAppAttribute( PwmApplication.AppAttribute.CONFIG_HASH, actualChecksum ); - } + try + { + outputConfigurationFile( storedConfiguration, pwmApplication, sessionLabel, backupRotations, backupDirectory ); + } + finally + { + saveInProgress = false; + } + } - LOGGER.trace( () -> "renaming file " + tempWriteFile.getAbsolutePath() + " to " + configFile.getAbsolutePath() ); - try - { - Files.move( tempWriteFile.toPath(), configFile.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE ); - } - catch ( Exception e ) - { - final String errorMsg = "unable to rename temporary save file from " + tempWriteFile.getAbsolutePath() - + " to " + configFile.getAbsolutePath() + "; error: " + e.getMessage(); - throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) ); - } + private static void auditModifiedSettings( final PwmApplication pwmApplication, final StoredConfiguration newConfig, final SessionLabel sessionLabel ) + throws PwmUnrecoverableException + { + final Set changedKeys = StoredConfigurationUtil.changedValues( newConfig, pwmApplication.getConfig().getStoredConfiguration() ); - if ( backupDirectory != null ) + for ( final StoredConfigItemKey key : changedKeys ) + { + if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING + || key.getRecordType() == StoredConfigItemKey.RecordType.LOCALE_BUNDLE ) { - final String configFileName = configFile.getName(); - final String backupFilePath = backupDirectory.getAbsolutePath() + File.separatorChar + configFileName + "-backup"; - final File backupFile = new File( backupFilePath ); - FileSystemUtility.rotateBackups( backupFile, backupRotations ); - try ( FileOutputStream fileOutputStream = new FileOutputStream( backupFile, false ) ) + final Optional storedValue = newConfig.readStoredValue( key ); + if ( storedValue.isPresent() ) { - storedConfiguration.toXml( fileOutputStream ); + final Optional valueMetaData = newConfig.readMetaData( key ); + final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null ); + final String modifyMessage = "configuration record '" + key.getLabel( PwmConstants.DEFAULT_LOCALE ) + + "' has been modified, new value: " + storedValue.get().toDebugString( PwmConstants.DEFAULT_LOCALE ); + pwmApplication.getAuditManager().submit( new AuditRecordFactory( pwmApplication ).createUserAuditRecord( + AuditEvent.MODIFY_CONFIGURATION, + userIdentity, + sessionLabel, + modifyMessage + ) ); } } } - finally + } + + private void outputConfigurationFile( + final StoredConfiguration storedConfiguration, + final PwmApplication pwmApplication, + final SessionLabel sessionLabel, + final int backupRotations, + final File backupDirectory + ) + throws IOException, PwmUnrecoverableException + { + final Instant saveFileStartTime = Instant.now(); + final File tempWriteFile = new File( configFile.getAbsoluteFile() + ".new" ); + LOGGER.info( sessionLabel, () -> "beginning write to configuration file " + tempWriteFile ); + saveInProgress = true; + + try ( FileOutputStream fileOutputStream = new FileOutputStream( tempWriteFile, false ) ) { - saveInProgress = false; + StoredConfigurationFactory.toXml( storedConfiguration, fileOutputStream ); + } + + LOGGER.info( () -> "saved configuration in " + TimeDuration.compactFromCurrent( saveFileStartTime ) ); + if ( pwmApplication != null ) + { + final String actualChecksum = storedConfiguration.valueHash(); + pwmApplication.writeAppAttribute( PwmApplication.AppAttribute.CONFIG_HASH, actualChecksum ); + } + + LOGGER.trace( () -> "renaming file " + tempWriteFile.getAbsolutePath() + " to " + configFile.getAbsolutePath() ); + try + { + Files.move( tempWriteFile.toPath(), configFile.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE ); + } + catch ( final Exception e ) + { + final String errorMsg = "unable to rename temporary save file from " + tempWriteFile.getAbsolutePath() + + " to " + configFile.getAbsolutePath() + "; error: " + e.getMessage(); + throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) ); + } + + if ( backupDirectory != null ) + { + final String configFileName = configFile.getName(); + final String backupFilePath = backupDirectory.getAbsolutePath() + File.separatorChar + configFileName + "-backup"; + final File backupFile = new File( backupFilePath ); + FileSystemUtility.rotateBackups( backupFile, backupRotations ); + try ( FileOutputStream fileOutputStream = new FileOutputStream( backupFile, false ) ) + { + StoredConfigurationFactory.toXml( storedConfiguration, fileOutputStream ); + } } } diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigData.java b/server/src/main/java/password/pwm/config/stored/StoredConfigData.java new file mode 100644 index 000000000..e75ac2b48 --- /dev/null +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigData.java @@ -0,0 +1,70 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2019 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.config.stored; + +import lombok.Builder; +import lombok.Singular; +import lombok.Value; +import password.pwm.config.StoredValue; + +import java.time.Instant; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +@Value +@Builder( toBuilder = true ) +class StoredConfigData +{ + @Builder.Default + private String createTime = ""; + + @Builder.Default + private Instant modifyTime = Instant.now(); + + @Singular + private Map storedValues; + + @Singular + private Map metaDatas; + + @Value + static class ValueAndMetaCarrier + { + private final StoredConfigItemKey key; + private final StoredValue value; + private final ValueMetaData metaData; + } + + static Map carrierAsMetaDataMap( final Collection input ) + { + return input.stream() + .filter( ( t ) -> t.getKey() != null && t.getMetaData() != null ) + .collect( Collectors.toMap( StoredConfigData.ValueAndMetaCarrier::getKey, StoredConfigData.ValueAndMetaCarrier::getMetaData ) ); + } + + static Map carrierAsStoredValueMap( final Collection input ) + { + return input.stream() + .filter( ( t ) -> t.getKey() != null && t.getValue() != null ) + .collect( Collectors.toMap( StoredConfigData.ValueAndMetaCarrier::getKey, StoredConfigData.ValueAndMetaCarrier::getValue ) ); + } +} diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java b/server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java new file mode 100644 index 000000000..7aa379ad1 --- /dev/null +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java @@ -0,0 +1,235 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2019 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.config.stored; + +import password.pwm.PwmConstants; +import password.pwm.config.PwmSetting; +import password.pwm.i18n.Config; +import password.pwm.i18n.PwmLocaleBundle; +import password.pwm.util.i18n.LocaleHelper; +import password.pwm.util.java.JavaHelper; +import password.pwm.util.java.StringUtil; + +import java.io.Serializable; +import java.util.Locale; +import java.util.Objects; + +public class StoredConfigItemKey implements Serializable, Comparable +{ + public enum RecordType + { + SETTING( "Setting" ), + LOCALE_BUNDLE ( "Localization" ), + PROPERTY ( "Property" ),; + + private final String label; + + RecordType( final String label ) + { + this.label = label; + } + + public String getLabel() + { + return label; + } + } + + private final RecordType recordType; + private final String recordID; + private final String profileID; + + private StoredConfigItemKey( final RecordType recordType, final String recordID, final String profileID ) + { + Objects.requireNonNull( recordType, "recordType can not be null" ); + Objects.requireNonNull( recordID, "recordID can not be null" ); + + this.recordType = recordType; + this.recordID = recordID; + this.profileID = profileID; + } + + public RecordType getRecordType() + { + return recordType; + } + + public String getRecordID() + { + return recordID; + } + + public String getProfileID() + { + return profileID; + } + + static StoredConfigItemKey fromSetting( final PwmSetting pwmSetting, final String profileID ) + { + return new StoredConfigItemKey( RecordType.SETTING, pwmSetting.getKey(), profileID ); + } + + static StoredConfigItemKey fromLocaleBundle( final PwmLocaleBundle localeBundle, final String key ) + { + return new StoredConfigItemKey( RecordType.LOCALE_BUNDLE, localeBundle.getKey(), key ); + } + + static StoredConfigItemKey fromConfigurationProperty( final ConfigurationProperty configurationProperty ) + { + return new StoredConfigItemKey( RecordType.PROPERTY, configurationProperty.getKey(), null ); + } + + public boolean isValid() + { + try + { + validate(); + return true; + } + catch ( final IllegalStateException e ) + { + /* ignore */ + } + return false; + } + + public void validate() + { + switch ( recordType ) + { + case SETTING: + { + final PwmSetting pwmSetting = this.toPwmSetting(); + final boolean hasProfileID = !StringUtil.isEmpty( profileID ); + if ( pwmSetting.getCategory().hasProfiles() && !hasProfileID ) + { + throw new IllegalStateException( "profileID is required for setting " + pwmSetting.getKey() ); + } + else if ( !pwmSetting.getCategory().hasProfiles() && hasProfileID ) + { + throw new IllegalStateException( "profileID is not required for setting " + pwmSetting.getKey() ); + } + } + break; + + case LOCALE_BUNDLE: + { + Objects.requireNonNull( profileID, "profileID is required when recordType is LOCALE_BUNDLE" ); + final PwmLocaleBundle pwmLocaleBundle = toLocaleBundle(); + if ( !pwmLocaleBundle.getDisplayKeys().contains( profileID ) ) + { + throw new IllegalStateException( "key '" + profileID + "' is unrecognized for locale bundle " + pwmLocaleBundle.name() ); + } + } + break; + + case PROPERTY: + break; + + default: + JavaHelper.unhandledSwitchStatement( recordType ); + } + } + + public String getLabel( final Locale locale ) + { + final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null ); + + switch ( recordType ) + { + case SETTING: + return recordType.getLabel() + separator + toPwmSetting().toMenuLocationDebug( profileID, locale ); + + case PROPERTY: + return recordType.getLabel() + separator + this.getRecordID(); + + case LOCALE_BUNDLE: + return recordType.getLabel() + + separator + + this.getRecordID() + + separator + + this.getProfileID(); + + default: + JavaHelper.unhandledSwitchStatement( recordType ); + } + + throw new IllegalStateException( ); + } + + public PwmSetting toPwmSetting() + { + if ( getRecordType() != RecordType.SETTING ) + { + throw new IllegalStateException( "attempt to read pwmSetting key for non-setting ConfigItemKey" ); + } + + return PwmSetting.forKey( this.recordID ); + } + + public PwmLocaleBundle toLocaleBundle() + { + if ( getRecordType() != RecordType.LOCALE_BUNDLE ) + { + throw new IllegalStateException( "attempt to read PwmLocaleBundle key for non-locale ConfigItemKey" ); + } + + return PwmLocaleBundle.forKey( this.recordID ) + .orElseThrow( () -> new IllegalStateException( "unexpected key value for locale bundle: " + this.recordID ) ); + } + + public ConfigurationProperty toConfigurationProperty() + { + if ( getRecordType() != RecordType.PROPERTY ) + { + throw new IllegalStateException( "attempt to read ConfigurationProperty key for non-config property ConfigItemKey" ); + } + + return ConfigurationProperty.valueOf( recordID ); + } + + @Override + public int hashCode() + { + return toString().hashCode(); + } + + @Override + public boolean equals( final Object anotherObject ) + { + return anotherObject != null + && anotherObject instanceof StoredConfigItemKey + && toString().equals( anotherObject.toString() ); + } + + @Override + public String toString() + { + return getLabel( PwmConstants.DEFAULT_LOCALE ); + // return getRecordType().name() + "-" + this.getRecordID() + "-" + this.getProfileID(); + } + + @Override + public int compareTo( final Object o ) + { + return getLabel( PwmConstants.DEFAULT_LOCALE ).compareTo( o.toString() ); + } +} diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigReferenceBean.java b/server/src/main/java/password/pwm/config/stored/StoredConfigReferenceBean.java deleted file mode 100644 index 721f9b364..000000000 --- a/server/src/main/java/password/pwm/config/stored/StoredConfigReferenceBean.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2019 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.config.stored; - -import lombok.Value; - -import java.io.Serializable; - -@Value -public class StoredConfigReferenceBean implements StoredConfigReference, Serializable, Comparable -{ - private RecordType recordType; - private String recordID; - private String profileID; - - public StoredConfigReferenceBean( final RecordType type, final String recordID, final String profileID ) - { - if ( type == null ) - { - throw new NullPointerException( "recordType can not be null" ); - } - - if ( recordID == null ) - { - throw new NullPointerException( "recordID can not be null" ); - } - - this.recordType = type; - this.recordID = recordID; - this.profileID = profileID; - } - - - @Override - public String toString( ) - { - return this.getRecordType().toString() - + "-" - + ( this.getProfileID() == null ? "" : this.getProfileID() ) - + "-" - + this.getRecordID(); - } - - @Override - public int compareTo( final Object o ) - { - return toString().compareTo( o.toString() ); - } -} diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigXmlConstants.java b/server/src/main/java/password/pwm/config/stored/StoredConfigXmlConstants.java new file mode 100644 index 000000000..20550b641 --- /dev/null +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigXmlConstants.java @@ -0,0 +1,50 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2019 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.config.stored; + +public class StoredConfigXmlConstants +{ + public static final String XML_ATTRIBUTE_TYPE = "type"; + public static final String XML_ELEMENT_ROOT = "PwmConfiguration"; + public static final String XML_ELEMENT_PROPERTIES = "properties"; + public static final String XML_ELEMENT_PROPERTY = "property"; + public static final String XML_ELEMENT_SETTINGS = "settings"; + public static final String XML_ELEMENT_SETTING = "setting"; + public static final String XML_ELEMENT_DEFAULT = "default"; + public static final String XML_ELEMENT_LOCALEBUNDLE = "localeBundle"; + public static final String XML_ELEMENT_LABEL = "label"; + public static final String XML_ELEMENT_VALUE = "value"; + + public static final String XML_ATTRIBUTE_KEY = "key"; + public static final String XML_ATTRIBUTE_SYNTAX = "syntax"; + public static final String XML_ATTRIBUTE_PROFILE = "profile"; + public static final String XML_ATTRIBUTE_VALUE_APP = "app"; + public static final String XML_ATTRIBUTE_VALUE_CONFIG = "config"; + public static final String XML_ATTRIBUTE_CREATE_TIME = "createTime"; + public static final String XML_ATTRIBUTE_MODIFY_TIME = "modifyTime"; + public static final String XML_ATTRIBUTE_MODIFY_USER = "modifyUser"; + public static final String XML_ATTRIBUTE_SYNTAX_VERSION = "syntaxVersion"; + public static final String XML_ATTRIBUTE_BUNDLE = "bundle"; + public static final String XML_ATTRIBUTE_XML_VERSION = "xmlVersion"; + public static final String XML_ATTRRIBUTE_PWM_BUILD = "pwmBuild"; + public static final String XML_ATTRIBUTE_PWM_VERSION = "pwmVersion"; + public static final String XML_ATTRIBUTE_LOCALE = "locale"; +} diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfiguration.java b/server/src/main/java/password/pwm/config/stored/StoredConfiguration.java index d0d908f17..dc7073c13 100644 --- a/server/src/main/java/password/pwm/config/stored/StoredConfiguration.java +++ b/server/src/main/java/password/pwm/config/stored/StoredConfiguration.java @@ -20,81 +20,48 @@ package password.pwm.config.stored; -import password.pwm.bean.UserIdentity; import password.pwm.config.PwmSetting; -import password.pwm.config.PwmSettingCategory; +import password.pwm.config.PwmSettingTemplateSet; import password.pwm.config.StoredValue; import password.pwm.error.PwmUnrecoverableException; +import password.pwm.i18n.PwmLocaleBundle; import password.pwm.util.secure.PwmSecurityKey; import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; public interface StoredConfiguration { - String XML_ELEMENT_ROOT = "PwmConfiguration"; - String XML_ELEMENT_PROPERTIES = "properties"; - String XML_ELEMENT_PROPERTY = "property"; - String XML_ELEMENT_SETTINGS = "settings"; - String XML_ELEMENT_SETTING = "setting"; - String XML_ELEMENT_DEFAULT = "default"; - String XML_ELEMENT_LOCALEBUNDLE = "localeBundle"; - - String XML_ATTRIBUTE_TYPE = "type"; - String XML_ATTRIBUTE_KEY = "key"; - String XML_ATTRIBUTE_SYNTAX = "syntax"; - String XML_ATTRIBUTE_PROFILE = "profile"; - String XML_ATTRIBUTE_VALUE_APP = "app"; - String XML_ATTRIBUTE_VALUE_CONFIG = "config"; - String XML_ATTRIBUTE_CREATE_TIME = "createTime"; - String XML_ATTRIBUTE_MODIFY_TIME = "modifyTime"; - String XML_ATTRIBUTE_MODIFY_USER = "modifyUser"; - String XML_ATTRIBUTE_MODIFY_USER_PROFILE = "modifyUserProfile"; - String XML_ATTRIBUTE_SYNTAX_VERSION = "syntaxVersion"; - String XML_ATTRIBUTE_BUNDLE = "bundle"; - - PwmSecurityKey getKey( ) throws PwmUnrecoverableException; - Instant modifyTime( ); + String createTime(); - boolean isLocked( ); + Instant modifyTime( ); - String readConfigProperty( ConfigurationProperty propertyName ); + Optional readConfigProperty( ConfigurationProperty propertyName ); - void writeConfigProperty( - ConfigurationProperty propertyName, - String value - ); + PwmSettingTemplateSet getTemplateSet(); - void lock( ); + List profilesForSetting( PwmSetting pwmSetting ); ValueMetaData readSettingMetadata( PwmSetting setting, String profileID ); - void resetSetting( PwmSetting setting, String profileID, UserIdentity userIdentity ); + Map readLocaleBundleMap( PwmLocaleBundle bundleName, String keyName ); - boolean isDefaultValue( PwmSetting setting ); + StoredValue readSetting( PwmSetting setting, String profileID ); boolean isDefaultValue( PwmSetting setting, String profileID ); - StoredValue readSetting( PwmSetting setting ); - - StoredValue readSetting( PwmSetting setting, String profileID ); - - void copyProfileID( PwmSettingCategory category, String sourceID, String destinationID, UserIdentity userIdentity ) - throws PwmUnrecoverableException; + String valueHash(); - void writeSetting( - PwmSetting setting, - StoredValue value, - UserIdentity userIdentity - ) throws PwmUnrecoverableException; + Set modifiedItems(); - void writeSetting( - PwmSetting setting, - String profileID, - StoredValue value, - UserIdentity userIdentity - ) throws PwmUnrecoverableException; + Optional readMetaData( StoredConfigItemKey storedConfigItemKey ); + Optional readStoredValue( StoredConfigItemKey storedConfigItemKey ); + StoredConfiguration copy(); } diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java b/server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java index c6979543a..8dc1af38f 100644 --- a/server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigurationFactory.java @@ -20,14 +20,682 @@ package password.pwm.config.stored; +import lombok.Builder; +import lombok.Value; +import password.pwm.PwmConstants; +import password.pwm.bean.UserIdentity; +import password.pwm.config.PwmSetting; +import password.pwm.config.PwmSettingFlag; +import password.pwm.config.PwmSettingSyntax; +import password.pwm.config.PwmSettingTemplate; +import password.pwm.config.PwmSettingTemplateSet; +import password.pwm.config.StoredValue; +import password.pwm.config.value.LocalizedStringValue; +import password.pwm.config.value.StoredValueEncoder; +import password.pwm.config.value.StringValue; +import password.pwm.config.value.ValueFactory; +import password.pwm.error.PwmException; import password.pwm.error.PwmUnrecoverableException; +import password.pwm.i18n.PwmLocaleBundle; +import password.pwm.util.java.JavaHelper; +import password.pwm.util.java.LazySupplier; +import password.pwm.util.java.StringUtil; +import password.pwm.util.java.XmlDocument; +import password.pwm.util.java.XmlElement; +import password.pwm.util.java.XmlFactory; +import password.pwm.util.logging.PwmLogger; +import password.pwm.util.macro.MacroMachine; +import password.pwm.util.secure.PwmSecurityKey; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.Set; -interface StoredConfigurationFactory +public class StoredConfigurationFactory { - StoredConfiguration fromXml( InputStream inputStream ) throws PwmUnrecoverableException; + private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationFactory.class ); - void toXml( OutputStream outputStream ); + private static final String XML_FORMAT_VERSION = "5"; + + + public static StoredConfiguration newConfig() throws PwmUnrecoverableException + { + final StoredConfiguration storedConfiguration = new StoredConfigurationImpl( ); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); + + StoredConfigurationUtil.initNewRandomSecurityKey( modifier ); + modifier.writeConfigProperty( + ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) ); + modifier.writeConfigProperty( + ConfigurationProperty.CONFIG_EPOCH, String.valueOf( 0 ) ); + + + return modifier.newStoredConfiguration(); + } + + public static StoredConfigurationModifier newModifiableConfig() throws PwmUnrecoverableException + { + final StoredConfiguration storedConfiguration = new StoredConfigurationImpl( ); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); + + StoredConfigurationUtil.initNewRandomSecurityKey( modifier ); + modifier.writeConfigProperty( + ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) ); + modifier.writeConfigProperty( + ConfigurationProperty.CONFIG_EPOCH, String.valueOf( 0 ) ); + + + return modifier; + } + + public static StoredConfiguration fromXml( final InputStream inputStream ) + throws PwmUnrecoverableException + { + final XmlFactory xmlFactory = XmlFactory.getFactory(); + final XmlDocument xmlDocument = xmlFactory.parseXml( inputStream ); + ConfigurationCleaner.preProcessXml( xmlDocument ); + + final XmlInputDocumentReader xmlInputDocumentReader = new XmlInputDocumentReader( xmlDocument ); + final StoredConfigData storedConfigData = xmlInputDocumentReader.getStoredConfigData(); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( new StoredConfigurationImpl( storedConfigData ) ); + ConfigurationCleaner.postProcessStoredConfig( modifier ); + + return modifier.newStoredConfiguration(); + } + + public static void toXml( + final StoredConfiguration storedConfiguration, + final OutputStream outputStream + ) + throws PwmUnrecoverableException, IOException + { + toXml( storedConfiguration, outputStream, OutputSettings.builder().build() ); + } + + public static void toXml( + final StoredConfiguration storedConfiguration, + final OutputStream outputStream, + final OutputSettings outputSettings + ) + throws PwmUnrecoverableException, IOException + { + final XmlFactory xmlFactory = XmlFactory.getFactory(); + final XmlDocument xmlDocument = xmlFactory.newDocument( StoredConfigXmlConstants.XML_ELEMENT_ROOT ); + + XmlOutputHandler.makeXmlOutput( storedConfiguration, xmlDocument.getRootElement(), outputSettings ); + + xmlFactory.outputDocument( xmlDocument, outputStream ); + } + + static class XmlInputDocumentReader + { + private final XmlDocument document; + + XmlInputDocumentReader( final XmlDocument document ) + { + this.document = document; + } + + StoredConfigData getStoredConfigData() + { + final String createTime = readCreateTime(); + final Instant modifyTime = readModifyTime(); + + final List values = new ArrayList<>(); + values.addAll( readProperties() ); + values.addAll( readSettings() ); + values.addAll( readLocaleBundles() ); + return StoredConfigData.builder() + .createTime( createTime ) + .modifyTime( modifyTime ) + .metaDatas( StoredConfigData.carrierAsMetaDataMap( values ) ) + .storedValues( StoredConfigData.carrierAsStoredValueMap( values ) ) + .build(); + } + + private Collection readProperties() + { + final List valueAndMetaWrapper = new ArrayList<>(); + for ( final ConfigurationProperty configurationProperty : ConfigurationProperty.values() ) + { + final Optional propertyElement = xpathForConfigProperty( configurationProperty ); + if ( propertyElement.isPresent() && !StringUtil.isEmpty( propertyElement.get().getText() ) ) + { + final StoredConfigItemKey key = StoredConfigItemKey.fromConfigurationProperty( configurationProperty ); + final StoredValue storedValue = new StringValue( propertyElement.get().getText() ); + final ValueMetaData metaData = readMetaDataFromXmlElement( key, propertyElement.get() ).orElse( null ); + valueAndMetaWrapper.add( new StoredConfigData.ValueAndMetaCarrier( key, storedValue, metaData ) ); + } + } + return valueAndMetaWrapper; + } + + private Collection readSettings() + { + final List returnList = new ArrayList<>(); + for ( final PwmSetting pwmSetting : PwmSetting.values() ) + { + if ( !pwmSetting.getCategory().hasProfiles() ) + { + readSetting( pwmSetting, null ).ifPresent( returnList::add ); + } + } + + for ( final PwmSetting pwmSetting : PwmSetting.values() ) + { + if ( pwmSetting.getCategory().hasProfiles() ) + { + final List profileIDs = profilesForSetting( pwmSetting ); + for ( final String profileID : profileIDs ) + { + readSetting( pwmSetting, profileID ).ifPresent( returnList::add ); + } + } + } + return returnList; + } + + Optional readSetting( final PwmSetting setting, final String profileID ) + { + final Optional settingElement = xpathForSetting( setting, profileID ); + + if ( !settingElement.isPresent() ) + { + return Optional.empty(); + } + + if ( settingElement.get().getChild( StoredConfigXmlConstants.XML_ELEMENT_DEFAULT ).isPresent() ) + { + return Optional.empty(); + } + + try + { + final StoredValue storedValue = ValueFactory.fromXmlValues( setting, settingElement.get(), getKey() ); + final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID ); + final ValueMetaData metaData = readMetaDataFromXmlElement( key, settingElement.get() ).orElse( null ); + return Optional.of( new StoredConfigData.ValueAndMetaCarrier( key, storedValue, metaData ) ); + } + catch ( final PwmException e ) + { + final String errorMsg = "unexpected error reading setting '" + setting.getKey() + "' profile '" + profileID + "', error: " + e.getMessage(); + throw new IllegalStateException( errorMsg ); + } + } + + List profilesForSetting( final PwmSetting pwmSetting ) + { + if ( !pwmSetting.getCategory().hasProfiles() && pwmSetting.getSyntax() != PwmSettingSyntax.PROFILE ) + { + return Collections.emptyList(); + } + + final PwmSetting profileSetting; + if ( pwmSetting.getSyntax() == PwmSettingSyntax.PROFILE ) + { + profileSetting = pwmSetting; + } + else + { + profileSetting = pwmSetting.getCategory().getProfileSetting(); + } + + final StoredValue effectiveValue; + { + final Optional configuredValue = readSetting( profileSetting, null ); + if ( configuredValue.isPresent() ) + { + effectiveValue = configuredValue.get().getValue(); + } + else + { + effectiveValue = profileSetting.getDefaultValue( templateSetSupplier.get() ); + } + } + + final List settingValues = ( List ) effectiveValue.toNativeObject(); + final LinkedList profiles = new LinkedList<>( settingValues ); + profiles.removeIf( StringUtil::isEmpty ); + return Collections.unmodifiableList( profiles ); + } + + + public PwmSecurityKey getKey() throws PwmUnrecoverableException + { + final XmlElement rootElement = document.getRootElement(); + final String createTimeString = rootElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_CREATE_TIME ); + return new PwmSecurityKey( createTimeString + "StoredConfiguration" ); + } + + String readCreateTime() + { + final XmlElement rootElement = document.getRootElement(); + final String createTimeString = rootElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_CREATE_TIME ); + if ( StringUtil.isEmpty( createTimeString ) ) + { + throw new IllegalStateException( "missing createTime timestamp" ); + } + else + { + return createTimeString; + } + } + + Instant readModifyTime() + { + final XmlElement rootElement = document.getRootElement(); + final String modifyTimeString = rootElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_MODIFY_TIME ); + if ( !StringUtil.isEmpty( modifyTimeString ) ) + { + try + { + return JavaHelper.parseIsoToInstant( modifyTimeString ); + } + catch ( final Exception e ) + { + LOGGER.error( "error parsing root last modified timestamp: " + e.getMessage() ); + } + } + + return null; + } + + private final LazySupplier templateSetSupplier = new LazySupplier<>( () -> + { + final Set templates = new HashSet<>(); + templates.add( readTemplateValue( PwmSetting.TEMPLATE_LDAP ) ); + templates.add( readTemplateValue( PwmSetting.TEMPLATE_STORAGE ) ); + templates.add( readTemplateValue( PwmSetting.DB_VENDOR_TEMPLATE ) ); + return new PwmSettingTemplateSet( templates ); + } ); + + private PwmSettingTemplate readTemplateValue( final PwmSetting pwmSetting ) + { + final Optional settingElement = xpathForSetting( pwmSetting, null ); + if ( settingElement.isPresent() ) + { + try + { + final String strValue = ( String ) ValueFactory.fromXmlValues( pwmSetting, settingElement.get(), null ).toNativeObject(); + return JavaHelper.readEnumFromString( PwmSettingTemplate.class, null, strValue ); + } + catch ( final IllegalStateException e ) + { + LOGGER.error( "error reading template", e ); + } + } + return null; + } + + private Collection readLocaleBundles() + { + final List returnWrapper = new ArrayList<>(); + + for ( final XmlElement localeBundleElement : xpathForLocaleBundles() ) + { + final String bundleName = localeBundleElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_BUNDLE ); + final Optional pwmLocaleBundle = PwmLocaleBundle.forKey( bundleName ); + pwmLocaleBundle.ifPresent( ( bundle ) -> + { + final String key = localeBundleElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY ); + if ( bundle.getDisplayKeys().contains( key ) ) + { + final Map bundleMap = new LinkedHashMap<>(); + for ( final XmlElement valueElement : localeBundleElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ) ) + { + final String localeStrValue = valueElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_LOCALE ); + bundleMap.put( localeStrValue == null ? "" : localeStrValue, valueElement.getText() ); + } + if ( !bundleMap.isEmpty() ) + { + final StoredConfigItemKey storedConfigItemKey = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle.get(), key ); + final StoredValue storedValue = new LocalizedStringValue( bundleMap ); + final ValueMetaData metaData = readMetaDataFromXmlElement( storedConfigItemKey, localeBundleElement ).orElse( null ); + returnWrapper.add( new StoredConfigData.ValueAndMetaCarrier( storedConfigItemKey, storedValue, metaData ) ); + } + } + } ); + } + return Collections.unmodifiableList( returnWrapper ); + } + + private Optional readMetaDataFromXmlElement( final StoredConfigItemKey key, final XmlElement xmlElement ) + { + Instant instant = null; + { + final String modifyTimeValue = xmlElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_MODIFY_TIME ); + if ( !StringUtil.isEmpty( modifyTimeValue ) ) + { + try + { + instant = JavaHelper.parseIsoToInstant( modifyTimeValue ); + } + catch ( final DateTimeParseException e ) + { + e.printStackTrace(); + } + } + } + + UserIdentity userIdentity = null; + { + final String modifyUserValue = xmlElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_MODIFY_USER ); + if ( !StringUtil.isEmpty( modifyUserValue ) ) + { + try + { + userIdentity = UserIdentity.fromDelimitedKey( modifyUserValue ); + } + catch ( final DateTimeParseException | PwmUnrecoverableException e ) + { + LOGGER.trace( () -> "unable to parse userIdentity metadata for key " + key.toString() ); + } + } + } + + if ( instant != null || userIdentity != null ) + { + return Optional.of( new ValueMetaData( instant, userIdentity ) ); + } + + return Optional.empty(); + } + + List xpathForLocaleBundles() + { + final String xpathString = "//localeBundle"; + return document.evaluateXpathToElements( xpathString ); + } + + XmlElement xpathForSettings() + { + return document.getRootElement().getChild( StoredConfigXmlConstants.XML_ELEMENT_SETTINGS ) + .orElseThrow( () -> new IllegalStateException( "configuration xml document missing 'settings' element" ) ); + } + + Optional xpathForSetting( final PwmSetting setting, final String profileID ) + { + final String xpathString; + if ( profileID == null || profileID.length() < 1 ) + { + xpathString = "//" + StoredConfigXmlConstants.XML_ELEMENT_SETTING + "[@" + StoredConfigXmlConstants.XML_ATTRIBUTE_KEY + + "=\"" + setting.getKey() + + "\"][(not (@" + StoredConfigXmlConstants.XML_ATTRIBUTE_PROFILE + ")) or @" + + StoredConfigXmlConstants.XML_ATTRIBUTE_PROFILE + "=\"\"]"; + } + else + { + xpathString = "//" + StoredConfigXmlConstants.XML_ELEMENT_SETTING + "[@" + StoredConfigXmlConstants.XML_ATTRIBUTE_KEY + + "=\"" + setting.getKey() + + "\"][@" + StoredConfigXmlConstants.XML_ATTRIBUTE_PROFILE + "=\"" + profileID + "\"]"; + } + + return document.evaluateXpathToElement( xpathString ); + } + + Optional xpathForConfigProperty( final ConfigurationProperty configProperty ) + { + final String xpathString = "//" + StoredConfigXmlConstants.XML_ELEMENT_PROPERTIES + "[@" + StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE + + "=\"" + StoredConfigXmlConstants.XML_ATTRIBUTE_VALUE_CONFIG + "\"]/" + + StoredConfigXmlConstants.XML_ELEMENT_PROPERTY + "[@" + StoredConfigXmlConstants.XML_ATTRIBUTE_KEY + "=\"" + configProperty.getKey() + "\"]"; + return document.evaluateXpathToElement( xpathString ); + } + + List xpathForAppProperties( ) + { + final String xpathString = "//" + StoredConfigXmlConstants.XML_ELEMENT_PROPERTIES + + "[@" + StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE + "=\"" + StoredConfigXmlConstants.XML_ATTRIBUTE_VALUE_APP + "\"]"; + return document.evaluateXpathToElements( xpathString ); + } + } + + + static class XmlOutputHandler + { + static void makeXmlOutput( final StoredConfiguration storedConfiguration, final XmlElement rootElement, final OutputSettings outputSettings ) + throws PwmUnrecoverableException + { + decorateRootElement( rootElement, storedConfiguration ); + + rootElement.addContent( makePropertiesElement( storedConfiguration ) ); + + rootElement.addContent( makeSettingsXmlElement( storedConfiguration, outputSettings ) ); + + rootElement.addContent( XmlOutputHandler.makeLocaleBundleXmlElements( storedConfiguration ) ); + } + + static void decorateRootElement( final XmlElement rootElement, final StoredConfiguration storedConfiguration ) + { + rootElement.setComment( Collections.singletonList( generateCommentText() ) ); + rootElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_PWM_VERSION, PwmConstants.BUILD_VERSION ); + rootElement.setAttribute( StoredConfigXmlConstants.XML_ATTRRIBUTE_PWM_BUILD, PwmConstants.BUILD_NUMBER ); + rootElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_XML_VERSION, XML_FORMAT_VERSION ); + + rootElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_CREATE_TIME, storedConfiguration.createTime() ); + rootElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( storedConfiguration.modifyTime() ) ); + } + + static XmlElement makeSettingsXmlElement( + final StoredConfiguration storedConfiguration, + final OutputSettings outputSettings + ) + throws PwmUnrecoverableException + { + final PwmSecurityKey pwmSecurityKey = storedConfiguration.getKey(); + + final XmlFactory xmlFactory = XmlFactory.getFactory(); + final XmlElement settingsElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_SETTINGS ); + + final XmlOutputProcessData xmlOutputProcessData = XmlOutputProcessData.builder() + .pwmSecurityKey( pwmSecurityKey ) + .storedValueEncoderMode( figureEncoderMode( storedConfiguration, outputSettings ) ) + .build(); + + for ( final PwmSetting pwmSetting : PwmSetting.sortedByMenuLocation( PwmConstants.DEFAULT_LOCALE ) ) + { + if ( !pwmSetting.getFlags().contains( PwmSettingFlag.Deprecated ) ) + { + if ( pwmSetting.getCategory().hasProfiles() ) + { + for ( final String profileID : storedConfiguration.profilesForSetting( pwmSetting ) ) + { + final StoredValue storedValue = storedConfiguration.readSetting( pwmSetting, profileID ); + final XmlElement settingElement = makeSettingXmlElement( storedConfiguration, pwmSetting, profileID, storedValue, xmlOutputProcessData ); + decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromSetting( pwmSetting, profileID ), settingElement ); + settingsElement.addContent( settingElement ); + } + } + else + { + final StoredValue storedValue = storedConfiguration.readSetting( pwmSetting, null ); + final XmlElement settingElement = makeSettingXmlElement( storedConfiguration, pwmSetting, null, storedValue, xmlOutputProcessData ); + decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromSetting( pwmSetting, null ), settingElement ); + settingsElement.addContent( settingElement ); + } + } + } + + return settingsElement; + } + + static StoredValueEncoder.Mode figureEncoderMode( + final StoredConfiguration storedConfiguration, + final OutputSettings outputSettings + ) + { + if ( outputSettings == null || outputSettings.getMode() == null ) + { + return StoredValueEncoder.Mode.ENCODED; + } + + if ( outputSettings.getMode() == OutputSettings.SecureOutputMode.STRIPPED ) + { + return StoredValueEncoder.Mode.STRIPPED; + } + + final Optional strValue = storedConfiguration.readConfigProperty( ConfigurationProperty.STORE_PLAINTEXT_VALUES ); + if ( strValue.isPresent() && Boolean.parseBoolean( strValue.get() ) ) + { + return StoredValueEncoder.Mode.PLAIN; + } + + return StoredValueEncoder.Mode.ENCODED; + } + + + static XmlElement makeSettingXmlElement( + final StoredConfiguration storedConfiguration, + final PwmSetting pwmSetting, + final String profileID, + final StoredValue storedValue, + final XmlOutputProcessData xmlOutputProcessData + ) + { + final XmlFactory xmlFactory = XmlFactory.getFactory(); + + final XmlElement settingElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_SETTING ); + settingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY, pwmSetting.getKey() ); + + if ( !StringUtil.isEmpty( profileID ) ) + { + settingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_PROFILE, profileID ); + } + + settingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_SYNTAX, pwmSetting.getSyntax().name() ); + + { + final XmlElement labelElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_LABEL ); + labelElement.addText( pwmSetting.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) ); + settingElement.addContent( labelElement ); + } + + final List valueElements = new ArrayList<>( ); + if ( storedConfiguration != null && storedConfiguration.isDefaultValue( pwmSetting, profileID ) ) + { + final XmlElement defaultValue = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_DEFAULT ); + valueElements.add( defaultValue ); + } + else + { + valueElements.addAll( storedValue.toXmlValues( StoredConfigXmlConstants.XML_ELEMENT_VALUE, xmlOutputProcessData ) ); + } + + settingElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_SYNTAX_VERSION, String.valueOf( storedValue.currentSyntaxVersion() ) ); + settingElement.addContent( valueElements ); + return settingElement; + } + + private static void decorateElementWithMetaData( + final StoredConfiguration storedConfiguration, + final StoredConfigItemKey key, + final XmlElement xmlElement + ) + { + final Optional valueMetaData = ( ( StoredConfiguration ) storedConfiguration ).readMetaData( key ); + + if ( valueMetaData.isPresent() ) + { + if ( valueMetaData.get().getUserIdentity() != null ) + { + xmlElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_MODIFY_USER, valueMetaData.get().getUserIdentity().toDelimitedKey() ); + } + + if ( valueMetaData.get().getModifyDate() != null ) + { + xmlElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( valueMetaData.get().getModifyDate() ) ); + } + } + } + + private static XmlElement makePropertiesElement( final StoredConfiguration storedConfiguration ) + { + final XmlFactory xmlFactory = XmlFactory.getFactory(); + final XmlElement propertiesElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_PROPERTIES ); + propertiesElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_TYPE, StoredConfigXmlConstants.XML_ATTRIBUTE_VALUE_CONFIG ); + + for ( final ConfigurationProperty configurationProperty : ConfigurationProperty.values() ) + { + storedConfiguration.readConfigProperty( configurationProperty ).ifPresent( s -> + { + final XmlElement propertyElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_PROPERTY ); + propertyElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY, configurationProperty.getKey() ); + propertyElement.addText( s ); + decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromConfigurationProperty( configurationProperty ), propertyElement ); + propertiesElement.addContent( propertyElement ); + } + ); + } + + return propertiesElement; + } + + private static List makeLocaleBundleXmlElements( final StoredConfiguration storedConfiguration ) + { + final XmlFactory xmlFactory = XmlFactory.getFactory(); + final List returnList = new ArrayList<>(); + for ( final PwmLocaleBundle pwmLocaleBundle : PwmLocaleBundle.values() ) + { + for ( final String key : pwmLocaleBundle.getDisplayKeys() ) + { + final Map localeBundle = storedConfiguration.readLocaleBundleMap( pwmLocaleBundle, key ); + if ( !JavaHelper.isEmpty( localeBundle ) ) + { + final XmlElement localeBundleElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_LOCALEBUNDLE ); + localeBundleElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_BUNDLE, pwmLocaleBundle.getKey() ); + localeBundleElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY, key ); + + final Map localeBundleMap = storedConfiguration.readLocaleBundleMap( pwmLocaleBundle, key ); + for ( final Map.Entry entry : localeBundleMap.entrySet() ) + { + final XmlElement valueElement = xmlFactory.newElement( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); + if ( !StringUtil.isEmpty( entry.getKey() ) ) + { + valueElement.setAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_LOCALE, entry.getKey() ); + } + valueElement.addText( entry.getValue() ); + localeBundleElement.addContent( valueElement ); + } + + decorateElementWithMetaData( storedConfiguration, StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, key ), localeBundleElement ); + returnList.add( localeBundleElement ); + } + } + } + return Collections.unmodifiableList( returnList ); + } + + private static String generateCommentText() + { + final String resourceText = ResourceBundle.getBundle( StoredConfigurationFactory.class.getName() ).getString( "configCommentText" ); + return MacroMachine.forStatic().expandMacros( resourceText ); + } + } + + @Value + @Builder + public static class OutputSettings + { + @Builder.Default + private SecureOutputMode mode = SecureOutputMode.NORMAL; + + public enum SecureOutputMode + { + NORMAL, + STRIPPED, + } + } } + diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java b/server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java index a738ed443..ceef6cf54 100644 --- a/server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java @@ -20,429 +20,163 @@ package password.pwm.config.stored; -import password.pwm.AppProperty; -import password.pwm.PwmConstants; -import password.pwm.bean.UserIdentity; -import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; -import password.pwm.config.PwmSettingCategory; -import password.pwm.config.PwmSettingSyntax; import password.pwm.config.PwmSettingTemplate; import password.pwm.config.PwmSettingTemplateSet; import password.pwm.config.StoredValue; -import password.pwm.config.value.NamedSecretValue; -import password.pwm.config.value.PasswordValue; -import password.pwm.config.value.PrivateKeyValue; -import password.pwm.config.value.StringArrayValue; -import password.pwm.config.value.ValueFactory; -import password.pwm.error.ErrorInformation; -import password.pwm.error.PwmError; -import password.pwm.error.PwmException; -import password.pwm.error.PwmOperationalException; +import password.pwm.config.value.LocalizedStringValue; +import password.pwm.config.value.StringValue; import password.pwm.error.PwmUnrecoverableException; -import password.pwm.i18n.Config; import password.pwm.i18n.PwmLocaleBundle; -import password.pwm.util.PasswordData; -import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.JavaHelper; -import password.pwm.util.java.JsonUtil; -import password.pwm.util.java.StringUtil; -import password.pwm.util.java.TimeDuration; -import password.pwm.util.java.XmlDocument; -import password.pwm.util.java.XmlElement; -import password.pwm.util.java.XmlFactory; +import password.pwm.util.java.LazySupplier; import password.pwm.util.logging.PwmLogger; -import password.pwm.util.secure.BCrypt; -import password.pwm.util.secure.PwmRandom; +import password.pwm.util.secure.HmacAlgorithm; import password.pwm.util.secure.PwmSecurityKey; import password.pwm.util.secure.SecureEngine; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; import java.time.Instant; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Queue; -import java.util.ResourceBundle; +import java.util.Objects; +import java.util.Optional; import java.util.Set; -import java.util.SortedSet; import java.util.TreeMap; -import java.util.TreeSet; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.stream.Collectors; +import java.util.function.Supplier; /** + * Immutable in-memory configuration. + * * @author Jason D. Rivard */ -@SuppressWarnings( "all" ) // this class will be replaced by NGStoredConfiguration public class StoredConfigurationImpl implements StoredConfiguration { + private final String createTime; + private final Instant modifyTime; + private final Map storedValues; + private final Map metaValues; + private final PwmSettingTemplateSet templateSet; - private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationImpl.class ); - static final String XML_FORMAT_VERSION = "4"; - - private XmlDocument document = XmlFactory.getFactory().newDocument( XML_ELEMENT_ROOT ); - private ChangeLog changeLog = new ChangeLog(); - - private boolean locked; - private final boolean setting_writeLabels = true; - private final ReentrantReadWriteLock domModifyLock = new ReentrantReadWriteLock(); + private final transient Supplier valueHashSupplier = new LazySupplier<>( this::valueHashImpl ); - private final XmlHelper xmlHelper = new XmlHelper(); + private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationImpl.class ); - public static StoredConfigurationImpl newStoredConfiguration( ) throws PwmUnrecoverableException + StoredConfigurationImpl( final StoredConfigData storedConfigData ) { - return new StoredConfigurationImpl(); - } + this.createTime = storedConfigData.getCreateTime(); + this.modifyTime = storedConfigData.getModifyTime(); + this.metaValues = Collections.unmodifiableMap( new TreeMap<>( storedConfigData.getMetaDatas() ) ); + this.templateSet = readTemplateSet( storedConfigData.getStoredValues() ); - public static StoredConfigurationImpl copy( final StoredConfigurationImpl input ) throws PwmUnrecoverableException - { - final StoredConfigurationImpl copy = new StoredConfigurationImpl(); - copy.document = input.document.copy(); - return copy; + final Map tempMap = new TreeMap<>( storedConfigData.getStoredValues() ); + removeAllDefaultValues( tempMap, templateSet ); + this.storedValues = Collections.unmodifiableMap( tempMap ); } - public static StoredConfigurationImpl fromXml( final InputStream xmlData ) - throws PwmUnrecoverableException + StoredConfigurationImpl() { - final Instant startTime = Instant.now(); - //validateXmlSchema(xmlData); - - final XmlDocument inputDocument = XmlFactory.getFactory().parseXml( xmlData ); - final StoredConfigurationImpl newConfiguration = StoredConfigurationImpl.newStoredConfiguration(); - - try - { - newConfiguration.document = inputDocument; - newConfiguration.createTime(); // verify create time; - ConfigurationCleaner.cleanup( newConfiguration, newConfiguration.document ); - } - catch ( Exception e ) - { - final String errorMsg = "error reading configuration file format, error=" + e.getMessage(); - final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { errorMsg } ); - throw new PwmUnrecoverableException( errorInfo ); - } - - checkIfXmlRequiresUpdate( newConfiguration ); - LOGGER.debug( () -> "successfully loaded configuration (" + TimeDuration.compactFromCurrent( startTime ) + ")" ); - return newConfiguration; + this.createTime = JavaHelper.toIsoDate( Instant.now() ); + this.modifyTime = Instant.now(); + this.storedValues = Collections.emptyMap(); + this.metaValues = Collections.emptyMap(); + this.templateSet = readTemplateSet( Collections.emptyMap() ); } - /** - * Loop through all settings to see if setting value has flag {@link StoredValue#requiresStoredUpdate()} set to true. - * If so, then call {@link #writeSetting(PwmSetting, StoredValue, password.pwm.bean.UserIdentity)} or {@link #writeSetting(PwmSetting, String, StoredValue, password.pwm.bean.UserIdentity)} - * for that value so that the xml dom can be updated. - * - * @param storedConfiguration stored configuration to check - */ - private static void checkIfXmlRequiresUpdate( final StoredConfigurationImpl storedConfiguration ) throws PwmUnrecoverableException + private static void removeAllDefaultValues( final Map valueMap, final PwmSettingTemplateSet pwmSettingTemplateSet ) { - for ( final PwmSetting setting : PwmSetting.values() ) - { - if ( setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles() ) - { - final StoredValue value = storedConfiguration.readSetting( setting ); - if ( value.requiresStoredUpdate() ) - { - storedConfiguration.writeSetting( setting, value, null ); - } - } - } - - for ( final PwmSettingCategory category : PwmSettingCategory.values() ) + valueMap.entrySet().removeIf( entry -> { - if ( category.hasProfiles() ) + final StoredConfigItemKey key = entry.getKey(); + if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) { - for ( final String profileID : storedConfiguration.profilesForSetting( category.getProfileSetting() ) ) - { - for ( final PwmSetting profileSetting : category.getSettings() ) - { - final StoredValue value = storedConfiguration.readSetting( profileSetting, profileID ); - if ( value.requiresStoredUpdate() ) - { - storedConfiguration.writeSetting( profileSetting, profileID, value, null ); - } - } - } + final StoredValue loopValue = entry.getValue(); + final StoredValue defaultValue = key.toPwmSetting().getDefaultValue( pwmSettingTemplateSet ); + return Objects.equals( loopValue.valueHash(), defaultValue.valueHash() ); } - } + return false; + } ); } - public void resetAllPasswordValues( final String comment ) - throws PwmUnrecoverableException - { - for ( final Iterator settingValueRecordIterator = new StoredValueIterator( false ); settingValueRecordIterator.hasNext(); ) - { - final SettingValueRecord settingValueRecord = settingValueRecordIterator.next(); - if ( settingValueRecord.getSetting().getSyntax() == PwmSettingSyntax.PASSWORD ) - { - final ValueMetaData valueMetaData = this.readSettingMetadata( settingValueRecord.getSetting(), settingValueRecord.getProfile() ); - final PasswordValue passwordValue = new PasswordValue( new PasswordData( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ) ); - this.writeSetting( settingValueRecord.getSetting(), settingValueRecord.getProfile(), passwordValue, valueMetaData.getUserIdentity() ); - if ( comment != null && !comment.isEmpty() ) - { - final XmlElement settingElement = xmlHelper.xpathForSetting( settingValueRecord.getSetting(), settingValueRecord.getProfile() ); - if ( settingElement != null ) - { - settingElement.setComment( Collections.singletonList( comment ) ); - } - } - } - } - final String pwdHash = this.readConfigProperty( ConfigurationProperty.PASSWORD_HASH ); - if ( pwdHash != null && !pwdHash.isEmpty() ) - { - this.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, comment ); - } - } - public StoredConfigurationImpl( ) throws PwmUnrecoverableException + @Override + public StoredConfiguration copy() { - try - { - ConfigurationCleaner.cleanup( this, document ); - final String createTime = JavaHelper.toIsoDate( Instant.now() ); - document.getRootElement().setAttribute( XML_ATTRIBUTE_CREATE_TIME, createTime ); - } - catch ( Exception e ) - { - e.printStackTrace( ); - throw new IllegalStateException( e ); - } + return new StoredConfigurationImpl( asStoredConfigData() ); } - - @Override - public String readConfigProperty( final ConfigurationProperty propertyName ) + StoredConfigData asStoredConfigData() { - final XmlElement propertyElement = xmlHelper.xpathForConfigProperty( propertyName ); - return propertyElement == null ? null : propertyElement.getText(); + return new StoredConfigData( createTime, modifyTime, storedValues, metaValues ); } @Override - public void writeConfigProperty( - final ConfigurationProperty propertyName, - final String value - ) + public Optional readConfigProperty( final ConfigurationProperty propertyName ) { - domModifyLock.writeLock().lock(); - try - { - - // remove existing element - { - final XmlElement propertyElement = xmlHelper.xpathForConfigProperty( propertyName ); - if ( propertyElement != null ) - { - propertyElement.detach(); - } - } - - // add new property - final XmlElement propertyElement = xmlHelper.getXmlFactory().newElement( XML_ELEMENT_PROPERTY ); - propertyElement.setAttribute( XML_ATTRIBUTE_KEY, propertyName.getKey() ); - propertyElement.addText( value ); - - if ( null == xmlHelper.xpathForConfigProperties() ) - { - final XmlElement configProperties = xmlHelper.getXmlFactory().newElement( XML_ELEMENT_PROPERTIES ); - configProperties.setAttribute( XML_ATTRIBUTE_TYPE, XML_ATTRIBUTE_VALUE_CONFIG ); - document.getRootElement().addContent( configProperties ); - } - - final XmlElement propertiesElement = xmlHelper.xpathForConfigProperties(); - propertyElement.setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) ); - propertiesElement.setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) ); - propertiesElement.addContent( propertyElement ); - } - finally + final StoredConfigItemKey key = StoredConfigItemKey.fromConfigurationProperty( propertyName ); + final StoredValue storedValue = storedValues.get( key ); + if ( storedValue != null ) { - domModifyLock.writeLock().unlock(); + return Optional.of( ( ( StringValue ) storedValue ).toNativeObject() ); } + return Optional.empty(); } - public void lock( ) + public boolean isDefaultValue( final PwmSetting setting, final String profileID ) { - locked = true; + final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID ); + return !storedValues.containsKey( key ); } - public Map readLocaleBundleMap( final String bundleName, final String keyName ) + @Override + public Map readLocaleBundleMap( final PwmLocaleBundle pwmLocaleBundle, final String keyName ) { - domModifyLock.readLock().lock(); - try - { - final XmlElement localeBundleElement = xmlHelper.xpathForLocaleBundleSetting( bundleName, keyName ); - - if ( localeBundleElement != null ) - { - final Map bundleMap = new LinkedHashMap<>(); - for ( final XmlElement valueElement : localeBundleElement.getChildren( "value" ) ) - { - final String localeStrValue = valueElement.getAttributeValue( "locale" ); - bundleMap.put( localeStrValue == null ? "" : localeStrValue, valueElement.getText() ); - } - if ( !bundleMap.isEmpty() ) - { - return bundleMap; - } - } - } - finally + final StoredConfigItemKey key = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, keyName ); + final StoredValue value = storedValues.get( key ); + if ( value != null ) { - domModifyLock.readLock().unlock(); + return ( ( LocalizedStringValue ) value ).toNativeObject(); } return Collections.emptyMap(); } - public Map toOutputMap( final Locale locale ) - { - final List> settingData = new ArrayList<>(); - for ( final StoredConfigurationImpl.SettingValueRecord settingValueRecord : this.modifiedSettings() ) - { - final Map recordMap = new HashMap<>(); - recordMap.put( "label", settingValueRecord.getSetting().getLabel( locale ) ); - if ( settingValueRecord.getProfile() != null ) - { - recordMap.put( "profile", settingValueRecord.getProfile() ); - } - if ( settingValueRecord.getStoredValue() != null ) - { - recordMap.put( "value", settingValueRecord.getStoredValue().toDebugString( locale ) ); - } - final ValueMetaData settingMetaData = readSettingMetadata( settingValueRecord.getSetting(), settingValueRecord.getProfile() ); - if ( settingMetaData != null ) - { - if ( settingMetaData.getModifyDate() != null ) - { - recordMap.put( "modifyTime", JavaHelper.toIsoDate( settingMetaData.getModifyDate() ) ); - } - if ( settingMetaData.getUserIdentity() != null ) - { - recordMap.put( "modifyUser", settingMetaData.getUserIdentity().toDisplayString() ); - } - } - settingData.add( recordMap ); - } - - final HashMap outputObj = new HashMap<>(); - outputObj.put( "settings", settingData ); - outputObj.put( "template", this.getTemplateSet().toString() ); - - return Collections.unmodifiableMap( outputObj ); - } - - public void resetLocaleBundleMap( final String bundleName, final String keyName ) - { - preModifyActions(); - domModifyLock.writeLock().lock(); - try - { - final XmlElement oldBundleElements = xmlHelper.xpathForLocaleBundleSetting( bundleName, keyName ); - if ( oldBundleElements != null ) - { - oldBundleElements.detach(); - } - } - finally - { - domModifyLock.writeLock().unlock(); - } - } - - public void resetSetting( final PwmSetting setting, final String profileID, final UserIdentity userIdentity ) - { - changeLog.updateChangeLog( setting, profileID, defaultValue( setting, this.getTemplateSet() ) ); - domModifyLock.writeLock().lock(); - try - { - preModifyActions(); - final XmlElement settingElement = createOrGetSettingElement( setting, profileID ); - settingElement.removeContent(); - settingElement.addContent( xmlHelper.getXmlFactory().newElement( XML_ELEMENT_DEFAULT ) ); - updateMetaData( settingElement, userIdentity ); - } - finally - { - domModifyLock.writeLock().unlock(); - } - } - - public boolean isDefaultValue( final PwmSetting setting ) - { - return isDefaultValue( setting, null ); - } - - public boolean isDefaultValue( final PwmSetting setting, final String profileID ) - { - domModifyLock.readLock().lock(); - try - { - final StoredValue currentValue = readSetting( setting, profileID ); - if ( setting.getSyntax() == PwmSettingSyntax.PASSWORD ) - { - return currentValue == null || currentValue.toNativeObject() == null; - } - final StoredValue defaultValue = defaultValue( setting, this.getTemplateSet() ); - final String currentJsonValue = JsonUtil.serialize( ( Serializable ) currentValue.toNativeObject() ); - final String defaultJsonValue = JsonUtil.serialize( ( Serializable ) defaultValue.toNativeObject() ); - return defaultJsonValue.equalsIgnoreCase( currentJsonValue ); - } - finally - { - domModifyLock.readLock().unlock(); - } - } - - private static StoredValue defaultValue( final PwmSetting pwmSetting, final PwmSettingTemplateSet template ) + @Override + public PwmSettingTemplateSet getTemplateSet() { - return pwmSetting.getDefaultValue( template ); + return templateSet; } - public PwmSettingTemplateSet getTemplateSet( ) + private static PwmSettingTemplateSet readTemplateSet( final Map valueMap ) { final Set templates = new HashSet<>(); - templates.add( readTemplateValue( PwmSetting.TEMPLATE_LDAP ) ); - templates.add( readTemplateValue( PwmSetting.TEMPLATE_STORAGE ) ); - templates.add( readTemplateValue( PwmSetting.DB_VENDOR_TEMPLATE ) ); + readTemplateValue( valueMap, PwmSetting.TEMPLATE_LDAP ).ifPresent( templates::add ); + readTemplateValue( valueMap, PwmSetting.TEMPLATE_STORAGE ).ifPresent( templates::add ); + readTemplateValue( valueMap, PwmSetting.DB_VENDOR_TEMPLATE ).ifPresent( templates::add ); return new PwmSettingTemplateSet( templates ); } - private PwmSettingTemplate readTemplateValue( final PwmSetting pwmSetting ) + private static Optional readTemplateValue( final Map valueMap, final PwmSetting pwmSetting ) { - final XmlElement settingElement = xmlHelper.xpathForSetting( pwmSetting, null ); - if ( settingElement != null ) + final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( pwmSetting, null ); + final StoredValue storedValue = valueMap.get( key ); + + if ( storedValue != null ) { try { - final String strValue = ( String ) ValueFactory.fromXmlValues( pwmSetting, settingElement, null ).toNativeObject(); - return JavaHelper.readEnumFromString( PwmSettingTemplate.class, null, strValue ); + final String strValue = ( String ) storedValue.toNativeObject(); + return Optional.ofNullable( JavaHelper.readEnumFromString( PwmSettingTemplate.class, null, strValue ) ); } - catch ( IllegalStateException e ) + catch ( final IllegalStateException e ) { LOGGER.error( "error reading template", e ); } } - return null; - } - public void setTemplate( final PwmSettingTemplate template ) - { - writeConfigProperty( ConfigurationProperty.LDAP_TEMPLATE, template.toString() ); + return Optional.empty(); } public String toString( final PwmSetting setting, final String profileID ) @@ -451,1130 +185,102 @@ public String toString( final PwmSetting setting, final String profileID ) return setting.getKey() + "=" + storedValue.toDebugString( null ); } - public Map getModifiedSettingDebugValues( final Locale locale, final boolean prettyPrint ) - { - final Map returnObj = new TreeMap<>(); - for ( final SettingValueRecord record : this.modifiedSettings() ) - { - final String label = record.getSetting().toMenuLocationDebug( record.getProfile(), locale ); - final String value = record.getStoredValue().toDebugString( locale ); - returnObj.put( label, value ); - } - return returnObj; - } - - public List modifiedSettings( ) - { - final List returnObj = new ArrayList<>(); - domModifyLock.readLock().lock(); - try - { - for ( final PwmSetting setting : PwmSetting.values() ) - { - if ( setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles() ) - { - if ( !isDefaultValue( setting, null ) ) - { - final StoredValue value = readSetting( setting ); - if ( value != null ) - { - returnObj.add( new SettingValueRecord( setting, null, value ) ); - } - } - } - } - - for ( final PwmSettingCategory category : PwmSettingCategory.values() ) - { - if ( category.hasProfiles() ) - { - for ( final String profileID : this.profilesForSetting( category.getProfileSetting() ) ) - { - for ( final PwmSetting profileSetting : category.getSettings() ) - { - if ( !isDefaultValue( profileSetting, profileID ) ) - { - final StoredValue value = readSetting( profileSetting, profileID ); - if ( value != null ) - { - returnObj.add( new SettingValueRecord( profileSetting, profileID, value ) ); - - } - } - } - } - } - } - - return returnObj; - } - finally - { - domModifyLock.readLock().unlock(); - } - } - - public Serializable toJsonDebugObject( ) - { - domModifyLock.readLock().lock(); - try - { - final TreeMap outputObject = new TreeMap<>(); - - for ( final PwmSetting setting : PwmSetting.values() ) - { - if ( setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles() ) - { - if ( !isDefaultValue( setting, null ) ) - { - final StoredValue value = readSetting( setting ); - outputObject.put( setting.getKey(), value.toDebugJsonObject( null ) ); - } - } - } - - for ( final PwmSettingCategory category : PwmSettingCategory.values() ) - { - if ( category.hasProfiles() ) - { - final TreeMap profiles = new TreeMap<>(); - for ( final String profileID : this.profilesForSetting( category.getProfileSetting() ) ) - { - final TreeMap profileObject = new TreeMap<>(); - for ( final PwmSetting profileSetting : category.getSettings() ) - { - if ( !isDefaultValue( profileSetting, profileID ) ) - { - final StoredValue value = readSetting( profileSetting, profileID ); - profileObject.put( profileSetting.getKey(), value.toDebugJsonObject( null ) ); - } - } - profiles.put( profileID, profileObject ); - } - outputObject.put( category.getProfileSetting().getKey(), profiles ); - } - } - - return outputObject; - } - finally - { - domModifyLock.readLock().unlock(); - } - } - - public void toXml( final OutputStream outputStream ) - throws IOException, PwmUnrecoverableException + public Set modifiedItems() { - ConfigurationCleaner.updateMandatoryElements( this, document ); - XmlFactory.getFactory().outputDocument( document, outputStream ); + return Collections.unmodifiableSet( storedValues.keySet() ); } + @Override public List profilesForSetting( final PwmSetting pwmSetting ) { - return StoredConfigurationUtil.profilesForSetting( pwmSetting, this ); - } - - public List validateValues( ) - { - final long startTime = System.currentTimeMillis(); - final List errorStrings = new ArrayList<>(); - - for ( final PwmSetting loopSetting : PwmSetting.values() ) + final List returnObj = new ArrayList<>(); + for ( final StoredConfigItemKey storedConfigItemKey : storedValues.keySet() ) { - - if ( loopSetting.getCategory().hasProfiles() ) + if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING + && Objects.equals( storedConfigItemKey.getRecordID(), pwmSetting.getKey() ) ) { - for ( final String profile : profilesForSetting( loopSetting ) ) - { - final StoredValue loopValue = readSetting( loopSetting, profile ); - - try - { - final List errors = loopValue.validateValue( loopSetting ); - for ( final String loopError : errors ) - { - errorStrings.add( loopSetting.toMenuLocationDebug( profile, PwmConstants.DEFAULT_LOCALE ) + " - " + loopError ); - } - } - catch ( Exception e ) - { - LOGGER.error( "unexpected error during validate value for " + loopSetting.toMenuLocationDebug( profile, PwmConstants.DEFAULT_LOCALE ) + ", error: " + e.getMessage(), e ); - } - } - } - else - { - final StoredValue loopValue = readSetting( loopSetting ); - - try - { - final List errors = loopValue.validateValue( loopSetting ); - for ( final String loopError : errors ) - { - errorStrings.add( loopSetting.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) + " - " + loopError ); - } - } - catch ( Exception e ) - { - LOGGER.error( "unexpected error during validate value for " + loopSetting.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) + ", error: " + e.getMessage(), e ); - } + returnObj.add( storedConfigItemKey.getProfileID() ); } } - - LOGGER.trace( () -> "StoredConfiguration validator completed in " + TimeDuration.fromCurrent( startTime ).asCompactString() ); - return errorStrings; + return Collections.unmodifiableList( returnObj ); } public ValueMetaData readSettingMetadata( final PwmSetting setting, final String profileID ) { - final XmlElement settingElement = xmlHelper.xpathForSetting( setting, profileID ); - - if ( settingElement == null ) - { - return null; - } - - Instant modifyDate = null; - try - { - if ( settingElement.getAttributeValue( XML_ATTRIBUTE_MODIFY_TIME ) != null ) - { - modifyDate = JavaHelper.parseIsoToInstant( - settingElement.getAttributeValue( XML_ATTRIBUTE_MODIFY_TIME ) ); - } - } - catch ( Exception e ) - { - LOGGER.error( "can't read modifyDate for setting " + setting.getKey() + ", profile " + profileID + ", error: " + e.getMessage() ); - } - - UserIdentity userIdentity = null; - try - { - if ( settingElement.getAttributeValue( XML_ATTRIBUTE_MODIFY_USER ) != null ) - { - userIdentity = UserIdentity.fromDelimitedKey( - settingElement.getAttributeValue( XML_ATTRIBUTE_MODIFY_USER ) ); - } - } - catch ( Exception e ) - { - LOGGER.error( "can't read userIdentity for setting " + setting.getKey() + ", profile " + profileID + ", error: " + e.getMessage() ); - } - - return new ValueMetaData( modifyDate, userIdentity ); + final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID ); + return metaValues.get( key ); } - public List search( final String searchTerm, final Locale locale ) + public StoredValue readSetting( final PwmSetting setting, final String profileID ) { - if ( searchTerm == null ) + final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID ); + final StoredValue storedValue = storedValues.get( key ); + if ( storedValue == null ) { - return Collections.emptyList(); + return setting.getDefaultValue( getTemplateSet() ); } - final SortedSet matches = new TreeSet<>( - allSettingConfigRecordIDs() - .parallelStream() - .filter( s -> matchSetting( s, searchTerm, locale ) ) - .collect( Collectors.toList() ) - ); - - return new ArrayList<>( matches ); + return storedValue; } - private boolean matchSetting( - final ConfigRecordID configRecordID, - final String searchTerm, - final Locale locale - ) + public String valueHash() { - - final PwmSetting pwmSetting = ( PwmSetting ) configRecordID.getRecordID(); - final StoredValue value = readSetting( pwmSetting, configRecordID.getProfileID() ); - - return StringUtil.whitespaceSplit( searchTerm ) - .parallelStream() - .allMatch( s -> matchSetting( pwmSetting, value, s, locale ) ); + return valueHashSupplier.get(); } - public boolean matchSetting( final PwmSetting setting, final StoredValue value, final String searchTerm, final Locale locale ) + private String valueHashImpl() { - if ( setting.isHidden() || setting.getCategory().isHidden() ) - { - return false; - } + final Set modifiedSettings = modifiedItems(); + final StringBuilder sb = new StringBuilder(); - if ( searchTerm == null || searchTerm.isEmpty() ) + for ( final StoredConfigItemKey storedConfigItemKey : modifiedSettings ) { - return false; + final StoredValue storedValue = storedValues.get( storedConfigItemKey ); + sb.append( storedValue.valueHash() ); } - final String lowerSearchTerm = searchTerm.toLowerCase(); - - { - final String key = setting.getKey(); - if ( key.toLowerCase().contains( lowerSearchTerm ) ) - { - return true; - } - } - { - final String label = setting.getLabel( locale ); - if ( label.toLowerCase().contains( lowerSearchTerm ) ) - { - return true; - } - } + try { - final String descr = setting.getDescription( locale ); - if ( descr.toLowerCase().contains( lowerSearchTerm ) ) - { - return true; - } + return SecureEngine.hmac( HmacAlgorithm.HMAC_SHA_512, getKey(), sb.toString() ); } + catch ( final PwmUnrecoverableException e ) { - final String menuLocationString = setting.toMenuLocationDebug( null, locale ); - if ( menuLocationString.toLowerCase().contains( lowerSearchTerm ) ) - { - return true; - } + throw new IllegalStateException( e ); } + } - if ( setting.isConfidential() ) - { - return false; - } - { - final String valueDebug = value.toDebugString( locale ); - if ( valueDebug != null && valueDebug.toLowerCase().contains( lowerSearchTerm ) ) - { - return true; - } - } - if ( PwmSettingSyntax.SELECT == setting.getSyntax() - || PwmSettingSyntax.OPTIONLIST == setting.getSyntax() - || PwmSettingSyntax.VERIFICATION_METHOD == setting.getSyntax() - ) + private PwmSecurityKey cachedKey; + + public PwmSecurityKey getKey() throws PwmUnrecoverableException + { + if ( cachedKey == null ) { - for ( final String key : setting.getOptions().keySet() ) - { - if ( key.toLowerCase().contains( lowerSearchTerm ) ) - { - return true; - } - final String optionValue = setting.getOptions().get( key ); - if ( optionValue != null && optionValue.toLowerCase().contains( lowerSearchTerm ) ) - { - return true; - } - } + cachedKey = new PwmSecurityKey( createTime + "StoredConfiguration" ); } - return false; + return cachedKey; } - - public StoredValue readSetting( final PwmSetting setting ) + @Override + public Instant modifyTime() { - return readSetting( setting, null ); + return modifyTime; } - public StoredValue readSetting( final PwmSetting setting, final String profileID ) + @Override + public String createTime() { - if ( profileID == null && setting.getCategory().hasProfiles() ) - { - final IllegalArgumentException e = new IllegalArgumentException( "reading of setting " + setting.getKey() + " requires a non-null profileID" ); - LOGGER.error( "error", e ); - throw e; - } - if ( profileID != null && !setting.getCategory().hasProfiles() ) - { - throw new IllegalStateException( "cannot read setting key " + setting.getKey() + " with non-null profileID" ); - } - domModifyLock.readLock().lock(); - try - { - final XmlElement settingElement = xmlHelper.xpathForSetting( setting, profileID ); - - if ( settingElement == null ) - { - return defaultValue( setting, getTemplateSet() ); - } - - if ( settingElement.getChild( XML_ELEMENT_DEFAULT ) != null ) - { - return defaultValue( setting, getTemplateSet() ); - } - - try - { - return ValueFactory.fromXmlValues( setting, settingElement, getKey() ); - } - catch ( PwmException e ) - { - final String errorMsg = "unexpected error reading setting '" + setting.getKey() + "' profile '" + profileID + "', error: " + e.getMessage(); - throw new IllegalStateException( errorMsg ); - } - } - finally - { - domModifyLock.readLock().unlock(); - } + return createTime; } - public void writeLocaleBundleMap( final String bundleName, final String keyName, final Map localeMap ) + @Override + public Optional readMetaData( final StoredConfigItemKey storedConfigItemKey ) { - ResourceBundle theBundle = null; - for ( final PwmLocaleBundle bundle : PwmLocaleBundle.values() ) - { - if ( bundle.getTheClass().getName().equals( bundleName ) ) - { - theBundle = ResourceBundle.getBundle( bundleName ); - } - } - - if ( theBundle == null ) - { - LOGGER.info( () -> "ignoring unknown locale bundle for bundle=" + bundleName + ", key=" + keyName ); - return; - } - - if ( theBundle.getString( keyName ) == null ) - { - LOGGER.info( () -> "ignoring unknown key for bundle=" + bundleName + ", key=" + keyName ); - return; - } - - - resetLocaleBundleMap( bundleName, keyName ); - if ( localeMap == null || localeMap.isEmpty() ) - { - LOGGER.info( () -> "cleared locale bundle map for bundle=" + bundleName + ", key=" + keyName ); - return; - } - - preModifyActions(); - changeLog.updateChangeLog( bundleName, keyName, localeMap ); - try - { - domModifyLock.writeLock().lock(); - final XmlElement localeBundleElement = xmlHelper.getXmlFactory().newElement( "localeBundle" ); - localeBundleElement.setAttribute( "bundle", bundleName ); - localeBundleElement.setAttribute( "key", keyName ); - for ( final Map.Entry entry : localeMap.entrySet() ) - { - final String locale = entry.getKey(); - final String value = entry.getValue(); - final XmlElement valueElement = xmlHelper.getXmlFactory().newElement( "value" ); - if ( locale != null && locale.length() > 0 ) - { - valueElement.setAttribute( "locale", locale ); - } - valueElement.addText( value ); - localeBundleElement.addContent( valueElement ); - } - localeBundleElement.setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) ); - document.getRootElement().addContent( localeBundleElement ); - } - finally - { - domModifyLock.writeLock().unlock(); - } - } - - - public void copyProfileID( final PwmSettingCategory category, final String sourceID, final String destinationID, final UserIdentity userIdentity ) - throws PwmUnrecoverableException - { - - if ( !category.hasProfiles() ) - { - throw PwmUnrecoverableException.newException( PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category " + category + ", category does not have profiles" ); - } - final List existingProfiles = this.profilesForSetting( category.getProfileSetting() ); - if ( !existingProfiles.contains( sourceID ) ) - { - throw PwmUnrecoverableException.newException( PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category, source profileID '" + sourceID + "' does not exist" ); - } - if ( existingProfiles.contains( destinationID ) ) - { - throw PwmUnrecoverableException.newException( PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category, destination profileID '" + destinationID + "' already exists" ); - } - - { - final Collection interestedCategories = PwmSettingCategory.associatedProfileCategories( category ); - for ( final PwmSettingCategory interestedCategory : interestedCategories ) - { - for ( final PwmSetting pwmSetting : interestedCategory.getSettings() ) - { - if ( !isDefaultValue( pwmSetting, sourceID ) ) - { - final StoredValue value = readSetting( pwmSetting, sourceID ); - writeSetting( pwmSetting, destinationID, value, userIdentity ); - } - } - } - } - - final List newProfileIDList = new ArrayList<>(); - newProfileIDList.addAll( existingProfiles ); - newProfileIDList.add( destinationID ); - writeSetting( category.getProfileSetting(), new StringArrayValue( newProfileIDList ), userIdentity ); - } - - - public void writeSetting( - final PwmSetting setting, - final StoredValue value, - final UserIdentity userIdentity - ) throws PwmUnrecoverableException - { - writeSetting( setting, null, value, userIdentity ); - } - - public void writeSetting( - final PwmSetting setting, - final String profileID, - final StoredValue value, - final UserIdentity userIdentity - ) throws PwmUnrecoverableException - { - if ( profileID == null && setting.getCategory().hasProfiles() ) - { - throw new IllegalArgumentException( "writing of setting " + setting.getKey() + " requires a non-null profileID" ); - } - if ( profileID != null && !setting.getCategory().hasProfiles() ) - { - throw new IllegalArgumentException( "cannot specify profile for non-profile setting" ); - } - - preModifyActions(); - changeLog.updateChangeLog( setting, profileID, value ); - domModifyLock.writeLock().lock(); - try - { - final XmlElement settingElement = createOrGetSettingElement( setting, profileID ); - settingElement.removeContent(); - settingElement.setAttribute( XML_ATTRIBUTE_SYNTAX, setting.getSyntax().toString() ); - settingElement.setAttribute( XML_ATTRIBUTE_SYNTAX_VERSION, Integer.toString( value.currentSyntaxVersion() ) ); - - if ( setting_writeLabels ) - { - { - final XmlElement existingLabel = settingElement.getChild( "label" ); - if ( existingLabel != null ) - { - existingLabel.detach(); - } - } - - { - final XmlElement newLabelElement = xmlHelper.getXmlFactory().newElement( "label" ); - newLabelElement.addText( setting.getLabel( PwmConstants.DEFAULT_LOCALE ) ); - settingElement.addContent( newLabelElement ); - } - } - - if ( setting.getSyntax() == PwmSettingSyntax.PASSWORD ) - { - final List commentLines = Arrays.asList( - "Note: This value is encrypted and can not be edited directly.", - "Please use the Configuration Manager GUI to modify this value." - ); - settingElement.setComment( commentLines ); - - final List valueElements = ( ( PasswordValue ) value ).toXmlValues( "value", getKey() ); - settingElement.addContent( valueElements ); - } - else if ( setting.getSyntax() == PwmSettingSyntax.PRIVATE_KEY ) - { - final List valueElements = ( ( PrivateKeyValue ) value ).toXmlValues( "value", getKey() ); - settingElement.addContent( valueElements ); - } - else if ( setting.getSyntax() == PwmSettingSyntax.NAMED_SECRET ) - { - final List valueElements = ( ( NamedSecretValue ) value ).toXmlValues( "value", getKey() ); - settingElement.addContent( valueElements ); - } - else - { - settingElement.addContent( value.toXmlValues( "value", getKey() ) ); - } - - - updateMetaData( settingElement, userIdentity ); - } - finally - { - domModifyLock.writeLock().unlock(); - } - } - - public String settingChecksum( ) - throws PwmUnrecoverableException - { - final Instant startTime = Instant.now(); - - final List modifiedSettings = modifiedSettings(); - final StringBuilder sb = new StringBuilder(); - sb.append( "PwmSettingsChecksum" ); - for ( final SettingValueRecord settingValueRecord : modifiedSettings ) - { - final StoredValue storedValue = settingValueRecord.getStoredValue(); - sb.append( storedValue.valueHash() ); - } - - - final String result = SecureEngine.hash( sb.toString(), PwmConstants.SETTING_CHECKSUM_HASH_METHOD ); - LOGGER.trace( () -> "computed setting checksum in " + TimeDuration.fromCurrent( startTime ).asCompactString() ); - return result; - } - - - private void preModifyActions( ) - { - if ( locked ) - { - throw new UnsupportedOperationException( "StoredConfiguration is locked and cannot be modified" ); - } - document.getRootElement().setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) ); - } - - public void setPassword( final String password ) - throws PwmOperationalException - { - if ( password == null || password.isEmpty() ) - { - throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { "can not set blank password" } ) ); - } - final String trimmedPassword = password.trim(); - if ( trimmedPassword.length() < 1 ) - { - throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { "can not set blank password" } ) ); - } - - - final String passwordHash = BCrypt.hashPassword( password ); - this.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, passwordHash ); - } - - public boolean verifyPassword( final String password, final Configuration configuration ) - { - if ( !hasPassword() ) - { - return false; - } - final String passwordHash = this.readConfigProperty( ConfigurationProperty.PASSWORD_HASH ); - return BCrypt.testAnswer( password, passwordHash, configuration ); - } - - public boolean hasPassword( ) - { - final String passwordHash = this.readConfigProperty( ConfigurationProperty.PASSWORD_HASH ); - return passwordHash != null && passwordHash.length() > 0; - } - - class XmlHelper - { - private XmlFactory getXmlFactory() - { - return XmlFactory.getFactory(); - } - - private XmlElement xpathForLocaleBundleSetting( final String bundleName, final String keyName ) - { - final String xpathString = "//localeBundle[@bundle=\"" + bundleName + "\"][@key=\"" + keyName + "\"]"; - return document.evaluateXpathToElement( xpathString ); - } - - XmlElement xpathForSetting( final PwmSetting setting, final String profileID ) - { - final String xpathString; - if ( profileID == null || profileID.length() < 1 ) - { - xpathString = "//setting[@key=\"" + setting.getKey() + "\"][(not (@profile)) or @profile=\"\"]"; - } - else - { - xpathString = "//setting[@key=\"" + setting.getKey() + "\"][@profile=\"" + profileID + "\"]"; - } - - return document.evaluateXpathToElement( xpathString ); - } - - private XmlElement xpathForAppProperty( final AppProperty appProperty ) - { - final String xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_APP + "\"]/" - + XML_ELEMENT_PROPERTY + "[@" + XML_ATTRIBUTE_KEY + "=\"" + appProperty.getKey() + "\"]"; - return document.evaluateXpathToElement( xpathString ); - } - - List xpathForAppProperties( ) - { - final String xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_APP + "\"]"; - return document.evaluateXpathToElements( xpathString ); - } - - private XmlElement xpathForConfigProperty( final ConfigurationProperty configProperty ) - { - final String xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]/" - + XML_ELEMENT_PROPERTY + "[@" + XML_ATTRIBUTE_KEY + "=\"" + configProperty.getKey() + "\"]"; - return document.evaluateXpathToElement( xpathString ); - } - - private XmlElement xpathForConfigProperties( ) - { - final String xpathString = "//" + XML_ELEMENT_PROPERTIES + "[@" + XML_ATTRIBUTE_TYPE + "=\"" + XML_ATTRIBUTE_VALUE_CONFIG + "\"]"; - return document.evaluateXpathToElement( xpathString ); - } - } - - - public static class ConfigRecordID implements Serializable, Comparable - { - private RecordType recordType; - private Object recordID; - private String profileID; - - public enum RecordType - { - SETTING, - LOCALE_BUNDLE, - } - - public ConfigRecordID( - final RecordType recordType, - final Object recordID, - final String profileID - ) - { - this.recordType = recordType; - this.recordID = recordID; - this.profileID = profileID; - } - - - public RecordType getRecordType( ) - { - return recordType; - } - - public Object getRecordID( ) - { - return recordID; - } - - public String getProfileID( ) - { - return profileID; - } - - @Override - public boolean equals( final Object o ) - { - return o != null - && o instanceof ConfigRecordID - && toString().equals( o.toString() ); - - } - - @Override - public int hashCode( ) - { - return toString().hashCode(); - } - - @Override - public String toString( ) - { - return this.getRecordType().toString() - + "-" - + ( this.getProfileID() == null ? "" : this.getProfileID() ) - + "-" - + this.getRecordID(); - } - - @Override - public int compareTo( final Object o ) - { - return toString().compareTo( o.toString() ); - } - } - - - public String changeLogAsDebugString( final Locale locale, final boolean asHtml ) - { - return changeLog.changeLogAsDebugString( locale, asHtml ); - } - - private PwmSecurityKey cachedKey; - - public PwmSecurityKey getKey( ) throws PwmUnrecoverableException - { - if ( cachedKey == null ) - { - cachedKey = new PwmSecurityKey( createTime() + "StoredConfiguration" ); - } - return cachedKey; - } - - public boolean isModified( ) - { - return changeLog.isModified(); - } - - private class ChangeLog - { - /* values contain the _original_ toJson version of the value. */ - private Map changeLog = new LinkedHashMap<>(); - - public boolean isModified( ) - { - return !changeLog.isEmpty(); - } - - public String changeLogAsDebugString( final Locale locale, final boolean asHtml ) - { - final Map outputMap = new TreeMap<>(); - final String SEPARATOR = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null ); - - for ( final ConfigRecordID configRecordID : changeLog.keySet() ) - { - switch ( configRecordID.recordType ) - { - case SETTING: - { - final StoredValue currentValue = readSetting( ( PwmSetting ) configRecordID.recordID, configRecordID.profileID ); - final PwmSetting pwmSetting = ( PwmSetting ) configRecordID.recordID; - final String keyName = pwmSetting.toMenuLocationDebug( configRecordID.getProfileID(), locale ); - final String debugValue = currentValue.toDebugString( locale ); - outputMap.put( keyName, debugValue ); - } - break; - - case LOCALE_BUNDLE: - { - final String key = ( String ) configRecordID.recordID; - final String bundleName = key.split( "!" )[ 0 ]; - final String keys = key.split( "!" )[ 1 ]; - final Map currentValue = readLocaleBundleMap( bundleName, keys ); - final String debugValue = JsonUtil.serializeMap( currentValue, JsonUtil.Flag.PrettyPrint ); - outputMap.put( "LocaleBundle" + SEPARATOR + bundleName + " " + keys, debugValue ); - } - break; - - default: - // continue processing - break; - } - } - final StringBuilder output = new StringBuilder(); - if ( outputMap.isEmpty() ) - { - output.append( "No setting changes." ); - } - else - { - for ( final Map.Entry entry : outputMap.entrySet() ) - { - final String keyName = entry.getKey(); - final String value = entry.getValue(); - if ( asHtml ) - { - output.append( "
" ); - output.append( keyName ); - output.append( "
" ); - output.append( StringUtil.escapeHtml( value ) ); - output.append( "
" ); - } - else - { - output.append( keyName ); - output.append( "\n" ); - output.append( " Value: " ); - output.append( value ); - output.append( "\n" ); - } - } - } - return output.toString(); - } - - public void updateChangeLog( final String bundleName, final String keyName, final Map localeValueMap ) - { - final String key = bundleName + "!" + keyName; - final Map currentValue = readLocaleBundleMap( bundleName, keyName ); - final String currentJsonValue = JsonUtil.serializeMap( currentValue ); - final String newJsonValue = JsonUtil.serializeMap( localeValueMap ); - final ConfigRecordID configRecordID = new ConfigRecordID( ConfigRecordID.RecordType.LOCALE_BUNDLE, key, null ); - updateChangeLog( configRecordID, currentJsonValue, newJsonValue ); - } - - public void updateChangeLog( final PwmSetting setting, final String profileID, final StoredValue newValue ) - { - final StoredValue currentValue = readSetting( setting, profileID ); - final String currentJsonValue = JsonUtil.serialize( currentValue ); - final String newJsonValue = JsonUtil.serialize( newValue ); - final ConfigRecordID configRecordID = new ConfigRecordID( ConfigRecordID.RecordType.SETTING, setting, profileID ); - updateChangeLog( configRecordID, currentJsonValue, newJsonValue ); - } - - public void updateChangeLog( final ConfigRecordID configRecordID, final String currentValueString, final String newValueString ) - { - if ( changeLog.containsKey( configRecordID ) ) - { - final String currentRecord = changeLog.get( configRecordID ); - - if ( currentRecord == null && newValueString == null ) - { - changeLog.remove( configRecordID ); - } - else if ( currentRecord != null && currentRecord.equals( newValueString ) ) - { - changeLog.remove( configRecordID ); - } - } - else - { - changeLog.put( configRecordID, currentValueString ); - } - } - } - - public static void validateXmlSchema( final String xmlDocument ) - throws PwmUnrecoverableException - { - return; - /* - try { - final InputStream xsdInputStream = PwmSetting.class.getClassLoader().getResourceAsStream("password/pwm/config/StoredConfiguration.xsd"); - final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - final Schema schema = factory.newSchema(new StreamSource(xsdInputStream)); - Validator validator = schema.newValidator(); - validator.validate(new StreamSource(new StringReader(xmlDocument))); - } catch (Exception e) { - final String errorMsg = "error while validating setting file schema definition: " + e.getMessage(); - throw new PwmUnrecoverableException(new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,errorMsg)); - } - */ - } - - private void updateMetaData( final XmlElement settingElement, final UserIdentity userIdentity ) - { - final XmlElement settingsElement = document.getRootElement().getChild( XML_ELEMENT_SETTINGS ); - settingElement.setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) ); - settingsElement.setAttribute( XML_ATTRIBUTE_MODIFY_TIME, JavaHelper.toIsoDate( Instant.now() ) ); - settingElement.removeAttribute( XML_ATTRIBUTE_MODIFY_USER ); - settingsElement.removeAttribute( XML_ATTRIBUTE_MODIFY_USER ); - if ( userIdentity != null ) - { - settingElement.setAttribute( XML_ATTRIBUTE_MODIFY_USER, userIdentity.toDelimitedKey() ); - settingsElement.setAttribute( XML_ATTRIBUTE_MODIFY_USER, userIdentity.toDelimitedKey() ); - } - } - - private XmlElement createOrGetSettingElement( - final PwmSetting setting, - final String profileID - ) - { - final XmlElement existingSettingElement = xmlHelper.xpathForSetting( setting, profileID ); - if ( existingSettingElement != null ) - { - return existingSettingElement; - } - - final XmlElement settingElement = xmlHelper.getXmlFactory().newElement( XML_ELEMENT_SETTING ); - settingElement.setAttribute( XML_ATTRIBUTE_KEY, setting.getKey() ); - settingElement.setAttribute( XML_ATTRIBUTE_SYNTAX, setting.getSyntax().toString() ); - if ( profileID != null && profileID.length() > 0 ) - { - settingElement.setAttribute( XML_ATTRIBUTE_PROFILE, profileID ); - } - - XmlElement settingsElement = document.getRootElement().getChild( XML_ELEMENT_SETTINGS ); - if ( settingsElement == null ) - { - settingsElement = xmlHelper.getXmlFactory().newElement( XML_ELEMENT_SETTINGS ); - document.getRootElement().addContent( settingsElement ); - } - settingsElement.addContent( settingElement ); - - return settingElement; - } - - public static class SettingValueRecord implements Serializable - { - private PwmSetting setting; - private String profile; - private StoredValue storedValue; - - public SettingValueRecord( - final PwmSetting setting, - final String profile, - final StoredValue storedValue - ) - { - this.setting = setting; - this.profile = profile; - this.storedValue = storedValue; - } - - public PwmSetting getSetting( ) - { - return setting; - } - - public String getProfile( ) - { - return profile; - } - - public StoredValue getStoredValue( ) - { - return storedValue; - } - } - - class StoredValueIterator implements Iterator - { - - private Queue settingQueue = new LinkedList<>(); - - StoredValueIterator( final boolean includeDefaults ) - { - for ( final PwmSetting setting : PwmSetting.values() ) - { - if ( setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles() ) - { - if ( includeDefaults || !isDefaultValue( setting ) ) - { - final SettingValueRecord settingValueRecord = new SettingValueRecord( setting, null, null ); - settingQueue.add( settingValueRecord ); - } - } - } - - for ( final PwmSettingCategory category : PwmSettingCategory.values() ) - { - if ( category.hasProfiles() ) - { - for ( final String profileID : profilesForSetting( category.getProfileSetting() ) ) - { - for ( final PwmSetting setting : category.getSettings() ) - { - if ( includeDefaults || !isDefaultValue( setting, profileID ) ) - { - final SettingValueRecord settingValueRecord = new SettingValueRecord( setting, profileID, null ); - settingQueue.add( settingValueRecord ); - } - } - } - } - } - } - - - @Override - public boolean hasNext( ) - { - return !settingQueue.isEmpty(); - } - - @Override - public SettingValueRecord next( ) - { - final StoredConfigurationImpl.SettingValueRecord settingValueRecord = settingQueue.poll(); - return new SettingValueRecord( - settingValueRecord.getSetting(), - settingValueRecord.getProfile(), - readSetting( settingValueRecord.getSetting(), settingValueRecord.getProfile() ) - ); - } - - @Override - public void remove( ) - { - - } - } - - private String createTime( ) - { - final XmlElement rootElement = document.getRootElement(); - final String createTimeString = rootElement.getAttributeValue( XML_ATTRIBUTE_CREATE_TIME ); - if ( createTimeString == null || createTimeString.isEmpty() ) - { - throw new IllegalStateException( "missing createTime timestamp" ); - } - return createTimeString; - } - - @Override - public Instant modifyTime( ) - { - final XmlElement rootElement = document.getRootElement(); - final String modifyTimeString = rootElement.getAttributeValue( XML_ATTRIBUTE_MODIFY_TIME ); - if ( modifyTimeString != null ) - { - try - { - return JavaHelper.parseIsoToInstant( modifyTimeString ); - } - catch ( Exception e ) - { - LOGGER.error( "error parsing root last modified timestamp: " + e.getMessage() ); - } - } - return null; - } - - public void initNewRandomSecurityKey( ) - throws PwmUnrecoverableException - { - if ( !isDefaultValue( PwmSetting.PWM_SECURITY_KEY ) ) - { - return; - } - - writeSetting( - PwmSetting.PWM_SECURITY_KEY, - new PasswordValue( new PasswordData( PwmRandom.getInstance().alphaNumericString( 1024 ) ) ), - null - ); - - LOGGER.debug( () -> "initialized new random security key" ); - } - + return Optional.ofNullable( metaValues.get( storedConfigItemKey ) ); + } @Override - public boolean isLocked( ) - { - return locked; - } - - private List allSettingConfigRecordIDs( ) - { - final LinkedHashSet loopResults = new LinkedHashSet<>(); - for ( final PwmSetting loopSetting : PwmSetting.values() ) - { - if ( loopSetting.getCategory().hasProfiles() ) - { - for ( final String profile : profilesForSetting( loopSetting ) ) - { - loopResults.add( new ConfigRecordID( ConfigRecordID.RecordType.SETTING, loopSetting, profile ) ); - } - } - else - { - loopResults.add( new ConfigRecordID( ConfigRecordID.RecordType.SETTING, loopSetting, null ) ); - } - } - return new ArrayList<>( loopResults ); - } - - XmlHelper getXmlHelper() + public Optional readStoredValue( final StoredConfigItemKey storedConfigItemKey ) { - return xmlHelper; + return Optional.ofNullable( storedValues.get( storedConfigItemKey ) ); } } diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigurationModifier.java b/server/src/main/java/password/pwm/config/stored/StoredConfigurationModifier.java new file mode 100644 index 000000000..ea8e5f91a --- /dev/null +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigurationModifier.java @@ -0,0 +1,291 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2019 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.config.stored; + +import password.pwm.bean.UserIdentity; +import password.pwm.config.PwmSetting; +import password.pwm.config.PwmSettingCategory; +import password.pwm.config.StoredValue; +import password.pwm.config.value.LocalizedStringValue; +import password.pwm.config.value.StringArrayValue; +import password.pwm.config.value.StringValue; +import password.pwm.error.ErrorInformation; +import password.pwm.error.PwmError; +import password.pwm.error.PwmOperationalException; +import password.pwm.error.PwmUnrecoverableException; +import password.pwm.i18n.PwmLocaleBundle; +import password.pwm.util.java.StringUtil; +import password.pwm.util.secure.BCrypt; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +public class StoredConfigurationModifier +{ + private final AtomicReference ref = new AtomicReference<>( ); + + private StoredConfigurationModifier( final StoredConfiguration storedConfiguration ) + { + this.ref.set( ( ( StoredConfigurationImpl ) storedConfiguration ).asStoredConfigData() ); + } + + public static StoredConfigurationModifier newModifier( final StoredConfiguration storedConfiguration ) + { + return new StoredConfigurationModifier( storedConfiguration ); + } + + public StoredConfiguration newStoredConfiguration() + { + return new StoredConfigurationImpl( ref.get() ); + } + + public void writeSetting( + final PwmSetting setting, + final String profileID, + final StoredValue value, + final UserIdentity userIdentity + ) + throws PwmUnrecoverableException + { + Objects.requireNonNull( setting ); + Objects.requireNonNull( value ); + + update( ( storedConfigData ) -> + { + if ( StringUtil.isEmpty( profileID ) && setting.getCategory().hasProfiles() ) + { + throw new IllegalArgumentException( "writing of setting " + setting.getKey() + " requires a non-null profileID" ); + } + if ( !StringUtil.isEmpty( profileID ) && !setting.getCategory().hasProfiles() ) + { + throw new IllegalArgumentException( "cannot specify profile for non-profile setting" ); + } + + final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID ); + + + return storedConfigData.toBuilder() + .storedValue( key, value ) + .metaData( key, new ValueMetaData( Instant.now(), userIdentity ) ) + .build(); + } ); + } + + public void writeConfigProperty( + final ConfigurationProperty propertyName, + final String value + ) + throws PwmUnrecoverableException + { + update( ( storedConfigData ) -> + { + final StoredConfigItemKey key = StoredConfigItemKey.fromConfigurationProperty( propertyName ); + final Map existingStoredValues = new HashMap<>( storedConfigData.getStoredValues() ); + + if ( StringUtil.isEmpty( value ) ) + { + existingStoredValues.remove( key ); + } + else + { + final StoredValue storedValue = new StringValue( value ); + existingStoredValues.put( key, storedValue ); + } + + return storedConfigData.toBuilder() + .clearStoredValues() + .storedValues( existingStoredValues ) + .build(); + } ); + } + + public void resetLocaleBundleMap( final PwmLocaleBundle pwmLocaleBundle, final String keyName ) + throws PwmUnrecoverableException + { + update( ( storedConfigData ) -> + { + final Map existingStoredValues = new HashMap<>( storedConfigData.getStoredValues() ); + + final StoredConfigItemKey key = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, keyName ); + existingStoredValues.remove( key ); + + return storedConfigData.toBuilder() + .clearStoredValues() + .storedValues( existingStoredValues ) + .build(); + } ); + } + + public void resetSetting( final PwmSetting setting, final String profileID, final UserIdentity userIdentity ) + throws PwmUnrecoverableException + { + update( ( storedConfigData ) -> + { + final Map existingStoredValues = new HashMap<>( storedConfigData.getStoredValues() ); + + final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( setting, profileID ); + existingStoredValues.remove( key ); + + return storedConfigData.toBuilder() + .clearStoredValues() + .storedValues( existingStoredValues ) + .metaData( key, new ValueMetaData( Instant.now(), userIdentity ) ) + .build(); + } ); + } + + public void writeLocaleBundleMap( + final PwmLocaleBundle pwmLocaleBundle, + final String keyName, + final Map localeMap + ) + throws PwmUnrecoverableException + { + update( ( storedConfigData ) -> + { + final StoredConfigItemKey key = StoredConfigItemKey.fromLocaleBundle( pwmLocaleBundle, keyName ); + final StoredValue value = new LocalizedStringValue( localeMap ); + + return storedConfigData.toBuilder() + .storedValue( key, value ) + .build(); + } ); + } + + public void copyProfileID( + final PwmSettingCategory category, + final String sourceID, + final String destinationID, + final UserIdentity userIdentity + ) + throws PwmUnrecoverableException + { + + if ( !category.hasProfiles() ) + { + throw PwmUnrecoverableException.newException( + PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category " + category + ", category does not have profiles" ); + } + + update( ( storedConfigData ) -> + { + final StoredConfiguration oldStoredConfiguration = new StoredConfigurationImpl( storedConfigData ); + + final List existingProfiles = oldStoredConfiguration.profilesForSetting( category.getProfileSetting() ); + if ( !existingProfiles.contains( sourceID ) ) + { + throw PwmUnrecoverableException.newException( + PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category, source profileID '" + sourceID + "' does not exist" ); + } + + if ( existingProfiles.contains( destinationID ) ) + { + throw PwmUnrecoverableException.newException( + PwmError.ERROR_INVALID_CONFIG, "can not copy profile ID for category, destination profileID '" + destinationID + "' already exists" ); + } + + final Collection interestedCategories = PwmSettingCategory.associatedProfileCategories( category ); + for ( final PwmSettingCategory interestedCategory : interestedCategories ) + { + for ( final PwmSetting pwmSetting : interestedCategory.getSettings() ) + { + if ( !oldStoredConfiguration.isDefaultValue( pwmSetting, sourceID ) ) + { + final StoredValue value = oldStoredConfiguration.readSetting( pwmSetting, sourceID ); + writeSetting( pwmSetting, destinationID, value, userIdentity ); + } + } + } + final List newProfileIDList = new ArrayList<>( existingProfiles ); + newProfileIDList.add( destinationID ); + + final StoredValue value = new StringArrayValue( newProfileIDList ); + final StoredConfigItemKey key = StoredConfigItemKey.fromSetting( category.getProfileSetting(), null ); + final ValueMetaData valueMetaData = new ValueMetaData( Instant.now(), userIdentity ); + + return storedConfigData.toBuilder() + .storedValue( key, value ) + .metaData( key, valueMetaData ) + .build(); + + } ); + } + + public void setPassword( final String password ) + throws PwmOperationalException, PwmUnrecoverableException + { + if ( password == null || password.isEmpty() ) + { + throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] + { + "can not set blank password", + } + ) ); + } + final String trimmedPassword = password.trim(); + if ( trimmedPassword.length() < 1 ) + { + throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] + { + "can not set blank password", + } + ) ); + } + + + final String passwordHash = BCrypt.hashPassword( password ); + this.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, passwordHash ); + } + + private void update( final FunctionWithException function ) throws PwmUnrecoverableException + { + try + { + ref.updateAndGet( storedConfigData -> + { + try + { + return function.applyThrows( storedConfigData ); + } + catch ( final PwmUnrecoverableException e ) + { + throw new RuntimeException( e ); + } + } ); + } + catch ( final RuntimeException e ) + { + throw ( PwmUnrecoverableException ) e.getCause(); + } + ref.updateAndGet( storedConfigData -> storedConfigData.toBuilder().modifyTime( Instant.now() ).build() ); + } + + interface FunctionWithException + { + T applyThrows( T value ) throws PwmUnrecoverableException; + } +} diff --git a/server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java b/server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java index 38c7143b4..14c67ad32 100644 --- a/server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java +++ b/server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java @@ -20,24 +20,49 @@ package password.pwm.config.stored; +import password.pwm.PwmConstants; +import password.pwm.bean.UserIdentity; +import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingCategory; import password.pwm.config.PwmSettingSyntax; import password.pwm.config.StoredValue; +import password.pwm.config.value.PasswordValue; +import password.pwm.error.ErrorInformation; +import password.pwm.error.PwmError; +import password.pwm.error.PwmOperationalException; +import password.pwm.error.PwmUnrecoverableException; +import password.pwm.util.PasswordData; import password.pwm.util.java.StringUtil; +import password.pwm.util.java.TimeDuration; +import password.pwm.util.logging.PwmLogger; +import password.pwm.util.secure.BCrypt; +import password.pwm.util.secure.PwmRandom; -import java.io.Serializable; +import java.time.Instant; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; 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.TreeSet; +import java.util.stream.Collectors; public abstract class StoredConfigurationUtil { + private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigurationUtil.class ); + public static List profilesForSetting - ( final PwmSetting pwmSetting, - final StoredConfiguration storedConfiguration + ( + final PwmSetting pwmSetting, + final StoredConfiguration storedConfiguration ) { if ( !pwmSetting.getCategory().hasProfiles() && pwmSetting.getSyntax() != PwmSettingSyntax.PROFILE ) @@ -88,7 +113,7 @@ private static List profilesForProfileSetting( profileSetting = pwmSetting.getCategory().getProfileSetting(); } - final Object nativeObject = storedConfiguration.readSetting( profileSetting ).toNativeObject(); + final Object nativeObject = storedConfiguration.readSetting( profileSetting, null ).toNativeObject(); final List settingValues = ( List ) nativeObject; final LinkedList profiles = new LinkedList<>( settingValues ); profiles.removeIf( profile -> StringUtil.isEmpty( profile ) ); @@ -96,66 +121,352 @@ private static List profilesForProfileSetting( } + public static String changeLogAsDebugString( + final StoredConfiguration storedConfiguration, + final Set configChangeLog, + final Locale locale + ) + throws PwmUnrecoverableException + { + + final Map outputMap = StoredConfigurationUtil.makeDebugMap( storedConfiguration, configChangeLog, locale ); + final StringBuilder output = new StringBuilder(); + if ( outputMap.isEmpty() ) + { + output.append( "No setting changes." ); + } + else + { + for ( final Map.Entry entry : outputMap.entrySet() ) + { + final String keyName = entry.getKey(); + final String value = entry.getValue(); + output.append( keyName ); + output.append( "\n" ); + output.append( " Value: " ); + output.append( value ); + output.append( "\n" ); + } + } + return output.toString(); + + } - public static List modifiedSettings( final StoredConfiguration storedConfiguration ) + public static StoredConfiguration copyConfigAndBlankAllPasswords( final StoredConfiguration input ) + throws PwmUnrecoverableException { - final List returnObj = new ArrayList<>(); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( input ); - for ( final PwmSetting setting : PwmSetting.values() ) + for ( final StoredConfigItemKey storedConfigItemKey : input.modifiedItems() ) { - if ( setting.getSyntax() != PwmSettingSyntax.PROFILE && !setting.getCategory().hasProfiles() ) + if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) { - if ( !storedConfiguration.isDefaultValue( setting, null ) ) + final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting(); + if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD ) { - final StoredConfigReference storedConfigReference = new StoredConfigReferenceBean( - StoredConfigReference.RecordType.SETTING, - setting.getKey(), - null - ); - returnObj.add( storedConfigReference ); + final ValueMetaData valueMetaData = input.readSettingMetadata( pwmSetting, storedConfigItemKey.getProfileID() ); + final UserIdentity userIdentity = valueMetaData == null ? null : valueMetaData.getUserIdentity(); + final PasswordValue passwordValue = new PasswordValue( new PasswordData( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ) ); + modifier.writeSetting( pwmSetting, storedConfigItemKey.getProfileID(), passwordValue, userIdentity ); } } } - for ( final PwmSettingCategory category : PwmSettingCategory.values() ) + + final Optional pwdHash = input.readConfigProperty( ConfigurationProperty.PASSWORD_HASH ); + if ( pwdHash.isPresent() ) { - if ( category.hasProfiles() ) + modifier.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ); + } + + return modifier.newStoredConfiguration(); + } + + public static List validateValues( final StoredConfiguration storedConfiguration ) + { + final Instant startTime = Instant.now(); + final List errorStrings = new ArrayList<>(); + + for ( final StoredConfigItemKey storedConfigItemKey : storedConfiguration.modifiedItems() ) + { + if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) { - for ( final String profileID : profilesForSetting( category.getProfileSetting(), storedConfiguration ) ) + final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting(); + final String profileID = storedConfigItemKey.getProfileID(); + final StoredValue loopValue = storedConfiguration.readSetting( pwmSetting, profileID ); + + try { - for ( final PwmSetting setting : category.getSettings() ) + final List errors = loopValue.validateValue( pwmSetting ); + for ( final String loopError : errors ) { - if ( !storedConfiguration.isDefaultValue( setting, profileID ) ) - { - final StoredConfigReference storedConfigReference = new StoredConfigReferenceBean( - StoredConfigReference.RecordType.SETTING, - setting.getKey(), - profileID - ); - returnObj.add( storedConfigReference ); - } + errorStrings.add( pwmSetting.toMenuLocationDebug( storedConfigItemKey.getProfileID(), PwmConstants.DEFAULT_LOCALE ) + " - " + loopError ); + } + } + catch ( final Exception e ) + { + LOGGER.error( "unexpected error during validate value for " + + pwmSetting.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + ", error: " + + e.getMessage(), e ); + } + } + } + + LOGGER.trace( () -> "StoredConfiguration validator completed in " + TimeDuration.compactFromCurrent( startTime ) ); + return errorStrings; + } + + public static Set search( final StoredConfiguration storedConfiguration, final String searchTerm, final Locale locale ) + { + return new SettingSearchMachine( storedConfiguration, searchTerm, locale ).search(); + } + + public static boolean matchSetting( + final StoredConfiguration storedConfiguration, + final PwmSetting setting, + final StoredValue storedValue, + final String term, + final Locale defaultLocale ) + { + return new SettingSearchMachine( storedConfiguration, term, defaultLocale ).matchSetting( setting, storedValue, term ); + } + + private static class SettingSearchMachine + { + private final StoredConfiguration storedConfiguration; + private final String searchTerm; + private final Locale locale; + + private SettingSearchMachine( final StoredConfiguration storedConfiguration, final String searchTerm, final Locale locale ) + { + this.storedConfiguration = storedConfiguration; + this.searchTerm = searchTerm; + this.locale = locale; + } + + public Set search() + { + if ( StringUtil.isEmpty( searchTerm ) ) + { + return Collections.emptySet(); + } + + return allPossibleSettingKeysForConfiguration( storedConfiguration ) + .parallelStream() + .filter( s -> s.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) + .filter( this::matchSetting ) + .collect( Collectors.toCollection( TreeSet::new ) ); + } + + private boolean matchSetting( + final StoredConfigItemKey storedConfigItemKey + ) + { + final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting(); + final StoredValue value = storedConfiguration.readSetting( pwmSetting, storedConfigItemKey.getProfileID() ); + + return StringUtil.whitespaceSplit( searchTerm ) + .parallelStream() + .allMatch( s -> matchSetting( pwmSetting, value, s ) ); + } + + private boolean matchSetting( final PwmSetting setting, final StoredValue value, final String searchTerm ) + { + if ( setting.isHidden() || setting.getCategory().isHidden() ) + { + return false; + } + + if ( searchTerm == null || searchTerm.isEmpty() ) + { + return false; + } + + final String lowerSearchTerm = searchTerm.toLowerCase(); + + { + final String key = setting.getKey(); + if ( key.toLowerCase().contains( lowerSearchTerm ) ) + { + return true; + } + } + { + final String label = setting.getLabel( locale ); + if ( label.toLowerCase().contains( lowerSearchTerm ) ) + { + return true; + } + } + { + final String descr = setting.getDescription( locale ); + if ( descr.toLowerCase().contains( lowerSearchTerm ) ) + { + return true; + } + } + { + final String menuLocationString = setting.toMenuLocationDebug( null, locale ); + if ( menuLocationString.toLowerCase().contains( lowerSearchTerm ) ) + { + return true; + } + } + + if ( setting.isConfidential() ) + { + return false; + } + { + final String valueDebug = value.toDebugString( locale ); + if ( valueDebug != null && valueDebug.toLowerCase().contains( lowerSearchTerm ) ) + { + return true; + } + } + if ( PwmSettingSyntax.SELECT == setting.getSyntax() + || PwmSettingSyntax.OPTIONLIST == setting.getSyntax() + || PwmSettingSyntax.VERIFICATION_METHOD == setting.getSyntax() + ) + { + for ( final String key : setting.getOptions().keySet() ) + { + if ( key.toLowerCase().contains( lowerSearchTerm ) ) + { + return true; + } + final String optionValue = setting.getOptions().get( key ); + if ( optionValue != null && optionValue.toLowerCase().contains( lowerSearchTerm ) ) + { + return true; } } } + return false; } + } - return returnObj; + public static boolean verifyPassword( final StoredConfiguration storedConfiguration, final String password ) + { + if ( !hasPassword( storedConfiguration ) ) + { + return false; + } + final Optional passwordHash = storedConfiguration.readConfigProperty( ConfigurationProperty.PASSWORD_HASH ); + return passwordHash.isPresent() && BCrypt.testAnswer( password, passwordHash.get(), new Configuration( storedConfiguration ) ); } - public static Serializable toJsonDebugObject( final StoredConfiguration storedConfiguration ) + public static boolean hasPassword( final StoredConfiguration storedConfiguration ) { - final TreeMap outputObject = new TreeMap<>(); + final Optional passwordHash = storedConfiguration.readConfigProperty( ConfigurationProperty.PASSWORD_HASH ); + return passwordHash.isPresent(); + } - for ( final StoredConfigReference storedConfigReference : modifiedSettings( storedConfiguration ) ) + public static void setPassword( final StoredConfigurationModifier storedConfiguration, final String password ) + throws PwmOperationalException, PwmUnrecoverableException + { + if ( StringUtil.isEmpty( password ) ) + { + throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] + { + "can not set blank password", + } + ) ); + } + final String trimmedPassword = password.trim(); + if ( trimmedPassword.length() < 1 ) { - final PwmSetting setting = PwmSetting.forKey( storedConfigReference.getRecordID() ); - if ( setting != null ) + throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] + { + "can not set blank password", + } + ) ); + } + + + final String passwordHash = BCrypt.hashPassword( password ); + storedConfiguration.writeConfigProperty( ConfigurationProperty.PASSWORD_HASH, passwordHash ); + } + + public static void initNewRandomSecurityKey( final StoredConfigurationModifier modifier ) + throws PwmUnrecoverableException + { + if ( !modifier.newStoredConfiguration().isDefaultValue( PwmSetting.PWM_SECURITY_KEY, null ) ) + { + return; + } + + modifier.writeSetting( + PwmSetting.PWM_SECURITY_KEY, null, + new PasswordValue( new PasswordData( PwmRandom.getInstance().alphaNumericString( 1024 ) ) ), + null + ); + + LOGGER.debug( () -> "initialized new random security key" ); + } + + + public static Map makeDebugMap( + final StoredConfiguration storedConfiguration, + final Collection interestedItems, + final Locale locale + ) + { + final Map outputMap = interestedItems.stream() + .filter( ( key ) -> key.getRecordType() != StoredConfigItemKey.RecordType.PROPERTY ) + .filter( ( key ) -> storedConfiguration.readStoredValue( key ).isPresent() ) + .collect( Collectors.toMap( + key -> key.getLabel( locale ), + key -> storedConfiguration.readStoredValue( key ).get().toDebugString( locale ) ) ); + + return Collections.unmodifiableMap( new TreeMap<>( outputMap ) ); + } + + public static Set allPossibleSettingKeysForConfiguration( + final StoredConfiguration storedConfiguration + ) + { + final Set loopResults = new HashSet<>(); + for ( final PwmSetting loopSetting : PwmSetting.values() ) + { + if ( loopSetting.getCategory().hasProfiles() ) { - final StoredValue value = storedConfiguration.readSetting( setting, storedConfigReference.getProfileID() ); - outputObject.put( setting.getKey(), value.toDebugJsonObject( null ) ); + for ( final String profile : storedConfiguration.profilesForSetting( loopSetting ) ) + { + loopResults.add( StoredConfigItemKey.fromSetting( loopSetting, profile ) ); + } + } + else + { + loopResults.add( StoredConfigItemKey.fromSetting( loopSetting, null ) ); } } - return outputObject; + return Collections.unmodifiableSet( loopResults ); } + public static Set changedValues ( + final StoredConfiguration originalConfiguration, + final StoredConfiguration modifiedConfiguration + ) + { + final Instant startTime = Instant.now(); + + final Set interestedReferences = new HashSet<>(); + interestedReferences.addAll( originalConfiguration.modifiedItems() ); + interestedReferences.addAll( modifiedConfiguration.modifiedItems() ); + + final Set deltaReferences = interestedReferences + .parallelStream() + .filter( reference -> + { + final Optional hash1 = originalConfiguration.readStoredValue( reference ).map( StoredValue::valueHash ); + final Optional hash2 = modifiedConfiguration.readStoredValue( reference ).map( StoredValue::valueHash ); + return hash1.isPresent() && hash2.isPresent() && !Objects.equals( hash1.get(), hash2.get() ); + } + ).collect( Collectors.toSet() ); + + LOGGER.trace( () -> "generated changeLog items via compare in " + TimeDuration.compactFromCurrent( startTime ) ); + + return Collections.unmodifiableSet( deltaReferences ); + } } diff --git a/server/src/main/java/password/pwm/config/stored/ConfigChangeLog.java b/server/src/main/java/password/pwm/config/stored/XmlOutputProcessData.java similarity index 61% rename from server/src/main/java/password/pwm/config/stored/ConfigChangeLog.java rename to server/src/main/java/password/pwm/config/stored/XmlOutputProcessData.java index 005be3e7a..19711f25d 100644 --- a/server/src/main/java/password/pwm/config/stored/ConfigChangeLog.java +++ b/server/src/main/java/password/pwm/config/stored/XmlOutputProcessData.java @@ -20,20 +20,16 @@ package password.pwm.config.stored; -import password.pwm.config.StoredValue; - -import java.util.Collection; -import java.util.Locale; - -public interface ConfigChangeLog +import lombok.Builder; +import lombok.Value; +import password.pwm.config.value.StoredValueEncoder; +import password.pwm.util.secure.PwmSecurityKey; + +@Value +@Builder +public class XmlOutputProcessData { - boolean isModified( ); - - String changeLogAsDebugString( Locale locale, boolean asHtml ); - - void updateChangeLog( StoredConfigReference reference, StoredValue newValue ); - - void updateChangeLog( StoredConfigReference reference, StoredValue currentValue, StoredValue newValue ); - - Collection changedValues( ); + @Builder.Default + private StoredValueEncoder.Mode storedValueEncoderMode = StoredValueEncoder.Mode.ENCODED; + private PwmSecurityKey pwmSecurityKey; } diff --git a/server/src/main/java/password/pwm/config/stored/ng/NGStorageEngineImpl.java b/server/src/main/java/password/pwm/config/stored/ng/NGStorageEngineImpl.java deleted file mode 100644 index 8a45626b1..000000000 --- a/server/src/main/java/password/pwm/config/stored/ng/NGStorageEngineImpl.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2019 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.config.stored.ng; - -import password.pwm.bean.UserIdentity; -import password.pwm.config.StoredValue; -import password.pwm.config.stored.StoredConfigReference; -import password.pwm.config.stored.ValueMetaData; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; - -class NGStorageEngineImpl -{ - private final Map storedValues = new HashMap<>(); - private final Map metaValues = new HashMap<>(); - - NGStorageEngineImpl() - { - } - - NGStorageEngineImpl( - final Map storedValues, - final Map metaValues - ) - { - this.storedValues.putAll( storedValues ); - this.metaValues.putAll( metaValues ); - } - - StoredValue read( final StoredConfigReference storedConfigReference ) - { - return storedValues.get( storedConfigReference ); - } - - ValueMetaData readMetaData( final StoredConfigReference storedConfigReference ) - { - return metaValues.get( storedConfigReference ); - } - - void writeMetaData( final StoredConfigReference storedConfigReference, final ValueMetaData valueMetaData ) - { - metaValues.put( storedConfigReference, valueMetaData ); - } - - void write( final StoredConfigReference reference, final StoredValue value, final UserIdentity userIdentity ) - { - if ( reference != null ) - { - if ( value != null ) - { - storedValues.put( reference, value ); - } - - updateUserIdentity( reference, userIdentity ); - } - } - - void reset( final StoredConfigReference reference, final UserIdentity userIdentity ) - { - if ( reference != null ) - { - storedValues.remove( reference ); - updateUserIdentity( reference, userIdentity ); - } - } - - private void updateUserIdentity( - final StoredConfigReference reference, - final UserIdentity userIdentity - ) - { - metaValues.put( - reference, - ValueMetaData.builder().modifyDate( Instant.now() ) - .userIdentity( userIdentity ) - .build() ); - - } -} diff --git a/server/src/main/java/password/pwm/config/stored/ng/NGStoredConfiguration.java b/server/src/main/java/password/pwm/config/stored/ng/NGStoredConfiguration.java deleted file mode 100644 index 9341afe0a..000000000 --- a/server/src/main/java/password/pwm/config/stored/ng/NGStoredConfiguration.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2019 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.config.stored.ng; - -import password.pwm.bean.UserIdentity; -import password.pwm.config.PwmSetting; -import password.pwm.config.PwmSettingCategory; -import password.pwm.config.StoredValue; -import password.pwm.config.stored.ConfigurationProperty; -import password.pwm.config.stored.StoredConfigReference; -import password.pwm.config.stored.StoredConfigReferenceBean; -import password.pwm.config.stored.StoredConfiguration; -import password.pwm.config.stored.ValueMetaData; -import password.pwm.config.value.StringValue; -import password.pwm.error.PwmUnrecoverableException; -import password.pwm.util.java.JavaHelper; -import password.pwm.util.logging.PwmLogger; -import password.pwm.util.secure.PwmSecurityKey; - -import java.time.Instant; - -public class NGStoredConfiguration implements StoredConfiguration -{ - private static final PwmLogger LOGGER = PwmLogger.forClass( NGStoredConfiguration.class ); - private final PwmSecurityKey configurationSecurityKey; - private final NGStorageEngineImpl engine; - private boolean readOnly = false; - - NGStoredConfiguration( - final NGStorageEngineImpl storageEngine, - final PwmSecurityKey pwmSecurityKey ) - { - engine = storageEngine; - configurationSecurityKey = pwmSecurityKey; - } - - public String readConfigProperty( final ConfigurationProperty configurationProperty ) - { - final StoredConfigReference storedConfigReference = new StoredConfigReferenceBean( - StoredConfigReference.RecordType.PROPERTY, - configurationProperty.getKey(), - null - ); - final StoredValue storedValue = engine.read( storedConfigReference ); - if ( storedValue == null | !( storedValue instanceof StringValue ) ) - { - return null; - } - return ( String ) storedValue.toNativeObject(); - } - - public void writeConfigProperty( - final ConfigurationProperty configurationProperty, - final String value ) - { - final StoredConfigReference storedConfigReference = new StoredConfigReferenceBean( - StoredConfigReference.RecordType.PROPERTY, - configurationProperty.getKey(), - null - ); - final StoredValue storedValue = new StringValue( value ); - engine.write( storedConfigReference, storedValue, null ); - } - - public void resetSetting( final PwmSetting setting, final String profileID, final UserIdentity userIdentity ) - { - final StoredConfigReference storedConfigReference = new StoredConfigReferenceBean( - StoredConfigReference.RecordType.SETTING, - setting.getKey(), - profileID - ); - engine.reset( storedConfigReference, userIdentity ); - } - - public boolean isDefaultValue( final PwmSetting setting ) - { - return isDefaultValue( setting, null ); - } - - public boolean isDefaultValue( final PwmSetting setting, final String profileID ) - { - final StoredConfigReference storedConfigReference = new StoredConfigReferenceBean( - StoredConfigReference.RecordType.SETTING, - setting.getKey(), - profileID - ); - final StoredValue value = engine.read( storedConfigReference ); - return value == null; - } - - public StoredValue readSetting( final PwmSetting setting ) - { - return readSetting( setting, null ); - } - - public StoredValue readSetting( final PwmSetting setting, final String profileID ) - { - final StoredConfigReference storedConfigReference = new StoredConfigReferenceBean( - StoredConfigReference.RecordType.SETTING, - setting.getKey(), - profileID - ); - return engine.read( storedConfigReference ); - } - - public void copyProfileID( final PwmSettingCategory category, final String sourceID, final String destinationID, final UserIdentity userIdentity ) - throws PwmUnrecoverableException - { - //@todo - throw new IllegalStateException( "not implemented" ); - } - - public void writeSetting( - final PwmSetting setting, - final StoredValue value, - final UserIdentity userIdentity - ) throws PwmUnrecoverableException - { - writeSetting( setting, null, value, userIdentity ); - } - - public void writeSetting( - final PwmSetting setting, - final String profileID, - final StoredValue value, - final UserIdentity userIdentity - ) - throws PwmUnrecoverableException - { - final StoredConfigReference storedConfigReference = new StoredConfigReferenceBean( - StoredConfigReference.RecordType.SETTING, - setting.getKey(), - profileID - ); - engine.write( storedConfigReference, value, userIdentity ); - } - - @Override - public PwmSecurityKey getKey( ) throws PwmUnrecoverableException - { - return configurationSecurityKey; - } - - @Override - public boolean isLocked( ) - { - return readOnly; - } - - @Override - public void lock( ) - { - readOnly = true; - } - - @Override - public ValueMetaData readSettingMetadata( final PwmSetting setting, final String profileID ) - { - final StoredConfigReference storedConfigReference = new StoredConfigReferenceBean( - StoredConfigReference.RecordType.SETTING, - setting.getKey(), - profileID - ); - return engine.readMetaData( storedConfigReference ); - } - - public Instant modifyTime( ) - { - final String modifyTimeString = readConfigProperty( ConfigurationProperty.MODIFIFICATION_TIMESTAMP ); - if ( modifyTimeString != null ) - { - try - { - return JavaHelper.parseIsoToInstant( ( modifyTimeString ) ); - } - catch ( Exception e ) - { - LOGGER.error( "error parsing last modified timestamp property: " + e.getMessage() ); - } - } - return null; - } - -} diff --git a/server/src/main/java/password/pwm/config/stored/ng/NGStoredConfigurationFactory.java b/server/src/main/java/password/pwm/config/stored/ng/NGStoredConfigurationFactory.java deleted file mode 100644 index f61de0d80..000000000 --- a/server/src/main/java/password/pwm/config/stored/ng/NGStoredConfigurationFactory.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2019 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.config.stored.ng; - -import password.pwm.bean.UserIdentity; -import password.pwm.config.PwmSetting; -import password.pwm.config.StoredValue; -import password.pwm.config.stored.StoredConfigReference; -import password.pwm.config.stored.StoredConfigReferenceBean; -import password.pwm.config.stored.StoredConfiguration; -import password.pwm.config.stored.ValueMetaData; -import password.pwm.config.value.StringValue; -import password.pwm.config.value.ValueFactory; -import password.pwm.error.PwmUnrecoverableException; -import password.pwm.util.java.JavaHelper; -import password.pwm.util.java.XmlDocument; -import password.pwm.util.java.XmlElement; -import password.pwm.util.java.XmlFactory; -import password.pwm.util.logging.PwmLogger; -import password.pwm.util.secure.PwmSecurityKey; - -import java.io.InputStream; -import java.io.OutputStream; -import java.time.Instant; - - -public class NGStoredConfigurationFactory -{ - private static final PwmLogger LOGGER = PwmLogger.forClass( NGStoredConfigurationFactory.class ); - - //@Override - public NGStoredConfiguration fromXml( final InputStream inputStream ) throws PwmUnrecoverableException - { - return XmlEngine.fromXmlImpl( inputStream ); - } - - //@Override - public void toXml( final OutputStream outputStream ) - { - } - - private static class XmlEngine - { - static NGStoredConfiguration fromXmlImpl( final InputStream inputStream ) - throws PwmUnrecoverableException - { - final NGStorageEngineImpl storageEngine = new NGStorageEngineImpl(); - - final XmlDocument inputDocument = XmlFactory.getFactory().parseXml( inputStream ); - final XmlElement rootElement = inputDocument.getRootElement(); - - final PwmSecurityKey pwmSecurityKey = readSecurityKey( rootElement ); - - final XmlElement settingsElement = rootElement.getChild( StoredConfiguration.XML_ELEMENT_SETTINGS ); - - for ( final XmlElement loopElement : settingsElement.getChildren() ) - { - if ( StoredConfiguration.XML_ELEMENT_PROPERTIES.equals( loopElement.getName() ) ) - { - for ( final XmlElement propertyElement : loopElement.getChildren( StoredConfiguration.XML_ELEMENT_PROPERTY ) ) - { - readInterestingElement( propertyElement, pwmSecurityKey, storageEngine ); - } - } - else if ( StoredConfiguration.XML_ELEMENT_SETTING.equals( loopElement.getName() ) ) - { - readInterestingElement( loopElement, pwmSecurityKey, storageEngine ); - } - } - return new NGStoredConfiguration( storageEngine, pwmSecurityKey ); - } - - static void readInterestingElement( - final XmlElement loopElement, - final PwmSecurityKey pwmSecurityKey, - final NGStorageEngineImpl engine - ) - { - final StoredConfigReference reference = referenceForElement( loopElement ); - if ( reference != null ) - { - switch ( reference.getRecordType() ) - { - case SETTING: - { - final StoredValue storedValue = readSettingValue( reference, loopElement, pwmSecurityKey ); - if ( storedValue != null ) - { - engine.write( reference, storedValue, null ); - } - } - break; - - case PROPERTY: - { - final StoredValue storedValue = readPropertyValue( reference, loopElement ); - if ( storedValue != null ) - { - engine.write( reference, storedValue, null ); - } - } - break; - - default: - throw new IllegalArgumentException( "unimplemented setting recordtype in reader" ); - } - engine.writeMetaData( reference, readValueMetaData( loopElement ) ); - } - } - - static PwmSecurityKey readSecurityKey( final XmlElement rootElement ) - throws PwmUnrecoverableException - { - final String createTime = rootElement.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_CREATE_TIME ); - return new PwmSecurityKey( createTime + "StoredConfiguration" ); - } - - static StoredValue readSettingValue( - final StoredConfigReference storedConfigReference, - final XmlElement settingElement, - final PwmSecurityKey pwmSecurityKey - ) - { - final String key = storedConfigReference.getRecordID(); - final PwmSetting pwmSetting = PwmSetting.forKey( key ); - - if ( pwmSetting == null ) - { - LOGGER.debug( () -> "ignoring setting for unknown key: " + key ); - } - else - { - LOGGER.trace( () -> "parsing setting key=" + key + ", profile=" + storedConfigReference.getProfileID() ); - final XmlElement defaultElement = settingElement.getChild( StoredConfiguration.XML_ELEMENT_DEFAULT ); - if ( defaultElement != null ) - { - return null; - } - - { - try - { - return ValueFactory.fromXmlValues( pwmSetting, settingElement, pwmSecurityKey ); - } - catch ( IllegalStateException e ) - { - LOGGER.error( "error parsing configuration setting " + storedConfigReference + ", error: " + e.getMessage() ); - } - } - } - return null; - } - - static StoredValue readPropertyValue( - final StoredConfigReference storedConfigReference, - final XmlElement settingElement - ) - { - final String key = storedConfigReference.getRecordID(); - - LOGGER.trace( () -> "parsing property key=" + key + ", profile=" + storedConfigReference.getProfileID() ); - if ( settingElement.getChild( StoredConfiguration.XML_ELEMENT_DEFAULT ) != null ) - { - return new StringValue( settingElement.getText() ); - } - return null; - } - - static StoredConfigReference referenceForElement( final XmlElement settingElement ) - { - final String key = settingElement.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_KEY ); - final String profileID = readProfileID( settingElement ); - final StoredConfigReference.RecordType recordType; - switch ( settingElement.getName() ) - { - case StoredConfiguration.XML_ELEMENT_SETTING: - recordType = StoredConfigReference.RecordType.SETTING; - break; - - case StoredConfiguration.XML_ELEMENT_PROPERTY: - recordType = StoredConfigReference.RecordType.PROPERTY; - break; - - case StoredConfiguration.XML_ELEMENT_LOCALEBUNDLE: - recordType = StoredConfigReference.RecordType.LOCALE_BUNDLE; - break; - - default: - LOGGER.warn( "unrecognized xml element " + settingElement.getName() + " in configuration" ); - return null; - } - - - return new StoredConfigReferenceBean( - recordType, - key, - profileID - ); - } - - static String readProfileID( final XmlElement settingElement ) - { - final String profileIDStr = settingElement.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_PROFILE ); - return profileIDStr != null && !profileIDStr.isEmpty() ? profileIDStr : null; - } - - static ValueMetaData readValueMetaData( final XmlElement element ) - { - final String modifyDateStr = element.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_MODIFY_TIME ); - Instant modifyDate = null; - try - { - modifyDate = modifyDateStr == null || modifyDateStr.isEmpty() - ? null - : JavaHelper.parseIsoToInstant( modifyDateStr ); - } - catch ( Exception e ) - { - LOGGER.warn( "error parsing stored date: " + e.getMessage() ); - } - final String modifyUser = element.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_MODIFY_USER ); - final String modifyUserProfile = element.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_MODIFY_USER_PROFILE ); - final UserIdentity userIdentity; - userIdentity = modifyUser != null - ? new UserIdentity( modifyUser, modifyUserProfile ) - : null; - - return ValueMetaData.builder() - .modifyDate( modifyDate ) - .userIdentity( userIdentity ) - .build(); - } - } - -} diff --git a/server/src/main/java/password/pwm/config/value/AbstractValue.java b/server/src/main/java/password/pwm/config/value/AbstractValue.java index 75e8d9e26..89ed08410 100644 --- a/server/src/main/java/password/pwm/config/value/AbstractValue.java +++ b/server/src/main/java/password/pwm/config/value/AbstractValue.java @@ -20,27 +20,31 @@ package password.pwm.config.value; -import lombok.Value; import password.pwm.PwmConstants; import password.pwm.config.StoredValue; -import password.pwm.error.ErrorInformation; -import password.pwm.error.PwmError; -import password.pwm.error.PwmOperationalException; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.error.PwmUnrecoverableException; import password.pwm.util.java.JsonUtil; -import password.pwm.util.secure.PwmBlockAlgorithm; -import password.pwm.util.secure.PwmRandom; +import password.pwm.util.java.LazySupplier; +import password.pwm.util.java.XmlDocument; +import password.pwm.util.java.XmlElement; +import password.pwm.util.java.XmlFactory; +import password.pwm.util.secure.PwmHashAlgorithm; import password.pwm.util.secure.PwmSecurityKey; import password.pwm.util.secure.SecureEngine; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.Serializable; +import java.util.List; import java.util.Locale; public abstract class AbstractValue implements StoredValue { - static final String ENC_PW_PREFIX = "ENC-PW:"; + private final transient LazySupplier valueHashSupplier = new LazySupplier<>( () -> valueHashComputer( AbstractValue.this ) ); - public String toString( ) + public String toString() { return toDebugString( null ); } @@ -57,84 +61,39 @@ public Serializable toDebugJsonObject( final Locale locale ) return ( Serializable ) this.toNativeObject(); } - public boolean requiresStoredUpdate( ) - { - return false; - } - @Override - public int currentSyntaxVersion( ) + public int currentSyntaxVersion() { return 0; } @Override - public String valueHash( ) throws PwmUnrecoverableException + public final String valueHash() { - return SecureEngine.hash( JsonUtil.serialize( ( Serializable ) this.toNativeObject() ), PwmConstants.SETTING_CHECKSUM_HASH_METHOD ); + return valueHashSupplier.get(); } - static String decryptPwValue( final String input, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException + static String valueHashComputer( final StoredValue storedValue ) { - if ( input == null ) + try { - return ""; - } + final PwmSecurityKey testingKey = new PwmSecurityKey( "test" ); + final XmlOutputProcessData xmlOutputProcessData = XmlOutputProcessData.builder() + .pwmSecurityKey( testingKey ) + .storedValueEncoderMode( StoredValueEncoder.Mode.PLAIN ) + .build(); + final List xmlValues = storedValue.toXmlValues( StoredConfigXmlConstants.XML_ELEMENT_VALUE, xmlOutputProcessData ); + final XmlDocument document = XmlFactory.getFactory().newDocument( "root" ); + document.getRootElement().addContent( xmlValues ); + final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + XmlFactory.getFactory().outputDocument( document, byteArrayOutputStream ); + final String stringToHash = new String( byteArrayOutputStream.toByteArray(), PwmConstants.DEFAULT_CHARSET ); + return SecureEngine.hash( stringToHash, PwmHashAlgorithm.SHA512 ); - if ( input.startsWith( ENC_PW_PREFIX ) ) - { - try - { - final String pwValueSuffix = input.substring( ENC_PW_PREFIX.length(), input.length() ); - final String decrpytedValue = SecureEngine.decryptStringValue( pwValueSuffix, pwmSecurityKey, PwmBlockAlgorithm.CONFIG ); - final StoredPwData storedPwData = JsonUtil.deserialize( decrpytedValue, StoredPwData.class ); - return storedPwData.getValue(); - } - catch ( Exception e ) - { - final String errorMsg = "unable to decrypt password value for setting: " + e.getMessage(); - final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg ); - throw new PwmOperationalException( errorInfo ); - } } - - return input; - } - - static String encryptPwValue( final String input, final PwmSecurityKey pwmSecurityKey ) - throws PwmOperationalException - { - if ( input == null ) + catch ( final IOException | PwmUnrecoverableException e ) { - return ""; + throw new IllegalStateException( e ); } - - if ( !input.startsWith( ENC_PW_PREFIX ) ) - { - try - { - final String salt = PwmRandom.getInstance().alphaNumericString( 32 ); - final StoredPwData storedPwData = new StoredPwData( salt, input ); - final String jsonData = JsonUtil.serialize( storedPwData ); - final String encryptedValue = SecureEngine.encryptToString( jsonData, pwmSecurityKey, PwmBlockAlgorithm.CONFIG ); - return ENC_PW_PREFIX + encryptedValue; - } - catch ( Exception e ) - { - final String errorMsg = "unable to encrypt password value for setting: " + e.getMessage(); - final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg ); - throw new PwmOperationalException( errorInfo ); - } - } - - return input; } - - @Value - static class StoredPwData implements Serializable - { - private String salt; - private String value; - } - } diff --git a/server/src/main/java/password/pwm/config/value/ActionValue.java b/server/src/main/java/password/pwm/config/value/ActionValue.java index 30bfd1cc9..16566b39a 100644 --- a/server/src/main/java/password/pwm/config/value/ActionValue.java +++ b/server/src/main/java/password/pwm/config/value/ActionValue.java @@ -25,9 +25,9 @@ import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingSyntax; import password.pwm.config.StoredValue; -import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.config.value.data.ActionConfiguration; -import password.pwm.config.value.data.ActionConfigurationOldVersion1; import password.pwm.error.PwmOperationalException; import password.pwm.util.java.JavaHelper; import password.pwm.util.java.JsonUtil; @@ -46,6 +46,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; public class ActionValue extends AbstractValue implements StoredValue @@ -98,8 +99,8 @@ public ActionValue fromXmlElement( final List values = new ArrayList<>(); final boolean oldType = PwmSettingSyntax.STRING_ARRAY.toString().equals( - settingElement.getAttributeValue( "syntax" ) ); - final List valueElements = settingElement.getChildren( "value" ); + settingElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_SYNTAX ) ); + final List valueElements = settingElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); for ( final XmlElement loopValueElement : valueElements ) { final String stringValue = loopValueElement.getText(); @@ -109,17 +110,28 @@ public ActionValue fromXmlElement( { if ( oldType ) { - if ( loopValueElement.getAttributeValue( "locale" ) == null ) + if ( loopValueElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_LOCALE ) == null ) { - final ActionConfigurationOldVersion1 oldVersion1 = ActionConfigurationOldVersion1.parseOldConfigString( stringValue ); + final ActionConfiguration.ActionConfigurationOldVersion1 oldVersion1 = ActionConfiguration.ActionConfigurationOldVersion1 + .parseOldConfigString( stringValue ); values.add( convertOldVersion1Values( oldVersion1 ) ); } } else { - final ActionConfigurationOldVersion1 parsedAc = JsonUtil.deserialize( stringValue, ActionConfigurationOldVersion1.class ); - parsedAc.setPassword( decryptPwValue( parsedAc.getPassword(), pwmSecurityKey ) ); - values.add( convertOldVersion1Values( parsedAc ) ); + final ActionConfiguration.ActionConfigurationOldVersion1 parsedAc = JsonUtil + .deserialize( stringValue, ActionConfiguration.ActionConfigurationOldVersion1.class ); + if ( parsedAc != null ) + { + final Optional decodedValue = StoredValueEncoder.decode( + parsedAc.getPassword(), + StoredValueEncoder.Mode.ENCODED, + pwmSecurityKey ); + decodedValue.ifPresent( s -> + { + values.add( convertOldVersion1Values( parsedAc.toBuilder().password( s ).build() ) ); + } ); + } } } else if ( syntaxVersion == 2 ) @@ -136,14 +148,23 @@ else if ( syntaxVersion == 2 ) // decrypt pw try { - clonedWebActions.add( webAction.toBuilder() - .password( decryptPwValue( webAction.getPassword(), pwmSecurityKey ) ) - .successStatus( successStatus ) - .build() ); + + + final Optional decodedValue = StoredValueEncoder.decode( + webAction.getPassword(), + StoredValueEncoder.Mode.ENCODED, + pwmSecurityKey ); + decodedValue.ifPresent( s -> + { + clonedWebActions.add( webAction.toBuilder() + .password( s ) + .successStatus( successStatus ) + .build() ); + } ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { - LOGGER.warn( "error decoding stored pw value: " + e.getMessage() ); + LOGGER.warn( "error decoding stored pw value on setting '" + pwmSetting.getKey() + "': " + e.getMessage() ); } } @@ -162,7 +183,7 @@ else if ( syntaxVersion == 2 ) }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final ActionConfiguration value : values ) @@ -170,15 +191,22 @@ public List toXmlValues( final String valueElementName, final PwmSec final List clonedWebActions = new ArrayList<>(); for ( final ActionConfiguration.WebAction webAction : value.getWebActions() ) { - try - { - clonedWebActions.add( webAction.toBuilder() - .password( encryptPwValue( webAction.getPassword(), pwmSecurityKey ) ) - .build() ); - } - catch ( PwmOperationalException e ) + if ( !StringUtil.isEmpty( webAction.getPassword() ) ) { - LOGGER.warn( "error encoding stored pw value: " + e.getMessage() ); + try + { + final String encodedValue = StoredValueEncoder.encode( + webAction.getPassword(), + xmlOutputProcessData.getStoredValueEncoderMode(), + xmlOutputProcessData.getPwmSecurityKey() ); + clonedWebActions.add( webAction.toBuilder() + .password( encodedValue ) + .build() ); + } + catch ( final PwmOperationalException e ) + { + LOGGER.warn( "error encoding stored pw value: " + e.getMessage() ); + } } } @@ -225,7 +253,7 @@ public List validateValue( final PwmSetting pwmSetting ) { loopConfig.validate(); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { return Collections.singletonList( "format error: " + e.getErrorInformation().toDebugStr() ); } @@ -367,14 +395,14 @@ public int currentSyntaxVersion( ) private static int figureCurrentStoredSyntax( final XmlElement settingElement ) { - final String storedSyntaxVersionString = settingElement.getAttributeValue( StoredConfiguration.XML_ATTRIBUTE_SYNTAX_VERSION ); + final String storedSyntaxVersionString = settingElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_SYNTAX_VERSION ); if ( !StringUtil.isEmpty( storedSyntaxVersionString ) ) { try { return Integer.parseInt( storedSyntaxVersionString ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { LOGGER.debug( () -> "unable to parse syntax version for setting " + e.getMessage() ); } @@ -382,7 +410,7 @@ private static int figureCurrentStoredSyntax( final XmlElement settingElement ) return 0; } - private static ActionConfiguration convertOldVersion1Values( final ActionConfigurationOldVersion1 oldAction ) + private static ActionConfiguration convertOldVersion1Values( final ActionConfiguration.ActionConfigurationOldVersion1 oldAction ) { final ActionConfiguration.ActionConfigurationBuilder builder = ActionConfiguration.builder(); builder.name( oldAction.getName() ); diff --git a/server/src/main/java/password/pwm/config/value/BooleanValue.java b/server/src/main/java/password/pwm/config/value/BooleanValue.java index 8fc82a168..684e0120b 100644 --- a/server/src/main/java/password/pwm/config/value/BooleanValue.java +++ b/server/src/main/java/password/pwm/config/value/BooleanValue.java @@ -23,7 +23,8 @@ import password.pwm.PwmConstants; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; -import password.pwm.error.PwmUnrecoverableException; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.i18n.Display; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.XmlElement; @@ -34,10 +35,11 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Optional; public class BooleanValue implements StoredValue { - boolean value; + private final boolean value; public BooleanValue( final boolean value ) { @@ -56,9 +58,13 @@ public BooleanValue fromJson( final String value ) public BooleanValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey input ) { - final XmlElement valueElement = settingElement.getChild( "value" ); - final String value = valueElement.getText(); - return new BooleanValue( Boolean.valueOf( value ) ); + final Optional valueElement = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); + if ( valueElement.isPresent() ) + { + final String value = valueElement.get().getTextTrim(); + return new BooleanValue( Boolean.valueOf( value ) ); + } + return new BooleanValue( false ); } }; @@ -70,7 +76,7 @@ public List validateValue( final PwmSetting pwmSetting ) } @Override - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName ); valueElement.addText( String.valueOf( value ) ); @@ -99,12 +105,6 @@ public Serializable toDebugJsonObject( final Locale locale ) return value; } - @Override - public boolean requiresStoredUpdate( ) - { - return false; - } - @Override public int currentSyntaxVersion( ) { @@ -112,7 +112,7 @@ public int currentSyntaxVersion( ) } @Override - public String valueHash( ) throws PwmUnrecoverableException + public String valueHash() { return value ? "1" : "0"; } diff --git a/server/src/main/java/password/pwm/config/value/ChallengeValue.java b/server/src/main/java/password/pwm/config/value/ChallengeValue.java index 9f9d5d534..a6cc5931b 100644 --- a/server/src/main/java/password/pwm/config/value/ChallengeValue.java +++ b/server/src/main/java/password/pwm/config/value/ChallengeValue.java @@ -23,6 +23,7 @@ import com.google.gson.reflect.TypeToken; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.config.value.data.ChallengeItemConfiguration; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.JsonUtil; @@ -43,11 +44,11 @@ public class ChallengeValue extends AbstractValue implements StoredValue private static final PwmLogger LOGGER = PwmLogger.forClass( ChallengeValue.class ); //locale str as key. - final Map> values; + private final Map> values; ChallengeValue( final Map> values ) { - this.values = values; + this.values = values == null ? Collections.emptyMap() : Collections.unmodifiableMap( values ); } public static StoredValueFactory factory( ) @@ -111,7 +112,7 @@ public ChallengeValue fromXmlElement( }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final Map.Entry> entry : values.entrySet() ) @@ -207,7 +208,7 @@ private static ChallengeItemConfiguration parseOldVersionString( { minLength = Integer.parseInt( s1[ 1 ] ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "unexpected error parsing config input '" + inputString + "' " + e.getMessage() ); } @@ -218,7 +219,7 @@ private static ChallengeItemConfiguration parseOldVersionString( { maxLength = Integer.parseInt( s1[ 2 ] ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "unexpected error parsing config input '" + inputString + "' " + e.getMessage() ); } @@ -231,7 +232,12 @@ private static ChallengeItemConfiguration parseOldVersionString( adminDefined = false; } - return new ChallengeItemConfiguration( challengeText, minLength, maxLength, adminDefined ); + return ChallengeItemConfiguration.builder() + .text( challengeText ) + .minLength( minLength ) + .maxLength( maxLength ) + .adminDefined( adminDefined ) + .build(); } public String toDebugString( final Locale locale ) diff --git a/server/src/main/java/password/pwm/config/value/CustomLinkValue.java b/server/src/main/java/password/pwm/config/value/CustomLinkValue.java index 91397f41f..998d0002c 100644 --- a/server/src/main/java/password/pwm/config/value/CustomLinkValue.java +++ b/server/src/main/java/password/pwm/config/value/CustomLinkValue.java @@ -21,7 +21,8 @@ package password.pwm.config.value; import com.google.gson.reflect.TypeToken; -import password.pwm.config.CustomLinkConfiguration; +import password.pwm.config.stored.XmlOutputProcessData; +import password.pwm.config.value.data.CustomLinkConfiguration; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; import password.pwm.error.PwmOperationalException; @@ -39,11 +40,11 @@ public class CustomLinkValue extends AbstractValue implements StoredValue { - final List values; + private final List values; public CustomLinkValue( final List values ) { - this.values = values; + this.values = values == null ? Collections.emptyList() : Collections.unmodifiableList( values ); } public static StoredValueFactory factory( ) @@ -88,7 +89,7 @@ public CustomLinkValue fromXmlElement( final PwmSetting pwmSetting, final XmlEle }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final CustomLinkConfiguration value : values ) diff --git a/server/src/main/java/password/pwm/config/value/EmailValue.java b/server/src/main/java/password/pwm/config/value/EmailValue.java index 7f63e5478..8a54a1f80 100644 --- a/server/src/main/java/password/pwm/config/value/EmailValue.java +++ b/server/src/main/java/password/pwm/config/value/EmailValue.java @@ -25,6 +25,7 @@ import password.pwm.bean.EmailItemBean; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.error.PwmOperationalException; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.JsonUtil; @@ -42,11 +43,11 @@ public class EmailValue extends AbstractValue implements StoredValue { //key is locale identifier - final Map values; + private final Map values; EmailValue( final Map values ) { - this.values = values; + this.values = values == null ? Collections.emptyMap() : Collections.unmodifiableMap( values ); } public static StoredValueFactory factory( ) @@ -99,7 +100,7 @@ public EmailValue fromXmlElement( }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final Map.Entry entry : values.entrySet() ) diff --git a/server/src/main/java/password/pwm/config/value/FileValue.java b/server/src/main/java/password/pwm/config/value/FileValue.java index fdca4f287..99e84d28f 100644 --- a/server/src/main/java/password/pwm/config/value/FileValue.java +++ b/server/src/main/java/password/pwm/config/value/FileValue.java @@ -20,11 +20,12 @@ package password.pwm.config.value; +import lombok.Builder; import lombok.Value; -import password.pwm.PwmConstants; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; -import password.pwm.error.PwmOperationalException; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.bean.ImmutableByteArray; import password.pwm.util.java.JsonUtil; @@ -45,36 +46,23 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; public class FileValue extends AbstractValue implements StoredValue { private static final PwmLogger LOGGER = PwmLogger.forClass( FileValue.class ); - private Map values = new LinkedHashMap<>(); + private static final int ENCODING_LINE_LENGTH = 120; + private static final String XML_ELEMENT_FILE_INFORMATION = "FileInformation"; + private static final String XML_ELEMENT_FILE_CONTENT = "FileContent"; + private final Map values; + + @Value public static class FileInformation implements Serializable { private String filename; private String filetype; - - public FileInformation( - final String filename, - final String filetype - ) - { - this.filename = filename; - this.filetype = filetype; - } - - public String getFilename( ) - { - return filename; - } - - public String getFiletype( ) - { - return filetype; - } } @Value @@ -82,30 +70,24 @@ public static class FileContent implements Serializable { private ImmutableByteArray contents; - - public static FileContent fromEncodedString( final String input ) + static FileContent fromEncodedString( final String input ) throws IOException { - final byte[] convertedBytes = StringUtil.base64Decode( input ); + final String whitespaceStrippedInput = StringUtil.stripAllWhitespace( input ); + final byte[] convertedBytes = StringUtil.base64Decode( whitespaceStrippedInput ); return new FileContent( ImmutableByteArray.of( convertedBytes ) ); } - public String toEncodedString( ) + String toEncodedString( ) throws IOException { return StringUtil.base64Encode( contents.copyOf(), StringUtil.Base64Options.GZIP ); } - public String md5sum( ) - throws PwmUnrecoverableException - { - return SecureEngine.hash( new ByteArrayInputStream( contents.copyOf() ), PwmHashAlgorithm.MD5 ); - } - - public String sha1sum( ) + String sha512sum( ) throws PwmUnrecoverableException { - return SecureEngine.hash( new ByteArrayInputStream( contents.copyOf() ), PwmHashAlgorithm.SHA1 ); + return SecureEngine.hash( new ByteArrayInputStream( contents.copyOf() ), PwmHashAlgorithm.SHA512 ); } public int size( ) @@ -116,7 +98,7 @@ public int size( ) public FileValue( final Map values ) { - this.values = values; + this.values = values == null ? Collections.emptyMap() : Collections.unmodifiableMap( values ); } public static StoredValueFactory factory( ) @@ -125,30 +107,29 @@ public static StoredValueFactory factory( ) { public FileValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey input ) - throws PwmOperationalException { - final List valueElements = settingElement.getChildren( "value" ); + final List valueElements = settingElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); final Map values = new LinkedHashMap<>(); for ( final XmlElement loopValueElement : valueElements ) { - final XmlElement loopFileInformation = loopValueElement.getChild( "FileInformation" ); - if ( loopFileInformation != null ) + final Optional loopFileInformation = loopValueElement.getChild( XML_ELEMENT_FILE_INFORMATION ); + if ( loopFileInformation.isPresent() ) { - final String loopFileInformationJson = loopFileInformation.getText(); + final String loopFileInformationJson = loopFileInformation.get().getText(); final FileInformation fileInformation = JsonUtil.deserialize( loopFileInformationJson, FileInformation.class ); - final XmlElement loopFileContentElement = loopValueElement.getChild( "FileContent" ); - if ( loopFileContentElement != null ) + final Optional loopFileContentElement = loopValueElement.getChild( XML_ELEMENT_FILE_CONTENT ); + if ( loopFileContentElement.isPresent() ) { - final String fileContentString = loopFileContentElement.getText(); + final String fileContentString = loopFileContentElement.get().getText(); final FileContent fileContent; try { fileContent = FileContent.fromEncodedString( fileContentString ); values.put( fileInformation, fileContent ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.error( "error reading file contents item: " + e.getMessage(), e ); } @@ -165,7 +146,7 @@ public StoredValue fromJson( final String input ) }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final Map.Entry entry : this.values.entrySet() ) @@ -174,16 +155,19 @@ public List toXmlValues( final String valueElementName, final PwmSec final FileContent fileContent = entry.getValue(); final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName ); - final XmlElement fileInformationElement = XmlFactory.getFactory().newElement( "FileInformation" ); + final XmlElement fileInformationElement = XmlFactory.getFactory().newElement( XML_ELEMENT_FILE_INFORMATION ); fileInformationElement.addText( JsonUtil.serialize( fileInformation ) ); valueElement.addContent( fileInformationElement ); - final XmlElement fileContentElement = XmlFactory.getFactory().newElement( "FileContent" ); + final XmlElement fileContentElement = XmlFactory.getFactory().newElement( XML_ELEMENT_FILE_CONTENT ); + try { - fileContentElement.addText( fileContent.toEncodedString() ); + final String encodedLineBreaks = StringUtil.insertRepeatedLineBreaks( + fileContent.toEncodedString(), ENCODING_LINE_LENGTH ); + fileContentElement.addText( encodedLineBreaks ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.error( "unexpected error writing setting to xml, IO error during base64 encoding: " + e.getMessage() ); } @@ -221,7 +205,7 @@ public Serializable toDebugJsonObject( final Locale locale ) return ( Serializable ) asMetaData(); } - public List> asMetaData( ) + List> asMetaData( ) { final List> output = new ArrayList<>(); for ( final Map.Entry entry : this.values.entrySet() ) @@ -234,9 +218,9 @@ public List> asMetaData( ) details.put( "size", fileContent.size() ); try { - details.put( "md5sum", fileContent.md5sum() ); + details.put( "sha512sum", fileContent.sha512sum() ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.trace( () -> "error generating file hash" ); } @@ -256,65 +240,30 @@ public List toInfoMap( ) { final FileValue.FileInformation fileInformation = entry.getKey(); final FileContent fileContent = entry.getValue(); - final FileInfo loopInfo = new FileInfo(); - loopInfo.name = fileInformation.getFilename(); - loopInfo.type = fileInformation.getFiletype(); - loopInfo.size = fileContent.size(); try { - loopInfo.md5sum = fileContent.md5sum(); - loopInfo.sha1sum = fileContent.sha1sum(); + returnObj.add( FileInfo.builder() + .name( fileInformation.getFilename() ) + .type( fileInformation.getFiletype() ) + .size( fileContent.size() ) + .sha512sum( fileContent.sha512sum() ) + .build() ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { - LOGGER.warn( "error generating hash for certificate: " + e.getMessage() ); + throw new IllegalStateException( e ); } - returnObj.add( loopInfo ); } return Collections.unmodifiableList( returnObj ); } - @Override - public String valueHash( ) throws PwmUnrecoverableException - { - return SecureEngine.hash( JsonUtil.serializeCollection( toInfoMap() ), PwmConstants.SETTING_CHECKSUM_HASH_METHOD ); - } - + @Value + @Builder public static class FileInfo implements Serializable { - public String name; - public String type; - public int size; - public String md5sum; - public String sha1sum; - - private FileInfo( ) - { - } - - public String getName( ) - { - return name; - } - - public String getType( ) - { - return type; - } - - public int getSize( ) - { - return size; - } - - public String getMd5sum( ) - { - return md5sum; - } - - public String getSha1sum( ) - { - return sha1sum; - } + private String name; + private String type; + private long size; + private String sha512sum; } } diff --git a/server/src/main/java/password/pwm/config/value/FormValue.java b/server/src/main/java/password/pwm/config/value/FormValue.java index a8fcaf6aa..1fea6b02f 100644 --- a/server/src/main/java/password/pwm/config/value/FormValue.java +++ b/server/src/main/java/password/pwm/config/value/FormValue.java @@ -24,6 +24,7 @@ import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingSyntax; import password.pwm.config.StoredValue; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.config.value.data.FormConfiguration; import password.pwm.error.PwmOperationalException; import password.pwm.util.java.JavaHelper; @@ -42,13 +43,11 @@ public class FormValue extends AbstractValue implements StoredValue { - final List values; - - private boolean needsXmlUpdate; + private final List values; public FormValue( final List values ) { - this.values = values; + this.values = values == null ? Collections.emptyList() : Collections.unmodifiableList( values ); } public static StoredValueFactory factory( ) @@ -59,14 +58,14 @@ public FormValue fromJson( final String input ) { if ( input == null ) { - return new FormValue( Collections.emptyList() ); + return new FormValue( Collections.emptyList() ); } else { List srcList = JsonUtil.deserialize( input, new TypeToken>() { } ); - srcList = srcList == null ? Collections.emptyList() : srcList; + srcList = srcList == null ? Collections.emptyList() : srcList; while ( srcList.contains( null ) ) { srcList.remove( null ); @@ -98,13 +97,12 @@ public FormValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement s } } final FormValue formValue = new FormValue( values ); - formValue.needsXmlUpdate = oldType; return formValue; } }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final FormConfiguration value : values ) @@ -147,7 +145,7 @@ public List validateValue( final PwmSetting pwmSetting ) { loopConfig.validate(); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { return Collections.singletonList( "format error: " + e.getErrorInformation().toDebugStr() ); } @@ -156,11 +154,6 @@ public List validateValue( final PwmSetting pwmSetting ) return Collections.emptyList(); } - public boolean isNeedsXmlUpdate( ) - { - return needsXmlUpdate; - } - public String toDebugString( final Locale locale ) { if ( values != null && !values.isEmpty() ) diff --git a/server/src/main/java/password/pwm/config/value/LocalizedStringArrayValue.java b/server/src/main/java/password/pwm/config/value/LocalizedStringArrayValue.java index 43ebfd922..583a70f7f 100644 --- a/server/src/main/java/password/pwm/config/value/LocalizedStringArrayValue.java +++ b/server/src/main/java/password/pwm/config/value/LocalizedStringArrayValue.java @@ -23,6 +23,7 @@ import com.google.gson.reflect.TypeToken; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.XmlElement; @@ -40,11 +41,11 @@ public class LocalizedStringArrayValue extends AbstractValue implements StoredValue { - final Map> values; + private final Map> values; LocalizedStringArrayValue( final Map> values ) { - this.values = values; + this.values = values == null ? Collections.emptyMap() : Collections.unmodifiableMap( values ); } public static StoredValueFactory factory( ) @@ -89,7 +90,7 @@ public LocalizedStringArrayValue fromXmlElement( final PwmSetting pwmSetting, fi }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final Map.Entry> entry : values.entrySet() ) diff --git a/server/src/main/java/password/pwm/config/value/LocalizedStringValue.java b/server/src/main/java/password/pwm/config/value/LocalizedStringValue.java index 06bad9ef6..02940ecc8 100644 --- a/server/src/main/java/password/pwm/config/value/LocalizedStringValue.java +++ b/server/src/main/java/password/pwm/config/value/LocalizedStringValue.java @@ -23,6 +23,8 @@ import com.google.gson.reflect.TypeToken; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.XmlElement; @@ -40,11 +42,11 @@ public class LocalizedStringValue extends AbstractValue implements StoredValue { - final Map value; + private final Map value; public LocalizedStringValue( final Map values ) { - this.value = Collections.unmodifiableMap( values ); + this.value = values == null ? Collections.emptyMap() : Collections.unmodifiableMap( values ); } public static StoredValueFactory factory( ) @@ -69,11 +71,11 @@ public LocalizedStringValue fromJson( final String input ) public LocalizedStringValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key ) { - final List elements = settingElement.getChildren( "value" ); + final List elements = settingElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); final Map values = new TreeMap<>(); for ( final XmlElement loopValueElement : elements ) { - final String localeString = loopValueElement.getAttributeValue( "locale" ); + final String localeString = loopValueElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_LOCALE ); final String value = loopValueElement.getText(); values.put( localeString == null ? "" : localeString, value ); } @@ -82,7 +84,7 @@ public LocalizedStringValue fromXmlElement( final PwmSetting pwmSetting, final X }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final Map.Entry entry : value.entrySet() ) diff --git a/server/src/main/java/password/pwm/config/value/NamedSecretValue.java b/server/src/main/java/password/pwm/config/value/NamedSecretValue.java index 7af63a0ef..e103089af 100644 --- a/server/src/main/java/password/pwm/config/value/NamedSecretValue.java +++ b/server/src/main/java/password/pwm/config/value/NamedSecretValue.java @@ -24,6 +24,8 @@ import password.pwm.PwmConstants; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.config.value.data.NamedSecretData; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; @@ -31,6 +33,7 @@ import password.pwm.error.PwmUnrecoverableException; import password.pwm.util.PasswordData; import password.pwm.util.java.JsonUtil; +import password.pwm.util.java.LazySupplier; import password.pwm.util.java.StringUtil; import password.pwm.util.java.XmlElement; import password.pwm.util.java.XmlFactory; @@ -45,23 +48,27 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; public class NamedSecretValue implements StoredValue { + + private final transient LazySupplier valueHashSupplier = new LazySupplier<>( () -> AbstractValue.valueHashComputer( NamedSecretValue.this ) ); + private static final String ELEMENT_NAME = "name"; private static final String ELEMENT_PASSWORD = "password"; private static final String ELEMENT_USAGE = "usage"; - private Map values; + private final Map values; NamedSecretValue( ) { + values = Collections.emptyMap(); } - public NamedSecretValue( final Map values ) { - this.values = values; + this.values = values == null ? Collections.emptyMap() : Collections.unmodifiableMap( values ); } public static StoredValue.StoredValueFactory factory( ) @@ -78,7 +85,7 @@ public NamedSecretValue fromJson( final String value ) final Map linkedValues = new LinkedHashMap<>( values ); return new NamedSecretValue( linkedValues ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new IllegalStateException( "NamedPasswordValue can not be json de-serialized: " + e.getMessage() ); @@ -93,34 +100,33 @@ public NamedSecretValue fromXmlElement( throws PwmOperationalException, PwmUnrecoverableException { final Map values = new LinkedHashMap<>(); - final List valueElements = settingElement.getChildren( "value" ); + final List valueElements = settingElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); try { - if ( valueElements != null ) + for ( final XmlElement value : valueElements ) { - for ( final XmlElement value : valueElements ) + final Optional nameElement = value.getChild( ELEMENT_NAME ); + final Optional passwordElement = value.getChild( ELEMENT_PASSWORD ); + if ( nameElement.isPresent() && passwordElement.isPresent() ) { - if ( value.getChild( ELEMENT_NAME ) != null && value.getChild( ELEMENT_PASSWORD ) != null ) + final String name = nameElement.get().getText(); + final String encodedValue = passwordElement.get().getText(); + final PasswordData passwordData = new PasswordData( SecureEngine.decryptStringValue( encodedValue, key, PwmBlockAlgorithm.CONFIG ) ); + final List usages = value.getChildren( ELEMENT_USAGE ); + final List strUsages = new ArrayList<>(); + if ( usages != null ) { - final String name = value.getChild( ELEMENT_NAME ).getText(); - final String encodedValue = value.getChild( ELEMENT_PASSWORD ).getText(); - final PasswordData passwordData = new PasswordData( SecureEngine.decryptStringValue( encodedValue, key, PwmBlockAlgorithm.CONFIG ) ); - final List usages = value.getChildren( ELEMENT_USAGE ); - final List strUsages = new ArrayList<>(); - if ( usages != null ) + for ( final XmlElement usageElement : usages ) { - for ( final XmlElement usageElement : usages ) - { - strUsages.add( usageElement.getText() ); - } + strUsages.add( usageElement.getText() ); } - values.put( name, new NamedSecretData( passwordData, Collections.unmodifiableList( strUsages ) ) ); } + values.put( name, new NamedSecretData( passwordData, Collections.unmodifiableList( strUsages ) ) ); } } } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unable to decode encrypted password value for setting: " + e.getMessage(); final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg ); @@ -154,7 +160,7 @@ public int currentSyntaxVersion( ) return 0; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey key ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { if ( values == null ) { @@ -168,7 +174,7 @@ public List toXmlValues( final String valueElementName, final PwmSec { final String name = entry.getKey(); final PasswordData passwordData = entry.getValue().getPassword(); - final String encodedValue = SecureEngine.encryptToString( passwordData.getStringValue(), key, PwmBlockAlgorithm.CONFIG ); + final String encodedValue = SecureEngine.encryptToString( passwordData.getStringValue(), xmlOutputProcessData.getPwmSecurityKey(), PwmBlockAlgorithm.CONFIG ); final XmlElement newValueElement = XmlFactory.getFactory().newElement( "value" ); final XmlElement nameElement = XmlFactory.getFactory().newElement( ELEMENT_NAME ); nameElement.addText( name ); @@ -189,7 +195,7 @@ public List toXmlValues( final String valueElementName, final PwmSec valuesElement.add( newValueElement ); } } - catch ( Exception e ) + catch ( final Exception e ) { throw new RuntimeException( "missing required AES and SHA1 libraries, or other crypto fault: " + e.getMessage() ); } @@ -239,21 +245,15 @@ public Serializable toDebugJsonObject( final Locale locale ) } return copiedValues; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { throw new IllegalStateException( e.getErrorInformation().toDebugStr() ); } } - public boolean requiresStoredUpdate( ) - { - return false; - } - @Override - public String valueHash( ) throws PwmUnrecoverableException + public String valueHash() { - return values == null ? "" : SecureEngine.hash( JsonUtil.serializeMap( values ), PwmConstants.SETTING_CHECKSUM_HASH_METHOD ); + return valueHashSupplier.get(); } - } diff --git a/server/src/main/java/password/pwm/config/value/NumericArrayValue.java b/server/src/main/java/password/pwm/config/value/NumericArrayValue.java index 639030ca8..ab4ff6c5d 100644 --- a/server/src/main/java/password/pwm/config/value/NumericArrayValue.java +++ b/server/src/main/java/password/pwm/config/value/NumericArrayValue.java @@ -22,6 +22,7 @@ import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.util.java.JavaHelper; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.XmlElement; @@ -38,11 +39,11 @@ public class NumericArrayValue extends AbstractValue implements StoredValue { - List values; + private final List values; public NumericArrayValue( final List values ) { - this.values = values; + this.values = values == null ? Collections.emptyList() : Collections.unmodifiableList( values ); } public static StoredValueFactory factory( ) @@ -72,7 +73,7 @@ public NumericArrayValue fromXmlElement( final PwmSetting pwmSetting, final XmlE } @Override - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final Long value : this.values ) diff --git a/server/src/main/java/password/pwm/config/value/NumericValue.java b/server/src/main/java/password/pwm/config/value/NumericValue.java index 01c988533..bd66c44c3 100644 --- a/server/src/main/java/password/pwm/config/value/NumericValue.java +++ b/server/src/main/java/password/pwm/config/value/NumericValue.java @@ -23,6 +23,8 @@ import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingProperty; import password.pwm.config.StoredValue; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.XmlElement; import password.pwm.util.java.XmlFactory; @@ -30,10 +32,11 @@ import java.util.Collections; import java.util.List; +import java.util.Optional; public class NumericValue extends AbstractValue implements StoredValue { - long value; + private final long value; public NumericValue( final long value ) { @@ -51,9 +54,16 @@ public NumericValue fromJson( final String value ) public NumericValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey input ) { - final XmlElement valueElement = settingElement.getChild( "value" ); - final String value = valueElement.getText(); - return new NumericValue( normalizeValue( pwmSetting, Long.parseLong( value ) ) ); + final Optional valueElement = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); + if ( valueElement.isPresent() ) + { + final String value = valueElement.get().getText(); + return new NumericValue( normalizeValue( pwmSetting, Long.parseLong( value ) ) ); + } + else + { + return new NumericValue( 0 ); + } } }; } @@ -77,7 +87,7 @@ private static long normalizeValue( final PwmSetting pwmSetting, final long valu } @Override - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName ); valueElement.addText( Long.toString( value ) ); diff --git a/server/src/main/java/password/pwm/config/value/OptionListValue.java b/server/src/main/java/password/pwm/config/value/OptionListValue.java index 8a550f0d1..75bdd2d80 100644 --- a/server/src/main/java/password/pwm/config/value/OptionListValue.java +++ b/server/src/main/java/password/pwm/config/value/OptionListValue.java @@ -23,6 +23,7 @@ import com.google.gson.reflect.TypeToken; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.error.PwmOperationalException; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.XmlElement; @@ -39,11 +40,11 @@ public class OptionListValue extends AbstractValue implements StoredValue { - final Set values; + private final Set values; public OptionListValue( final Set values ) { - this.values = new TreeSet( values ); + this.values = values == null ? Collections.emptySet() : Collections.unmodifiableSet( new TreeSet<>( values ) ); } public static StoredValueFactory factory( ) @@ -88,7 +89,7 @@ public OptionListValue fromXmlElement( final PwmSetting pwmSetting, final XmlEle }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final String value : values ) diff --git a/server/src/main/java/password/pwm/config/value/PasswordValue.java b/server/src/main/java/password/pwm/config/value/PasswordValue.java index 079d11fc5..f20bfb54d 100644 --- a/server/src/main/java/password/pwm/config/value/PasswordValue.java +++ b/server/src/main/java/password/pwm/config/value/PasswordValue.java @@ -24,33 +24,36 @@ import password.pwm.PwmConstants; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.util.PasswordData; import password.pwm.util.java.JsonUtil; +import password.pwm.util.java.LazySupplier; import password.pwm.util.java.XmlElement; import password.pwm.util.java.XmlFactory; -import password.pwm.util.secure.PwmBlockAlgorithm; import password.pwm.util.secure.PwmSecurityKey; -import password.pwm.util.secure.SecureEngine; import java.io.Serializable; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Optional; public class PasswordValue implements StoredValue { - private PasswordData value; + private final transient LazySupplier valueHashSupplier = new LazySupplier<>( () -> AbstractValue.valueHashComputer( PasswordValue.this ) ); + + private final PasswordData value; PasswordValue( ) { + value = null; } - boolean requiresStoredUpdate; - public PasswordValue( final PasswordData passwordData ) { value = passwordData; @@ -69,7 +72,7 @@ public PasswordValue fromJson( final String value ) { return new PasswordValue( new PasswordData( strValue ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { throw new IllegalStateException( "PasswordValue can not be json de-serialized: " + e.getMessage() ); @@ -85,50 +88,54 @@ public PasswordValue fromXmlElement( ) throws PwmOperationalException, PwmUnrecoverableException { - final XmlElement valueElement = settingElement.getChild( "value" ); - final String rawValue = valueElement.getText(); - - final PasswordValue newPasswordValue = new PasswordValue(); - if ( rawValue == null || rawValue.isEmpty() ) + final Optional valueElement = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); + if ( valueElement.isPresent() ) { - return newPasswordValue; - } - - final boolean plainTextSetting; - { - final String plainTextAttributeStr = valueElement.getAttributeValue( "plaintext" ); - plainTextSetting = plainTextAttributeStr != null && Boolean.parseBoolean( plainTextAttributeStr ); - } + final String rawValue = valueElement.get().getText(); - if ( plainTextSetting ) - { - newPasswordValue.value = new PasswordData( rawValue ); - newPasswordValue.requiresStoredUpdate = true; - } - else - { - try + final PasswordValue newPasswordValue = new PasswordValue(); + if ( rawValue == null || rawValue.isEmpty() ) { - newPasswordValue.value = new PasswordData( SecureEngine.decryptStringValue( rawValue, key, PwmBlockAlgorithm.CONFIG ) ); return newPasswordValue; } - catch ( Exception e ) + + final boolean plainTextSetting; { - final String errorMsg = "unable to decode encrypted password value for setting: " + e.getMessage(); - final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg ); - throw new PwmOperationalException( errorInfo ); + final String plainTextAttributeStr = valueElement.get().getAttributeValue( "plaintext" ); + plainTextSetting = plainTextAttributeStr != null && Boolean.parseBoolean( plainTextAttributeStr ); + } + + if ( plainTextSetting ) + { + return new PasswordValue( new PasswordData( rawValue ) ); + } + else + { + try + { + final Optional encodedValue = StoredValueEncoder.decode( rawValue, StoredValueEncoder.Mode.CONFIG_PW, key ); + if ( encodedValue.isPresent() ) + { + return new PasswordValue( new PasswordData( encodedValue.get() ) ); + } + else + { + return new PasswordValue( new PasswordData( "" ) ); + } + } + catch ( final Exception e ) + { + final String errorMsg = "unable to decode encrypted password value for setting: " + e.getMessage(); + final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg ); + throw new PwmOperationalException( errorInfo ); + } } } - return newPasswordValue; + return new PasswordValue(); } }; } - public List toXmlValues( final String valueElementName ) - { - throw new IllegalStateException( "password xml output requires hash key" ); - } - @Override public Object toNativeObject( ) { @@ -147,7 +154,7 @@ public int currentSyntaxVersion( ) return 0; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey key ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { if ( value == null ) { @@ -157,10 +164,14 @@ public List toXmlValues( final String valueElementName, final PwmSec final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName ); try { - final String encodedValue = SecureEngine.encryptToString( value.getStringValue(), key, PwmBlockAlgorithm.CONFIG ); + final String encodedValue = StoredValueEncoder.encode( + value.getStringValue(), + xmlOutputProcessData.getStoredValueEncoderMode(), + xmlOutputProcessData.getPwmSecurityKey() ); + valueElement.addText( encodedValue ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new RuntimeException( "missing required AES and SHA1 libraries, or other crypto fault: " + e.getMessage() ); } @@ -184,14 +195,9 @@ public Serializable toDebugJsonObject( final Locale locale ) return PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT; } - public boolean requiresStoredUpdate( ) - { - return requiresStoredUpdate; - } - @Override - public String valueHash( ) throws PwmUnrecoverableException + public String valueHash() { - return value == null ? "" : SecureEngine.hash( JsonUtil.serialize( value.getStringValue() ), PwmConstants.SETTING_CHECKSUM_HASH_METHOD ); + return valueHashSupplier.get(); } } diff --git a/server/src/main/java/password/pwm/config/value/PrivateKeyValue.java b/server/src/main/java/password/pwm/config/value/PrivateKeyValue.java index f2639fa1e..36be30426 100644 --- a/server/src/main/java/password/pwm/config/value/PrivateKeyValue.java +++ b/server/src/main/java/password/pwm/config/value/PrivateKeyValue.java @@ -23,14 +23,14 @@ import password.pwm.bean.PrivateKeyCertificate; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.StringUtil; import password.pwm.util.java.XmlElement; import password.pwm.util.java.XmlFactory; import password.pwm.util.logging.PwmLogger; -import password.pwm.util.secure.PwmBlockAlgorithm; import password.pwm.util.secure.PwmSecurityKey; -import password.pwm.util.secure.SecureEngine; import password.pwm.util.secure.X509Utils; import java.io.Serializable; @@ -44,6 +44,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; public class PrivateKeyValue extends AbstractValue { @@ -52,7 +53,7 @@ public class PrivateKeyValue extends AbstractValue private static final String ELEMENT_NAME_CERTIFICATE = "certificate"; private static final String ELEMENT_NAME_KEY = "key"; - private PrivateKeyCertificate privateKeyCertificate; + private final PrivateKeyCertificate privateKeyCertificate; public static StoredValue.StoredValueFactory factory( ) { @@ -60,14 +61,14 @@ public static StoredValue.StoredValueFactory factory( ) { public PrivateKeyValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key ) { - if ( settingElement != null && settingElement.getChild( "value" ) != null ) + if ( settingElement != null && settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_VALUE ).isPresent() ) { - final XmlElement valueElement = settingElement.getChild( "value" ); - if ( valueElement != null ) + final Optional valueElement = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); + if ( valueElement.isPresent() ) { final List certificates = new ArrayList<>(); - for ( final XmlElement certificateElement : valueElement.getChildren( ELEMENT_NAME_CERTIFICATE ) ) + for ( final XmlElement certificateElement : valueElement.get().getChildren( ELEMENT_NAME_CERTIFICATE ) ) { try { @@ -75,7 +76,7 @@ public PrivateKeyValue fromXmlElement( final PwmSetting pwmSetting, final XmlEle final X509Certificate cert = X509Utils.certificateFromBase64( b64Text ); certificates.add( cert ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error reading certificate: " + e.getMessage(), e ); } @@ -86,15 +87,25 @@ public PrivateKeyValue fromXmlElement( final PwmSetting pwmSetting, final XmlEle PrivateKey privateKey = null; try { - final XmlElement keyElement = valueElement.getChild( ELEMENT_NAME_KEY ); - final String encryptedText = keyElement.getText(); - final String decryptedText = SecureEngine.decryptStringValue( encryptedText, key, PwmBlockAlgorithm.CONFIG ); - final byte[] privateKeyBytes = StringUtil.base64Decode( decryptedText ); - privateKey = KeyFactory.getInstance( "RSA" ).generatePrivate( new PKCS8EncodedKeySpec( privateKeyBytes ) ); + final Optional keyElement = valueElement.get().getChild( ELEMENT_NAME_KEY ); + if ( keyElement.isPresent() ) + { + final String encryptedText = keyElement.get().getText(); + final Optional decryptedText = StoredValueEncoder.decode( encryptedText, StoredValueEncoder.Mode.CONFIG_PW, key ); + if ( decryptedText.isPresent() ) + { + final byte[] privateKeyBytes = StringUtil.base64Decode( decryptedText.get() ); + privateKey = KeyFactory.getInstance( "RSA" ).generatePrivate( new PKCS8EncodedKeySpec( privateKeyBytes ) ); + } + } + else + { + LOGGER.error( "error reading privateKey for setting: '" + pwmSetting.getKey() + "': missing 'value' element" ); + } } - catch ( Exception e ) + catch ( final Exception e ) { - LOGGER.error( "error reading privateKey: " + e.getMessage(), e ); + LOGGER.error( "error reading privateKey for setting: '" + pwmSetting.getKey() + "': " + e.getMessage(), e ); } if ( !certificates.isEmpty() && privateKey != null ) @@ -143,9 +154,9 @@ public int currentSyntaxVersion( ) return 0; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey key ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { - final XmlElement valueElement = XmlFactory.getFactory().newElement( "value" ); + final XmlElement valueElement = XmlFactory.getFactory().newElement( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); if ( privateKeyCertificate != null ) { try @@ -161,12 +172,16 @@ public List toXmlValues( final String valueElementName, final PwmSec { final XmlElement keyElement = XmlFactory.getFactory().newElement( ELEMENT_NAME_KEY ); final String b64EncodedKey = StringUtil.base64Encode( privateKeyCertificate.getKey().getEncoded() ); - final String encryptedKey = SecureEngine.encryptToString( b64EncodedKey, key, PwmBlockAlgorithm.CONFIG ); + final String encryptedKey = StoredValueEncoder.encode( + b64EncodedKey, + xmlOutputProcessData.getStoredValueEncoderMode(), + xmlOutputProcessData.getPwmSecurityKey() ); + keyElement.addText( encryptedKey ); valueElement.addContent( keyElement ); } } - catch ( Exception e ) + catch ( final Exception e ) { throw new RuntimeException( "missing required AES and SHA1 libraries, or other crypto fault: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/config/value/RemoteWebServiceValue.java b/server/src/main/java/password/pwm/config/value/RemoteWebServiceValue.java index 4a7b1a443..32abf3df2 100644 --- a/server/src/main/java/password/pwm/config/value/RemoteWebServiceValue.java +++ b/server/src/main/java/password/pwm/config/value/RemoteWebServiceValue.java @@ -24,6 +24,8 @@ import password.pwm.PwmConstants; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.config.value.data.RemoteWebServiceConfiguration; import password.pwm.error.PwmOperationalException; import password.pwm.util.java.JsonUtil; @@ -43,6 +45,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; public class RemoteWebServiceValue extends AbstractValue implements StoredValue @@ -74,7 +77,7 @@ public RemoteWebServiceValue fromJson( final String input ) } ); - srcList = srcList == null ? Collections.emptyList() : srcList; + srcList = srcList == null ? new ArrayList<>() : srcList; srcList.removeIf( Objects::isNull ); return new RemoteWebServiceValue( Collections.unmodifiableList( srcList ) ); } @@ -87,7 +90,7 @@ public RemoteWebServiceValue fromXmlElement( ) throws PwmOperationalException { - final List valueElements = settingElement.getChildren( "value" ); + final List valueElements = settingElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); final List values = new ArrayList<>(); for ( final XmlElement loopValueElement : valueElements ) { @@ -95,8 +98,15 @@ public RemoteWebServiceValue fromXmlElement( if ( value != null && value.length() > 0 ) { final RemoteWebServiceConfiguration parsedValue = JsonUtil.deserialize( value, RemoteWebServiceConfiguration.class ); - parsedValue.setPassword( decryptPwValue( parsedValue.getPassword(), pwmSecurityKey ) ); - values.add( parsedValue ); + final Optional decodedValue = StoredValueEncoder.decode( + parsedValue.getPassword(), + StoredValueEncoder.Mode.ENCODED, + pwmSecurityKey + ); + decodedValue.ifPresent( ( s ) -> + { + values.add( parsedValue.toBuilder().password( s ).build() ); + } ); } } return new RemoteWebServiceValue( values ); @@ -104,22 +114,27 @@ public RemoteWebServiceValue fromXmlElement( }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final RemoteWebServiceConfiguration value : values ) { final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName ); - final RemoteWebServiceConfiguration clonedValue = JsonUtil.cloneUsingJson( value, RemoteWebServiceConfiguration.class ); + + String encodedValue = value.getPassword(); try { - clonedValue.setPassword( encryptPwValue( clonedValue.getPassword(), pwmSecurityKey ) ); + encodedValue = StoredValueEncoder.encode( + value.getPassword(), + xmlOutputProcessData.getStoredValueEncoderMode(), + xmlOutputProcessData.getPwmSecurityKey() ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.warn( "error decoding stored pw value: " + e.getMessage() ); } + final RemoteWebServiceConfiguration clonedValue = value.toBuilder().password( encodedValue ).build(); valueElement.addText( JsonUtil.serialize( clonedValue ) ); returnList.add( valueElement ); } @@ -200,12 +215,14 @@ public Serializable toDebugJsonObject( final Locale locale ) final ArrayList output = new ArrayList<>(); for ( final RemoteWebServiceConfiguration remoteWebServiceConfiguration : values ) { - final RemoteWebServiceConfiguration clone = JsonUtil.cloneUsingJson( remoteWebServiceConfiguration, RemoteWebServiceConfiguration.class ); - if ( !StringUtil.isEmpty( clone.getPassword() ) ) + if ( !StringUtil.isEmpty( remoteWebServiceConfiguration.getPassword() ) ) + { + output.add( remoteWebServiceConfiguration.toBuilder().password( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ).build() ); + } + else { - clone.setPassword( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ); + output.add( remoteWebServiceConfiguration ); } - output.add( clone ); } return output; } diff --git a/server/src/main/java/password/pwm/config/value/StoredValueEncoder.java b/server/src/main/java/password/pwm/config/value/StoredValueEncoder.java new file mode 100644 index 000000000..2f2ef1e51 --- /dev/null +++ b/server/src/main/java/password/pwm/config/value/StoredValueEncoder.java @@ -0,0 +1,268 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2019 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.config.value; + +import lombok.Value; +import password.pwm.PwmConstants; +import password.pwm.error.ErrorInformation; +import password.pwm.error.PwmError; +import password.pwm.error.PwmOperationalException; +import password.pwm.util.java.JsonUtil; +import password.pwm.util.java.StringUtil; +import password.pwm.util.logging.PwmLogger; +import password.pwm.util.secure.PwmBlockAlgorithm; +import password.pwm.util.secure.PwmRandom; +import password.pwm.util.secure.PwmSecurityKey; +import password.pwm.util.secure.SecureEngine; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public abstract class StoredValueEncoder +{ + private StoredValueEncoder() + { + } + + public enum Mode + { + PLAIN( new PlaintextModeEngine(), "PLAIN" + DELIMITER, "PLAINTEXT" + DELIMITER, "RAW" + DELIMITER ), + STRIPPED( new StrippedModeEngine(), "REMOVED" + DELIMITER ), + CONFIG_PW( new ConfigPwModeEngine(), "CONFIG-PW" + DELIMITER ), + ENCODED( new EncodedModeEngine(), "ENC-PW" + DELIMITER, "ENCODED" + DELIMITER ),; + + private final List prefixes; + private final SecureOutputEngine secureOutputEngine; + + Mode( final SecureOutputEngine secureOutputEngine, final String... prefixes ) + { + this.secureOutputEngine = secureOutputEngine; + this.prefixes = Collections.unmodifiableList( Arrays.asList( prefixes ) ); + } + + public List getPrefixes() + { + return prefixes; + } + + public String getPrefix() + { + return prefixes.iterator().next(); + } + + public SecureOutputEngine getSecureOutputEngine() + { + return secureOutputEngine; + } + } + + + private static final PwmLogger LOGGER = PwmLogger.forClass( StoredValueEncoder.class ); + private static final String DELIMITER = ":"; + + public static Optional decode( + final String input, + final Mode modeHint, + final PwmSecurityKey pwmSecurityKey + ) + throws PwmOperationalException + { + if ( StringUtil.isEmpty( input ) ) + { + return Optional.empty(); + } + + final ParsedInput parsedInput = ParsedInput.parseInput( input ); + final Mode requestedMode = modeHint == null ? Mode.PLAIN : modeHint; + final Mode effectiveMode = parsedInput.getMode() == null + ? requestedMode + : parsedInput.getMode(); + return Optional.ofNullable( effectiveMode.getSecureOutputEngine().decode( parsedInput, pwmSecurityKey ) ); + } + + public static String encode( final String realValue, final Mode mode, final PwmSecurityKey pwmSecurityKey ) + throws PwmOperationalException + { + return mode.getSecureOutputEngine().encode( realValue, pwmSecurityKey ); + } + + @Value + private static class ParsedInput + { + private Mode mode; + private String value; + + static ParsedInput parseInput( final String value ) + { + if ( !StringUtil.isEmpty( value ) ) + { + for ( final Mode mode : Mode.values() ) + { + for ( final String prefix : mode.getPrefixes() ) + { + if ( value.startsWith( prefix ) ) + { + return new ParsedInput( mode, value.substring( prefix.length() ) ); + } + } + } + } + + return new ParsedInput( null, value ); + } + } + + private interface SecureOutputEngine + { + String encode( String rawOutput, PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException; + + String decode( ParsedInput input, PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException; + } + + + private static class PlaintextModeEngine implements SecureOutputEngine + { + @Override + public String encode( final String rawValue, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException + { + return Mode.PLAIN.getPrefix() + rawValue; + } + + @Override + public String decode( final ParsedInput input, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException + { + return input.getValue(); + } + } + + private static class StrippedModeEngine implements SecureOutputEngine + { + @Override + public String encode( final String rawValue, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException + { + return PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT; + } + + @Override + public String decode( final ParsedInput input, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException + { + return PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT; + } + } + + private static class ConfigPwModeEngine implements SecureOutputEngine + { + @Override + public String encode( final String rawValue, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException + { + try + { + final String encryptedValue = SecureEngine.encryptToString( rawValue, pwmSecurityKey, PwmBlockAlgorithm.CONFIG ); + return Mode.CONFIG_PW + encryptedValue; + } + catch ( final Exception e ) + { + final String errorMsg = "unable to encrypt config-password value for setting: " + e.getMessage(); + final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg ); + throw new PwmOperationalException( errorInfo ); + } + } + + @Override + public String decode( final ParsedInput input, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException + { + try + { + return SecureEngine.decryptStringValue( input.getValue(), pwmSecurityKey, PwmBlockAlgorithm.CONFIG ); + } + catch ( final Exception e ) + { + final String errorMsg = "unable to decrypt config password value for setting: " + e.getMessage(); + final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg ); + LOGGER.warn( errorInfo.toDebugStr() ); + throw new PwmOperationalException( errorInfo ); + } + } + } + + private static class EncodedModeEngine implements SecureOutputEngine + { + @Override + public String encode( final String rawValue, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException + { + if ( rawValue == null ) + { + return Mode.ENCODED.getPrefix(); + } + + // make sure value isn't already encoded + if ( ParsedInput.parseInput( rawValue ).getMode() == null ) + { + try + { + final String salt = PwmRandom.getInstance().alphaNumericString( 32 ); + final StoredPwData storedPwData = new StoredPwData( salt, rawValue ); + final String jsonData = JsonUtil.serialize( storedPwData ); + final String encryptedValue = SecureEngine.encryptToString( jsonData, pwmSecurityKey, PwmBlockAlgorithm.CONFIG ); + return Mode.ENCODED.getPrefix() + encryptedValue; + } + catch ( final Exception e ) + { + final String errorMsg = "unable to encrypt password value for setting: " + e.getMessage(); + final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg ); + throw new PwmOperationalException( errorInfo ); + } + } + + return rawValue; + } + + @Override + public String decode( final ParsedInput input, final PwmSecurityKey pwmSecurityKey ) throws PwmOperationalException + { + try + { + final String pwValueSuffix = input.getValue( ); + final String decryptedValue = SecureEngine.decryptStringValue( pwValueSuffix, pwmSecurityKey, PwmBlockAlgorithm.CONFIG ); + final StoredPwData storedPwData = JsonUtil.deserialize( decryptedValue, StoredPwData.class ); + return storedPwData.getValue(); + } + catch ( final Exception e ) + { + final String errorMsg = "unable to decrypt password value for setting: " + e.getMessage(); + final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg ); + LOGGER.warn( errorInfo.toDebugStr() ); + throw new PwmOperationalException( errorInfo ); + } + } + } + + @Value + private static class StoredPwData implements Serializable + { + private String salt; + private String value; + } + +} diff --git a/server/src/main/java/password/pwm/config/value/StringArrayValue.java b/server/src/main/java/password/pwm/config/value/StringArrayValue.java index 3250045d6..f267fb928 100644 --- a/server/src/main/java/password/pwm/config/value/StringArrayValue.java +++ b/server/src/main/java/password/pwm/config/value/StringArrayValue.java @@ -22,6 +22,8 @@ import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.XmlElement; import password.pwm.util.java.XmlFactory; @@ -37,11 +39,11 @@ public class StringArrayValue extends AbstractValue implements StoredValue { - final List values; + private final List values; public StringArrayValue( final List values ) { - this.values = values; + this.values = values == null ? Collections.emptyList() : Collections.unmodifiableList( values ); } public static StoredValueFactory factory( ) @@ -68,7 +70,7 @@ public StringArrayValue fromJson( final String input ) public StringArrayValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key ) { - final List valueElements = settingElement.getChildren( "value" ); + final List valueElements = settingElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); final List values = new ArrayList<>(); for ( final XmlElement loopValueElement : valueElements ) { @@ -80,7 +82,7 @@ public StringArrayValue fromXmlElement( final PwmSetting pwmSetting, final XmlEl }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final String value : this.values ) diff --git a/server/src/main/java/password/pwm/config/value/StringValue.java b/server/src/main/java/password/pwm/config/value/StringValue.java index 734a0b61b..6c7ced554 100644 --- a/server/src/main/java/password/pwm/config/value/StringValue.java +++ b/server/src/main/java/password/pwm/config/value/StringValue.java @@ -23,6 +23,8 @@ import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingFlag; import password.pwm.config.StoredValue; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.config.value.data.FormConfiguration; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.XmlElement; @@ -32,6 +34,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -60,13 +63,14 @@ public StringValue fromJson( final String input ) public StringValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key ) { - final XmlElement valueElement = settingElement.getChild( "value" ); - return new StringValue( valueElement == null ? "" : valueElement.getText() ); + final Optional valueElement = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); + final String value = valueElement.map( XmlElement::getText ).orElse( "" ); + return new StringValue( value ); } }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName ); valueElement.addText( value ); diff --git a/server/src/main/java/password/pwm/config/value/UserPermissionValue.java b/server/src/main/java/password/pwm/config/value/UserPermissionValue.java index 7e95fea8e..a2c0fb82e 100644 --- a/server/src/main/java/password/pwm/config/value/UserPermissionValue.java +++ b/server/src/main/java/password/pwm/config/value/UserPermissionValue.java @@ -24,7 +24,8 @@ import org.apache.commons.lang3.StringUtils; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.config.value.data.UserPermission; import password.pwm.error.PwmOperationalException; import password.pwm.i18n.Display; @@ -40,13 +41,13 @@ public class UserPermissionValue extends AbstractValue implements StoredValue { - final List values; + private final List values; private boolean needsXmlUpdate; public UserPermissionValue( final List values ) { - this.values = values; + this.values = values == null ? Collections.emptyList() : Collections.unmodifiableList( values ); } public static StoredValueFactory factory( ) @@ -57,14 +58,14 @@ public UserPermissionValue fromJson( final String input ) { if ( input == null ) { - return new UserPermissionValue( Collections.emptyList() ); + return new UserPermissionValue( Collections.emptyList() ); } else { List srcList = JsonUtil.deserialize( input, new TypeToken>() { } ); - srcList = srcList == null ? Collections.emptyList() : srcList; + srcList = srcList == null ? Collections.emptyList() : srcList; while ( srcList.contains( null ) ) { srcList.remove( null ); @@ -77,7 +78,7 @@ public UserPermissionValue fromXmlElement( final PwmSetting pwmSetting, final Xm throws PwmOperationalException { final boolean newType = "2".equals( - settingElement.getAttributeValue( StoredConfigurationImpl.XML_ATTRIBUTE_SYNTAX_VERSION ) ); + settingElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_SYNTAX_VERSION ) ); final List valueElements = settingElement.getChildren( "value" ); final List values = new ArrayList<>(); for ( final XmlElement loopValueElement : valueElements ) @@ -92,7 +93,10 @@ public UserPermissionValue fromXmlElement( final PwmSetting pwmSetting, final Xm } else { - values.add( new UserPermission( UserPermission.Type.ldapQuery, null, value, null ) ); + values.add( UserPermission.builder() + .type( UserPermission.Type.ldapQuery ) + .ldapQuery( value ) + .build() ); } } } @@ -103,7 +107,7 @@ public UserPermissionValue fromXmlElement( final PwmSetting pwmSetting, final Xm }; } - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final UserPermission value : values ) @@ -129,7 +133,7 @@ public List validateValue( final PwmSetting pwmSetting ) { validateLdapSearchFilter( userPermission.getLdapQuery() ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { returnObj.add( e.getMessage() + " for filter " + userPermission.getLdapQuery() ); } diff --git a/server/src/main/java/password/pwm/config/value/ValueFactory.java b/server/src/main/java/password/pwm/config/value/ValueFactory.java index ae2983467..3d4e9c9b2 100644 --- a/server/src/main/java/password/pwm/config/value/ValueFactory.java +++ b/server/src/main/java/password/pwm/config/value/ValueFactory.java @@ -39,10 +39,10 @@ public static StoredValue fromJson( final PwmSetting setting, final String input { try { - final StoredValue.StoredValueFactory factory = setting.getSyntax().getStoredValueImpl(); + final StoredValue.StoredValueFactory factory = setting.getSyntax().getFactory(); return factory.fromJson( input ); } - catch ( Exception e ) + catch ( final Exception e ) { final StringBuilder errorMsg = new StringBuilder(); errorMsg.append( "error parsing value stored configuration value: " ).append( e.getMessage() ); @@ -59,10 +59,10 @@ public static StoredValue fromXmlValues( final PwmSetting setting, final XmlElem { try { - final StoredValue.StoredValueFactory factory = setting.getSyntax().getStoredValueImpl(); + final StoredValue.StoredValueFactory factory = setting.getSyntax().getFactory(); return factory.fromXmlElement( setting, settingElement, key ); } - catch ( Exception e ) + catch ( final Exception e ) { final StringBuilder errorMsg = new StringBuilder(); errorMsg.append( "error parsing stored configuration value: " ).append( e.getMessage() ); @@ -71,7 +71,7 @@ public static StoredValue fromXmlValues( final PwmSetting setting, final XmlElem errorMsg.append( ", cause: " ).append( e.getCause().getMessage() ); } LOGGER.error( errorMsg, e ); - throw new IllegalStateException( "unable to read xml element '" + settingElement.getName() + "' from setting '" + setting.getKey() + "' error: " + e.getMessage() ); + throw new IllegalStateException( "unable to read xml element '" + settingElement.getName() + "' from setting '" + setting.getKey() + "' error: " + e.getMessage(), e ); } } } diff --git a/server/src/main/java/password/pwm/config/value/VerificationMethodValue.java b/server/src/main/java/password/pwm/config/value/VerificationMethodValue.java index b7719ada5..211b4e55d 100644 --- a/server/src/main/java/password/pwm/config/value/VerificationMethodValue.java +++ b/server/src/main/java/password/pwm/config/value/VerificationMethodValue.java @@ -24,6 +24,8 @@ import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; import password.pwm.config.option.IdentityVerificationMethod; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.error.PwmOperationalException; import password.pwm.i18n.Display; import password.pwm.util.i18n.LocaleHelper; @@ -36,17 +38,17 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; public class VerificationMethodValue extends AbstractValue implements StoredValue { private static final PwmLogger LOGGER = PwmLogger.forClass( VerificationMethodValue.class ); - private VerificationMethodSettings value = new VerificationMethodSettings(); + private final VerificationMethodSettings value; public enum EnabledState @@ -58,16 +60,18 @@ public enum EnabledState public static class VerificationMethodSettings implements Serializable { - private Map methodSettings = new HashMap<>(); - private int minOptionalRequired; + private final Map methodSettings; + private final int minOptionalRequired; public VerificationMethodSettings( ) { + methodSettings = Collections.emptyMap(); + minOptionalRequired = 0; } public VerificationMethodSettings( final Map methodSettings, final int minOptionalRequired ) { - this.methodSettings = methodSettings; + this.methodSettings = methodSettings == null ? Collections.emptyMap() : Collections.unmodifiableMap( methodSettings ); this.minOptionalRequired = minOptionalRequired; } @@ -87,7 +91,7 @@ public int getMinOptionalRequired( ) @Value public static class VerificationMethodSetting implements Serializable { - private final EnabledState enabledState; + private EnabledState enabledState; } public VerificationMethodValue( ) @@ -127,16 +131,20 @@ public VerificationMethodValue fromJson( final String input ) public VerificationMethodValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key ) throws PwmOperationalException { - final XmlElement valueElement = settingElement.getChild( "value" ); - final String inputStr = valueElement.getText(); - final VerificationMethodSettings settings = JsonUtil.deserialize( inputStr, VerificationMethodSettings.class ); - return new VerificationMethodValue( settings ); + final Optional valueElement = settingElement.getChild( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); + if ( valueElement.isPresent() ) + { + final String inputStr = valueElement.get().getText(); + final VerificationMethodSettings settings = JsonUtil.deserialize( inputStr, VerificationMethodSettings.class ); + return new VerificationMethodValue( settings ); + } + return new VerificationMethodValue( ); } }; } @Override - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName ); valueElement.addText( JsonUtil.serialize( value ) ); diff --git a/server/src/main/java/password/pwm/config/value/X509CertificateValue.java b/server/src/main/java/password/pwm/config/value/X509CertificateValue.java index 7ca5f7899..52f1f36fc 100644 --- a/server/src/main/java/password/pwm/config/value/X509CertificateValue.java +++ b/server/src/main/java/password/pwm/config/value/X509CertificateValue.java @@ -22,8 +22,11 @@ import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; +import password.pwm.config.stored.StoredConfigXmlConstants; +import password.pwm.config.stored.XmlOutputProcessData; import password.pwm.error.PwmUnrecoverableException; import password.pwm.util.java.JavaHelper; +import password.pwm.util.java.StringUtil; import password.pwm.util.java.XmlElement; import password.pwm.util.java.XmlFactory; import password.pwm.util.logging.PwmLogger; @@ -47,7 +50,7 @@ public class X509CertificateValue extends AbstractValue implements StoredValue { private static final PwmLogger LOGGER = PwmLogger.forClass( X509CertificateValue.class ); - private X509Certificate[] certificates; + private final X509Certificate[] certificates; public static StoredValueFactory factory( ) { @@ -56,7 +59,7 @@ public static StoredValueFactory factory( ) public X509CertificateValue fromXmlElement( final PwmSetting pwmSetting, final XmlElement settingElement, final PwmSecurityKey key ) { final List certificates = new ArrayList<>(); - final List valueElements = settingElement.getChildren( "value" ); + final List valueElements = settingElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE ); for ( final XmlElement loopValueElement : valueElements ) { final String b64encodedStr = loopValueElement.getText(); @@ -64,7 +67,7 @@ public X509CertificateValue fromXmlElement( final PwmSetting pwmSetting, final X { certificates.add( X509Utils.certificateFromBase64( b64encodedStr ) ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error decoding certificate: " + e.getMessage() ); } @@ -99,12 +102,12 @@ public X509CertificateValue( final Collection certificates ) { throw new NullPointerException( "certificates cannot be null" ); } - this.certificates = certificates.toArray( new X509Certificate[ certificates.size() ] ); + this.certificates = certificates.toArray( new X509Certificate[0] ); } @Override - public List toXmlValues( final String valueElementName, final PwmSecurityKey pwmSecurityKey ) + public List toXmlValues( final String valueElementName, final XmlOutputProcessData xmlOutputProcessData ) { final List returnList = new ArrayList<>(); for ( final X509Certificate value : certificates ) @@ -112,9 +115,11 @@ public List toXmlValues( final String valueElementName, final PwmSec final XmlElement valueElement = XmlFactory.getFactory().newElement( valueElementName ); try { - valueElement.addText( X509Utils.certificateToBase64( value ) ); + final String b64Value = X509Utils.certificateToBase64( value ); + final String splitValue = StringUtil.insertRepeatedLineBreaks( b64Value, 80 ); + valueElement.addText( splitValue ); } - catch ( CertificateEncodingException e ) + catch ( final CertificateEncodingException e ) { LOGGER.error( "error encoding certificate: " + e.getMessage() ); } @@ -154,7 +159,7 @@ public String toDebugString( final Locale locale ) sb.append( " SHA1 Hash: " ).append( SecureEngine.hash( new ByteArrayInputStream( cert.getEncoded() ), PwmHashAlgorithm.SHA1 ) ).append( "\n" ); } - catch ( PwmUnrecoverableException | CertificateEncodingException e ) + catch ( final PwmUnrecoverableException | CertificateEncodingException e ) { LOGGER.warn( "error generating hash for certificate: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/config/value/data/ActionConfiguration.java b/server/src/main/java/password/pwm/config/value/data/ActionConfiguration.java index 2a6507e0a..5f74bae9d 100644 --- a/server/src/main/java/password/pwm/config/value/data/ActionConfiguration.java +++ b/server/src/main/java/password/pwm/config/value/data/ActionConfiguration.java @@ -123,4 +123,148 @@ public void validate( ) throws PwmOperationalException } } } + + @Value + @Builder( toBuilder = true ) + public static class ActionConfigurationOldVersion1 implements Serializable + { + public enum Type + { + webservice, + ldap,; + } + + public enum WebMethod + { + delete( ActionConfiguration.WebMethod.delete ), + get( ActionConfiguration.WebMethod.get ), + post( ActionConfiguration.WebMethod.post ), + put( ActionConfiguration.WebMethod.put ), + patch( ActionConfiguration.WebMethod.patch ),; + + private final ActionConfiguration.WebMethod newMethod; + + WebMethod( final ActionConfiguration.WebMethod newMethod ) + { + this.newMethod = newMethod; + } + + public ActionConfiguration.WebMethod getNewMethod( ) + { + return newMethod; + } + } + + public enum LdapMethod + { + replace( ActionConfiguration.LdapMethod.replace ), + add( ActionConfiguration.LdapMethod.add ), + remove( ActionConfiguration.LdapMethod.remove ),; + + private final ActionConfiguration.LdapMethod newMethod; + + LdapMethod( final ActionConfiguration.LdapMethod newType ) + { + this.newMethod = newType; + } + + public ActionConfiguration.LdapMethod getNewMethod( ) + { + return newMethod; + } + } + + private String name; + private String description; + + @Builder.Default + private Type type = Type.webservice; + + @Builder.Default + private WebMethod method = WebMethod.get; + + private Map headers; + private String url; + private String body; + private String username; + private String password; + private List certificates; + + private LdapMethod ldapMethod = LdapMethod.replace; + private String attributeName; + private String attributeValue; + + public static ActionConfigurationOldVersion1 parseOldConfigString( final String value ) + { + final String[] splitString = value.split( "=" ); + final String attributeName = splitString[ 0 ]; + final String attributeValue = splitString[ 1 ]; + return ActionConfigurationOldVersion1.builder() + .name( attributeName ) + .description( attributeName ) + .type( Type.ldap ) + .attributeName( attributeName ) + .attributeValue( attributeValue ) + .build(); + } + + public void validate( ) throws PwmOperationalException + { + if ( this.getName() == null || this.getName().length() < 1 ) + { + throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] + { + " form field name is required", + } + ) ); + } + + if ( this.getType() == null ) + { + throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] + { + " type is required for field " + this.getName(), + } + ) ); + } + + if ( this.getType() == Type.webservice ) + { + if ( this.getMethod() == null ) + { + throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] + { + " method for webservice action " + this.getName() + " is required", + } + ) ); + } + if ( this.getUrl() == null || this.getUrl().length() < 1 ) + { + throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] + { + " url for webservice action " + this.getName() + " is required", + } ) ); + } + } + else if ( this.getType() == Type.ldap ) + { + if ( this.getAttributeName() == null || this.getAttributeName().length() < 1 ) + { + throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] + { + " attribute name for ldap action " + this.getName() + " is required", + } + ) ); + } + if ( this.getAttributeValue() == null || this.getAttributeValue().length() < 1 ) + { + throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] + { + " attribute value for ldap action " + this.getName() + " is required", + } + ) ); + } + } + } + } } diff --git a/server/src/main/java/password/pwm/config/value/data/ActionConfigurationOldVersion1.java b/server/src/main/java/password/pwm/config/value/data/ActionConfigurationOldVersion1.java deleted file mode 100644 index 4f8af6fcc..000000000 --- a/server/src/main/java/password/pwm/config/value/data/ActionConfigurationOldVersion1.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Password Management Servlets (PWM) - * http://www.pwm-project.org - * - * Copyright (c) 2006-2009 Novell, Inc. - * Copyright (c) 2009-2019 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.config.value.data; - -import lombok.Data; -import password.pwm.error.ErrorInformation; -import password.pwm.error.PwmError; -import password.pwm.error.PwmOperationalException; -import password.pwm.util.java.JsonUtil; - -import java.io.Serializable; -import java.security.cert.X509Certificate; -import java.util.List; -import java.util.Map; - -@Data -public class ActionConfigurationOldVersion1 implements Serializable -{ - public enum Type - { - webservice, - ldap,; - } - - public enum WebMethod - { - delete( ActionConfiguration.WebMethod.delete ), - get( ActionConfiguration.WebMethod.get ), - post( ActionConfiguration.WebMethod.post ), - put( ActionConfiguration.WebMethod.put ), - patch( ActionConfiguration.WebMethod.patch ),; - - private final ActionConfiguration.WebMethod newMethod; - - WebMethod( final ActionConfiguration.WebMethod newMethod ) - { - this.newMethod = newMethod; - } - - public ActionConfiguration.WebMethod getNewMethod( ) - { - return newMethod; - } - } - - public enum LdapMethod - { - replace( ActionConfiguration.LdapMethod.replace ), - add( ActionConfiguration.LdapMethod.add ), - remove( ActionConfiguration.LdapMethod.remove ),; - - private final ActionConfiguration.LdapMethod newMethod; - - LdapMethod( final ActionConfiguration.LdapMethod newType ) - { - this.newMethod = newType; - } - - public ActionConfiguration.LdapMethod getNewMethod( ) - { - return newMethod; - } - } - - private String name; - private String description; - - private ActionConfigurationOldVersion1.Type type = ActionConfigurationOldVersion1.Type.webservice; - - private ActionConfigurationOldVersion1.WebMethod method = ActionConfigurationOldVersion1.WebMethod.get; - private Map headers; - private String url; - private String body; - private String username; - private String password; - private List certificates; - - - private ActionConfigurationOldVersion1.LdapMethod ldapMethod = ActionConfigurationOldVersion1.LdapMethod.replace; - private String attributeName; - private String attributeValue; - - public static ActionConfigurationOldVersion1 parseOldConfigString( final String value ) - { - final String[] splitString = value.split( "=" ); - final String attributeName = splitString[ 0 ]; - final String attributeValue = splitString[ 1 ]; - final ActionConfigurationOldVersion1 actionConfiguration = new ActionConfigurationOldVersion1(); - actionConfiguration.name = attributeName; - actionConfiguration.description = attributeName; - actionConfiguration.type = ActionConfigurationOldVersion1.Type.ldap; - actionConfiguration.attributeName = attributeName; - actionConfiguration.attributeValue = attributeValue; - return actionConfiguration; - } - - public void validate( ) throws PwmOperationalException - { - if ( this.getName() == null || this.getName().length() < 1 ) - { - throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] - { - " form field name is required", - } - ) ); - } - - if ( this.getType() == null ) - { - throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] - { - " type is required for field " + this.getName(), - } - ) ); - } - - if ( this.getType() == ActionConfigurationOldVersion1.Type.webservice ) - { - if ( this.getMethod() == null ) - { - throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] - { - " method for webservice action " + this.getName() + " is required", - } - ) ); - } - if ( this.getUrl() == null || this.getUrl().length() < 1 ) - { - throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] - { - " url for webservice action " + this.getName() + " is required", - } ) ); - } - } - else if ( this.getType() == ActionConfigurationOldVersion1.Type.ldap ) - { - if ( this.getAttributeName() == null || this.getAttributeName().length() < 1 ) - { - throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] - { - " attribute name for ldap action " + this.getName() + " is required", - } - ) ); - } - if ( this.getAttributeValue() == null || this.getAttributeValue().length() < 1 ) - { - throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] - { - " attribute value for ldap action " + this.getName() + " is required", - } - ) ); - } - } - } - - public ActionConfigurationOldVersion1 copyWithNewCertificate( final List certificates ) - { - final ActionConfigurationOldVersion1 clone = JsonUtil.cloneUsingJson( this, ActionConfigurationOldVersion1.class ); - clone.certificates = certificates; - return clone; - } -} diff --git a/server/src/main/java/password/pwm/config/value/data/ChallengeItemConfiguration.java b/server/src/main/java/password/pwm/config/value/data/ChallengeItemConfiguration.java index ea787dc07..81b586c52 100644 --- a/server/src/main/java/password/pwm/config/value/data/ChallengeItemConfiguration.java +++ b/server/src/main/java/password/pwm/config/value/data/ChallengeItemConfiguration.java @@ -20,11 +20,13 @@ package password.pwm.config.value.data; -import lombok.Getter; +import lombok.Builder; +import lombok.Value; import java.io.Serializable; -@Getter +@Value +@Builder public class ChallengeItemConfiguration implements Serializable { private String text; @@ -37,17 +39,4 @@ public class ChallengeItemConfiguration implements Serializable private int points; private String setupGuide; private String regex; - - public ChallengeItemConfiguration( - final String challengeText, - final int minimumLength, - final int maximumLength, - final boolean adminDefined - ) - { - this.text = challengeText; - this.minLength = minimumLength; - this.maxLength = maximumLength; - this.adminDefined = adminDefined; - } } diff --git a/server/src/main/java/password/pwm/config/CustomLinkConfiguration.java b/server/src/main/java/password/pwm/config/value/data/CustomLinkConfiguration.java similarity index 96% rename from server/src/main/java/password/pwm/config/CustomLinkConfiguration.java rename to server/src/main/java/password/pwm/config/value/data/CustomLinkConfiguration.java index 37880c432..a8cd92b42 100644 --- a/server/src/main/java/password/pwm/config/CustomLinkConfiguration.java +++ b/server/src/main/java/password/pwm/config/value/data/CustomLinkConfiguration.java @@ -18,9 +18,9 @@ * limitations under the License. */ -package password.pwm.config; +package password.pwm.config.value.data; -import lombok.Getter; +import lombok.Value; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.JsonUtil; @@ -32,7 +32,7 @@ /** * @author Richard A. Keil */ -@Getter +@Value public class CustomLinkConfiguration implements Serializable { diff --git a/server/src/main/java/password/pwm/config/value/data/FormConfiguration.java b/server/src/main/java/password/pwm/config/value/data/FormConfiguration.java index f1aa4812f..73eb9f49b 100644 --- a/server/src/main/java/password/pwm/config/value/data/FormConfiguration.java +++ b/server/src/main/java/password/pwm/config/value/data/FormConfiguration.java @@ -20,10 +20,8 @@ package password.pwm.config.value.data; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Getter; +import lombok.Value; import password.pwm.AppProperty; import password.pwm.PwmConstants; import password.pwm.config.Configuration; @@ -51,15 +49,10 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -/** - * @author Jason D. Rivard - */ -@Getter -@Builder -@AllArgsConstructor( access = AccessLevel.PRIVATE ) +@Value +@Builder( toBuilder = true ) public class FormConfiguration implements Serializable { - public enum Type { text, @@ -150,23 +143,23 @@ public static FormConfiguration parseOldConfigString( final String config ) throw new NullPointerException( "config can not be null" ); } - final FormConfiguration formItem = new FormConfiguration(); + final FormConfiguration.FormConfigurationBuilder builder = FormConfiguration.builder(); final StringTokenizer st = new StringTokenizer( config, ":" ); // attribute name - formItem.name = st.nextToken(); + builder.name( st.nextToken() ); // label - formItem.labels = Collections.singletonMap( "", st.nextToken() ); + builder.labels( Collections.singletonMap( "", st.nextToken() ) ); // type { final String typeStr = st.nextToken(); try { - formItem.type = Type.valueOf( typeStr.toLowerCase() ); + builder.type( Type.valueOf( typeStr.toLowerCase() ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { "unknown type for form config: " + typeStr, @@ -177,9 +170,9 @@ public static FormConfiguration parseOldConfigString( final String config ) //minimum length try { - formItem.minimumLength = Integer.parseInt( st.nextToken() ); + builder.minimumLength( Integer.parseInt( st.nextToken() ) ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { "invalid minimum length type for form config: " + e.getMessage(), @@ -189,9 +182,9 @@ public static FormConfiguration parseOldConfigString( final String config ) //maximum length try { - formItem.maximumLength = Integer.parseInt( st.nextToken() ); + builder.maximumLength( Integer.parseInt( st.nextToken() ) ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { "invalid maximum length type for form config: " + e.getMessage(), @@ -199,17 +192,17 @@ public static FormConfiguration parseOldConfigString( final String config ) } //required - formItem.required = Boolean.TRUE.toString().equalsIgnoreCase( st.nextToken() ); + builder.required( Boolean.TRUE.toString().equalsIgnoreCase( st.nextToken() ) ); //confirmation - formItem.confirmationRequired = Boolean.TRUE.toString().equalsIgnoreCase( st.nextToken() ); + builder.confirmationRequired( Boolean.TRUE.toString().equalsIgnoreCase( st.nextToken() ) ); - return formItem; + return builder.build(); } public void validate( ) throws PwmOperationalException { - if ( this.getName() == null || this.getName().length() < 1 ) + if ( StringUtil.isEmpty( this.getName() ) ) { throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { " form field name is required", @@ -236,7 +229,7 @@ public void validate( ) throws PwmOperationalException { Pattern.compile( this.getRegex() ); } - catch ( PatternSyntaxException e ) + catch ( final PatternSyntaxException e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { " regular expression for '" + this.getName() + " ' is not valid: " + e.getMessage(), @@ -255,12 +248,6 @@ public void validate( ) throws PwmOperationalException } } - public FormConfiguration( ) - { - labels = Collections.singletonMap( "", "" ); - regexErrors = Collections.singletonMap( "", "" ); - } - public String getLabel( final Locale locale ) { return LocaleHelper.resolveStringKeyLocaleMap( locale, labels ); @@ -341,7 +328,7 @@ public void checkValue( final Configuration config, final String value, final Lo { new BigInteger( value ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { final ErrorInformation error = new ErrorInformation( PwmError.ERROR_FIELD_NOT_A_NUMBER, null, new String[] { getLabel( locale ), diff --git a/server/src/main/java/password/pwm/config/value/data/NamedSecretData.java b/server/src/main/java/password/pwm/config/value/data/NamedSecretData.java index 4e6d1d351..5edf101e3 100644 --- a/server/src/main/java/password/pwm/config/value/data/NamedSecretData.java +++ b/server/src/main/java/password/pwm/config/value/data/NamedSecretData.java @@ -20,15 +20,13 @@ package password.pwm.config.value.data; -import lombok.AllArgsConstructor; -import lombok.Getter; +import lombok.Value; import password.pwm.util.PasswordData; import java.io.Serializable; import java.util.List; -@Getter -@AllArgsConstructor +@Value public class NamedSecretData implements Serializable { private PasswordData password; diff --git a/server/src/main/java/password/pwm/config/value/data/RemoteWebServiceConfiguration.java b/server/src/main/java/password/pwm/config/value/data/RemoteWebServiceConfiguration.java index b5202dd78..2773b09f5 100644 --- a/server/src/main/java/password/pwm/config/value/data/RemoteWebServiceConfiguration.java +++ b/server/src/main/java/password/pwm/config/value/data/RemoteWebServiceConfiguration.java @@ -20,16 +20,16 @@ package password.pwm.config.value.data; -import lombok.Getter; -import lombok.Setter; +import lombok.Builder; +import lombok.Value; import java.io.Serializable; import java.security.cert.X509Certificate; import java.util.List; import java.util.Map; -@Getter -@Setter +@Value +@Builder( toBuilder = true ) public class RemoteWebServiceConfiguration implements Serializable { diff --git a/server/src/main/java/password/pwm/config/value/data/ShortcutItem.java b/server/src/main/java/password/pwm/config/value/data/ShortcutItem.java index 7605ff22f..797db16ea 100644 --- a/server/src/main/java/password/pwm/config/value/data/ShortcutItem.java +++ b/server/src/main/java/password/pwm/config/value/data/ShortcutItem.java @@ -20,15 +20,13 @@ package password.pwm.config.value.data; -import lombok.AllArgsConstructor; -import lombok.Getter; +import lombok.Value; import password.pwm.util.logging.PwmLogger; import java.io.Serializable; import java.net.URI; -@Getter -@AllArgsConstructor +@Value public class ShortcutItem implements Serializable { @@ -63,7 +61,7 @@ public static ShortcutItem parsePwmConfigInput( final String input ) splitSettings[ 3 ] ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "malformed ShortcutItem configuration value of '" + input + "', " + e.getMessage() ); } @@ -86,7 +84,7 @@ public static ShortcutItem parseHeaderInput( final String input ) splitSettings[ 2 ] ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "malformed ShortcutItem configuration value of '" + input + "', " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/config/value/data/UserPermission.java b/server/src/main/java/password/pwm/config/value/data/UserPermission.java index 974c7dda8..a544aeaef 100644 --- a/server/src/main/java/password/pwm/config/value/data/UserPermission.java +++ b/server/src/main/java/password/pwm/config/value/data/UserPermission.java @@ -20,10 +20,13 @@ package password.pwm.config.value.data; -import password.pwm.PwmConstants; +import lombok.Builder; +import lombok.Value; import java.io.Serializable; +@Value +@Builder public class UserPermission implements Serializable { public enum Type @@ -32,38 +35,12 @@ public enum Type ldapGroup, } - private String ldapProfileID = PwmConstants.PROFILE_ID_ALL; + @Builder.Default + private Type type = Type.ldapQuery; + + private String ldapProfileID; private String ldapQuery; private String ldapBase; - private Type type; - - public UserPermission( - final Type type, - final String ldapProfileID, - final String ldapQuery, - final String ldapBase - ) - { - this.type = type; - this.ldapProfileID = ldapProfileID; - this.ldapQuery = ldapQuery; - this.ldapBase = ldapBase; - } - - public String getLdapProfileID( ) - { - return ldapProfileID == null ? null : ldapProfileID.trim(); - } - - public String getLdapQuery( ) - { - return ldapQuery; - } - - public String getLdapBase( ) - { - return ldapBase; - } public Type getType( ) { diff --git a/server/src/main/java/password/pwm/error/PwmInternalException.java b/server/src/main/java/password/pwm/error/PwmInternalException.java new file mode 100644 index 000000000..37c9219bb --- /dev/null +++ b/server/src/main/java/password/pwm/error/PwmInternalException.java @@ -0,0 +1,29 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2019 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.error; + +public class PwmInternalException extends RuntimeException +{ + public PwmInternalException( final String message, final Throwable cause ) + { + super( message, cause ); + } +} diff --git a/server/src/main/java/password/pwm/health/ApplianceStatusChecker.java b/server/src/main/java/password/pwm/health/ApplianceStatusChecker.java index 2dcd4ba45..6afb5f21b 100644 --- a/server/src/main/java/password/pwm/health/ApplianceStatusChecker.java +++ b/server/src/main/java/password/pwm/health/ApplianceStatusChecker.java @@ -72,7 +72,7 @@ public List doHealthCheck( final PwmApplication pwmApplication ) { healthRecords.addAll( readApplianceHealthStatus( pwmApplication ) ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( SessionLabel.HEALTH_SESSION_LABEL, "error communicating with client " + e.getMessage() ); } @@ -174,7 +174,7 @@ private String readFileContents( final String filename ) throws PwmOperationalEx } return ""; } - catch ( IOException e ) + catch ( final IOException e ) { final String msg = "unable to read contents of file '" + filename + "', error: " + e.getMessage(); throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_INTERNAL, msg ), e ); diff --git a/server/src/main/java/password/pwm/health/CertificateChecker.java b/server/src/main/java/password/pwm/health/CertificateChecker.java index 1ca72d4b8..394a3b1a7 100644 --- a/server/src/main/java/password/pwm/health/CertificateChecker.java +++ b/server/src/main/java/password/pwm/health/CertificateChecker.java @@ -27,9 +27,8 @@ import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingSyntax; import password.pwm.config.profile.LdapProfile; -import password.pwm.config.stored.StoredConfigReference; -import password.pwm.config.stored.StoredConfigurationImpl; -import password.pwm.config.stored.StoredConfigurationUtil; +import password.pwm.config.stored.StoredConfigItemKey; +import password.pwm.config.stored.StoredConfiguration; import password.pwm.config.value.ActionValue; import password.pwm.config.value.data.ActionConfiguration; import password.pwm.error.ErrorInformation; @@ -46,6 +45,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; public class CertificateChecker implements HealthChecker { @@ -60,7 +60,7 @@ public List doHealthCheck( final PwmApplication pwmApplication ) { records.addAll( doActionHealthCheck( pwmApplication.getConfig() ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error while checking action certificates: " + e.getMessage(), e ); } @@ -92,24 +92,24 @@ private static List doHealthCheck( final Configuration configurati private static List doActionHealthCheck( final Configuration configuration ) throws PwmUnrecoverableException { - final StoredConfigurationImpl storedConfiguration = configuration.getStoredConfiguration(); + final StoredConfiguration storedConfiguration = configuration.getStoredConfiguration(); final List returnList = new ArrayList<>(); - final List modifiedReferences = StoredConfigurationUtil.modifiedSettings( storedConfiguration ); - for ( final StoredConfigReference storedConfigReference : modifiedReferences ) + final Set modifiedReferences = storedConfiguration.modifiedItems(); + for ( final StoredConfigItemKey storedConfigItemKey : modifiedReferences ) { - if ( storedConfigReference.getRecordType() == StoredConfigReference.RecordType.SETTING ) + if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) { - final PwmSetting pwmSetting = PwmSetting.forKey( storedConfigReference.getRecordID() ); + final PwmSetting pwmSetting = PwmSetting.forKey( storedConfigItemKey.getRecordID() ); if ( pwmSetting != null && pwmSetting.getSyntax() == PwmSettingSyntax.ACTION ) { - final ActionValue value = ( ActionValue ) storedConfiguration.readSetting( pwmSetting, storedConfigReference.getProfileID() ); + final ActionValue value = ( ActionValue ) storedConfiguration.readSetting( pwmSetting, storedConfigItemKey.getProfileID() ); for ( final ActionConfiguration actionConfiguration : value.toNativeObject() ) { for ( final ActionConfiguration.WebAction webAction : actionConfiguration.getWebActions() ) { final List certificates = webAction.getCertificates(); - returnList.addAll( doHealthCheck( configuration, pwmSetting, storedConfigReference.getProfileID(), certificates ) ); + returnList.addAll( doHealthCheck( configuration, pwmSetting, storedConfigItemKey.getProfileID(), certificates ) ); } } } @@ -137,7 +137,7 @@ private static List doHealthCheck( checkCertificate( certificate, warnDurationMs ); return Collections.emptyList(); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorDetail = e.getErrorInformation().getDetailedErrorMsg(); final HealthRecord record = HealthRecord.forMessage( HealthMessage.Config_Certificate, @@ -164,7 +164,7 @@ public static void checkCertificate( final X509Certificate certificate, final lo { certificate.checkValidity(); } - catch ( CertificateException e ) + catch ( final CertificateException e ) { final StringBuilder errorMsg = new StringBuilder(); errorMsg.append( "certificate for subject " ); diff --git a/server/src/main/java/password/pwm/health/ConfigurationChecker.java b/server/src/main/java/password/pwm/health/ConfigurationChecker.java index 6def3cdb3..c1c41dc12 100644 --- a/server/src/main/java/password/pwm/health/ConfigurationChecker.java +++ b/server/src/main/java/password/pwm/health/ConfigurationChecker.java @@ -24,21 +24,20 @@ import password.pwm.PwmApplication; import password.pwm.PwmApplicationMode; import password.pwm.PwmConstants; -import password.pwm.bean.SessionLabel; import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingSyntax; import password.pwm.config.StoredValue; import password.pwm.config.option.DataStorageMethod; import password.pwm.config.option.MessageSendMethod; +import password.pwm.config.profile.ActivateUserProfile; 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.PwmPasswordPolicy; -import password.pwm.config.profile.ActivateUserProfile; +import password.pwm.config.stored.StoredConfigItemKey; import password.pwm.config.value.data.FormConfiguration; -import password.pwm.error.PwmException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.i18n.Config; import password.pwm.util.PasswordData; @@ -49,6 +48,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -86,7 +86,7 @@ public List doHealthCheck( final PwmApplication pwmApplication ) { newUserProfile.getNewUserPasswordPolicy( pwmApplication, PwmConstants.DEFAULT_LOCALE ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { records.add( new HealthRecord( HealthStatus.WARN, HealthTopic.Configuration, e.getMessage() ) ); } @@ -100,183 +100,173 @@ public List doHealthCheck( final PwmApplication pwmApplication ) public List doHealthCheck( final Configuration config, final Locale locale ) { - - - final List records = new ArrayList<>(); - if ( config.readSettingAsBoolean( PwmSetting.HIDE_CONFIGURATION_HEALTH_WARNINGS ) ) { - return records; + return Collections.emptyList(); } - records.addAll( allChecks( config, locale ) ); + return Collections.unmodifiableList( allChecks( config, locale ) ); + } - final String siteUrl = config.readSettingAsString( PwmSetting.PWM_SITE_URL ); - final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null ); - if ( siteUrl == null || siteUrl.isEmpty() || siteUrl.equals( - PwmSetting.PWM_SITE_URL.getDefaultValue( config.getTemplate() ).toNativeObject() ) ) + private List allChecks( + final Configuration config, + final Locale locale + ) + { + final List records = new ArrayList<>(); + for ( final Class clazz : ALL_CHECKS ) { - records.add( - HealthRecord.forMessage( HealthMessage.Config_NoSiteURL, PwmSetting.PWM_SITE_URL.toMenuLocationDebug( null, locale ) ) ); + final ConfigHealthCheck healthCheckClass; + try + { + healthCheckClass = clazz.getDeclaredConstructor().newInstance(); + records.addAll( healthCheckClass.healthCheck( config, locale ) ); + } + catch ( final Exception e ) + { + LOGGER.error( "unexpected error during health check operation for class " + clazz.toString() + ", error:" + e.getMessage(), e ); + } } + return records; + } + private static final List> ALL_CHECKS = Collections.unmodifiableList( Arrays.asList( + VerifyBasicConfigs.class, + VerifyPasswordStrengthLevels.class, + VerifyPasswordPolicyConfigs.class, + VerifyResponseLdapAttribute.class, + VerifyDbConfiguredIfNeeded.class, + VerifyIfDeprecatedSendMethodValuesUsed.class, + VerifyIfDeprecatedJsFormOptionUsed.class + ) ); - - if ( config.readSettingAsBoolean( PwmSetting.LDAP_ENABLE_WIRE_TRACE ) ) + static class VerifyBasicConfigs implements ConfigHealthCheck + { + @Override + public List healthCheck( final Configuration config, final Locale locale ) { - records.add( - HealthRecord.forMessage( HealthMessage.Config_LDAPWireTrace, PwmSetting.LDAP_ENABLE_WIRE_TRACE.toMenuLocationDebug( null, locale ) ) ); - } + final List records = new ArrayList<>(); + final String siteUrl = config.readSettingAsString( PwmSetting.PWM_SITE_URL ); + final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null ); - if ( Boolean.parseBoolean( config.readAppProperty( AppProperty.LDAP_PROMISCUOUS_ENABLE ) ) ) - { - final String appPropertyKey = "AppProperty" + separator + AppProperty.LDAP_PROMISCUOUS_ENABLE.getKey(); - records.add( HealthRecord.forMessage( HealthMessage.Config_PromiscuousLDAP, appPropertyKey ) ); - } + if ( siteUrl == null || siteUrl.isEmpty() || siteUrl.equals( + PwmSetting.PWM_SITE_URL.getDefaultValue( config.getTemplate() ).toNativeObject() ) ) + { + records.add( + HealthRecord.forMessage( HealthMessage.Config_NoSiteURL, PwmSetting.PWM_SITE_URL.toMenuLocationDebug( null, locale ) ) ); + } - if ( config.readSettingAsBoolean( PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS ) ) - { - records.add( HealthRecord.forMessage( HealthMessage.Config_ShowDetailedErrors, PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS.toMenuLocationDebug( null, locale ) ) ); - } + if ( config.readSettingAsBoolean( PwmSetting.LDAP_ENABLE_WIRE_TRACE ) ) + { + records.add( + HealthRecord.forMessage( HealthMessage.Config_LDAPWireTrace, PwmSetting.LDAP_ENABLE_WIRE_TRACE.toMenuLocationDebug( null, locale ) ) ); + } - for ( final LdapProfile ldapProfile : config.getLdapProfiles().values() ) - { - final String testUserDN = ldapProfile.readSettingAsString( PwmSetting.LDAP_TEST_USER_DN ); - if ( testUserDN == null || testUserDN.length() < 1 ) + if ( Boolean.parseBoolean( config.readAppProperty( AppProperty.LDAP_PROMISCUOUS_ENABLE ) ) ) { - records.add( HealthRecord.forMessage( HealthMessage.Config_AddTestUser, PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), locale ) ) ); + final String appPropertyKey = "AppProperty" + separator + AppProperty.LDAP_PROMISCUOUS_ENABLE.getKey(); + records.add( HealthRecord.forMessage( HealthMessage.Config_PromiscuousLDAP, appPropertyKey ) ); } - } - for ( final LdapProfile ldapProfile : config.getLdapProfiles().values() ) - { - final List ldapServerURLs = ldapProfile.getLdapUrls(); - if ( ldapServerURLs != null && !ldapServerURLs.isEmpty() ) + if ( config.readSettingAsBoolean( PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS ) ) + { + records.add( HealthRecord.forMessage( HealthMessage.Config_ShowDetailedErrors, PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS.toMenuLocationDebug( null, locale ) ) ); + } + + for ( final LdapProfile ldapProfile : config.getLdapProfiles().values() ) { - for ( final String urlStringValue : ldapServerURLs ) + final String testUserDN = ldapProfile.readSettingAsString( PwmSetting.LDAP_TEST_USER_DN ); + if ( testUserDN == null || testUserDN.length() < 1 ) { - try + records.add( HealthRecord.forMessage( + HealthMessage.Config_AddTestUser, + PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), locale ) + ) ); + } + } + + for ( final LdapProfile ldapProfile : config.getLdapProfiles().values() ) + { + final List ldapServerURLs = ldapProfile.getLdapUrls(); + if ( ldapServerURLs != null && !ldapServerURLs.isEmpty() ) + { + for ( final String urlStringValue : ldapServerURLs ) { - final URI url = new URI( urlStringValue ); - final boolean secure = "ldaps".equalsIgnoreCase( url.getScheme() ); - if ( !secure ) + try { - records.add( HealthRecord.forMessage( - HealthMessage.Config_LDAPUnsecure, - PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug( ldapProfile.getIdentifier(), locale ) + final URI url = new URI( urlStringValue ); + final boolean secure = "ldaps".equalsIgnoreCase( url.getScheme() ); + if ( !secure ) + { + records.add( HealthRecord.forMessage( + HealthMessage.Config_LDAPUnsecure, + PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug( ldapProfile.getIdentifier(), locale ) + ) ); + } + } + catch ( final URISyntaxException e ) + { + records.add( HealthRecord.forMessage( HealthMessage.Config_ParseError, + e.getMessage(), + PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug( ldapProfile.getIdentifier(), locale ), + urlStringValue ) ); } } - catch ( URISyntaxException e ) - { - records.add( HealthRecord.forMessage( HealthMessage.Config_ParseError, - e.getMessage(), - PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug( ldapProfile.getIdentifier(), locale ), - urlStringValue - ) ); - } } } - } - - records.addAll( passwordStrengthChecks( config, locale ) ); - return records; + return records; + } } - private List passwordStrengthChecks( - final Configuration config, - final Locale locale - ) + static class VerifyPasswordStrengthLevels implements ConfigHealthCheck { - final List records = new ArrayList<>( ); - - for ( final PwmSetting setting : PwmSetting.values() ) + @Override + public List healthCheck( final Configuration config, final Locale locale ) { - if ( - setting.getSyntax() == PwmSettingSyntax.PASSWORD - && !setting.getCategory().hasProfiles() - && !config.isDefaultValue( setting ) - ) + final List records = new ArrayList<>(); + + try { - try + for ( final StoredConfigItemKey key : config.getStoredConfiguration().modifiedItems() ) { - final PasswordData passwordValue = config.readSettingAsPassword( setting ); - final String stringValue = passwordValue.getStringValue(); - if ( !StringUtil.isEmpty( stringValue ) ) + final Instant startTime = Instant.now(); + if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) { - final int strength = PasswordUtility.judgePasswordStrength( config, stringValue ); - if ( strength < 50 ) + final PwmSetting pwmSetting = key.toPwmSetting(); + if ( pwmSetting.getSyntax() == PwmSettingSyntax.PASSWORD ) { - records.add( HealthRecord.forMessage( HealthMessage.Config_WeakPassword, - setting.toMenuLocationDebug( null, locale ), String.valueOf( strength ) ) ); + final StoredValue storedValue = config.getStoredConfiguration().readSetting( pwmSetting, key.getProfileID() ); + final PasswordData passwordValue = ( PasswordData ) storedValue.toNativeObject(); + if ( passwordValue != null ) + { + final String stringValue = passwordValue.getStringValue(); + + if ( !StringUtil.isEmpty( stringValue ) ) + { + final int strength = PasswordUtility.judgePasswordStrength( config, stringValue ); + if ( strength < 50 ) + { + records.add( HealthRecord.forMessage( HealthMessage.Config_WeakPassword, + pwmSetting.toMenuLocationDebug( key.getProfileID(), locale ), String.valueOf( strength ) ) ); + } + } + } } } } - catch ( Exception e ) - { - LOGGER.error( SessionLabel.HEALTH_SESSION_LABEL, "error while inspecting setting " - + setting.toMenuLocationDebug( null, locale ) + ", error: " + e.getMessage() ); - } } - } - for ( final LdapProfile profile : config.getLdapProfiles().values() ) - { - final PwmSetting setting = PwmSetting.LDAP_PROXY_USER_PASSWORD; - try - { - final PasswordData passwordValue = profile.readSettingAsPassword( setting ); - final int strength = PasswordUtility.judgePasswordStrength( config, passwordValue == null ? null : passwordValue.getStringValue() ); - if ( strength < 50 ) - { - records.add( HealthRecord.forMessage( HealthMessage.Config_WeakPassword, - setting.toMenuLocationDebug( profile.getIdentifier(), locale ), - String.valueOf( strength ) ) ); - } - } - catch ( PwmException e ) + catch ( final PwmUnrecoverableException e ) { - LOGGER.error( - SessionLabel.HEALTH_SESSION_LABEL, - "error while inspecting setting " + setting.toMenuLocationDebug( profile.getIdentifier(), locale ) - + ", error: " + e.getMessage() ); + LOGGER.error( "unexpected error examining password strength of configuration: " ); } - } - - return records; - } - private List allChecks( - final Configuration config, - final Locale locale - ) - { - final List records = new ArrayList<>(); - for ( final Class clazz : ALL_CHECKS ) - { - final ConfigHealthCheck healthCheckClass; - try - { - healthCheckClass = clazz.newInstance(); - records.addAll( healthCheckClass.healthCheck( config, locale ) ); - } - catch ( Exception e ) - { - LOGGER.error( "unexpected error during health check operation for class " + clazz.toString() + ", error:" + e.getMessage(), e ); - } + return Collections.unmodifiableList( records ); } - return records; } - private static final List> ALL_CHECKS = Collections.unmodifiableList( Arrays.asList( - VerifyPasswordPolicyConfigs.class, - VerifyResponseLdapAttribute.class, - VerifyDbConfiguredIfNeeded.class, - VerifyIfDeprecatedSendMethodValuesUsed.class, - VerifyIfDeprecatedJsFormOptionUsed.class - ) ); - static class VerifyResponseLdapAttribute implements ConfigHealthCheck { @Override @@ -381,7 +371,7 @@ public List healthCheck( final Configuration config, final Locale final PwmPasswordPolicy pwmPasswordPolicy = config.getPasswordPolicy( profileID, locale ); records.addAll( pwmPasswordPolicy.health( locale ) ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error during password policy health check: " + e.getMessage(), e ); } @@ -403,30 +393,25 @@ public List healthCheck( final Configuration config, final Locale { if ( loopSetting.getCategory().hasProfiles() ) { - try + + final List profiles = config.getStoredConfiguration().profilesForSetting( loopSetting ); + for ( final String profile : profiles ) { - final List profiles = config.getStoredConfiguration().profilesForSetting( loopSetting ); - for ( final String profile : profiles ) + final StoredValue storedValue = config.getStoredConfiguration().readSetting( loopSetting, profile ); + final List forms = (List) storedValue.toNativeObject(); + for ( final FormConfiguration form : forms ) { - final StoredValue storedValue = config.getStoredConfiguration().readSetting( loopSetting, profile ); - final List forms = (List) storedValue.toNativeObject(); - for ( final FormConfiguration form : forms ) + if ( !StringUtil.isEmpty( form.getJavascript() ) ) { - if ( !StringUtil.isEmpty( form.getJavascript() ) ) - { - records.add( HealthRecord.forMessage( - HealthMessage.Config_DeprecatedJSForm, - loopSetting.toMenuLocationDebug( profile, locale ), - PwmSetting.DISPLAY_CUSTOM_JAVASCRIPT.toMenuLocationDebug( null, locale ) - ) ); - } + records.add( HealthRecord.forMessage( + HealthMessage.Config_DeprecatedJSForm, + loopSetting.toMenuLocationDebug( profile, locale ), + PwmSetting.DISPLAY_CUSTOM_JAVASCRIPT.toMenuLocationDebug( null, locale ) + ) ); } } } - catch ( PwmUnrecoverableException e ) - { - LOGGER.error( "unexpected error examining profiles for deprecated form js option check: " + e.getMessage() ); - } + } else { diff --git a/server/src/main/java/password/pwm/health/DatabaseStatusChecker.java b/server/src/main/java/password/pwm/health/DatabaseStatusChecker.java index 9859c8855..eefd2f5da 100644 --- a/server/src/main/java/password/pwm/health/DatabaseStatusChecker.java +++ b/server/src/main/java/password/pwm/health/DatabaseStatusChecker.java @@ -62,7 +62,7 @@ private static List checkDatabaseStatus( final PwmApplication pwmA accessor.get( DatabaseTable.PWM_META, "test" ); return runtimeInstance.getDatabaseService().healthCheck(); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( "error during healthcheck: " + e.getMessage() ); e.printStackTrace(); diff --git a/server/src/main/java/password/pwm/health/HealthMonitor.java b/server/src/main/java/password/pwm/health/HealthMonitor.java index 1831d53e0..5d619210d 100644 --- a/server/src/main/java/password/pwm/health/HealthMonitor.java +++ b/server/src/main/java/password/pwm/health/HealthMonitor.java @@ -227,7 +227,7 @@ private void doHealthChecks( ) tempResults.addAll( loopResults ); } } - catch ( Exception e ) + catch ( final Exception e ) { if ( status == STATUS.OPEN ) { @@ -245,7 +245,7 @@ private void doHealthChecks( ) tempResults.addAll( loopResults ); } } - catch ( Exception e ) + catch ( final Exception e ) { if ( status == STATUS.OPEN ) { @@ -291,7 +291,7 @@ public void run( ) doHealthChecks(); LOGGER.trace( () -> "completed health check dredge " + TimeDuration.compactFromCurrent( startTime ) ); } - catch ( Throwable e ) + catch ( final Throwable e ) { LOGGER.error( "error during health check execution: " + e.getMessage(), e ); } @@ -341,7 +341,7 @@ public void run() { writeSupportZipToAppPath(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( SessionLabel.HEALTH_SESSION_LABEL, () -> "error writing support zip to file system: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/health/HealthRecord.java b/server/src/main/java/password/pwm/health/HealthRecord.java index bc918e5ac..6a75c7557 100644 --- a/server/src/main/java/password/pwm/health/HealthRecord.java +++ b/server/src/main/java/password/pwm/health/HealthRecord.java @@ -183,10 +183,10 @@ public static HealthData asHealthDataBean( { final List healthRecordBeans = password.pwm.ws.server.rest.bean.HealthRecord.fromHealthRecords( profileRecords, locale, configuration ); - final HealthData healthData = new HealthData(); - healthData.timestamp = Instant.now(); - healthData.overall = HealthMonitor.getMostSevereHealthStatus( profileRecords ).toString(); - healthData.records = healthRecordBeans; - return healthData; + return HealthData.builder() + .timestamp( Instant.now() ) + .overall( HealthMonitor.getMostSevereHealthStatus( profileRecords ).toString() ) + .records( healthRecordBeans ) + .build(); } } diff --git a/server/src/main/java/password/pwm/health/LDAPHealthChecker.java b/server/src/main/java/password/pwm/health/LDAPHealthChecker.java index 81efbe0dc..cb7d530ff 100644 --- a/server/src/main/java/password/pwm/health/LDAPHealthChecker.java +++ b/server/src/main/java/password/pwm/health/LDAPHealthChecker.java @@ -179,7 +179,7 @@ public List doLdapTestUserCheck( testUserDN = ldapProfile.readCanonicalDN( pwmApplication, testUserDN ); proxyUserDN = ldapProfile.readCanonicalDN( pwmApplication, proxyUserDN ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String msgString = e.getMessage(); LOGGER.trace( SessionLabel.HEALTH_SESSION_LABEL, () -> "unexpected error while testing test user (during object creation): message=" @@ -220,7 +220,7 @@ public List doLdapTestUserCheck( theUser = chaiProvider.getEntryFactory().newChaiUser( testUserDN ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { returnRecords.add( HealthRecord.forMessage( HealthMessage.LDAP_TestUserUnavailable, PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ), @@ -228,7 +228,7 @@ public List doLdapTestUserCheck( ) ); return returnRecords; } - catch ( Throwable e ) + catch ( final Throwable e ) { final String msgString = e.getMessage(); LOGGER.trace( @@ -247,7 +247,7 @@ public List doLdapTestUserCheck( { theUser.readObjectClass(); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { returnRecords.add( HealthRecord.forMessage( HealthMessage.LDAP_TestUserError, PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ), @@ -272,7 +272,7 @@ public List doLdapTestUserCheck( { theUser.readPassword(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( SessionLabel.HEALTH_SESSION_LABEL, () -> "error reading user password from directory " + e.getMessage() ); returnRecords.add( HealthRecord.forMessage( HealthMessage.LDAP_TestUserReadPwError, @@ -337,7 +337,7 @@ public List doLdapTestUserCheck( theUser.setPassword( newPassword.getStringValue() ); LOGGER.debug( SessionLabel.HEALTH_SESSION_LABEL, () -> "set random password on test user " + userIdentity.toDisplayString() ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { returnRecords.add( HealthRecord.forMessage( HealthMessage.LDAP_TestUserWritePwError, PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ), @@ -349,7 +349,7 @@ public List doLdapTestUserCheck( } } } - catch ( Exception e ) + catch ( final Exception e ) { final String msg = "error setting test user password: " + JavaHelper.readHostileExceptionMessage( e ); LOGGER.error( SessionLabel.HEALTH_SESSION_LABEL, msg, e ); @@ -382,7 +382,7 @@ public List doLdapTestUserCheck( userInfo.getUserEmailAddress(); userInfo.getUserSmsNumber(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { returnRecords.add( new HealthRecord( HealthStatus.WARN, @@ -400,7 +400,7 @@ public List doLdapTestUserCheck( { chaiProvider.close(); } - catch ( Exception e ) + catch ( final Exception e ) { // ignore } @@ -438,7 +438,7 @@ public List checkLdapServerUrls( final ChaiUser proxyUser = chaiProvider.getEntryFactory().newChaiUser( proxyDN ); proxyUser.exists(); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorString = "error connecting to ldap server '" + loopURL + "': " + e.getMessage(); returnRecords.add( new HealthRecord( @@ -454,7 +454,7 @@ public List checkLdapServerUrls( { chaiProvider.close(); } - catch ( Exception e ) + catch ( final Exception e ) { /* ignore */ } @@ -512,7 +512,7 @@ public List checkBasicLdapConnectivity( } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final ChaiError chaiError = ChaiErrors.getErrorForMessage( e.getMessage() ); final PwmError pwmError = PwmError.forChaiError( chaiError ); @@ -538,7 +538,7 @@ public List checkBasicLdapConnectivity( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, errorString.toString() ) ); return returnRecords; } - catch ( Exception e ) + catch ( final Exception e ) { final HealthRecord record = HealthRecord.forMessage( HealthMessage.LDAP_No_Connection, e.getMessage() ); returnRecords.add( record ); @@ -567,7 +567,7 @@ public List checkBasicLdapConnectivity( returnRecords.add( new HealthRecord( HealthStatus.WARN, makeLdapTopic( ldapProfile, config ), errorString ) ); } } - catch ( Exception e ) + catch ( final Exception e ) { final String errorString = "ldap root context '" + loopContext + "' is not valid: " + e.getMessage(); returnRecords.add( new HealthRecord( HealthStatus.WARN, makeLdapTopic( ldapProfile, config ), errorString ) ); @@ -583,7 +583,7 @@ public List checkBasicLdapConnectivity( { chaiProvider.close(); } - catch ( Exception e ) + catch ( final Exception e ) { /* ignore */ } @@ -619,7 +619,7 @@ private static List checkAd( final PwmApplication pwmApplication, ) ); } } - catch ( MalformedURLException | UnknownHostException e ) + catch ( final MalformedURLException | UnknownHostException e ) { returnList.add( HealthRecord.forMessage( HealthMessage.Config_ParseError, @@ -695,7 +695,7 @@ private List checkVendorSameness( final PwmApplication pwmApplicat } } } - catch ( Exception e ) + catch ( final Exception e ) { errorReachingServer = true; LOGGER.error( SessionLabel.HEALTH_SESSION_LABEL, "error during replica vendor sameness check: " + e.getMessage() ); @@ -793,7 +793,7 @@ private static List checkAdPasswordPolicyApi( final PwmApplication } } } - catch ( Exception e ) + catch ( final Exception e ) { errorReachingServer = true; LOGGER.error( SessionLabel.HEALTH_SESSION_LABEL, @@ -826,7 +826,7 @@ private static List checkUserPermissionValues( final PwmApplicatio { returnList.addAll( checkUserPermission( pwmApplication, userPermission, pwmSetting ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error checking configured permission settings:" + e.getMessage() ); } @@ -884,7 +884,7 @@ else if ( pwmSetting.getSyntax() == PwmSettingSyntax.STRING_ARRAY ) } } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( "error while checking DN ldap syntax values: " + e.getMessage() ); } @@ -946,12 +946,12 @@ private static List checkNewUserPasswordTemplateSetting( ); } } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error checking new user password policy user settings:" + e.getMessage() ); } @@ -1065,11 +1065,11 @@ private static Optional validateDN( } } } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { LOGGER.error( "error while evaluating ldap DN '" + dnValue + "', error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/ContextManager.java b/server/src/main/java/password/pwm/http/ContextManager.java index ae0e9d37b..972d563c8 100644 --- a/server/src/main/java/password/pwm/http/ContextManager.java +++ b/server/src/main/java/password/pwm/http/ContextManager.java @@ -32,7 +32,8 @@ import password.pwm.config.profile.LdapProfile; import password.pwm.config.stored.ConfigurationProperty; import password.pwm.config.stored.ConfigurationReader; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.config.value.X509CertificateValue; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; @@ -42,7 +43,9 @@ import password.pwm.util.PropertyConfigurationImporter; import password.pwm.util.PwmScheduler; import password.pwm.util.java.JavaHelper; +import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; +import password.pwm.util.logging.PwmLogManager; import password.pwm.util.logging.PwmLogger; import password.pwm.util.secure.X509Utils; @@ -59,10 +62,12 @@ import java.security.cert.X509Certificate; import java.time.Instant; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -75,6 +80,8 @@ public class ContextManager implements Serializable private static final PwmLogger LOGGER = PwmLogger.forClass( ContextManager.class ); private static final SessionLabel SESSION_LABEL = SessionLabel.CONTEXT_SESSION_LABEL; + private static final TimeDuration RESTART_DELAY = TimeDuration.of( 5, TimeDuration.Unit.SECONDS ); + private transient ServletContext servletContext; private transient ScheduledExecutorService taskMaster; @@ -196,7 +203,7 @@ public void initialize( ) { Locale.setDefault( PwmConstants.DEFAULT_LOCALE ); } - catch ( Exception e ) + catch ( final Exception e ) { outputError( "unable to set default locale as Java machine default locale: " + e.getMessage() ); } @@ -225,7 +232,6 @@ public void initialize( ) configurationFile = locateConfigurationFile( applicationPath, PwmConstants.DEFAULT_CONFIG_FILE_FILENAME ); configReader = new ConfigurationReader( configurationFile ); - configReader.getStoredConfiguration().lock(); configuration = configReader.getConfiguration(); mode = startupErrorInformation == null ? configReader.getConfigMode() : PwmApplicationMode.ERROR; @@ -240,7 +246,7 @@ public void initialize( ) outputError( "Startup Error: " + ( startupErrorInformation == null ? "un-specified error" : startupErrorInformation.toDebugStr() ) ); } } - catch ( Throwable e ) + catch ( final Throwable e ) { handleStartupError( "unable to initialize application due to configuration related error: ", e ); } @@ -251,7 +257,13 @@ public void initialize( ) } final Collection applicationFlags = parameterReader.readApplicationFlags(); - final Map applicationParams = parameterReader.readApplicationParams(); + final Map applicationParams = parameterReader.readApplicationParams( applicationPath ); + + if ( applicationParams != null && applicationParams.containsKey( PwmEnvironment.ApplicationParameter.InitConsoleLogLevel ) ) + { + final String logLevel = applicationParams.get( PwmEnvironment.ApplicationParameter.InitConsoleLogLevel ); + PwmLogManager.preInitConsoleLogLevel( logLevel ); + } try { @@ -264,7 +276,7 @@ public void initialize( ) .createPwmEnvironment(); pwmApplication = new PwmApplication( pwmEnvironment ); } - catch ( Exception e ) + catch ( final Exception e ) { handleStartupError( "unable to initialize application: ", e ); } @@ -293,8 +305,6 @@ public void initialize( ) taskMaster.scheduleWithFixedDelay( new ConfigFileWatcher(), fileScanFrequencyMs, fileScanFrequencyMs, TimeUnit.MILLISECONDS ); } - checkConfigForSaveOnRestart( configReader, pwmApplication ); - checkConfigForAutoImportLdapCerts( configReader ); } @@ -306,38 +316,6 @@ public void initialize( ) LOGGER.trace( SESSION_LABEL, () -> "initialization complete (" + TimeDuration.compactFromCurrent( startTime ) + ")" ); } - private void checkConfigForSaveOnRestart( - final ConfigurationReader configReader, - final PwmApplication pwmApplication - ) - { - if ( configReader == null || configReader.getStoredConfiguration() == null ) - { - return; - } - - if ( !Boolean.parseBoolean( configReader.getStoredConfiguration().readConfigProperty( ConfigurationProperty.CONFIG_ON_START ) ) ) - { - return; - } - - LOGGER.warn( SESSION_LABEL, "configuration file contains property \"" - + ConfigurationProperty.CONFIG_ON_START.getKey() + "\"=true, will save configuration and set property to false." ); - - try - { - final StoredConfigurationImpl newConfig = StoredConfigurationImpl.copy( configReader.getStoredConfiguration() ); - newConfig.writeConfigProperty( ConfigurationProperty.CONFIG_ON_START, "false" ); - configReader.saveConfiguration( newConfig, pwmApplication, SESSION_LABEL ); - requestPwmApplicationRestart(); - } - catch ( Exception e ) - { - LOGGER.error( SESSION_LABEL, "error while saving configuration file commanded by property \"" - + ConfigurationProperty.CONFIG_ON_START + "\"=true, error: " + e.getMessage() ); - } - } - private void checkConfigForAutoImportLdapCerts( final ConfigurationReader configReader ) @@ -347,15 +325,17 @@ private void checkConfigForAutoImportLdapCerts( return; } - if ( !Boolean.parseBoolean( configReader.getStoredConfiguration().readConfigProperty( ConfigurationProperty.IMPORT_LDAP_CERTIFICATES ) ) ) { - return; + final Optional importLdapCerts = configReader.getStoredConfiguration().readConfigProperty( ConfigurationProperty.IMPORT_LDAP_CERTIFICATES ); + if ( !importLdapCerts.isPresent() || !Boolean.parseBoolean( importLdapCerts.get() ) ) + { + return; + } } LOGGER.info( SESSION_LABEL, () -> "configuration file contains property \"" + ConfigurationProperty.IMPORT_LDAP_CERTIFICATES.getKey() - + "\"=true, will import attempt ldap certificate import every 5 seconds until successful" ); - final long secondsDelay = 5; - taskMaster.scheduleWithFixedDelay( new AutoImportLdapCertJob(), secondsDelay, secondsDelay, TimeUnit.SECONDS ); + + "\"=true, will import attempt ldap certificate import every " + RESTART_DELAY.asLongString() + " until successful" ); + taskMaster.scheduleWithFixedDelay( new AutoImportLdapCertJob(), RESTART_DELAY.asMillis(), RESTART_DELAY.asMillis(), TimeUnit.MILLISECONDS ); } private void handleStartupError( final String msgPrefix, final Throwable throwable ) @@ -381,7 +361,7 @@ else if ( throwable instanceof PwmException ) { LOGGER.fatal( SESSION_LABEL, startupErrorInformation.getDetailedErrorMsg() ); } - catch ( Exception e2 ) + catch ( final Exception e2 ) { // noop } @@ -399,7 +379,7 @@ public void shutdown( ) { pwmApplication.shutdown(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error attempting to close application: " + e.getMessage() ); } @@ -459,13 +439,13 @@ public void run() try { final PropertyConfigurationImporter importer = new PropertyConfigurationImporter(); - final StoredConfigurationImpl storedConfiguration = importer.readConfiguration( new FileInputStream( silentPropertiesFile ) ); + final StoredConfiguration storedConfiguration = importer.readConfiguration( new FileInputStream( silentPropertiesFile ) ); configReader.saveConfiguration( storedConfiguration, pwmApplication, SESSION_LABEL ); LOGGER.info( SESSION_LABEL, () -> "file " + silentPropertiesFile.getAbsolutePath() + " has been successfully imported and saved as configuration file" ); requestPwmApplicationRestart(); success = true; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( SESSION_LABEL, "error importing " + silentPropertiesFile.getAbsolutePath() + ", error: " + e.getMessage() ); } @@ -479,7 +459,7 @@ public void run() Files.move( source, dest ); LOGGER.info( SESSION_LABEL, () -> "file " + source.toString() + " has been renamed to " + dest.toString() ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.error( SESSION_LABEL, "error renaming file " + source.toString() + " to " + dest.toString() + ", error: " + e.getMessage() ); } @@ -537,12 +517,12 @@ private void doRestart( ) oldPwmApplication.shutdown(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( SESSION_LABEL, "unexpected error attempting to close application: " + e.getMessage() ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.fatal( "unexpected error during shutdown: " + e.getMessage(), e ); } @@ -674,17 +654,38 @@ Collection readApplicationFlags( ) return PwmEnvironment.ParseHelper.readApplicationFlagsFromSystem( contextPath ); } - Map readApplicationParams( ) + Map readApplicationParams( final File applicationPath ) { - final String contextAppParamsValue = readEnvironmentParameter( PwmEnvironment.EnvironmentParameter.applicationParamFile ); + // attempt to read app params finle from specified env param file value + { + final String contextAppParamsValue = readEnvironmentParameter( PwmEnvironment.EnvironmentParameter.applicationParamFile ); + if ( !StringUtil.isEmpty( contextAppParamsValue ) ) + { + return PwmEnvironment.ParseHelper.readAppParametersFromPath( contextAppParamsValue ); + } + } - if ( contextAppParamsValue != null && !contextAppParamsValue.isEmpty() ) + // attempt to read app params file from specified system file value { - return PwmEnvironment.ParseHelper.parseApplicationParamValueParameter( contextAppParamsValue ); + final String contextPath = servletContext.getContextPath().replace( "/", "" ); + final Map results = PwmEnvironment.ParseHelper.readApplicationParmsFromSystem( contextPath ); + if ( !results.isEmpty() ) + { + return results; + } } - final String contextPath = servletContext.getContextPath().replace( "/", "" ); - return PwmEnvironment.ParseHelper.readApplicationParmsFromSystem( contextPath ); + // attempt to read via application.properties in applicationPath + if ( applicationPath != null && applicationPath.exists() ) + { + final File appPropertiesFile = new File( applicationPath.getPath() + File.separator + "application.properties" ); + if ( appPropertiesFile.exists() ) + { + return PwmEnvironment.ParseHelper.readAppParametersFromPath( appPropertiesFile.getPath() ); + } + } + + return Collections.emptyMap(); } @@ -725,7 +726,7 @@ public void run() { importLdapCert(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( SESSION_LABEL, "error trying to auto-import certs: " + e.getMessage() ); } @@ -736,7 +737,7 @@ private void importLdapCert() throws PwmUnrecoverableException, IOException, Pwm LOGGER.trace( SESSION_LABEL, () -> "beginning auto-import ldap cert due to config property '" + ConfigurationProperty.IMPORT_LDAP_CERTIFICATES.getKey() + "'" ); final Configuration configuration = new Configuration( configReader.getStoredConfiguration() ); - final StoredConfigurationImpl newStoredConfig = StoredConfigurationImpl.copy( configReader.getStoredConfiguration() ); + final StoredConfigurationModifier modifiedConfig = StoredConfigurationModifier.newModifier( configReader.getStoredConfiguration() ); int importedCerts = 0; for ( final LdapProfile ldapProfile : configuration.getLdapProfiles().values() ) @@ -753,7 +754,8 @@ private void importLdapCert() throws PwmUnrecoverableException, IOException, Pwm LOGGER.trace( SESSION_LABEL, () -> "imported cert: " + X509Utils.makeDebugText( cert ) ); } final StoredValue storedValue = new X509CertificateValue( certs ); - newStoredConfig.writeSetting( PwmSetting.LDAP_SERVER_CERTS, ldapProfile.getIdentifier(), storedValue, null ); + + modifiedConfig.writeSetting( PwmSetting.LDAP_SERVER_CERTS, ldapProfile.getIdentifier(), storedValue, null ); } } @@ -765,8 +767,8 @@ private void importLdapCert() throws PwmUnrecoverableException, IOException, Pwm LOGGER.trace( SESSION_LABEL, () -> "completed auto-import ldap cert due to config property '" + ConfigurationProperty.IMPORT_LDAP_CERTIFICATES.getKey() + "'" + ", imported " + totalImportedCerts + " certificates" ); - newStoredConfig.writeConfigProperty( ConfigurationProperty.IMPORT_LDAP_CERTIFICATES, "false" ); - configReader.saveConfiguration( newStoredConfig, pwmApplication, SESSION_LABEL ); + modifiedConfig.writeConfigProperty( ConfigurationProperty.IMPORT_LDAP_CERTIFICATES, "false" ); + configReader.saveConfiguration( modifiedConfig.newStoredConfiguration(), pwmApplication, SESSION_LABEL ); requestPwmApplicationRestart(); } else diff --git a/server/src/main/java/password/pwm/http/HttpEventManager.java b/server/src/main/java/password/pwm/http/HttpEventManager.java index ada15c38d..d75a8a8fb 100644 --- a/server/src/main/java/password/pwm/http/HttpEventManager.java +++ b/server/src/main/java/password/pwm/http/HttpEventManager.java @@ -73,7 +73,7 @@ public void sessionCreated( final HttpSessionEvent httpSessionEvent ) LOGGER.trace( () -> "new http session created" ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( "error during sessionCreated event: " + e.getMessage() ); } @@ -107,7 +107,7 @@ public void sessionDestroyed( final HttpSessionEvent httpSessionEvent ) LOGGER.trace( () -> "invalidated uninitialized session" ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( "error during httpSessionDestroyed: " + e.getMessage() ); } @@ -129,12 +129,12 @@ public void contextInitialized( final ServletContextEvent servletContextEvent ) newContextManager.initialize(); servletContextEvent.getServletContext().setAttribute( PwmConstants.CONTEXT_ATTR_CONTEXT_MANAGER, newContextManager ); } - catch ( OutOfMemoryError e ) + catch ( final OutOfMemoryError e ) { LOGGER.fatal( "JAVA OUT OF MEMORY ERROR!, please allocate more memory for java: " + e.getMessage(), e ); throw e; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.fatal( "error initializing context: " + e, e ); System.err.println( "error initializing context: " + e ); @@ -150,7 +150,7 @@ public void contextDestroyed( final ServletContextEvent servletContextEvent ) final ContextManager contextManager = ContextManager.getContextManager( servletContextEvent.getServletContext() ); contextManager.shutdown(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "unable to destroy context: " + e.getMessage() ); } @@ -164,7 +164,7 @@ public void sessionWillPassivate( final HttpSessionEvent event ) final PwmSession pwmSession = PwmSessionWrapper.readPwmSession( event.getSession() ); LOGGER.trace( pwmSession.getLabel(), () -> "passivating session" ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "unable to passivate session: " + e.getMessage() ); } @@ -183,7 +183,7 @@ public void sessionDidActivate( final HttpSessionEvent event ) pwmApplication.getSessionTrackService().addSessionData( pwmSession ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "unable to activate (de-passivate) session: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java b/server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java index a36d732b1..534c2b14a 100644 --- a/server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java +++ b/server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java @@ -243,7 +243,7 @@ public static TimeDuration idleTimeoutForRequest( } } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmSession, "error while figuring max idle timeout for session: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/JspUtility.java b/server/src/main/java/password/pwm/http/JspUtility.java index a634bf69c..fe56339b1 100644 --- a/server/src/main/java/password/pwm/http/JspUtility.java +++ b/server/src/main/java/password/pwm/http/JspUtility.java @@ -62,7 +62,7 @@ public static E getSessionBean( final PageContext pag { return pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, theClass ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( "unable to load pwmRequest object during jsp execution: " + e.getMessage() ); } @@ -97,7 +97,7 @@ public static void setFlag( final PageContext pageContext, final PwmRequestFlag ( HttpServletResponse ) pageContext.getResponse() ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( "unable to load pwmRequest object during jsp execution: " + e.getMessage() ); return; @@ -133,7 +133,7 @@ public static long numberSetting( final HttpServletRequest request, final PwmSet { return pwmRequest.getConfig().readSettingAsLong( pwmSetting ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( pwmRequest, "error reading number setting " + pwmSetting.getKey() + ", error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java b/server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java index c2b50e14e..146a5be56 100644 --- a/server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java +++ b/server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java @@ -270,7 +270,7 @@ public int readParameterAsInt( final String name, final int defaultValue ) { return Integer.parseInt( strValue ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { return defaultValue; } @@ -416,7 +416,7 @@ private static String decodeStringToDefaultCharSet( final String input ) { decodedValue = new String( input.getBytes( "ISO-8859-1" ), PwmConstants.DEFAULT_CHARSET ); } - catch ( UnsupportedEncodingException e ) + catch ( final UnsupportedEncodingException e ) { LOGGER.error( "error decoding request parameter: " + e.getMessage() ); } @@ -554,7 +554,7 @@ public T readBodyAsJsonObject( final Class classOfT ) { return JsonUtil.deserialize( json, classOfT ); } - catch ( Exception e ) + catch ( final Exception e ) { if ( e instanceof JsonParseException ) { diff --git a/server/src/main/java/password/pwm/http/PwmHttpResponseWrapper.java b/server/src/main/java/password/pwm/http/PwmHttpResponseWrapper.java index 0b9e33fd7..47d954046 100644 --- a/server/src/main/java/password/pwm/http/PwmHttpResponseWrapper.java +++ b/server/src/main/java/password/pwm/http/PwmHttpResponseWrapper.java @@ -222,7 +222,7 @@ void addSameSiteCookieAttribute( ) final String value = pwmApplication.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_SAMESITE_VALUE ); CookieManagementFilter.addSameSiteCookieAttribute( httpServletResponse, value ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.trace( () -> "unable to load application configuration while checking samesite cookie attribute config", e ); } diff --git a/server/src/main/java/password/pwm/http/PwmRequest.java b/server/src/main/java/password/pwm/http/PwmRequest.java index 7478afaba..51a2990f3 100644 --- a/server/src/main/java/password/pwm/http/PwmRequest.java +++ b/server/src/main/java/password/pwm/http/PwmRequest.java @@ -234,7 +234,7 @@ public InputStream readFileUploadStream( final String filePartName ) } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error reading file upload: " + e.getMessage() ); } @@ -277,7 +277,7 @@ public Map readFileUploads( } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error reading file upload: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/PwmResponse.java b/server/src/main/java/password/pwm/http/PwmResponse.java index 8ddb0ac6f..0586d3e5e 100644 --- a/server/src/main/java/password/pwm/http/PwmResponse.java +++ b/server/src/main/java/password/pwm/http/PwmResponse.java @@ -109,7 +109,7 @@ public void forwardToJsp( { LOGGER.trace( pwmRequest, () -> "forwarding to " + url ); } - catch ( Exception e ) + catch ( final Exception e ) { /* noop, server may not be up enough to do the log output */ } @@ -149,7 +149,7 @@ public void forwardToSuccessPage( final String message, final Flag... flags ) { forwardToJsp( JspUrl.SUCCESS ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "unexpected error sending user to success page: " + e.toString() ); } @@ -194,7 +194,7 @@ else if ( pwmRequest.isHtmlRequest() ) { forwardToJsp( JspUrl.ERROR ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "unexpected error sending user to error page: " + e.toString() ); } diff --git a/server/src/main/java/password/pwm/http/PwmSession.java b/server/src/main/java/password/pwm/http/PwmSession.java index 512575ddb..1531b9bec 100644 --- a/server/src/main/java/password/pwm/http/PwmSession.java +++ b/server/src/main/java/password/pwm/http/PwmSession.java @@ -204,7 +204,7 @@ public SessionLabel getLabel( ) ? this.getUserInfo().getUsername() : null; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "unexpected error reading username: " + e.getMessage(), e ); } @@ -254,7 +254,7 @@ public void unauthenticateUser( final PwmRequest pwmRequest ) { pwmRequest.getPwmApplication().getSessionStateService().clearLoginSession( pwmRequest ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "unexpected error writing removing login cookie from response: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -295,7 +295,7 @@ public String toString( ) debugData.put( "locale", getSessionStateBean().getLocale() ); debugData.put( "theme", getSessionStateBean().getTheme() ); } - catch ( Exception e ) + catch ( final Exception e ) { return "exception generating PwmSession.toString(): " + e.getMessage(); } diff --git a/server/src/main/java/password/pwm/http/PwmURL.java b/server/src/main/java/password/pwm/http/PwmURL.java index d10410258..be5637694 100644 --- a/server/src/main/java/password/pwm/http/PwmURL.java +++ b/server/src/main/java/password/pwm/http/PwmURL.java @@ -465,7 +465,7 @@ public static boolean testIfUrlMatchesAllowedPattern( LOGGER.trace( sessionLabel, () -> "negative URL match for regex pattern: " + strPattern ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( sessionLabel, "error while testing URL match for regex pattern: '" + loopFragment + "', error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/SessionManager.java b/server/src/main/java/password/pwm/http/SessionManager.java index 5cfa7f730..e2fbf7a4e 100644 --- a/server/src/main/java/password/pwm/http/SessionManager.java +++ b/server/src/main/java/password/pwm/http/SessionManager.java @@ -114,7 +114,7 @@ public void updateUserPassword( final PwmApplication pwmApplication, final UserI final String userDN = userIdentity.getUserDN(); chaiProvider.getEntryFactory().newChaiEntry( userDN ).exists(); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, @@ -134,7 +134,7 @@ public void closeConnections( ) chaiProvider.close(); chaiProvider = null; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmSession, "error while closing user connection: " + e.getMessage() ); } @@ -182,7 +182,7 @@ public ChaiUser getActor( final PwmApplication pwmApplication, final UserIdentit final ChaiProvider provider = this.getChaiProvider(); return provider.getEntryFactory().newChaiUser( userIdentity.getUserDN() ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } diff --git a/server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java b/server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java index 8afd9b3f0..315f1a860 100644 --- a/server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java +++ b/server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java @@ -80,7 +80,7 @@ public void attemptAuthentication( pwmSession.getLoginInfoBean().setBasicAuth( basicAuthInfo ); } - catch ( PwmException e ) + catch ( final PwmException e ) { if ( e.getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE ) { diff --git a/server/src/main/java/password/pwm/http/auth/CASFilterAuthenticationProvider.java b/server/src/main/java/password/pwm/http/auth/CASFilterAuthenticationProvider.java index c27896f5d..52809ac9c 100644 --- a/server/src/main/java/password/pwm/http/auth/CASFilterAuthenticationProvider.java +++ b/server/src/main/java/password/pwm/http/auth/CASFilterAuthenticationProvider.java @@ -105,15 +105,15 @@ public void attemptAuthentication( } } } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } - catch ( UnsupportedEncodingException e ) + catch ( final UnsupportedEncodingException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "error during CAS authentication: " + e.getMessage() ) ); } @@ -188,7 +188,7 @@ private static boolean authUserUsingCASClearPass( new URL( clearPassRequestUrl ), new HttpsURLConnectionFactory(), "UTF-8" ); password = new PasswordData( XmlUtils.getTextForElement( response, "credentials" ) ); } - catch ( MalformedURLException e ) + catch ( final MalformedURLException e ) { LOGGER.error( pwmSession, "Invalid CAS clearPassUrl" ); } @@ -247,44 +247,44 @@ private static PasswordData decryptPassword( final String alg, { password = new PasswordData( new String( cipherData, PwmConstants.DEFAULT_CHARSET ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "Decryption failed", e ); return password; } } } - catch ( NoSuchAlgorithmException e1 ) + catch ( final NoSuchAlgorithmException e1 ) { LOGGER.error( "Decryption failed", e1 ); return password; } - catch ( InvalidKeySpecException e1 ) + catch ( final InvalidKeySpecException e1 ) { LOGGER.error( "Decryption failed", e1 ); return password; } - catch ( NoSuchPaddingException e1 ) + catch ( final NoSuchPaddingException e1 ) { LOGGER.error( "Decryption failed", e1 ); return password; } - catch ( IOException e1 ) + catch ( final IOException e1 ) { LOGGER.error( "Decryption failed", e1 ); return password; } - catch ( InvalidKeyException e1 ) + catch ( final InvalidKeyException e1 ) { LOGGER.error( "Decryption failed", e1 ); return password; } - catch ( IllegalBlockSizeException e ) + catch ( final IllegalBlockSizeException e ) { LOGGER.error( "Decryption failed", e ); return password; } - catch ( BadPaddingException e ) + catch ( final BadPaddingException e ) { LOGGER.error( "Decryption failed", e ); return password; diff --git a/server/src/main/java/password/pwm/http/auth/HttpAuthenticationUtilities.java b/server/src/main/java/password/pwm/http/auth/HttpAuthenticationUtilities.java index c9ecf3658..d0a902f5d 100644 --- a/server/src/main/java/password/pwm/http/auth/HttpAuthenticationUtilities.java +++ b/server/src/main/java/password/pwm/http/auth/HttpAuthenticationUtilities.java @@ -64,7 +64,7 @@ public static ProcessStatus attemptAuthenticationMethods( final PwmRequest pwmRe final Object newInstance = clazz.newInstance(); filterAuthenticationProvider = ( PwmHttpFilterAuthenticationProvider ) newInstance; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.trace( () -> "could not load authentication class '" + authenticationMethod + "', will ignore" ); IGNORED_AUTH_METHODS.add( authenticationMethod ); @@ -89,7 +89,7 @@ public static ProcessStatus attemptAuthenticationMethods( final PwmRequest pwmRe } } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation; if ( e instanceof PwmException ) @@ -149,7 +149,7 @@ public static void handleAuthenticationCookie( final PwmRequest pwmRequest ) thr pwmRequest.getPwmResponse().writeEncryptedCookie( cookieName, httpAuthRecord, cookieAgeSeconds, PwmHttpResponseWrapper.CookiePath.Application ); LOGGER.debug( pwmRequest, () -> "wrote auth record cookie to user browser for use during forgotten password" ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmRequest, "error while setting authentication record cookie: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/auth/SSOHeaderFilterAuthenticationProvider.java b/server/src/main/java/password/pwm/http/auth/SSOHeaderFilterAuthenticationProvider.java index be685e0df..8e509a6ca 100644 --- a/server/src/main/java/password/pwm/http/auth/SSOHeaderFilterAuthenticationProvider.java +++ b/server/src/main/java/password/pwm/http/auth/SSOHeaderFilterAuthenticationProvider.java @@ -69,7 +69,7 @@ public void attemptAuthentication( { sessionAuthenticator.authUserWithUnknownPassword( headerValue, AuthenticationType.AUTH_WITHOUT_PASSWORD ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } diff --git a/server/src/main/java/password/pwm/http/bean/ConfigManagerBean.java b/server/src/main/java/password/pwm/http/bean/ConfigManagerBean.java index f2423017b..eeb5a9e76 100644 --- a/server/src/main/java/password/pwm/http/bean/ConfigManagerBean.java +++ b/server/src/main/java/password/pwm/http/bean/ConfigManagerBean.java @@ -20,18 +20,19 @@ package password.pwm.http.bean; +import lombok.Data; import password.pwm.config.option.SessionBeanMode; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; import java.util.Collections; import java.util.Set; +@Data public class ConfigManagerBean extends PwmSessionBean { - private transient StoredConfigurationImpl storedConfiguration; + private transient StoredConfiguration storedConfiguration; private boolean passwordVerified; private boolean configUnlockedWarningShown; - private String prePasswordEntryUrl; public ConfigManagerBean( ) @@ -43,47 +44,6 @@ public Type getType( ) return Type.AUTHENTICATED; } - - public StoredConfigurationImpl getStoredConfiguration( ) - { - return storedConfiguration; - } - - public void setConfiguration( final StoredConfigurationImpl storedConfiguration ) - { - this.storedConfiguration = storedConfiguration; - } - - public boolean isPasswordVerified( ) - { - return passwordVerified; - } - - public void setPasswordVerified( final boolean passwordVerified ) - { - this.passwordVerified = passwordVerified; - } - - public String getPrePasswordEntryUrl( ) - { - return prePasswordEntryUrl; - } - - public void setPrePasswordEntryUrl( final String prePasswordEntryUrl ) - { - this.prePasswordEntryUrl = prePasswordEntryUrl; - } - - public boolean isConfigUnlockedWarningShown( ) - { - return configUnlockedWarningShown; - } - - public void setConfigUnlockedWarningShown( final boolean configUnlockedWarningShown ) - { - this.configUnlockedWarningShown = configUnlockedWarningShown; - } - @Override public Set supportedModes( ) { diff --git a/server/src/main/java/password/pwm/http/bean/SetupOtpBean.java b/server/src/main/java/password/pwm/http/bean/SetupOtpBean.java index eab1ba75b..0432405b3 100644 --- a/server/src/main/java/password/pwm/http/bean/SetupOtpBean.java +++ b/server/src/main/java/password/pwm/http/bean/SetupOtpBean.java @@ -92,12 +92,12 @@ public Long getChallenge( ) { random = SecureRandom.getInstance( "SHA1PRNG", "SUN" ); } - catch ( NoSuchAlgorithmException ex ) + catch ( final NoSuchAlgorithmException ex ) { random = new SecureRandom(); LOGGER.error( ex.getMessage(), ex ); } - catch ( NoSuchProviderException ex ) + catch ( final NoSuchProviderException ex ) { random = new SecureRandom(); LOGGER.error( ex.getMessage(), ex ); diff --git a/server/src/main/java/password/pwm/http/filter/AbstractPwmFilter.java b/server/src/main/java/password/pwm/http/filter/AbstractPwmFilter.java index 95463bd1a..6a9691a2f 100644 --- a/server/src/main/java/password/pwm/http/filter/AbstractPwmFilter.java +++ b/server/src/main/java/password/pwm/http/filter/AbstractPwmFilter.java @@ -65,7 +65,7 @@ public void doFilter( final PwmURL pwmURL = new PwmURL( req ); interested = isInterested( mode, pwmURL ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error processing filter chain during isInterested(): " + e.getMessage(), e ); resp.sendError( 500, "unexpected error processing filter chain during isInterested" ); @@ -79,7 +79,7 @@ public void doFilter( { pwmRequest = PwmRequest.forRequest( req, resp ); } - catch ( PwmException e ) + catch ( final PwmException e ) { final PwmURL pwmURL = new PwmURL( req ); if ( pwmURL.isResourceURL() ) @@ -96,11 +96,11 @@ public void doFilter( final PwmFilterChain pwmFilterChain = new PwmFilterChain( servletRequest, servletResponse, filterChain ); processFilter( mode, pwmRequest, pwmFilterChain ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( pwmRequest, "unexpected error processing filter chain: " + e.getMessage(), e ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.debug( pwmRequest, () -> "i/o error processing request: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/filter/ApplicationModeFilter.java b/server/src/main/java/password/pwm/http/filter/ApplicationModeFilter.java index b06c628cf..4f9f889b2 100644 --- a/server/src/main/java/password/pwm/http/filter/ApplicationModeFilter.java +++ b/server/src/main/java/password/pwm/http/filter/ApplicationModeFilter.java @@ -61,7 +61,7 @@ public void processFilter( return; } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { if ( e.getError() == PwmError.ERROR_INTERNAL ) { @@ -69,7 +69,7 @@ public void processFilter( { LOGGER.error( e.getMessage() ); } - catch ( Exception ignore ) + catch ( final Exception ignore ) { /* noop */ } diff --git a/server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java b/server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java index f0af07654..0d9d75fbd 100644 --- a/server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java +++ b/server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java @@ -111,7 +111,7 @@ public void processFilter( this.processUnAuthenticatedSession( pwmRequest, chain ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( e.getErrorInformation() ); pwmRequest.respondWithError( e.getErrorInformation(), true ); @@ -197,7 +197,7 @@ private void processAuthenticatedSession( LoginServlet.redirectToLoginServlet( pwmRequest ); return; } - catch ( Throwable e1 ) + catch ( final Throwable e1 ) { LOGGER.error( "error while marking pre-login url:" + e1.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/filter/AuthorizationFilter.java b/server/src/main/java/password/pwm/http/filter/AuthorizationFilter.java index 31beee011..9c9dd2680 100644 --- a/server/src/main/java/password/pwm/http/filter/AuthorizationFilter.java +++ b/server/src/main/java/password/pwm/http/filter/AuthorizationFilter.java @@ -72,7 +72,7 @@ public void processFilter( { hasPermission = pwmSession.getSessionManager().checkPermission( pwmApplication, Permission.PWMADMIN ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( pwmRequest, "error during authorization check: " + e.getMessage() ); } @@ -85,7 +85,7 @@ public void processFilter( return; } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( pwmRequest, "unexpected error executing filter chain: " + e.getMessage() ); return; diff --git a/server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java b/server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java index e45b75930..34046024b 100644 --- a/server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java +++ b/server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java @@ -24,7 +24,8 @@ import password.pwm.Permission; import password.pwm.PwmApplicationMode; import password.pwm.config.stored.ConfigurationReader; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmException; @@ -63,7 +64,7 @@ void processFilter( final PwmApplicationMode mode, final PwmRequest pwmRequest, { UserAgentUtils.checkIfPreIE11( pwmRequest ); } - catch ( PwmException e ) + catch ( final PwmException e ) { pwmRequest.respondWithError( e.getErrorInformation() ); return; @@ -78,7 +79,7 @@ void processFilter( final PwmApplicationMode mode, final PwmRequest pwmRequest, filterChain.doFilter(); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { pwmRequest.respondWithError( e.getErrorInformation() ); } @@ -97,7 +98,7 @@ public static ProcessStatus checkAuthentication( throws IOException, PwmUnrecoverableException, ServletException { final ConfigurationReader runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader(); - final StoredConfigurationImpl storedConfig = runningConfigReader.getStoredConfiguration(); + final StoredConfiguration storedConfig = runningConfigReader.getStoredConfiguration(); checkPreconditions( pwmRequest, storedConfig ); @@ -117,12 +118,12 @@ public static ProcessStatus checkAuthentication( private static void checkPreconditions( final PwmRequest pwmRequest, - final StoredConfigurationImpl storedConfig + final StoredConfiguration storedConfig ) throws PwmUnrecoverableException { - if ( !storedConfig.hasPassword() ) + if ( !StoredConfigurationUtil.hasPassword( storedConfig ) ) { final String errorMsg = "config file does not have a configuration password"; final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, errorMsg, new String[] diff --git a/server/src/main/java/password/pwm/http/filter/CookieManagementFilter.java b/server/src/main/java/password/pwm/http/filter/CookieManagementFilter.java index ad3f3b292..274015e2f 100644 --- a/server/src/main/java/password/pwm/http/filter/CookieManagementFilter.java +++ b/server/src/main/java/password/pwm/http/filter/CookieManagementFilter.java @@ -58,7 +58,7 @@ public void init( final FilterConfig filterConfig ) pwmApplication = ContextManager.getPwmApplication( filterConfig.getServletContext() ); value = pwmApplication.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_SAMESITE_VALUE ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.trace( () -> "unable to load application configuration while checking samesite cookie attribute config", e ); } @@ -94,7 +94,7 @@ private void markSessionForRecycle( final HttpServletRequest httpServletRequest { pwmSession = PwmSessionWrapper.readPwmSession( httpSession ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.trace( () -> "unable to load session while checking samesite cookie attribute config", e ); } diff --git a/server/src/main/java/password/pwm/http/filter/GZIPFilter.java b/server/src/main/java/password/pwm/http/filter/GZIPFilter.java index 9d7d99346..a4c4c4777 100644 --- a/server/src/main/java/password/pwm/http/filter/GZIPFilter.java +++ b/server/src/main/java/password/pwm/http/filter/GZIPFilter.java @@ -57,7 +57,7 @@ public void init( final FilterConfig filterConfig ) pwmApplication = ContextManager.getPwmApplication( filterConfig.getServletContext() ); enabled = Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty( AppProperty.HTTP_ENABLE_GZIP ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( "unable to load application configuration, defaulting to disabled" ); } @@ -96,7 +96,7 @@ private boolean interestInRequest( final ServletRequest servletRequest ) return false; } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unable to parse request url, defaulting to non-gzip: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java b/server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java index 095c45d56..7cd4e544f 100644 --- a/server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java +++ b/server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java @@ -106,7 +106,7 @@ public void doFilter( { localPwmApplication = ContextManager.getPwmApplication( req ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.trace( () -> "unable to load pwmApplication: " + e.getMessage() ); } @@ -142,7 +142,7 @@ public void doFilter( servletRequest.setAttribute( PwmRequestAttribute.PwmErrorInfo.toString(), startupError ); } } - catch ( Exception e ) + catch ( final Exception e ) { if ( pwmURL.isResourceURL() ) { @@ -183,7 +183,7 @@ private void initializeServletRequest( checkAndInitSessionState( req ); PwmRequest.forRequest( req, resp ); } - catch ( Throwable e ) + catch ( final Throwable e ) { LOGGER.error( "can't load application: " + e.getMessage(), e ); if ( !( new PwmURL( req ).isResourceURL() ) ) @@ -210,7 +210,7 @@ private void initializeServletRequest( { handleRequestSecurityChecks( pwmRequest ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmRequest, e.getErrorInformation() ); pwmRequest.respondWithError( e.getErrorInformation() ); @@ -222,7 +222,7 @@ private void initializeServletRequest( } } - catch ( Throwable e ) + catch ( final Throwable e ) { final String logMsg = "can't init request: " + e.getMessage(); if ( e instanceof PwmException && ( ( PwmException ) e ).getError() != PwmError.ERROR_INTERNAL ) @@ -256,7 +256,7 @@ private void respondWithUnavailableError( final HttpServletRequest req, final Ht errorInformation = contextManager.getStartupErrorInformation(); } } - catch ( PwmUnrecoverableException e2 ) + catch ( final PwmUnrecoverableException e2 ) { LOGGER.error( "error reading session context from servlet container: " + e2.getMessage() ); } @@ -441,7 +441,7 @@ public static String readUserHostname( final HttpServletRequest request, final C { return InetAddress.getByName( userIPAddress ).getCanonicalHostName(); } - catch ( UnknownHostException e ) + catch ( final UnknownHostException e ) { LOGGER.trace( () -> "unknown host while trying to compute hostname for src request: " + e.getMessage() ); } @@ -690,12 +690,12 @@ private static void checkSourceNetworkAddress( final PwmRequest pwmRequest ) match = true; } } - catch ( IPMatcher.IPMatcherException e ) + catch ( final IPMatcher.IPMatcherException e ) { LOGGER.error( "error while attempting to match permitted address range '" + ipMatchString + "', error: " + e ); } } - catch ( IPMatcher.IPMatcherException e ) + catch ( final IPMatcher.IPMatcherException e ) { LOGGER.error( "error parsing permitted address range '" + ipMatchString + "', error: " + e ); } diff --git a/server/src/main/java/password/pwm/http/filter/SessionFilter.java b/server/src/main/java/password/pwm/http/filter/SessionFilter.java index d4238be05..17fd9e12d 100644 --- a/server/src/main/java/password/pwm/http/filter/SessionFilter.java +++ b/server/src/main/java/password/pwm/http/filter/SessionFilter.java @@ -109,12 +109,12 @@ public void processFilter( { chain.doFilter(); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.trace( pwmRequest, () -> "IO exception during servlet processing: " + e.getMessage() ); throw new ServletException( e ); } - catch ( Throwable e ) + catch ( final Throwable e ) { if ( e instanceof ServletException && e.getCause() != null @@ -165,7 +165,7 @@ private ProcessStatus handleStandardRequestOperations( { pwmApplication.getSessionStateService().readLoginSessionState( pwmRequest ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( pwmRequest, "error while reading login session state: " + e.getMessage() ); } @@ -225,7 +225,7 @@ private ProcessStatus handleStandardRequestOperations( { checkUrlAgainstWhitelist( pwmApplication, pwmRequest.getSessionLabel(), forwardURL ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( pwmRequest, e.getErrorInformation() ); pwmRequest.respondWithError( e.getErrorInformation() ); @@ -245,7 +245,7 @@ private ProcessStatus handleStandardRequestOperations( { checkUrlAgainstWhitelist( pwmApplication, pwmRequest.getSessionLabel(), logoutURL ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( pwmRequest, e.getErrorInformation() ); pwmRequest.respondWithError( e.getErrorInformation() ); @@ -543,7 +543,7 @@ private static void checkUrlAgainstWhitelist( { inputURI = URI.create( inputURL ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { LOGGER.error( sessionLabel, "unable to parse requested redirect url '" + inputURL + "', error: " + e.getMessage() ); // dont put input uri in error response @@ -573,7 +573,7 @@ private static void checkUrlAgainstWhitelist( throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_REDIRECT_ILLEGAL, errorMsg ) ); } } - catch ( UnknownHostException e ) + catch ( final UnknownHostException e ) { /* noop */ } diff --git a/server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java b/server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java index 4fe35a90d..06cec06e4 100644 --- a/server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java @@ -92,7 +92,7 @@ private void handleRequest( { Validator.validatePwmRequestCounter( pwmRequest ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { if ( e.getError() == PwmError.ERROR_INCORRECT_REQ_SEQUENCE ) { @@ -122,14 +122,14 @@ private void handleRequest( this.processAction( pwmRequest ); } - catch ( Exception e ) + catch ( final Exception e ) { final PwmRequest pwmRequest; try { pwmRequest = PwmRequest.forRequest( req, resp ); } - catch ( Exception e2 ) + catch ( final Exception e2 ) { try { @@ -137,7 +137,7 @@ private void handleRequest( "exception occurred, but exception handler unable to load request instance; error=" + e.getMessage(), e ); } - catch ( Exception e3 ) + catch ( final Exception e3 ) { e3.printStackTrace(); } @@ -165,7 +165,7 @@ private void clearModuleBeans( final PwmRequest pwmRequest ) { pwmRequest.getPwmApplication().getSessionStateService().clearBean( pwmRequest, theClass ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.debug( pwmRequest, () -> "error while clearing module bean during after module error output: " + e.getMessage() ); } @@ -205,7 +205,7 @@ private PwmUnrecoverableException convertToPwmUnrecoverableException( { stackTraceHash = SecureEngine.hash( stackTraceText, PwmHashAlgorithm.SHA1 ); } - catch ( PwmUnrecoverableException e1 ) + catch ( final PwmUnrecoverableException e1 ) { /* */ } @@ -233,7 +233,7 @@ private boolean processUnrecoverableException( { pwmApplication.getStatisticsManager().incrementValue( Statistic.LDAP_UNAVAILABLE_COUNT ); } - catch ( Throwable e1 ) + catch ( final Throwable e1 ) { //noop } @@ -250,7 +250,7 @@ private boolean processUnrecoverableException( LoginServlet.redirectToLoginServlet( PwmRequest.forRequest( req, resp ) ); return true; } - catch ( Throwable e1 ) + catch ( final Throwable e1 ) { LOGGER.error( "error while marking pre-login url:" + e1.getMessage() ); } @@ -268,7 +268,7 @@ private boolean processUnrecoverableException( pwmApplication.getStatisticsManager().incrementValue( Statistic.PWM_UNKNOWN_ERRORS ); } } - catch ( Throwable e1 ) + catch ( final Throwable e1 ) { //noop } diff --git a/server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java b/server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java index 35f388ec9..aacb90874 100644 --- a/server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java @@ -32,7 +32,6 @@ import password.pwm.config.option.SelectableContextMode; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; -import password.pwm.error.PwmException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.HttpHeader; import password.pwm.http.HttpMethod; @@ -199,7 +198,7 @@ public ProcessStatus doGetStringsData( final PwmRequest pwmRequest final RestResultBean restResultBean = RestResultBean.withData( displayData ); pwmRequest.outputJsonResult( restResultBean ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMSg = "error during rest /strings call for bundle " + bundleName + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMSg ); @@ -223,13 +222,7 @@ public ProcessStatus restHealthProcessor( final PwmRequest pwmRequest ) final RestResultBean restResultBean = RestResultBean.withData( jsonOutput ); pwmRequest.outputJsonResult( restResultBean ); } - catch ( PwmException e ) - { - final ErrorInformation errorInformation = e.getErrorInformation(); - LOGGER.debug( pwmRequest, errorInformation ); - pwmRequest.respondWithError( errorInformation ); - } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMessage = "unexpected error executing web service: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMessage ); @@ -338,7 +331,7 @@ private static Map makeClientData( final TimeDuration maxIdleTime = IdleTimeoutCalculator.idleTimeoutForRequest( pwmURL, pwmApplication, pwmSession ); idleSeconds = maxIdleTime.as( TimeDuration.Unit.SECONDS ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmSession, "error determining idle timeout time for request: " + e.getMessage() ); } @@ -444,7 +437,7 @@ private Map makeDisplayData( displayStrings.put( key, displayValue ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmSession, "error expanding macro display value: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java b/server/src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java index 8ea9f27a0..ae6ab0775 100644 --- a/server/src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java @@ -92,7 +92,7 @@ protected ProcessAction readProcessAction( final PwmRequest request ) final Enum answer = JavaHelper.readEnumFromString( processStatusClass, null, inputParameter ); return ( ProcessAction ) answer; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error", e ); } @@ -119,7 +119,7 @@ private ProcessStatus dispatchMethod( return ( ProcessStatus ) interestedMethod.invoke( this, pwmRequest ); } } - catch ( InvocationTargetException e ) + catch ( final InvocationTargetException e ) { final Throwable cause = e.getCause(); if ( cause != null ) @@ -136,7 +136,7 @@ private ProcessStatus dispatchMethod( } LOGGER.error( "uncased invocation error: " + e.getMessage(), e ); } - catch ( Throwable e ) + catch ( final Throwable e ) { final String msg = "unexpected error invoking action handler for '" + action + "', error: " + e.getMessage(); LOGGER.error( msg, e ); @@ -215,7 +215,7 @@ private Map createMethodCache() { final Map map = new HashMap<>(); final Collection methods = JavaHelper.getAllMethodsForClass( this.getClass() ); - for ( Method method : methods ) + for ( final Method method : methods ) { if ( method.getAnnotation( ActionHandler.class ) != null ) { diff --git a/server/src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java b/server/src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java index acd407efb..25ae0901c 100644 --- a/server/src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java @@ -217,7 +217,7 @@ private ProcessStatus handleDeleteRequest( { actionExecutor.executeActions( actions, pwmRequest.getSessionLabel() ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( "error during user delete action execution: " + e.getMessage() ); throw new PwmUnrecoverableException( e.getErrorInformation(), e.getCause() ); @@ -248,7 +248,7 @@ private ProcessStatus handleDeleteRequest( { chaiUser.getChaiProvider().deleteEntry( chaiUser.getEntryDN() ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final PwmUnrecoverableException pwmException = PwmUnrecoverableException.fromChaiException( e ); LOGGER.error( "error during user delete", pwmException ); diff --git a/server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java b/server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java index 9ffe669a4..fe4026eb9 100644 --- a/server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java @@ -89,7 +89,7 @@ protected ForgottenUsernameAction readProcessAction( final PwmRequest request ) { return ForgottenUsernameAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { return null; } @@ -221,7 +221,7 @@ public void handleSearchRequest( return; } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final ErrorInformation errorInfo; errorInfo = e.getError() == PwmError.ERROR_INTERNAL diff --git a/server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java b/server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java index 136eca799..ae14ed0c6 100644 --- a/server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java @@ -125,7 +125,7 @@ protected GuestRegistrationAction readProcessAction( final PwmRequest request ) { return GuestRegistrationAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { return null; } @@ -197,7 +197,7 @@ protected void handleSelectPageRequest( { guestRegistrationBean.setCurrentPage( Page.valueOf( requestedPage ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { LOGGER.error( pwmRequest, "unknown page select request: " + requestedPage ); } @@ -268,12 +268,12 @@ protected void handleUpdateRequest( pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_UpdateGuest ); return; } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( pwmSession, e.getErrorInformation().toDebugStr() ); setLastError( pwmRequest, e.getErrorInformation() ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final ErrorInformation info = new ErrorInformation( PwmError.ERROR_INTERNAL, "unexpected error writing to ldap: " + e.getMessage() ); LOGGER.error( pwmSession, info ); @@ -390,12 +390,12 @@ protected void handleSearchRequest( this.forwardToUpdateJSP( pwmRequest, guestRegistrationBean ); return; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( pwmSession, "error reading current attributes for user: " + e.getMessage() ); } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final ErrorInformation error = e.getErrorInformation(); setLastError( pwmRequest, error ); @@ -509,14 +509,14 @@ private void handleCreateRequest( pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_CreateGuest ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final ErrorInformation info = new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, "error creating user: " + e.getMessage() ); setLastError( pwmRequest, info ); LOGGER.warn( pwmSession, info ); this.forwardToJSP( pwmRequest, guestRegistrationBean ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( pwmSession, e.getErrorInformation().toDebugStr() ); setLastError( pwmRequest, e.getErrorInformation() ); @@ -546,7 +546,7 @@ private static Instant readExpirationFromRequest( { expirationDate = new SimpleDateFormat( "yyyy-MM-dd" ).parse( expirationDateStr ); } - catch ( ParseException e ) + catch ( final ParseException e ) { final String errorMsg = "unable to read expiration date value: " + e.getMessage(); throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_FIELD_REQUIRED, errorMsg, new String[] diff --git a/server/src/main/java/password/pwm/http/servlet/LoginServlet.java b/server/src/main/java/password/pwm/http/servlet/LoginServlet.java index 9911bd3d3..d73e3d335 100644 --- a/server/src/main/java/password/pwm/http/servlet/LoginServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/LoginServlet.java @@ -134,7 +134,7 @@ private ProcessStatus processLogin( final PwmRequest pwmRequest ) { handleLoginRequest( pwmRequest, valueMap, passwordOnly ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { setLastError( pwmRequest, e.getErrorInformation() ); forwardToJSP( pwmRequest, passwordOnly ); @@ -164,7 +164,7 @@ private ProcessStatus processRestLogin( final PwmRequest pwmRequest ) { handleLoginRequest( pwmRequest, valueMap, passwordOnly ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final ErrorInformation errorInformation = e.getErrorInformation(); LOGGER.trace( pwmRequest, () -> "returning rest login error to client: " + errorInformation.toDebugStr() ); diff --git a/server/src/main/java/password/pwm/http/servlet/LogoutServlet.java b/server/src/main/java/password/pwm/http/servlet/LogoutServlet.java index 1667fddd2..a77127304 100644 --- a/server/src/main/java/password/pwm/http/servlet/LogoutServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/LogoutServlet.java @@ -255,7 +255,7 @@ private static Optional readAndValidateNextUrlParameter( } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "error parsing client specified url parameter: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java b/server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java index 16a2e68c6..e5fc85af3 100644 --- a/server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java +++ b/server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java @@ -129,7 +129,7 @@ public enum Flag { this.patterns = getWebServletAnnotation( pwmServletClass ).urlPatterns(); } - catch ( Exception e ) + catch ( final Exception e ) { throw new IllegalStateException( "error initializing PwmServletInfo value " + this.toString() + ", error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java b/server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java index a7c264239..720830f19 100644 --- a/server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java @@ -202,7 +202,7 @@ protected void nextStep( final PwmRequest pwmRequest ) pwmApplication.getStatisticsManager().incrementValue( Statistic.SETUP_OTP_SECRET ); } } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation; if ( e instanceof PwmException ) @@ -303,7 +303,7 @@ private ProcessStatus handleRestValidateCode( LOGGER.trace( pwmSession, () -> "returning result for restValidateCode: " + JsonUtil.serialize( restResultBean ) ); pwmRequest.outputJsonResult( restResultBean ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorMsg = "error during otp code validation: " + e.getMessage(); @@ -330,7 +330,7 @@ private ProcessStatus handleClearOtpSecret( { service.clearOTPUserConfiguration( pwmSession, theUser, pwmSession.getSessionManager().getActor( pwmApplication ) ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { setLastError( pwmRequest, e.getErrorInformation() ); LOGGER.error( pwmRequest, e.getErrorInformation() ); @@ -382,7 +382,7 @@ private ProcessStatus handleTestOtpSecret( setLastError( pwmRequest, new ErrorInformation( PwmError.ERROR_TOKEN_INCORRECT ) ); } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( pwmRequest, "error validating otp token: " + e.getMessage() ); setLastError( pwmRequest, e.getErrorInformation() ); @@ -419,7 +419,7 @@ private void initializeBean( { existingUserRecord = service.readOTPUserConfiguration( pwmRequest.getSessionLabel(), theUser ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -456,7 +456,7 @@ private void initializeBean( LOGGER.trace( pwmRequest, () -> "newly generated otp record: " + JsonUtil.serialize( otpUserRecord ) ); } } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error setting up new OTP secret: " + e.getMessage(); LOGGER.error( pwmSession, errorMsg ); @@ -505,7 +505,7 @@ private static String makeQrCodeDataImageUrl( .stream() .toByteArray(); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error generating qrcode image: " + e.getMessage() + ", payload length=" + qrCodeContent.length(); LOGGER.error( pwmRequest, errorMsg, e ); diff --git a/server/src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java b/server/src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java index 74912d7f3..526047ba9 100644 --- a/server/src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java @@ -225,7 +225,7 @@ private ProcessStatus handleClearExisting( pwmRequest.sendRedirect( PwmServletDefinition.SetupResponses ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.debug( pwmSession, e.getErrorInformation() ); setLastError( pwmRequest, e.getErrorInformation() ); @@ -279,7 +279,7 @@ private ProcessStatus restValidateResponses( pwmApplication.getCrService().validateResponses( setupData.getChallengeSet(), responseMap, minRandomRequiredSetup ); generateResponseInfoBean( pwmRequest, setupData.getChallengeSet(), responseMap, Collections.emptyMap() ); } - catch ( PwmDataValidationException e ) + catch ( final PwmDataValidationException e ) { success = false; userMessage = e.getErrorInformation().toUserStr( pwmSession, pwmApplication ); @@ -372,12 +372,12 @@ protected void nextStep( final PwmRequest pwmRequest ) pwmRequest.getPwmApplication().getSessionStateService().clearBean( pwmRequest, SetupResponsesBean.class ); pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_SetupResponse ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( pwmRequest.getSessionLabel(), e.getErrorInformation() ); pwmRequest.respondWithError( e.getErrorInformation() ); } - catch ( ChaiValidationException e ) + catch ( final ChaiValidationException e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_RANDOM_RESPONSE, e.getMessage() ); LOGGER.error( pwmRequest.getSessionLabel(), errorInformation ); @@ -406,7 +406,7 @@ private void setupResponses( final int minRandomRequiredSetup = setupData.getMinRandomSetup(); pwmRequest.getPwmApplication().getCrService().validateResponses( challengeSet, responseMap, minRandomRequiredSetup ); } - catch ( PwmDataValidationException e ) + catch ( final PwmDataValidationException e ) { LOGGER.debug( pwmRequest, () -> "error with new " + ( helpdeskMode ? "helpdesk" : "user" ) + " responses: " + e.getErrorInformation().toDebugStr() ); setLastError( pwmRequest, e.getErrorInformation() ); @@ -565,7 +565,7 @@ private static ResponseInfoBean generateResponseInfoBean( return responseInfoBean; } - catch ( ChaiValidationException e ) + catch ( final ChaiValidationException e ) { final ErrorInformation errorInfo = convertChaiValidationException( e ); throw new PwmDataValidationException( errorInfo ); diff --git a/server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java b/server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java index 04399fc36..d406eb71b 100644 --- a/server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java @@ -80,7 +80,7 @@ protected ShortcutAction readProcessAction( final PwmRequest request ) { return ShortcutAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { return null; } diff --git a/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java b/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java index 14878a6f4..610861c59 100644 --- a/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java +++ b/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java @@ -126,7 +126,7 @@ public static List makeAuditInfo( { auditRecords.addAll( pwmApplication.getAuditManager().readUserHistory( userInfo ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.debug( sessionLabel, () -> "error reading audit data for user: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationServlet.java b/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationServlet.java index b9e4246ed..b3d976a3d 100644 --- a/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationServlet.java @@ -90,7 +90,7 @@ protected void nextStep( final PwmRequest pwmRequest ) throws ServletException, ); pwmRequest.setAttribute( PwmRequestAttribute.AccountInfo, accountInformationBean ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( pwmRequest, "error reading user form data: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java b/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java index a0a8ece7c..c252fc978 100644 --- a/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java @@ -275,7 +275,7 @@ public ProcessStatus handleSearchRequest( final PwmRequest pwmRequest ) pwmApplication.getIntruderManager().convenience().clearAttributes( formValues ); pwmApplication.getIntruderManager().convenience().clearAddressAndSession( pwmSession ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { pwmApplication.getIntruderManager().convenience().markAttributes( formValues, pwmRequest.getSessionLabel() ); pwmApplication.getIntruderManager().convenience().markAddressAndSession( pwmSession ); @@ -351,7 +351,7 @@ public ProcessStatus handleEnterCode( return ProcessStatus.Halt; } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.debug( pwmRequest, () -> "error while checking entered token: " ); errorInformation = e.getErrorInformation(); @@ -474,7 +474,7 @@ protected void nextStep( final PwmRequest pwmRequest ) throws PwmUnrecoverableEx ActivateUserUtils.activateUser( pwmRequest, activateUserBean.getUserIdentity() ); pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_ActivateUser ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.debug( pwmRequest, e.getErrorInformation() ); pwmApplication.getIntruderManager().convenience().markUserIdentity( activateUserBean.getUserIdentity(), pwmSession ); diff --git a/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java b/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java index 5fe9ea0b2..e0f2e352b 100644 --- a/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java +++ b/server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java @@ -93,7 +93,7 @@ static void activateUser( { theUser.unlockPassword(); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error unlocking user " + userIdentity + ": " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_ACTIVATION_FAILURE, errorMsg ); @@ -139,7 +139,7 @@ static void activateUser( // send email or sms sendPostActivationNotice( pwmRequest ); } - catch ( ImpossiblePasswordPolicyException e ) + catch ( final ImpossiblePasswordPolicyException e ) { final ErrorInformation info = new ErrorInformation( PwmError.ERROR_INTERNAL, "unexpected ImpossiblePasswordPolicyException error while activating user" ); LOGGER.warn( pwmSession, info, e ); @@ -187,7 +187,7 @@ static void validateParamsAgainstLDAP( } LOGGER.trace( pwmSession, () -> "successful validation of ldap value for '" + attrName + "'" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.error( pwmSession.getLabel(), "error during param validation of '" + attrName + "', error: " + e.getMessage() ); throw new PwmDataValidationException( new ErrorInformation( @@ -274,7 +274,7 @@ static boolean sendPostActivationSms( final PwmRequest pwmRequest ) { toSmsNumber = userInfo.readStringAttribute( ldapProfile.readSettingAsString( PwmSetting.SMS_USER_PHONE_ATTRIBUTE ) ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( pwmSession, () -> "error reading SMS attribute from user '" + pwmSession.getUserInfo().getUserIdentity() + "': " + e.getMessage() ); return false; diff --git a/server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java b/server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java index e69125f60..ad0d83cdc 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java @@ -207,7 +207,7 @@ private ProcessStatus downloadAuditLogCsv( { pwmApplication.getAuditManager().outputVaultToCsv( outputStream, pwmRequest.getLocale(), true ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ); pwmRequest.respondWithError( errorInformation ); @@ -238,7 +238,7 @@ private ProcessStatus downloadUserReportCsv( final ReportCsvUtility reportCsvUtility = new ReportCsvUtility( pwmApplication ); reportCsvUtility.outputToCsv( outputStream, true, pwmRequest.getLocale() ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ); pwmRequest.respondWithError( errorInformation ); @@ -269,7 +269,7 @@ private ProcessStatus downloadUserSummaryCsv( final ReportCsvUtility reportCsvUtility = new ReportCsvUtility( pwmApplication ); reportCsvUtility.outputSummaryToCsv( outputStream, pwmRequest.getLocale() ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ); pwmRequest.respondWithError( errorInformation ); @@ -298,7 +298,7 @@ private ProcessStatus downloadStatisticsLogCsv( final PwmRequest pwmRequest ) final StatisticsManager statsManager = pwmApplication.getStatisticsManager(); statsManager.outputStatsToCsv( outputStream, pwmRequest.getLocale(), true ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ); pwmRequest.respondWithError( errorInformation ); @@ -326,7 +326,7 @@ private ProcessStatus downloadSessionsCsv( final PwmRequest pwmRequest ) { pwmApplication.getSessionTrackService().outputToCsv( pwmRequest.getLocale(), pwmRequest.getConfig(), outputStream ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ); pwmRequest.respondWithError( errorInformation ); @@ -529,7 +529,7 @@ private ProcessStatus restIntruderDataHandler( final PwmRequest pwmRequest ) returnData.put( recordType.toString(), pwmRequest.getPwmApplication().getIntruderManager().getRecords( recordType, max ) ); } } - catch ( PwmException e ) + catch ( final PwmException e ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ); LOGGER.debug( pwmRequest, errorInfo ); @@ -558,7 +558,7 @@ private void processDebugUserSearch( final PwmRequest pwmRequest ) final AdminBean adminBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, AdminBean.class ); adminBean.setLastUserDebug( userIdentity ); } - catch ( PwmUnrecoverableException | PwmOperationalException e ) + catch ( final PwmUnrecoverableException | PwmOperationalException e ) { setLastError( pwmRequest, e.getErrorInformation() ); return; diff --git a/server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java b/server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java index 962705488..c10385c77 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java @@ -413,7 +413,7 @@ private static Map makeLocalDbTableSizes( returnData.put( db, numberFormat.format( localDB.size( db ) ) ); } } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { LOGGER.error( "error making localDB size bean: " + e.getMessage() ); } @@ -547,7 +547,7 @@ private static List makeNodeData( ) ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.trace( () -> "error building AppDashboardData node-state: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java b/server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java index c1060530b..78c041173 100644 --- a/server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java +++ b/server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java @@ -83,7 +83,7 @@ public static UserDebugDataBean readUserDebugData( { readablePassword = null != LdapOperationsHelper.readLdapPassword( pwmApplication, sessionLabel, userIdentity ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { /* disregard */ } @@ -174,7 +174,7 @@ private static PwNotifyUserStatus readPwNotifyUserStatus( return value.get(); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.debug( () -> "error reading user pwNotify status: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java b/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java index 37e3ccaa6..13e47c93f 100644 --- a/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java @@ -194,7 +194,7 @@ ProcessStatus processChangeAction( final PwmRequest pwmRequest ) throws ServletE final PasswordData oldPassword = pwmRequest.getPwmSession().getLoginInfoBean().getUserCurrentPassword(); pwmPasswordRuleValidator.testPassword( password1, oldPassword, userInfo, theUser ); } - catch ( PwmDataValidationException e ) + catch ( final PwmDataValidationException e ) { setLastError( pwmRequest, e.getErrorInformation() ); LOGGER.debug( pwmRequest, () -> "failed password validation check: " + e.getErrorInformation().toDebugStr() ); @@ -216,7 +216,7 @@ ProcessStatus processChangeAction( final PwmRequest pwmRequest ) throws ServletE { ChangePasswordServletUtil.executeChangePassword( pwmRequest, password1 ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.debug( () -> e.getErrorInformation().toDebugStr() ); setLastError( pwmRequest, e.getErrorInformation() ); @@ -299,7 +299,7 @@ ProcessStatus processFormAction( final PwmRequest pwmRequest ) cpb.setFormPassed( true ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { pwmRequest.getPwmApplication().getIntruderManager().convenience().markAddressAndSession( pwmRequest.getPwmSession() ); pwmRequest.getPwmApplication().getIntruderManager().convenience().markUserIdentity( userInfo.getUserIdentity(), pwmRequest.getSessionLabel() ); @@ -366,7 +366,7 @@ public ProcessStatus processCompleteAction( final PwmRequest pwmRequest ) throws pwmRequest.getPwmApplication().getStatisticsManager().updateAverageValue( AvgStatistic.AVG_PASSWORD_SYNC_TIME, totalTime.asMillis() ); LOGGER.trace( pwmRequest, () -> "password sync process marked completed (" + totalTime.asCompactString() + ")" ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "unable to update average password sync time statistic: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java b/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java index 6f1d4cdd4..380f9d244 100644 --- a/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java +++ b/server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java @@ -125,7 +125,7 @@ static void validateParamsAgainstLDAP( } LOGGER.trace( pwmSession, () -> "successful validation of ldap value for '" + attrName + "'" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.error( pwmSession, "error during param validation of '" + attrName + "', error: " + e.getMessage() ); throw new PwmDataValidationException( new ErrorInformation( diff --git a/server/src/main/java/password/pwm/http/servlet/command/CommandServlet.java b/server/src/main/java/password/pwm/http/servlet/command/CommandServlet.java index 521013b8d..d5fe91ba8 100644 --- a/server/src/main/java/password/pwm/http/servlet/command/CommandServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/command/CommandServlet.java @@ -107,7 +107,7 @@ private ProcessStatus processCspReport( final Map map = JsonUtil.deserializeStringObjectMap( body ); LOGGER.trace( () -> "CSP Report: " + JsonUtil.serializeMap( map, JsonUtil.Flag.PrettyPrint ) ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error processing csp report: " + e.getMessage() + ", body=" + body ); } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java b/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java index a077c1875..a729aff67 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java @@ -38,7 +38,10 @@ import password.pwm.config.StoredValue; import password.pwm.config.profile.PwmPasswordPolicy; import password.pwm.config.stored.ConfigurationProperty; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigItemKey; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationModifier; +import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.config.stored.ValueMetaData; import password.pwm.config.value.ActionValue; import password.pwm.config.value.FileValue; @@ -51,7 +54,6 @@ import password.pwm.error.PwmException; import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; -import password.pwm.health.ConfigurationChecker; import password.pwm.health.DatabaseStatusChecker; import password.pwm.health.HealthRecord; import password.pwm.health.HealthStatus; @@ -72,6 +74,7 @@ import password.pwm.i18n.PwmLocaleBundle; import password.pwm.ldap.LdapBrowser; import password.pwm.util.PasswordData; +import password.pwm.util.java.JavaHelper; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; @@ -104,6 +107,8 @@ import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @WebServlet( name = "ConfigEditorServlet", @@ -173,8 +178,8 @@ public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) if ( configManagerBean.getStoredConfiguration() == null ) { - final StoredConfigurationImpl loadedConfig = ConfigManagerServlet.readCurrentConfiguration( pwmRequest ); - configManagerBean.setConfiguration( loadedConfig ); + final StoredConfiguration loadedConfig = ConfigManagerServlet.readCurrentConfiguration( pwmRequest ); + configManagerBean.setStoredConfiguration( loadedConfig ); } return ProcessStatus.Continue; @@ -208,12 +213,14 @@ private ProcessStatus restExecuteSettingFunction( try { final Class implementingClass = Class.forName( functionName ); - final SettingUIFunction function = ( SettingUIFunction ) implementingClass.newInstance(); - final Serializable result = function.provideFunction( pwmRequest, configManagerBean.getStoredConfiguration(), pwmSetting, profileID, extraData ); + final SettingUIFunction function = ( SettingUIFunction ) implementingClass.getDeclaredConstructor().newInstance(); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() ); + final Serializable result = function.provideFunction( pwmRequest, modifier, pwmSetting, profileID, extraData ); + configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() ); final RestResultBean restResultBean = RestResultBean.forSuccessMessage( result, pwmRequest, Message.Success_Unknown ); pwmRequest.outputJsonResult( restResultBean ); } - catch ( Exception e ) + catch ( final Exception e ) { final RestResultBean restResultBean; if ( e instanceof PwmException ) @@ -238,7 +245,7 @@ private ProcessStatus restReadSetting( throws IOException, PwmUnrecoverableException { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); - final StoredConfigurationImpl storedConfig = configManagerBean.getStoredConfiguration(); + final StoredConfiguration storedConfig = configManagerBean.getStoredConfiguration(); final String key = pwmRequest.readParameterAsString( "key" ); final Object returnValue; @@ -249,16 +256,18 @@ private ProcessStatus restReadSetting( { final StringTokenizer st = new StringTokenizer( key, "-" ); st.nextToken(); - final PwmLocaleBundle bundleName = PwmLocaleBundle.valueOf( st.nextToken() ); + final String localeBundleName = st.nextToken(); + final PwmLocaleBundle pwmLocaleBundle = PwmLocaleBundle.forKey( localeBundleName ) + .orElseThrow( () -> new IllegalArgumentException( "unknown locale bundle name '" + localeBundleName + "'" ) ); final String keyName = st.nextToken(); - final Map bundleMap = storedConfig.readLocaleBundleMap( bundleName.getTheClass().getName(), keyName ); + final Map bundleMap = storedConfig.readLocaleBundleMap( pwmLocaleBundle, keyName ); if ( bundleMap == null || bundleMap.isEmpty() ) { final Map defaultValueMap = new LinkedHashMap<>(); - final String defaultLocaleValue = ResourceBundle.getBundle( bundleName.getTheClass().getName(), PwmConstants.DEFAULT_LOCALE ).getString( keyName ); + final String defaultLocaleValue = ResourceBundle.getBundle( pwmLocaleBundle.getTheClass().getName(), PwmConstants.DEFAULT_LOCALE ).getString( keyName ); for ( final Locale locale : pwmRequest.getConfig().getKnownLocales() ) { - final ResourceBundle localeBundle = ResourceBundle.getBundle( bundleName.getTheClass().getName(), locale ); + final ResourceBundle localeBundle = ResourceBundle.getBundle( pwmLocaleBundle.getTheClass().getName(), locale ); if ( locale.toString().equalsIgnoreCase( PwmConstants.DEFAULT_LOCALE.toString() ) ) { defaultValueMap.put( "", defaultLocaleValue ); @@ -359,7 +368,7 @@ private ProcessStatus restWriteSetting( throws IOException, PwmUnrecoverableException { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); - final StoredConfigurationImpl storedConfig = configManagerBean.getStoredConfiguration(); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() ); final String key = pwmRequest.readParameterAsString( "key" ); final String bodyString = pwmRequest.readRequestBodyAsString(); final PwmSetting setting = PwmSetting.forKey( key ); @@ -372,12 +381,13 @@ private ProcessStatus restWriteSetting( { final StringTokenizer st = new StringTokenizer( key, "-" ); st.nextToken(); - final PwmLocaleBundle bundleName = PwmLocaleBundle.valueOf( st.nextToken() ); + final PwmLocaleBundle pwmLocaleBundle = PwmLocaleBundle.forKey( st.nextToken() ) + .orElseThrow( () -> new IllegalArgumentException( "unknown locale bundle name" ) ); final String keyName = st.nextToken(); final Map valueMap = JsonUtil.deserializeStringMap( bodyString ); final Map outputMap = new LinkedHashMap<>( valueMap ); - storedConfig.writeLocaleBundleMap( bundleName.getTheClass().getName(), keyName, outputMap ); + modifier.writeLocaleBundleMap( pwmLocaleBundle, keyName, outputMap ); returnMap.put( "isDefault", outputMap.isEmpty() ); returnMap.put( "key", key ); } @@ -392,9 +402,9 @@ private ProcessStatus restWriteSetting( { returnMap.put( "errorMessage", setting.getLabel( pwmRequest.getLocale() ) + ": " + errorMsgs.get( 0 ) ); } - storedConfig.writeSetting( setting, profileID, storedValue, loggedInUser ); + modifier.writeSetting( setting, profileID, storedValue, loggedInUser ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error writing default value for setting " + setting.toString() + ", error: " + e.getMessage(); LOGGER.error( errorMsg, e ); @@ -403,8 +413,9 @@ private ProcessStatus restWriteSetting( returnMap.put( "key", key ); returnMap.put( "category", setting.getCategory().toString() ); returnMap.put( "syntax", setting.getSyntax().toString() ); - returnMap.put( "isDefault", storedConfig.isDefaultValue( setting, profileID ) ); + returnMap.put( "isDefault", configManagerBean.getStoredConfiguration().isDefaultValue( setting, profileID ) ); } + configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() ); pwmRequest.outputJsonResult( RestResultBean.withData( returnMap ) ); return ProcessStatus.Halt; } @@ -416,7 +427,7 @@ private ProcessStatus restResetSetting( throws IOException, PwmUnrecoverableException { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); - final StoredConfigurationImpl storedConfig = configManagerBean.getStoredConfiguration(); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() ); final UserIdentity loggedInUser = pwmRequest.getUserInfoIfLoggedIn(); final String key = pwmRequest.readParameterAsString( "key" ); final PwmSetting setting = PwmSetting.forKey( key ); @@ -425,16 +436,18 @@ private ProcessStatus restResetSetting( { final StringTokenizer st = new StringTokenizer( key, "-" ); st.nextToken(); - final PwmLocaleBundle bundleName = PwmLocaleBundle.valueOf( st.nextToken() ); + final PwmLocaleBundle pwmLocaleBundle = PwmLocaleBundle.forKey( st.nextToken() ) + .orElseThrow( () -> new IllegalArgumentException( "unknown locale bundle name" ) ); final String keyName = st.nextToken(); - storedConfig.resetLocaleBundleMap( bundleName.getTheClass().getName(), keyName ); + modifier.resetLocaleBundleMap( pwmLocaleBundle, keyName ); } else { final String profileID = setting.getCategory().hasProfiles() ? pwmRequest.readParameterAsString( "profile" ) : null; - storedConfig.resetSetting( setting, profileID, loggedInUser ); + modifier.resetSetting( setting, profileID, loggedInUser ); } + configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() ); pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) ); return ProcessStatus.Halt; } @@ -446,24 +459,26 @@ private ProcessStatus restSetConfigurationPassword( throws IOException, ServletException, PwmUnrecoverableException { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() ); try { final Map postData = pwmRequest.readBodyAsJsonStringMap(); final String password = postData.get( "password" ); - configManagerBean.getStoredConfiguration().setPassword( password ); + StoredConfigurationUtil.setPassword( modifier, password ); configManagerBean.setPasswordVerified( true ); LOGGER.debug( pwmRequest, () -> "config password updated" ); final RestResultBean restResultBean = RestResultBean.forConfirmMessage( pwmRequest, Config.Confirm_ConfigPasswordStored ); pwmRequest.outputJsonResult( restResultBean ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest ); pwmRequest.outputJsonResult( restResultBean ); } + configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() ); return ProcessStatus.Halt; } @@ -473,7 +488,7 @@ private ProcessStatus restFinishEditing( final PwmRequest pwmRequest ) { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); final PwmSession pwmSession = pwmRequest.getPwmSession(); - final List validationErrors = configManagerBean.getStoredConfiguration().validateValues(); + final List validationErrors = StoredConfigurationUtil.validateValues( configManagerBean.getStoredConfiguration() ); if ( !validationErrors.isEmpty() ) { final String errorString = validationErrors.get( 0 ); @@ -491,12 +506,12 @@ private ProcessStatus restFinishEditing( final PwmRequest pwmRequest ) try { ConfigManagerServlet.saveConfiguration( pwmRequest, configManagerBean.getStoredConfiguration() ); - configManagerBean.setConfiguration( null ); - configManagerBean.setConfiguration( null ); + configManagerBean.setStoredConfiguration( null ); + configManagerBean.setStoredConfiguration( null ); LOGGER.debug( pwmSession, () -> "save configuration operation completed" ); pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final ErrorInformation errorInfo = e.getErrorInformation(); pwmRequest.outputJsonResult( RestResultBean.fromError( errorInfo, pwmRequest ) ); @@ -514,7 +529,7 @@ private ProcessStatus restCancelEditing( throws IOException, ServletException, PwmUnrecoverableException { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); - configManagerBean.setConfiguration( null ); + configManagerBean.setStoredConfiguration( null ); pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) ); return ProcessStatus.Halt; } @@ -527,19 +542,19 @@ private ProcessStatus setOptions( throws IOException, PwmUnrecoverableException { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); + final StoredConfigurationModifier modifer = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() ); { final String updateDescriptionTextCmd = pwmRequest.readParameterAsString( "updateNotesText" ); - if ( updateDescriptionTextCmd != null && "true".equalsIgnoreCase( updateDescriptionTextCmd ) ) + if ( StringUtil.nullSafeEqualsIgnoreCase( "true", updateDescriptionTextCmd ) ) { try { final String bodyString = pwmRequest.readRequestBodyAsString(); final String value = JsonUtil.deserialize( bodyString, String.class ); - configManagerBean.getStoredConfiguration().writeConfigProperty( ConfigurationProperty.NOTES, - value ); + modifer.writeConfigProperty( ConfigurationProperty.NOTES, value ); LOGGER.trace( () -> "updated notesText" ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error updating notesText: " + e.getMessage() ); } @@ -551,20 +566,19 @@ private ProcessStatus setOptions( try { final PwmSettingTemplate template = PwmSettingTemplate.valueOf( requestedTemplate ); - configManagerBean.getStoredConfiguration().writeConfigProperty( - ConfigurationProperty.LDAP_TEMPLATE, template.toString() ); + modifer.writeConfigProperty( ConfigurationProperty.LDAP_TEMPLATE, template.toString() ); LOGGER.trace( () -> "setting template to: " + requestedTemplate ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { - configManagerBean.getStoredConfiguration().writeConfigProperty( - ConfigurationProperty.LDAP_TEMPLATE, PwmSettingTemplate.DEFAULT.toString() ); + modifer.writeConfigProperty( ConfigurationProperty.LDAP_TEMPLATE, PwmSettingTemplate.DEFAULT.toString() ); LOGGER.error( "unknown template set request: " + requestedTemplate ); } } } } + configManagerBean.setStoredConfiguration( modifer.newStoredConfiguration() ); return ProcessStatus.Halt; } @@ -575,35 +589,22 @@ private ProcessStatus restReadChangeLog( throws IOException, PwmUnrecoverableException { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); - final Locale locale = pwmRequest.getLocale(); - final HashMap returnObj = new HashMap<>(); - returnObj.put( "html", configManagerBean.getStoredConfiguration().changeLogAsDebugString( locale, true ) ); - returnObj.put( "modified", configManagerBean.getStoredConfiguration().isModified() ); - try - { - final ConfigurationChecker configurationChecker = new ConfigurationChecker(); - final Configuration config = new Configuration( configManagerBean.getStoredConfiguration() ); - final List healthRecords = configurationChecker.doHealthCheck( - config, - pwmRequest.getLocale() - ); - final HealthData healthData = new HealthData(); - healthData.setOverall( "CONFIG" ); - healthData.setRecords( password.pwm.ws.server.rest.bean.HealthRecord.fromHealthRecords( healthRecords, locale, config ) ); - returnObj.put( "health", healthData ); - } - catch ( Exception e ) - { - LOGGER.error( pwmRequest, "error generating health records: " + e.getMessage() ); - } + final Map returnObj = new ConcurrentHashMap<>(); + final ExecutorService executor = Executors.newFixedThreadPool( 3 ); + executor.execute( () -> ConfigEditorServletUtils.outputChangeLogData( pwmRequest, configManagerBean, returnObj ) ); + executor.execute( () -> returnObj.put( "health", ConfigEditorServletUtils.configurationHealth( pwmRequest, configManagerBean ) ) ); + JavaHelper.closeAndWaitExecutor( executor, TimeDuration.MINUTE ); - final RestResultBean restResultBean = RestResultBean.withData( returnObj ); + final RestResultBean restResultBean = RestResultBean.withData( new HashMap<>( returnObj ) ); pwmRequest.outputJsonResult( restResultBean ); + return ProcessStatus.Halt; } + + @ActionHandler( action = "search" ) private ProcessStatus restSearchSettings( final PwmRequest pwmRequest @@ -617,19 +618,19 @@ private ProcessStatus restSearchSettings( final Locale locale = pwmRequest.getLocale(); final RestResultBean restResultBean; final String searchTerm = valueMap.get( "search" ); - final StoredConfigurationImpl storedConfiguration = configManagerBean.getStoredConfiguration(); + final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration(); if ( searchTerm != null && !searchTerm.isEmpty() ) { - final ArrayList searchResults = new ArrayList<>( configManagerBean.getStoredConfiguration().search( searchTerm, locale ) ); + final Set searchResults = StoredConfigurationUtil.search( storedConfiguration, searchTerm, locale ); final ConcurrentHashMap> returnData = new ConcurrentHashMap<>(); searchResults .parallelStream() - .filter( recordID -> recordID.getRecordType() == StoredConfigurationImpl.ConfigRecordID.RecordType.SETTING ) + .filter( key -> key.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) .forEach( recordID -> { - final PwmSetting setting = ( PwmSetting ) recordID.getRecordID(); + final PwmSetting setting = recordID.toPwmSetting(); final SearchResultItem item = new SearchResultItem( setting.getCategory().toString(), storedConfiguration.readSetting( setting, recordID.getProfileID() ).toDebugString( locale ), @@ -655,7 +656,7 @@ private ProcessStatus restSearchSettings( } else { - restResultBean = RestResultBean.withData( new ArrayList() ); + restResultBean = RestResultBean.withData( new ArrayList() ); } pwmRequest.outputJsonResult( restResultBean ); @@ -731,7 +732,7 @@ private ProcessStatus restSmsHealthCheck( returnRecords.add( new HealthRecord( HealthStatus.INFO, HealthTopic.SMS, "message sent" ) ); returnRecords.add( new HealthRecord( HealthStatus.INFO, HealthTopic.SMS, "response body: \n" + StringUtil.escapeHtml( responseBody ) ) ); } - catch ( PwmException e ) + catch ( final PwmException e ) { returnRecords.add( new HealthRecord( HealthStatus.WARN, HealthTopic.SMS, "unable to send message: " + e.getMessage() ) ); } @@ -751,11 +752,13 @@ private ProcessStatus doUploadFile( throws PwmUnrecoverableException, IOException, ServletException { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() ); + + final String key = pwmRequest.readParameterAsString( "key" ); final PwmSetting setting = PwmSetting.forKey( key ); final int maxFileSize = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.CONFIG_MAX_JDBC_JAR_SIZE ) ); - if ( setting == PwmSetting.HTTPS_CERT ) { try @@ -767,7 +770,7 @@ private ProcessStatus doUploadFile( { keyStoreFormat = HttpsServerCertificateManager.KeyStoreFormat.valueOf( pwmRequest.readParameterAsString( "format" ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "unknown format type: " + e.getMessage(), new String[] { @@ -780,7 +783,7 @@ private ProcessStatus doUploadFile( final ByteArrayInputStream fileIs = new ByteArrayInputStream( fileUploads.get( PwmConstants.PARAM_FILE_UPLOAD ).getContent().copyOf() ); HttpsServerCertificateManager.importKey( - configManagerBean.getStoredConfiguration(), + modifier, keyStoreFormat, fileIs, passwordData, @@ -790,7 +793,7 @@ private ProcessStatus doUploadFile( pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) ); return ProcessStatus.Halt; } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( pwmRequest, "error during https certificate upload: " + e.getMessage() ); pwmRequest.respondWithError( e.getErrorInformation(), false ); @@ -805,10 +808,11 @@ private ProcessStatus doUploadFile( ? pwmRequest.getPwmSession().getUserInfo().getUserIdentity() : null; - configManagerBean.getStoredConfiguration().writeSetting( setting, fileValue, userIdentity ); + modifier.writeSetting( setting, null, fileValue, userIdentity ); pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) ); } + configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() ); return ProcessStatus.Halt; } @@ -836,7 +840,7 @@ private ProcessStatus restMenuTreeData( } { - final StoredConfigurationImpl storedConfiguration = configManagerBean.getStoredConfiguration(); + final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration(); final List categories = NavTreeHelper.filteredCategories( pwmRequest.getPwmApplication(), storedConfiguration, @@ -867,7 +871,7 @@ private ProcessStatus restMenuTreeData( categoryInfo.setName( localeBundle.getTheClass().getSimpleName() ); categoryInfo.setParent( "DISPLAY_TEXT" ); categoryInfo.setType( NavTreeHelper.NavItemType.displayText ); - categoryInfo.setKeys( new TreeSet<>( modifiedSettingsOnly ? modifiedKeys : localeBundle.getKeys() ) ); + categoryInfo.setKeys( new TreeSet<>( modifiedSettingsOnly ? modifiedKeys : localeBundle.getDisplayKeys() ) ); navigationData.add( categoryInfo ); includeDisplayText = true; } @@ -903,7 +907,7 @@ private ProcessStatus restConfigSettingData( final PwmRequest pwmRequest ) configManagerBean.getStoredConfiguration(), pwmRequest.getSessionLabel(), pwmRequest.getLocale() - ) + ) ); if ( pwmRequest.getPwmApplication().getApplicationMode() == PwmApplicationMode.CONFIGURATION && !PwmConstants.TRIAL_MODE ) @@ -922,11 +926,11 @@ private ProcessStatus restConfigSettingData( final PwmRequest pwmRequest ) public static Map generateSettingData( final PwmApplication pwmApplication, - final StoredConfigurationImpl storedConfiguration, + final StoredConfiguration storedConfiguration, final SessionLabel sessionLabel, final Locale locale - ) throws PwmUnrecoverableException + ) throws PwmUnrecoverableException { final LinkedHashMap returnMap = new LinkedHashMap<>(); final MacroMachine macroMachine = MacroMachine.forNonUserSpecific( pwmApplication, sessionLabel ); @@ -963,7 +967,7 @@ public static Map generateSettingData( } { final LinkedHashMap varMap = new LinkedHashMap<>(); - varMap.put( "ldapProfileIds", storedConfiguration.readSetting( PwmSetting.LDAP_PROFILE_LIST ).toNativeObject() ); + varMap.put( "ldapProfileIds", storedConfiguration.readSetting( PwmSetting.LDAP_PROFILE_LIST, null ).toNativeObject() ); varMap.put( "currentTemplate", storedConfiguration.getTemplateSet() ); varMap.put( "configurationNotes", storedConfiguration.readConfigProperty( ConfigurationProperty.NOTES ) ); returnMap.put( "var", varMap ); @@ -997,7 +1001,7 @@ private ProcessStatus restTestMacro( final PwmRequest pwmRequest ) throws IOExce final String output = macroMachine.expandMacros( input ); pwmRequest.outputJsonResult( RestResultBean.withData( output ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmRequest, e.getErrorInformation() ); pwmRequest.respondWithError( e.getErrorInformation() ); @@ -1025,7 +1029,7 @@ private ProcessStatus restBrowseLdap( final PwmRequest pwmRequest ) { result = ldapBrowser.doBrowse( profile, dn ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { // Probably was given a bad dn, better just browse without a DN than error out completely result = ldapBrowser.doBrowse( profile, "" ); @@ -1049,6 +1053,7 @@ private ProcessStatus restCopyProfile( final PwmRequest pwmRequest ) throws IOException, ServletException, PwmUnrecoverableException { final ConfigManagerBean configManagerBean = getBean( pwmRequest ); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() ); final Map inputMap = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation ); final String settingKey = inputMap.get( "setting" ); @@ -1069,13 +1074,15 @@ private ProcessStatus restCopyProfile( final PwmRequest pwmRequest ) final String destinationID = inputMap.get( "destinationID" ); try { - configManagerBean.getStoredConfiguration().copyProfileID( category, sourceID, destinationID, pwmRequest.getUserInfoIfLoggedIn() ); + modifier.copyProfileID( category, sourceID, destinationID, pwmRequest.getUserInfoIfLoggedIn() ); pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); } + + configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() ); return ProcessStatus.Halt; } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java b/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java index 1d80bc394..cd09fcc3e 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java @@ -21,19 +21,32 @@ package password.pwm.http.servlet.configeditor; import password.pwm.PwmConstants; +import password.pwm.config.Configuration; +import password.pwm.config.stored.StoredConfigItemKey; +import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.config.value.FileValue; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmException; import password.pwm.error.PwmUnrecoverableException; +import password.pwm.health.ConfigurationChecker; +import password.pwm.health.HealthRecord; import password.pwm.http.PwmRequest; +import password.pwm.http.bean.ConfigManagerBean; +import password.pwm.util.java.StringUtil; +import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; import password.pwm.ws.server.RestResultBean; +import password.pwm.ws.server.rest.bean.HealthData; import javax.servlet.ServletException; import java.io.IOException; +import java.time.Instant; import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Set; public class ConfigEditorServletUtils { @@ -53,13 +66,13 @@ public static FileValue readFileUploadToSettingValue( { fileUploads = pwmRequest.readFileUploads( maxFileSize, 1 ); } - catch ( PwmException e ) + catch ( final PwmException e ) { pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); LOGGER.error( pwmRequest, "error during file upload: " + e.getErrorInformation().toDebugStr() ); return null; } - catch ( Throwable e ) + catch ( final Throwable e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "error during file upload: " + e.getMessage() ); pwmRequest.outputJsonResult( RestResultBean.fromError( errorInformation, pwmRequest ) ); @@ -83,4 +96,72 @@ public static FileValue readFileUploadToSettingValue( return null; } + static void outputChangeLogData( + final PwmRequest pwmRequest, + final ConfigManagerBean configManagerBean, + final Map outputMap + ) + { + final Locale locale = pwmRequest.getLocale(); + + final Set changeLog = StoredConfigurationUtil.changedValues( + pwmRequest.getPwmApplication().getConfig().getStoredConfiguration(), + configManagerBean.getStoredConfiguration() ); + + final Map changeLogMap = StoredConfigurationUtil.makeDebugMap( + configManagerBean.getStoredConfiguration(), + changeLog, + locale ); + + final StringBuilder output = new StringBuilder(); + if ( changeLogMap.isEmpty() ) + { + output.append( "No setting changes." ); + } + else + { + for ( final Map.Entry entry : changeLogMap.entrySet() ) + { + output.append( "
" ); + output.append( entry.getKey() ); + output.append( "
" ); + output.append( StringUtil.escapeHtml( entry.getValue() ) ); + output.append( "
" ); + } + } + outputMap.put( "html", output.toString() ); + outputMap.put( "modified", !changeLog.isEmpty() ); + + } + + static HealthData configurationHealth( + final PwmRequest pwmRequest, + final ConfigManagerBean configManagerBean + ) + { + final Instant startTime = Instant.now(); + try + { + final Locale locale = pwmRequest.getLocale(); + final ConfigurationChecker configurationChecker = new ConfigurationChecker(); + final Configuration config = new Configuration( configManagerBean.getStoredConfiguration() ); + final List healthRecords = configurationChecker.doHealthCheck( + config, + pwmRequest.getLocale() + ); + + LOGGER.debug( () -> "config health check done in " + TimeDuration.compactFromCurrent( startTime ) ); + + return HealthData.builder() + .overall( "CONFIG" ) + .records( password.pwm.ws.server.rest.bean.HealthRecord.fromHealthRecords( healthRecords, locale, config ) ) + .build(); + } + catch ( final Exception e ) + { + LOGGER.error( pwmRequest, "error generating health records: " + e.getMessage() ); + } + + return HealthData.builder().build(); + } } diff --git a/server/src/main/java/password/pwm/http/servlet/configeditor/NavTreeHelper.java b/server/src/main/java/password/pwm/http/servlet/configeditor/NavTreeHelper.java index 1c2a7807e..425a05fb3 100644 --- a/server/src/main/java/password/pwm/http/servlet/configeditor/NavTreeHelper.java +++ b/server/src/main/java/password/pwm/http/servlet/configeditor/NavTreeHelper.java @@ -27,7 +27,7 @@ import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingCategory; import password.pwm.config.StoredValue; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.i18n.Config; import password.pwm.i18n.PwmLocaleBundle; @@ -47,13 +47,13 @@ class NavTreeHelper static Set determineModifiedKeysSettings( final PwmLocaleBundle bundle, final Configuration config, - final StoredConfigurationImpl storedConfiguration + final StoredConfiguration storedConfiguration ) { final Set modifiedKeys = new TreeSet<>(); - for ( final String key : bundle.getKeys() ) + for ( final String key : bundle.getDisplayKeys() ) { - final Map storedBundle = storedConfiguration.readLocaleBundleMap( bundle.getTheClass().getName(), key ); + final Map storedBundle = storedConfiguration.readLocaleBundleMap( bundle, key ); if ( !storedBundle.isEmpty() ) { for ( final Locale locale : config.getKnownLocales() ) @@ -77,7 +77,7 @@ static Set determineModifiedKeysSettings( static boolean categoryMatcher( final PwmApplication pwmApplication, final PwmSettingCategory category, - final StoredConfigurationImpl storedConfiguration, + final StoredConfiguration storedConfiguration, final boolean modifiedOnly, final int minLevel, final String text @@ -138,7 +138,7 @@ static boolean categoryMatcher( static List filteredCategories( final PwmApplication pwmApplication, - final StoredConfigurationImpl storedConfiguration, + final StoredConfiguration storedConfiguration, final Locale locale, final boolean modifiedSettingsOnly, final double level, @@ -170,7 +170,7 @@ static List filteredCategories( */ static List makeSettingNavItems( final List categories, - final StoredConfigurationImpl storedConfiguration, + final StoredConfiguration storedConfiguration, final Locale locale ) { @@ -277,7 +277,7 @@ private static NavTreeItem navTreeItemForCategory( } private static boolean settingMatches( - final StoredConfigurationImpl storedConfiguration, + final StoredConfiguration storedConfiguration, final PwmSetting setting, final String profileID, final boolean modifiedOnly, @@ -313,7 +313,7 @@ private static boolean settingMatches( final StoredValue storedValue = storedConfiguration.readSetting( setting, profileID ); for ( final String term : StringUtil.whitespaceSplit( text ) ) { - if ( storedConfiguration.matchSetting( setting, storedValue, term, PwmConstants.DEFAULT_LOCALE ) ) + if ( StoredConfigurationUtil.matchSetting( storedConfiguration, setting, storedValue, term, PwmConstants.DEFAULT_LOCALE ) ) { return true; } diff --git a/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java b/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java index bad86f2b8..6f531a7be 100644 --- a/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java +++ b/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java @@ -23,7 +23,9 @@ import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingTemplate; import password.pwm.config.StoredValue; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationFactory; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.config.value.BooleanValue; import password.pwm.config.value.ChallengeValue; import password.pwm.config.value.FileValue; @@ -69,7 +71,7 @@ public static Map defaultForm( ) private static void updateStoredConfigTemplateValue( final Map formData, - final StoredConfigurationImpl storedConfiguration, + final StoredConfigurationModifier modifier, final PwmSetting pwmSetting, final ConfigGuideFormField formField, final PwmSettingTemplate.Type type @@ -80,20 +82,20 @@ private static void updateStoredConfigTemplateValue( if ( !StringUtil.isEmpty( formValue ) ) { final PwmSettingTemplate template = PwmSettingTemplate.templateForString( formValue, type ); - storedConfiguration.writeSetting( pwmSetting, null, new StringValue( template.toString() ), null ); + modifier.writeSetting( pwmSetting, null, new StringValue( template.toString() ), null ); } } private static final String LDAP_PROFILE_NAME = "default"; - public static StoredConfigurationImpl generateStoredConfig( + public static StoredConfiguration generateStoredConfig( final ConfigGuideBean configGuideBean ) throws PwmUnrecoverableException { final Map formData = configGuideBean.getFormData(); - final StoredConfigurationImpl storedConfiguration = StoredConfigurationImpl.newStoredConfiguration(); + final StoredConfigurationModifier storedConfiguration = StoredConfigurationModifier.newModifier( StoredConfigurationFactory.newConfig() ); // templates updateStoredConfigTemplateValue( @@ -170,8 +172,11 @@ public static StoredConfigurationImpl generateStoredConfig( { // set admin query final String groupDN = formData.get( ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP ); - final List userPermissions = Collections.singletonList( new UserPermission( UserPermission.Type.ldapGroup, null, null, groupDN ) ); - storedConfiguration.writeSetting( PwmSetting.QUERY_MATCH_PWM_ADMIN, new UserPermissionValue( userPermissions ), null ); + final List userPermissions = Collections.singletonList( UserPermission.builder() + .type( UserPermission.Type.ldapGroup ) + .ldapBase( groupDN ) + .build() ); + storedConfiguration.writeSetting( PwmSetting.QUERY_MATCH_PWM_ADMIN, null, new UserPermissionValue( userPermissions ), null ); } { @@ -215,12 +220,12 @@ public static StoredConfigurationImpl generateStoredConfig( } // set site url - storedConfiguration.writeSetting( PwmSetting.PWM_SITE_URL, new StringValue( formData.get( ConfigGuideFormField.PARAM_APP_SITEURL ) ), null ); + storedConfiguration.writeSetting( PwmSetting.PWM_SITE_URL, null, new StringValue( formData.get( ConfigGuideFormField.PARAM_APP_SITEURL ) ), null ); // enable debug mode storedConfiguration.writeSetting( PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS, null, new BooleanValue( true ), null ); - return storedConfiguration; + return storedConfiguration.newStoredConfiguration(); } static String figureLdapUrlFromFormConfig( final Map ldapForm ) @@ -236,12 +241,12 @@ public static String figureLdapHostnameExample( final ConfigGuideBean configGuid { try { - final StoredConfigurationImpl storedConfiguration = generateStoredConfig( configGuideBean ); + final StoredConfiguration storedConfiguration = generateStoredConfig( configGuideBean ); final String uriString = PwmSetting.LDAP_SERVER_URLS.getExample( storedConfiguration.getTemplateSet() ); final URI uri = new URI( uriString ); return uri.getHost(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error calculating ldap hostname example: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java b/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java index e6e7f7871..4a58d4930 100644 --- a/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java @@ -33,7 +33,10 @@ import password.pwm.config.function.UserMatchViewerFunction; import password.pwm.config.profile.LdapProfile; import password.pwm.config.stored.ConfigurationProperty; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationFactory; +import password.pwm.config.stored.StoredConfigurationModifier; +import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.config.value.FileValue; import password.pwm.config.value.ValueFactory; import password.pwm.error.ErrorInformation; @@ -97,7 +100,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet private static final PwmLogger LOGGER = PwmLogger.getLogger( ConfigGuideServlet.class.getName() ); - private static final String LDAP_PROFILE_KEY = "default"; + private static final String LDAP_PROFILE_KEY = PwmConstants.PROFILE_ID_DEFAULT; public enum ConfigGuideAction implements AbstractPwmServlet.ProcessAction { @@ -191,7 +194,7 @@ public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUn configGuideBean.setCertsTrustedbyKeystore( false ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error reading/testing ldap server certificates: " + e.getMessage() ); } @@ -230,8 +233,8 @@ private ProcessStatus restLdapHealth( { final ConfigGuideBean configGuideBean = getBean( pwmRequest ); - final StoredConfigurationImpl storedConfigurationImpl = ConfigGuideForm.generateStoredConfig( configGuideBean ); - final Configuration tempConfiguration = new Configuration( storedConfigurationImpl ); + final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean ); + final Configuration tempConfiguration = new Configuration( storedConfiguration ); final PwmApplication tempApplication = new PwmApplication( pwmRequest.getPwmApplication() .getPwmEnvironment() .makeRuntimeInstance( tempConfiguration ) ); @@ -249,7 +252,7 @@ private ProcessStatus restLdapHealth( ConfigGuideUtils.checkLdapServer( configGuideBean ); records.add( password.pwm.health.HealthRecord.forMessage( HealthMessage.LDAP_OK ) ); } - catch ( Exception e ) + catch ( final Exception e ) { records.add( new HealthRecord( HealthStatus.WARN, HealthTopic.LDAP, "Can not connect to remote server: " + e.getMessage() ) ); } @@ -285,7 +288,7 @@ private ProcessStatus restLdapHealth( final Collection results = userMatchViewerFunction.discoverMatchingUsers( pwmRequest.getPwmApplication(), 2, - storedConfigurationImpl, + storedConfiguration, PwmSetting.QUERY_MATCH_PWM_ADMIN, null ); @@ -299,11 +302,11 @@ private ProcessStatus restLdapHealth( records.add( new HealthRecord( HealthStatus.GOOD, HealthTopic.LDAP, "Admin group validated" ) ); } } - catch ( PwmException e ) + catch ( final PwmException e ) { records.add( new HealthRecord( HealthStatus.WARN, HealthTopic.LDAP, "Error during admin group validation: " + e.getErrorInformation().toDebugStr() ) ); } - catch ( Exception e ) + catch ( final Exception e ) { records.add( new HealthRecord( HealthStatus.WARN, HealthTopic.LDAP, "Error during admin group validation: " + e.getMessage() ) ); } @@ -335,11 +338,11 @@ private ProcessStatus restLdapHealth( JavaHelper.unhandledSwitchStatement( configGuideBean.getStep() ); } - final HealthData jsonOutput = new HealthData(); - jsonOutput.records = password.pwm.ws.server.rest.bean.HealthRecord.fromHealthRecords( records, - pwmRequest.getLocale(), tempConfiguration ); - jsonOutput.timestamp = Instant.now(); - jsonOutput.overall = HealthMonitor.getMostSevereHealthStatus( records ).toString(); + final HealthData jsonOutput = HealthData.builder() + .records( password.pwm.ws.server.rest.bean.HealthRecord.fromHealthRecords( records, pwmRequest.getLocale(), tempConfiguration ) ) + .timestamp( Instant.now() ) + .overall( HealthMonitor.getMostSevereHealthStatus( records ).toString() ) + .build(); final RestResultBean restResultBean = RestResultBean.withData( jsonOutput ); pwmRequest.outputJsonResult( restResultBean ); return ProcessStatus.Halt; @@ -356,16 +359,17 @@ private ProcessStatus restViewAdminMatches( try { final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction(); - final StoredConfigurationImpl storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean ); - final Serializable output = userMatchViewerFunction.provideFunction( pwmRequest, storedConfiguration, PwmSetting.QUERY_MATCH_PWM_ADMIN, null, null ); + final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean ); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); + final Serializable output = userMatchViewerFunction.provideFunction( pwmRequest, modifier, PwmSetting.QUERY_MATCH_PWM_ADMIN, null, null ); pwmRequest.outputJsonResult( RestResultBean.withData( output ) ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( pwmRequest, e.getErrorInformation() ); pwmRequest.respondWithError( e.getErrorInformation(), false ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "error while testing matches = " + e.getMessage() ); LOGGER.error( pwmRequest, errorInformation ); @@ -382,11 +386,13 @@ private ProcessStatus restBrowseLdap( { final ConfigGuideBean configGuideBean = getBean( pwmRequest ); - final StoredConfigurationImpl storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean ); + StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean ); if ( configGuideBean.getStep() == GuideStep.LDAP_PROXY ) { - storedConfiguration.resetSetting( PwmSetting.LDAP_PROXY_USER_DN, LDAP_PROFILE_KEY, null ); - storedConfiguration.resetSetting( PwmSetting.LDAP_PROXY_USER_PASSWORD, LDAP_PROFILE_KEY, null ); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); + modifier.resetSetting( PwmSetting.LDAP_PROXY_USER_DN, LDAP_PROFILE_KEY, null ); + modifier.resetSetting( PwmSetting.LDAP_PROXY_USER_PASSWORD, LDAP_PROFILE_KEY, null ); + storedConfiguration = modifier.newStoredConfiguration(); } final Instant startTime = Instant.now(); @@ -447,7 +453,7 @@ private ProcessStatus restGotoStep( final PwmRequest pwmRequest ) { step = GuideStep.valueOf( requestedStep ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { final String errorMsg = "unknown goto step request: " + requestedStep; LOGGER.error( pwmRequest, errorMsg ); @@ -484,13 +490,13 @@ else if ( step == GuideStep.PREVIOUS ) ConfigGuideUtils.writeConfig( contextManager, configGuideBean ); pwmRequest.getPwmSession().getSessionStateBean().setTheme( null ); } - catch ( PwmException e ) + catch ( final PwmException e ) { final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest ); pwmRequest.outputJsonResult( restResultBean ); return ProcessStatus.Halt; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "error during save: " + e.getMessage(), e ); final RestResultBean restResultBean = RestResultBean.fromError( new ErrorInformation( @@ -530,7 +536,7 @@ private ProcessStatus restExtendSchema( final PwmRequest pwmRequest ) final SchemaOperationResult schemaOperationResult = ConfigGuideUtils.extendSchema( pwmRequest.getPwmApplication(), configGuideBean, true ); pwmRequest.outputJsonResult( RestResultBean.withData( schemaOperationResult.getOperationLog() ) ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ); pwmRequest.outputJsonResult( RestResultBean.fromError( errorInformation, pwmRequest ) ); @@ -553,7 +559,7 @@ private ProcessStatus restUploadJDBCDriver( final PwmRequest pwmRequest ) final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ); pwmRequest.getPwmResponse().outputJsonResult( restResultBean ); } - catch ( PwmException e ) + catch ( final PwmException e ) { final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest ); pwmRequest.getPwmResponse().outputJsonResult( restResultBean ); @@ -571,14 +577,14 @@ private ProcessStatus restSkipGuide( final PwmRequest pwmRequest ) throws PwmUnr final ContextManager contextManager = ContextManager.getContextManager( pwmRequest ); try { - final StoredConfigurationImpl storedConfiguration = new StoredConfigurationImpl(); + final StoredConfigurationModifier storedConfiguration = StoredConfigurationModifier.newModifier( StoredConfigurationFactory.newConfig() ); storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, "true" ); - storedConfiguration.setPassword( password ); - ConfigGuideUtils.writeConfig( contextManager, storedConfiguration ); + StoredConfigurationUtil.setPassword( storedConfiguration, password ); + ConfigGuideUtils.writeConfig( contextManager, storedConfiguration.newStoredConfiguration() ); pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) ); pwmRequest.invalidateSession(); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( "error during skip config guide: " + e.getMessage(), e ); } @@ -591,15 +597,15 @@ private ProcessStatus restReadSetting( final PwmRequest pwmRequest ) throws PwmU { final String profileID = "default"; final ConfigGuideBean configGuideBean = getBean( pwmRequest ); - final StoredConfigurationImpl storedConfigurationImpl = ConfigGuideForm.generateStoredConfig( configGuideBean ); + final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean ); final String key = pwmRequest.readParameterAsString( "key" ); final LinkedHashMap returnMap = new LinkedHashMap<>(); final PwmSetting theSetting = PwmSetting.forKey( key ); final Object returnValue; - returnValue = storedConfigurationImpl.readSetting( theSetting, profileID ).toNativeObject(); - returnMap.put( "isDefault", storedConfigurationImpl.isDefaultValue( theSetting, profileID ) ); + returnValue = storedConfiguration.readSetting( theSetting, profileID ).toNativeObject(); + returnMap.put( "isDefault", storedConfiguration.isDefaultValue( theSetting, profileID ) ); returnMap.put( "key", key ); returnMap.put( "category", theSetting.getCategory().toString() ); returnMap.put( "syntax", theSetting.getSyntax().toString() ); @@ -619,7 +625,7 @@ private ProcessStatus restWriteSetting( final PwmRequest pwmRequest ) final String bodyString = pwmRequest.readRequestBodyAsString(); final PwmSetting setting = PwmSetting.forKey( key ); final ConfigGuideBean configGuideBean = getBean( pwmRequest ); - final StoredConfigurationImpl storedConfigurationImpl = ConfigGuideForm.generateStoredConfig( configGuideBean ); + final StoredConfiguration storedConfigurationImpl = ConfigGuideForm.generateStoredConfig( configGuideBean ); final LinkedHashMap returnMap = new LinkedHashMap<>(); @@ -638,7 +644,7 @@ private ProcessStatus restWriteSetting( final PwmRequest pwmRequest ) configGuideBean.getFormData().put( ConfigGuideFormField.CHALLENGE_RESPONSE_DATA, JsonUtil.serialize( (Serializable) storedValue.toNativeObject() ) ); } } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error writing default value for setting " + setting.toString() + ", error: " + e.getMessage(); LOGGER.error( errorMsg, e ); @@ -659,11 +665,11 @@ private ProcessStatus restSettingData( final PwmRequest pwmRequest ) throws IOException, PwmUnrecoverableException { final ConfigGuideBean configGuideBean = getBean( pwmRequest ); - final StoredConfigurationImpl storedConfigurationImpl = ConfigGuideForm.generateStoredConfig( configGuideBean ); + final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean ); final LinkedHashMap returnMap = new LinkedHashMap<>( ConfigEditorServlet.generateSettingData( pwmRequest.getPwmApplication(), - storedConfigurationImpl, + storedConfiguration, pwmRequest.getSessionLabel(), pwmRequest.getLocale() ) diff --git a/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java b/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java index dbfaf2b00..52414b9c2 100644 --- a/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java +++ b/server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java @@ -30,7 +30,10 @@ import password.pwm.config.Configuration; import password.pwm.config.stored.ConfigurationProperty; import password.pwm.config.stored.ConfigurationReader; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationFactory; +import password.pwm.config.stored.StoredConfigurationModifier; +import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmException; @@ -70,13 +73,14 @@ public class ConfigGuideUtils static void writeConfig( final ContextManager contextManager, final ConfigGuideBean configGuideBean - ) throws PwmOperationalException, PwmUnrecoverableException + ) + throws PwmOperationalException, PwmUnrecoverableException { - final StoredConfigurationImpl storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean ); + final StoredConfigurationModifier storedConfiguration = StoredConfigurationModifier.newModifier( ConfigGuideForm.generateStoredConfig( configGuideBean ) ); final String configPassword = configGuideBean.getFormData().get( ConfigGuideFormField.PARAM_CONFIG_PASSWORD ); if ( configPassword != null && configPassword.length() > 0 ) { - storedConfiguration.setPassword( configPassword ); + StoredConfigurationUtil.setPassword( storedConfiguration, configPassword ); } else { @@ -84,31 +88,33 @@ static void writeConfig( } storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, "false" ); - ConfigGuideUtils.writeConfig( contextManager, storedConfiguration ); + ConfigGuideUtils.writeConfig( contextManager, storedConfiguration.newStoredConfiguration() ); } static void writeConfig( final ContextManager contextManager, - final StoredConfigurationImpl storedConfiguration - ) throws PwmOperationalException, PwmUnrecoverableException + final StoredConfiguration storedConfiguration + ) + throws PwmOperationalException, PwmUnrecoverableException { final ConfigurationReader configReader = contextManager.getConfigReader(); final PwmApplication pwmApplication = contextManager.getPwmApplication(); try { + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); // add a random security key - storedConfiguration.initNewRandomSecurityKey(); + StoredConfigurationUtil.initNewRandomSecurityKey( modifier ); - configReader.saveConfiguration( storedConfiguration, pwmApplication, null ); + configReader.saveConfiguration( modifier.newStoredConfiguration(), pwmApplication, null ); contextManager.requestPwmApplicationRestart(); } - catch ( PwmException e ) + catch ( final PwmException e ) { throw new PwmOperationalException( e.getErrorInformation() ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INVALID_CONFIG, "unable to save configuration: " + e.getLocalizedMessage() ); throw new PwmOperationalException( errorInformation ); @@ -148,7 +154,7 @@ public static SchemaOperationResult extendSchema( return SchemaManager.checkExistingSchema( chaiProvider ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unable to create schema extender object: " + e.getMessage() ); return null; @@ -232,8 +238,8 @@ public static void restUploadConfig( final PwmRequest pwmRequest ) { try { - final StoredConfigurationImpl storedConfig = StoredConfigurationImpl.fromXml( uploadedFile ); - final List configErrors = storedConfig.validateValues(); + final StoredConfiguration storedConfig = StoredConfigurationFactory.fromXml( uploadedFile ); + final List configErrors = StoredConfigurationUtil.validateValues( storedConfig ); if ( configErrors != null && !configErrors.isEmpty() ) { throw new PwmOperationalException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, configErrors.get( 0 ) ) ); @@ -244,7 +250,7 @@ public static void restUploadConfig( final PwmRequest pwmRequest ) pwmRequest.getPwmResponse().outputJsonResult( restResultBean ); req.getSession().invalidate(); } - catch ( PwmException e ) + catch ( final PwmException e ) { final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest ); pwmRequest.getPwmResponse().outputJsonResult( restResultBean ); diff --git a/server/src/main/java/password/pwm/http/servlet/configguide/GuideStep.java b/server/src/main/java/password/pwm/http/servlet/configguide/GuideStep.java index 234741859..64aa344c6 100644 --- a/server/src/main/java/password/pwm/http/servlet/configguide/GuideStep.java +++ b/server/src/main/java/password/pwm/http/servlet/configguide/GuideStep.java @@ -107,7 +107,7 @@ boolean visible( final ConfigGuideBean configGuideBean ) visibilityCheckImpl = visibilityCheckClass.newInstance(); return visibilityCheckImpl.visible( configGuideBean ); } - catch ( ReflectiveOperationException e ) + catch ( final ReflectiveOperationException e ) { LOGGER.error( "unexpected error during step visibility check: " + e.getMessage(), e ); } @@ -130,7 +130,7 @@ public boolean visible( final ConfigGuideBean configGuideBean ) final Set templates = ConfigGuideForm.generateStoredConfig( configGuideBean ).getTemplateSet().getTemplates(); return templates.contains( PwmSettingTemplate.LDAP ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { return true; } @@ -146,7 +146,7 @@ public boolean visible( final ConfigGuideBean configGuideBean ) final Set templates = ConfigGuideForm.generateStoredConfig( configGuideBean ).getTemplateSet().getTemplates(); return templates.contains( PwmSettingTemplate.DB ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { return true; } diff --git a/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java b/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java index 6b582e03d..8ad41f07a 100644 --- a/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java @@ -28,9 +28,8 @@ import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingSyntax; import password.pwm.config.StoredValue; -import password.pwm.config.stored.StoredConfigReference; -import password.pwm.config.stored.StoredConfigurationImpl; -import password.pwm.config.stored.StoredConfigurationUtil; +import password.pwm.config.stored.StoredConfigItemKey; +import password.pwm.config.stored.StoredConfiguration; import password.pwm.config.value.data.ActionConfiguration; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.HttpMethod; @@ -54,6 +53,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; @WebServlet( name = "ConfigManagerCertificateServlet", @@ -89,7 +89,7 @@ protected ConfigManagerCertificateAction readProcessAction( final PwmRequest req { return ConfigManagerCertificateAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { return null; } @@ -115,14 +115,14 @@ protected void processAction( final PwmRequest pwmRequest ) List makeCertificateDebugData( final Configuration configuration ) throws PwmUnrecoverableException { - final StoredConfigurationImpl storedConfiguration = configuration.getStoredConfiguration(); - final List modifiedSettings = StoredConfigurationUtil.modifiedSettings( storedConfiguration ); + final StoredConfiguration storedConfiguration = configuration.getStoredConfiguration(); + final Set modifiedSettings = storedConfiguration.modifiedItems(); final List certificateDebugDataItems = new ArrayList<>(); - for ( final StoredConfigReference ref : modifiedSettings ) + for ( final StoredConfigItemKey ref : modifiedSettings ) { - if ( ref.getRecordType() == StoredConfigReference.RecordType.SETTING ) + if ( ref.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) { final PwmSetting pwmSetting = PwmSetting.forKey( ref.getRecordID() ); if ( pwmSetting.getSyntax() == PwmSettingSyntax.X509CERT ) @@ -134,7 +134,7 @@ List makeCertificateDebugData( final Configuration con } else { - storedValue = storedConfiguration.readSetting( pwmSetting ); + storedValue = storedConfiguration.readSetting( pwmSetting, null ); } final X509Certificate[] arrayCerts = ( X509Certificate[] ) storedValue.toNativeObject(); final List certificates = arrayCerts == null ? Collections.emptyList() : Arrays.asList( arrayCerts ); @@ -149,7 +149,7 @@ else if ( pwmSetting.getSyntax() == PwmSettingSyntax.ACTION ) } else { - storedValue = storedConfiguration.readSetting( pwmSetting ); + storedValue = storedConfiguration.readSetting( pwmSetting, null ); } final List actionConfigurations = ( List ) storedValue.toNativeObject(); for ( final ActionConfiguration actionConfiguration : actionConfigurations ) @@ -206,7 +206,7 @@ CertificateDebugDataItem makeItem( { builder.detail( X509Utils.makeDetailText( certificate ) ); } - catch ( CertificateEncodingException e ) + catch ( final CertificateEncodingException e ) { LOGGER.error( "unexpected error parsing certificate detail text: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLocalDBServlet.java b/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLocalDBServlet.java index 791349d44..135c55288 100644 --- a/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLocalDBServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLocalDBServlet.java @@ -95,7 +95,7 @@ protected ConfigManagerAction readProcessAction( final PwmRequest request ) { return ConfigManagerAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { return null; } @@ -146,7 +146,7 @@ private void doExportLocalDB( final PwmRequest pwmRequest ) localDBUtility.exportLocalDB( bos, LOGGER.asAppendable( PwmLogLevel.DEBUG, pwmRequest.getSessionLabel() ) ); LOGGER.debug( pwmRequest, () -> "completed localDBExport process in " + TimeDuration.fromCurrent( startTime ).asCompactString() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "error downloading export localdb: " + e.getMessage() ); } @@ -196,7 +196,7 @@ void restUploadLocalDB( final PwmRequest pwmRequest ) LOGGER.asAppendable( PwmLogLevel.DEBUG, pwmRequest.getSessionLabel() ) ); LOGGER.info( pwmRequest, () -> "completed LocalDB import" ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = e instanceof PwmException ? ( ( PwmException ) e ).getErrorInformation() @@ -213,7 +213,7 @@ void restUploadLocalDB( final PwmRequest pwmRequest ) { localDB.close(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "error closing LocalDB after import process: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java b/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java index f42f5bb14..fa50b63c5 100644 --- a/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java @@ -32,7 +32,7 @@ import password.pwm.config.stored.ConfigurationProperty; import password.pwm.config.stored.ConfigurationReader; import password.pwm.config.stored.StoredConfiguration; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmUnrecoverableException; @@ -60,10 +60,12 @@ import java.io.IOException; import java.io.Serializable; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; @WebServlet( name = "ConfigManagerLogin", @@ -133,12 +135,12 @@ protected void processLoginRequest( final PwmRequest pwmRequest ) { final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); final ConfigurationReader runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader(); - final StoredConfigurationImpl storedConfig = runningConfigReader.getStoredConfiguration(); + final StoredConfiguration storedConfig = runningConfigReader.getStoredConfiguration(); final String password = pwmRequest.readParameterAsString( "password" ); if ( !StringUtil.isEmpty( password ) ) { - if ( storedConfig.verifyPassword( password, pwmRequest.getConfig() ) ) + if ( StoredConfigurationUtil.verifyPassword( storedConfig, password ) ) { LOGGER.trace( pwmRequest, () -> "valid configuration password accepted" ); updateLoginHistory( pwmRequest, pwmRequest.getUserInfoIfLoggedIn(), true ); @@ -168,7 +170,7 @@ protected ConfigManagerLoginAction readProcessAction( final PwmRequest request ) { return ConfigManagerLoginAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { return null; } @@ -300,11 +302,9 @@ public static void writePersistentLoginCookie( final PwmRequest pwmRequest ) if ( persistentSeconds > 0 ) { - final TimeDuration persistenceDuration = TimeDuration.of( persistentSeconds, TimeDuration.Unit.SECONDS ); - final Instant expirationDate = persistenceDuration.incrementFromInstant( Instant.now() ); - final StoredConfigurationImpl storedConfig = pwmRequest.getConfig().getStoredConfiguration(); + final StoredConfiguration storedConfig = pwmRequest.getConfig().getStoredConfiguration(); final String persistentLoginValue = makePersistentLoginPassword( pwmRequest, storedConfig ); - final PersistentLoginInfo persistentLoginInfo = new PersistentLoginInfo( expirationDate, persistentLoginValue ); + final PersistentLoginInfo persistentLoginInfo = new PersistentLoginInfo( Instant.now(), persistentLoginValue ); final String cookieValue = pwmRequest.getPwmApplication().getSecureService().encryptObjectToString( persistentLoginInfo ); pwmRequest.getPwmResponse().writeCookie( COOKIE_NAME, @@ -312,10 +312,7 @@ public static void writePersistentLoginCookie( final PwmRequest pwmRequest ) persistentSeconds, COOKIE_PATH ); - LOGGER.debug( pwmRequest, () -> "set persistent config login cookie (expires " - + JavaHelper.toIsoDate( expirationDate ) - + ")" - ); + LOGGER.debug( pwmRequest, () -> "issued persistent config login cookie" ); } } @@ -330,7 +327,7 @@ private static void checkPersistentLoginCookie( } final ConfigurationReader runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader(); - final StoredConfigurationImpl storedConfig = runningConfigReader.getStoredConfiguration(); + final StoredConfiguration storedConfig = runningConfigReader.getStoredConfiguration(); try { @@ -338,29 +335,45 @@ private static void checkPersistentLoginCookie( if ( !StringUtil.isEmpty( cookieValue ) ) { final PersistentLoginInfo persistentLoginInfo = pwmRequest.getPwmApplication().getSecureService().decryptObject( cookieValue, PersistentLoginInfo.class ); - if ( persistentLoginInfo != null ) + if ( persistentLoginInfo != null && persistentLoginInfo.getIssueTimestamp() != null ) { - if ( persistentLoginInfo.getExpireDate().isAfter( Instant.now() ) ) + final int maxLoginSeconds = figureMaxLoginSeconds( pwmRequest ); + final TimeDuration cookieAge = TimeDuration.fromCurrent( persistentLoginInfo.getIssueTimestamp() ); + + if ( cookieAge.isShorterThan( TimeDuration.of( maxLoginSeconds, TimeDuration.Unit.SECONDS ) ) ) { final String persistentLoginPassword = makePersistentLoginPassword( pwmRequest, storedConfig ); if ( StringUtil.nullSafeEquals( persistentLoginPassword, persistentLoginInfo.getPassword() ) ) { - LOGGER.debug( pwmRequest, () -> "accepting persistent config login from cookie (expires " - + JavaHelper.toIsoDate( persistentLoginInfo.getExpireDate() ) + final Instant expireTime = Instant.now().plus( maxLoginSeconds, ChronoUnit.SECONDS ); + LOGGER.debug( pwmRequest, () -> "accepting persistent config login from cookie (expires at " + + expireTime.toString() + ")" ); final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigManagerBean.class ); configManagerBean.setPasswordVerified( true ); } + else + { + LOGGER.debug( pwmRequest, () -> "discarding persistent login cookie with incorrect password value" ); + pwmRequest.getPwmResponse().removeCookie( COOKIE_NAME, COOKIE_PATH ); + } } - + else + { + LOGGER.debug( pwmRequest, () -> "removing expired (" + cookieAge.asCompactString() + ") persistent config login cookie" ); + pwmRequest.getPwmResponse().removeCookie( COOKIE_NAME, COOKIE_PATH ); + } + } + else + { + LOGGER.debug( pwmRequest, () -> "discarding invalid persistent login cookie " ); pwmRequest.getPwmResponse().removeCookie( COOKIE_NAME, COOKIE_PATH ); - LOGGER.debug( pwmRequest, () -> "removing non-working persistent config login cookie" ); } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "error examining persistent config login cookie: " + e.getMessage() ); } @@ -370,8 +383,8 @@ private static void checkPersistentLoginCookie( @Value private static class PersistentLoginInfo implements Serializable { - @SerializedName( "e" ) - private Instant expireDate; + @SerializedName( "i" ) + private Instant issueTimestamp; @SerializedName( "p" ) private String password; @@ -393,25 +406,42 @@ private static String makePersistentLoginPassword( throws PwmUnrecoverableException { final int hashChars = 32; - String hashValue = storedConfig.readConfigProperty( ConfigurationProperty.PASSWORD_HASH ); - if ( PwmApplicationMode.RUNNING == pwmRequest.getPwmApplication().getApplicationMode() ) + if ( !persistentLoginEnabled( pwmRequest ) ) { - final PwmSession pwmSession = pwmRequest.getPwmSession(); - hashValue += pwmSession.getUserInfo().getUserIdentity().toDelimitedKey(); + throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "persistent login not enabled" ); } + final PwmSession pwmSession = pwmRequest.getPwmSession(); + final String configPasswordHash = storedConfig.readConfigProperty( ConfigurationProperty.PASSWORD_HASH ) + .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "missing config password" ) ); + + final String hashValue = configPasswordHash + pwmSession.getUserInfo().getUserIdentity().toDelimitedKey(); + return StringUtil.truncate( SecureEngine.hash( hashValue, PwmHashAlgorithm.SHA512 ), hashChars ); } private static boolean persistentLoginEnabled( final PwmRequest pwmRequest - ) + ) throws PwmUnrecoverableException { + if ( PwmApplicationMode.RUNNING != pwmRequest.getPwmApplication().getApplicationMode() ) + { + LOGGER.debug( pwmRequest, () -> "app not in running mode, persistent login not possible." ); + return false; + } + if ( pwmRequest.getConfig().isDefaultValue( PwmSetting.PWM_SECURITY_KEY ) ) { - LOGGER.debug( pwmRequest, () -> "security not available, persistent login not possible." ); + LOGGER.debug( pwmRequest, () -> "security key not available, persistent login not possible." ); + return false; + } + + final Optional configPasswordHash = pwmRequest.getConfig().getStoredConfiguration().readConfigProperty( ConfigurationProperty.PASSWORD_HASH ); + if ( !configPasswordHash.isPresent() ) + { + LOGGER.debug( pwmRequest, () -> "config password is not present, persistent login not possible." ); return false; } diff --git a/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java b/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java index 4174df503..254b86fd3 100644 --- a/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java @@ -29,7 +29,10 @@ import password.pwm.PwmConstants; import password.pwm.config.stored.ConfigurationProperty; import password.pwm.config.stored.ConfigurationReader; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationFactory; +import password.pwm.config.stored.StoredConfigurationModifier; +import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmException; @@ -52,10 +55,6 @@ import password.pwm.i18n.Admin; import password.pwm.i18n.Config; import password.pwm.i18n.Display; -import password.pwm.svc.PwmService; -import password.pwm.svc.event.AuditEvent; -import password.pwm.svc.event.AuditRecord; -import password.pwm.svc.event.AuditRecordFactory; import password.pwm.util.LDAPPermissionCalculator; import password.pwm.util.i18n.LocaleHelper; import password.pwm.util.java.JavaHelper; @@ -72,6 +71,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.zip.ZipOutputStream; @WebServlet( @@ -117,7 +117,7 @@ protected ConfigManagerAction readProcessAction( final PwmRequest request ) { return ConfigManagerAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { return null; } @@ -205,7 +205,7 @@ void initRequestAttributes( final PwmRequest pwmRequest ) pwmRequest.setAttribute( PwmRequestAttribute.ConfigHasPassword, LocaleHelper.booleanString( - configurationReader.getStoredConfiguration().hasPassword(), + StoredConfigurationUtil.hasPassword( configurationReader.getStoredConfiguration() ), pwmRequest.getLocale(), pwmRequest.getConfig() ) @@ -251,8 +251,8 @@ private void restLockConfiguration( final PwmRequest pwmRequest ) try { - final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration( pwmRequest ); - if ( !storedConfiguration.hasPassword() ) + final StoredConfiguration storedConfiguration = readCurrentConfiguration( pwmRequest ); + if ( !StoredConfigurationUtil.hasPassword( storedConfiguration ) ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { @@ -265,12 +265,13 @@ private void restLockConfiguration( final PwmRequest pwmRequest ) return; } - storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, "false" ); - saveConfiguration( pwmRequest, storedConfiguration ); + final StoredConfigurationModifier modifiedConfig = StoredConfigurationModifier.newModifier( storedConfiguration ); + modifiedConfig.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, "false" ); + saveConfiguration( pwmRequest, modifiedConfig.newStoredConfiguration() ); final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigManagerBean.class ); - configManagerBean.setConfiguration( null ); + configManagerBean.setStoredConfiguration( null ); } - catch ( PwmException e ) + catch ( final PwmException e ) { final ErrorInformation errorInfo = e.getErrorInformation(); final RestResultBean restResultBean = RestResultBean.fromError( errorInfo, pwmRequest ); @@ -278,7 +279,7 @@ private void restLockConfiguration( final PwmRequest pwmRequest ) pwmRequest.outputJsonResult( restResultBean ); return; } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ); final RestResultBean restResultBean = RestResultBean.fromError( errorInfo, pwmRequest ); @@ -293,12 +294,12 @@ private void restLockConfiguration( final PwmRequest pwmRequest ) public static void saveConfiguration( final PwmRequest pwmRequest, - final StoredConfigurationImpl storedConfiguration + final StoredConfiguration storedConfiguration ) throws PwmUnrecoverableException { { - final List errorStrings = storedConfiguration.validateValues(); + final List errorStrings = StoredConfigurationUtil.validateValues( storedConfiguration ); if ( errorStrings != null && !errorStrings.isEmpty() ) { final String errorString = errorStrings.get( 0 ); @@ -319,30 +320,17 @@ public static void saveConfiguration( pwmRequest.getSessionLabel() ); - final PwmApplication pwmApplication = pwmRequest.getPwmApplication(); - if ( pwmApplication.getAuditManager() != null && pwmApplication.getAuditManager().status() == PwmService.STATUS.OPEN ) - { - final String modifyMessage = "Configuration Changes: " + storedConfiguration.changeLogAsDebugString( PwmConstants.DEFAULT_LOCALE, false ); - final AuditRecord auditRecord = new AuditRecordFactory( pwmApplication ).createUserAuditRecord( - AuditEvent.MODIFY_CONFIGURATION, - pwmRequest.getUserInfoIfLoggedIn(), - pwmRequest.getSessionLabel(), - modifyMessage - ); - pwmApplication.getAuditManager().submit( auditRecord ); - } - contextManager.requestPwmApplicationRestart(); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorString = "error saving file: " + e.getMessage(); - LOGGER.error( pwmRequest, errorString ); + LOGGER.error( pwmRequest, errorString, e ); throw new PwmUnrecoverableException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { errorString, } - ) ); + ), e ); } } @@ -361,14 +349,14 @@ private void doDownloadConfig( final PwmRequest pwmRequest ) try { - final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration( pwmRequest ); + final StoredConfiguration storedConfiguration = readCurrentConfiguration( pwmRequest ); final OutputStream responseWriter = resp.getOutputStream(); resp.setHeader( HttpHeader.ContentDisposition, "attachment;filename=" + PwmConstants.DEFAULT_CONFIG_FILE_FILENAME ); resp.setContentType( HttpContentType.xml ); - storedConfiguration.toXml( responseWriter ); + StoredConfigurationFactory.toXml( storedConfiguration, responseWriter ); responseWriter.close(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmSession, "unable to download configuration: " + e.getMessage() ); } @@ -385,35 +373,32 @@ private void doGenerateSupportZip( final PwmRequest pwmRequest ) { debugItemGenerator.outputZipDebugFile( zipOutput ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "error during zip debug building: " + e.getMessage() ); } } - public static StoredConfigurationImpl readCurrentConfiguration( final PwmRequest pwmRequest ) + public static StoredConfiguration readCurrentConfiguration( final PwmRequest pwmRequest ) throws PwmUnrecoverableException { - final ContextManager contextManager = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ); - final ConfigurationReader runningConfigReader = contextManager.getConfigReader(); - final StoredConfigurationImpl runningConfig = runningConfigReader.getStoredConfiguration(); - return StoredConfigurationImpl.copy( runningConfig ); + return pwmRequest.getConfig().getStoredConfiguration(); } private void showSummary( final PwmRequest pwmRequest ) throws IOException, ServletException, PwmUnrecoverableException { - final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration( pwmRequest ); - final LinkedHashMap outputMap = new LinkedHashMap<>( storedConfiguration.toOutputMap( pwmRequest.getLocale() ) ); - pwmRequest.setAttribute( PwmRequestAttribute.ConfigurationSummaryOutput, outputMap ); + final StoredConfiguration storedConfiguration = readCurrentConfiguration( pwmRequest ); + final Map outputMap = StoredConfigurationUtil.makeDebugMap( storedConfiguration, storedConfiguration.modifiedItems(), pwmRequest.getLocale() ); + pwmRequest.setAttribute( PwmRequestAttribute.ConfigurationSummaryOutput, new LinkedHashMap<>( outputMap ) ); pwmRequest.forwardToJsp( JspUrl.CONFIG_MANAGER_EDITOR_SUMMARY ); } private void showPermissions( final PwmRequest pwmRequest ) throws IOException, ServletException, PwmUnrecoverableException { - final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration( pwmRequest ); + final StoredConfiguration storedConfiguration = readCurrentConfiguration( pwmRequest ); final LDAPPermissionCalculator ldapPermissionCalculator = new LDAPPermissionCalculator( storedConfiguration ); pwmRequest.setAttribute( PwmRequestAttribute.LdapPermissionItems, ldapPermissionCalculator ); pwmRequest.forwardToJsp( JspUrl.CONFIG_MANAGER_PERMISSIONS ); @@ -434,7 +419,7 @@ private void downloadPermissionReportCsv( try { - final StoredConfigurationImpl storedConfiguration = readCurrentConfiguration( pwmRequest ); + final StoredConfiguration storedConfiguration = readCurrentConfiguration( pwmRequest ); final LDAPPermissionCalculator ldapPermissionCalculator = new LDAPPermissionCalculator( storedConfiguration ); for ( final LDAPPermissionCalculator.PermissionRecord permissionRecord : ldapPermissionCalculator.getPermissionRecords() ) @@ -451,7 +436,7 @@ private void downloadPermissionReportCsv( } } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ); LOGGER.error( pwmRequest, errorInformation ); diff --git a/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerWordlistServlet.java b/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerWordlistServlet.java index 5761cf714..a170c5730 100644 --- a/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerWordlistServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerWordlistServlet.java @@ -94,7 +94,7 @@ protected ConfigManagerAction readProcessAction( final PwmRequest request ) { return ConfigManagerAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { return null; } @@ -162,7 +162,7 @@ void restUploadWordlist( final PwmRequest pwmRequest ) { wordlistType.forType( pwmApplication ).populate( inputStream ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ); final RestResultBean restResultBean = RestResultBean.fromError( errorInfo, pwmRequest ); @@ -192,7 +192,7 @@ void restClearWordlist( final PwmRequest pwmRequest ) { wordlistType.forType( pwmRequest.getPwmApplication() ).clear(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error clearing wordlist " + wordlistType + ", error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java b/server/src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java index f593546b8..508f446a9 100644 --- a/server/src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java +++ b/server/src/main/java/password/pwm/http/servlet/configmanager/DebugItemGenerator.java @@ -31,7 +31,11 @@ import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; import password.pwm.config.Configuration; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.StoredValue; +import password.pwm.config.stored.StoredConfigItemKey; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationFactory; +import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.error.PwmUnrecoverableException; import password.pwm.health.HealthRecord; import password.pwm.http.ContextManager; @@ -46,6 +50,7 @@ import password.pwm.svc.stats.Statistic; import password.pwm.svc.stats.StatisticsManager; import password.pwm.util.LDAPPermissionCalculator; +import password.pwm.util.java.ClosableIterator; import password.pwm.util.java.DebugOutputBuilder; import password.pwm.util.java.FileSystemUtility; import password.pwm.util.java.JavaHelper; @@ -128,9 +133,8 @@ public DebugItemGenerator( final PwmApplication pwmApplication, final SessionLab this.pwmApplication = pwmApplication; this.sessionLabel = sessionLabel; - final StoredConfigurationImpl storedConfiguration = StoredConfigurationImpl.copy( pwmApplication.getConfig().getStoredConfiguration() ); - storedConfiguration.resetAllPasswordValues( "value removed from " + getFilenameBase() + " configuration export" ); - this.obfuscatedConfiguration = new Configuration( storedConfiguration ); + final StoredConfiguration obfuscatedStoredConfig = StoredConfigurationUtil.copyConfigAndBlankAllPasswords( pwmApplication.getConfig().getStoredConfiguration() ); + this.obfuscatedConfiguration = new Configuration( obfuscatedStoredConfig ); } private String getFilenameBase() @@ -169,7 +173,7 @@ public void outputZipDebugFile( final ZipOutputStream zipOutput ) LOGGER.trace( sessionLabel, () -> finishMsg ); debugGeneratorLogFile.appendLine( finishMsg ); } - catch ( Throwable e ) + catch ( final Throwable e ) { final String errorMsg = "unexpected error executing debug item output class '" + serviceClass.getName() + "', error: " + e.toString(); LOGGER.error( sessionLabel, errorMsg, e ); @@ -192,7 +196,7 @@ public void outputZipDebugFile( final ZipOutputStream zipOutput ) zipOutput.write( debugGeneratorLogFile.toString().getBytes( PwmConstants.DEFAULT_CHARSET ) ); zipOutput.closeEntry(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error generating " + debugFileName + ": " + e.getMessage() ); } @@ -211,8 +215,20 @@ public String getFilename( ) @Override public void outputItem( final DebugItemInput debugItemInput, final OutputStream outputStream ) throws Exception { - final StoredConfigurationImpl storedConfiguration = debugItemInput.getObfuscatedConfiguration().getStoredConfiguration(); - final String jsonOutput = JsonUtil.serialize( storedConfiguration.toJsonDebugObject(), JsonUtil.Flag.PrettyPrint ); + final StoredConfiguration storedConfiguration = debugItemInput.getObfuscatedConfiguration().getStoredConfiguration(); + final TreeMap outputObject = new TreeMap<>(); + + for ( final StoredConfigItemKey storedConfigItemKey : storedConfiguration.modifiedItems() ) + { + if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) + { + final String key = storedConfigItemKey.getLabel( PwmConstants.DEFAULT_LOCALE ); + final StoredValue value = storedConfiguration.readSetting( storedConfigItemKey.toPwmSetting(), storedConfigItemKey.getProfileID() ); + outputObject.put( key, value ); + } + } + + final String jsonOutput = JsonUtil.serializeMap( outputObject, JsonUtil.Flag.PrettyPrint ); outputStream.write( jsonOutput.getBytes( PwmConstants.DEFAULT_CHARSET ) ); } } @@ -228,7 +244,8 @@ public String getFilename( ) @Override public void outputItem( final DebugItemInput debugItemInput, final OutputStream outputStream ) throws Exception { - final StoredConfigurationImpl storedConfiguration = debugItemInput.getObfuscatedConfiguration().getStoredConfiguration(); + final Locale locale = PwmConstants.DEFAULT_LOCALE; + final StoredConfiguration storedConfiguration = debugItemInput.getObfuscatedConfiguration().getStoredConfiguration(); final StringWriter writer = new StringWriter(); writer.write( "Configuration Debug Output for " @@ -238,19 +255,21 @@ public void outputItem( final DebugItemInput debugItemInput, final OutputStream writer.write( "This file is " + PwmConstants.DEFAULT_CHARSET.displayName() + " encoded\n" ); writer.write( "\n" ); - final Map modifiedSettings = new TreeMap<>( - storedConfiguration.getModifiedSettingDebugValues( LOCALE, true ) - ); + final Set modifiedSettings = storedConfiguration.modifiedItems(); - for ( final Map.Entry entry : modifiedSettings.entrySet() ) + for ( final StoredConfigItemKey storedConfigItemKey : modifiedSettings ) { - final String key = entry.getKey(); - final String value = entry.getValue(); - writer.write( ">> Setting > " + key ); - writer.write( "\n" ); - writer.write( value ); - writer.write( "\n" ); - writer.write( "\n" ); + if ( storedConfigItemKey.getRecordType() == StoredConfigItemKey.RecordType.SETTING ) + { + final String key = storedConfigItemKey.toPwmSetting().toMenuLocationDebug( storedConfigItemKey.getProfileID(), locale ); + final String value = storedConfiguration.readSetting( storedConfigItemKey.toPwmSetting(), storedConfigItemKey.getProfileID() ).toDebugString( locale ); + writer.write( ">> Setting > " + key ); + writer.write( "\n" ); + writer.write( value ); + writer.write( "\n" ); + writer.write( "\n" ); + + } } outputStream.write( writer.toString().getBytes( PwmConstants.DEFAULT_CHARSET ) ); @@ -268,11 +287,14 @@ public String getFilename( ) @Override public void outputItem( final DebugItemInput debugItemInput, final OutputStream outputStream ) throws Exception { - final StoredConfigurationImpl storedConfiguration = debugItemInput.getObfuscatedConfiguration().getStoredConfiguration(); + final StoredConfiguration storedConfiguration = debugItemInput.getObfuscatedConfiguration().getStoredConfiguration(); // temporary output stream required because .toXml closes stream. final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - storedConfiguration.toXml( byteArrayOutputStream ); + final StoredConfigurationFactory.OutputSettings outputSettings = StoredConfigurationFactory.OutputSettings.builder() + .mode( StoredConfigurationFactory.OutputSettings.SecureOutputMode.STRIPPED ) + .build(); + StoredConfigurationFactory.toXml( storedConfiguration, byteArrayOutputStream, outputSettings ); outputStream.write( byteArrayOutputStream.toByteArray() ); } } @@ -447,9 +469,9 @@ public String getFilename( ) @Override public void outputItem( final DebugItemInput debugItemInput, final OutputStream outputStream ) throws Exception { - final List fileSummaryInformations = new ArrayList<>(); final PwmApplication pwmApplication = debugItemInput.getPwmApplication(); final File applicationPath = pwmApplication.getPwmEnvironment().getApplicationPath(); + final List interestedFiles = new ArrayList<>( ); if ( pwmApplication.getPwmEnvironment().getContextManager() != null ) { @@ -462,11 +484,11 @@ public void outputItem( final DebugItemInput debugItemInput, final OutputStream if ( servletRootPath != null ) { - fileSummaryInformations.addAll( FileSystemUtility.readFileInformation( webInfPath ) ); + interestedFiles.add( webInfPath ); } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( debugItemInput.getSessionLabel(), "unable to generate webInfPath fileMd5sums during zip debug building: " + e.getMessage() ); } @@ -476,14 +498,16 @@ public void outputItem( final DebugItemInput debugItemInput, final OutputStream { try { - fileSummaryInformations.addAll( FileSystemUtility.readFileInformation( applicationPath ) ); + interestedFiles.add( applicationPath ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( debugItemInput.getSessionLabel(), "unable to generate appPath fileMd5sums during zip debug building: " + e.getMessage() ); } } + + try ( ClosableIterator iter = FileSystemUtility.readFileInformation( interestedFiles ); ) { final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter( outputStream ); { @@ -495,8 +519,10 @@ public void outputItem( final DebugItemInput debugItemInput, final OutputStream headerRow.add( "Checksum" ); csvPrinter.printComment( StringUtil.join( headerRow, "," ) ); } - for ( final FileSystemUtility.FileSummaryInformation fileSummaryInformation : fileSummaryInformations ) + + while ( iter.hasNext() ) { + final FileSystemUtility.FileSummaryInformation fileSummaryInformation = iter.next(); try { final List dataRow = new ArrayList<>(); @@ -507,7 +533,7 @@ public void outputItem( final DebugItemInput debugItemInput, final OutputStream dataRow.add( Long.toString( fileSummaryInformation.getChecksum() ) ); csvPrinter.printRecord( dataRow ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.trace( () -> "error generating file summary info: " + e.getMessage() ); } @@ -571,7 +597,7 @@ public String getFilename( ) public void outputItem( final DebugItemInput debugItemInput, final OutputStream outputStream ) throws Exception { - final StoredConfigurationImpl storedConfiguration = debugItemInput.getObfuscatedConfiguration().getStoredConfiguration(); + final StoredConfiguration storedConfiguration = debugItemInput.getObfuscatedConfiguration().getStoredConfiguration(); final LDAPPermissionCalculator ldapPermissionCalculator = new LDAPPermissionCalculator( storedConfiguration ); final CSVPrinter csvPrinter = JavaHelper.makeCsvPrinter( outputStream ); @@ -782,7 +808,7 @@ public void outputItem( final DebugItemInput debugItemInput, final OutputStream dataRow.add( sValue ); csvPrinter.printRecord( dataRow ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.trace( () -> "error generating csv-stats summary info: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java index 138deac58..a363de1b2 100644 --- a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java @@ -352,7 +352,7 @@ private ProcessStatus processVerificationChoice( final PwmRequest pwmRequest ) { requestedChoice = IdentityVerificationMethod.valueOf( requestedChoiceStr ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { final String errorMsg = "unknown verification method requested"; final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, errorMsg ); @@ -467,7 +467,7 @@ private ProcessStatus processSearch( final PwmRequest pwmRequest ) return ProcessStatus.Continue; } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { if ( e.getError() != PwmError.ERROR_CANT_MATCH_USER || !bogusUserModeEnabled ) { @@ -534,12 +534,12 @@ private ProcessStatus processEnterCode( final PwmRequest pwmRequest ) return ProcessStatus.Halt; } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.debug( pwmRequest, () -> "error while checking entered token: " ); errorInformation = e.getErrorInformation(); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorMsg = "token incorrect: " + e.getMessage(); errorInformation = new ErrorInformation( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ); @@ -643,7 +643,7 @@ private ProcessStatus processEnterOtpToken( final PwmRequest pwmRequest ) handleUserVerificationBadAttempt( pwmRequest, forgottenPasswordBean, new ErrorInformation( PwmError.ERROR_INCORRECT_OTP_TOKEN ) ); } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { handleUserVerificationBadAttempt( pwmRequest, forgottenPasswordBean, new ErrorInformation( PwmError.ERROR_INCORRECT_OTP_TOKEN, @@ -695,7 +695,7 @@ private ProcessStatus processOAuthReturn( final PwmRequest pwmRequest ) { oauthUserIdentity = userSearchEngine.resolveUsername( userDNfromOAuth, null, null, pwmRequest.getSessionLabel() ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorMsg = "unexpected error searching for oauth supplied username in ldap; error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, errorMsg ); @@ -756,7 +756,7 @@ private ProcessStatus processCheckResponses( final PwmRequest pwmRequest ) { responsesPassed = responseSet.test( crMap ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { if ( e.getCause() instanceof PwmUnrecoverableException ) { @@ -783,7 +783,7 @@ private ProcessStatus processCheckResponses( final PwmRequest pwmRequest ) return ProcessStatus.Continue; } } - catch ( ChaiValidationException e ) + catch ( final ChaiValidationException e ) { LOGGER.debug( pwmRequest, () -> "chai validation error checking user responses: " + e.getMessage() ); final ErrorInformation errorInformation = new ErrorInformation( PwmError.forChaiError( e.getErrorCode() ) ); @@ -911,7 +911,7 @@ private ProcessStatus processCheckAttributes( final PwmRequest pwmRequest ) ) ); } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.error( pwmRequest, "error during param validation of '" + attrName + "', error: " + e.getMessage() ); throw new PwmDataValidationException( new ErrorInformation( PwmError.ERROR_INCORRECT_RESPONSE, "ldap error testing value for '" + attrName + "'", new String[] @@ -924,7 +924,7 @@ private ProcessStatus processCheckAttributes( final PwmRequest pwmRequest ) forgottenPasswordBean.getProgress().getSatisfiedMethods().add( IdentityVerificationMethod.ATTRIBUTES ); } - catch ( PwmDataValidationException e ) + catch ( final PwmDataValidationException e ) { handleUserVerificationBadAttempt( pwmRequest, forgottenPasswordBean, new ErrorInformation( PwmError.ERROR_INCORRECT_RESPONSE, e.getErrorInformation().toDebugStr() ) ); } @@ -1158,7 +1158,7 @@ private void executeUnlock( final PwmRequest pwmRequest ) pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_UnlockAccount ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "unable to unlock user " + userIdentity + " error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNLOCK_FAILURE, errorMsg ); @@ -1193,7 +1193,7 @@ private void executeResetPassword( final PwmRequest pwmRequest ) theUser.unlockPassword(); LOGGER.trace( pwmSession, () -> "unlock account succeeded" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "unable to unlock user " + theUser.getEntryDN() + " error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNLOCK_FAILURE, errorMsg ); @@ -1222,7 +1222,7 @@ private void executeResetPassword( final PwmRequest pwmRequest ) // redirect user to change password screen. pwmRequest.sendRedirect( PwmServletDefinition.PublicChangePassword.servletUrlName() ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( pwmSession, "unexpected error authenticating during forgotten password recovery process user: " + e.getMessage() ); @@ -1292,7 +1292,7 @@ private void checkForLocaleSwitch( final PwmRequest pwmRequest, final ForgottenP forgottenPasswordBean ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { clearForgottenPasswordBean( pwmRequest ); final ErrorInformation errorInformation = new ErrorInformation( diff --git a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java index 416ca6f6c..47515e1db 100644 --- a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java +++ b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java @@ -286,11 +286,11 @@ public void applyForm( final ForgottenPasswordStateMachine forgottenPasswordStat password1 ); } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -498,7 +498,7 @@ public void applyForm( final ForgottenPasswordStateMachine forgottenPasswordStat true ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { errorInformation = new ErrorInformation( PwmError.ERROR_INCORRECT_OTP_TOKEN, e.getErrorInformation().toDebugStr() ); } @@ -600,7 +600,7 @@ public void applyForm( final ForgottenPasswordStateMachine forgottenPasswordStat return; } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.debug( commonValues.getSessionLabel(), () -> "error while checking entered token: " ); errorInformation = e.getErrorInformation(); @@ -677,7 +677,7 @@ public void applyForm( final ForgottenPasswordStateMachine forgottenPasswordStat { responsesPassed = responseSet.test( crMap ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -797,7 +797,7 @@ public void applyForm( final ForgottenPasswordStateMachine forgottenPasswordStat ) ); } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.error( sessionLabel, "error during param validation of '" + attrName + "', error: " + e.getMessage() ); throw new PwmDataValidationException( new ErrorInformation( @@ -807,7 +807,7 @@ public void applyForm( final ForgottenPasswordStateMachine forgottenPasswordStat } ) ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -815,7 +815,7 @@ public void applyForm( final ForgottenPasswordStateMachine forgottenPasswordStat forgottenPasswordBean.getProgress().getSatisfiedMethods().add( IdentityVerificationMethod.ATTRIBUTES ); } - catch ( PwmDataValidationException e ) + catch ( final PwmDataValidationException e ) { handleUserVerificationBadAttempt( forgottenPasswordStateMachine.getCommonValues(), @@ -1006,7 +1006,7 @@ public void applyForm( final ForgottenPasswordStateMachine forgottenPasswordStat return; } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { if ( e.getError() != PwmError.ERROR_CANT_MATCH_USER || !bogusUserModeEnabled ) { diff --git a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java index ce4060fbb..fcf597e80 100644 --- a/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java +++ b/server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java @@ -105,7 +105,7 @@ static Set figureRemainingAvailableOptionalAuthMetho { verifyRequirementsForAuthMethod( commonValues, forgottenPasswordBean, recoveryVerificationMethods ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { result.remove( recoveryVerificationMethods ); } @@ -176,7 +176,7 @@ static ResponseSet readResponseSet( theUser ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -243,7 +243,7 @@ static boolean checkAuthRecord( final PwmRequest pwmRequest, final String userGu } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "unexpected error while examining cookie auth record: " + e.getMessage() ); } @@ -336,7 +336,7 @@ static void verifyRequirementsForAuthMethod( } } } - catch ( ChaiValidationException e ) + catch ( final ChaiValidationException e ) { final String errorMsg = "stored response set for user '" + userInfo.getUserIdentity() + "' do not meet current challenge set requirements: " + e.getLocalizedMessage(); @@ -440,7 +440,7 @@ static void doActionSendNewPassword( final PwmRequest pwmRequest ) theUser.unlockPassword(); LOGGER.trace( pwmRequest, () -> "unlock account succeeded" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "unable to unlock user " + theUser.getEntryDN() + " error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNLOCK_FAILURE, errorMsg ); @@ -478,7 +478,7 @@ static void doActionSendNewPassword( final PwmRequest pwmRequest ) LOGGER.trace( pwmRequest, () -> "set user " + userIdentity.toDisplayString() + " password to system generated random value" ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -512,12 +512,12 @@ static void doActionSendNewPassword( final PwmRequest pwmRequest ) pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_PasswordSend, toAddress ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.warn( pwmRequest, "unexpected error setting new password during recovery process for user: " + e.getMessage() ); pwmRequest.respondWithError( e.getErrorInformation() ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, @@ -558,7 +558,7 @@ static void initBogusForgottenPasswordBean( final CommonValues commonValues, fin final List formData = new ArrayList<>( ); { int counter = 0; - for ( Challenge challenge: challengeList ) + for ( final Challenge challenge: challengeList ) { final FormConfiguration formConfiguration = FormConfiguration.builder() .name( "challenge" + counter++ ) @@ -605,7 +605,7 @@ public static boolean permitPwChangeDuringMinLifetime( userIdentity ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.debug( sessionLabel, () -> "can't read user's forgotten password profile - assuming no profile assigned, error: " + e.getMessage() ); } @@ -704,13 +704,13 @@ static void initForgottenPasswordBean( ); challengeSet = responseSet == null ? null : responseSet.getPresentableChallengeSet(); } - catch ( ChaiValidationException e ) + catch ( final ChaiValidationException e ) { final String errorMsg = "unable to determine presentable challengeSet for stored responses: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NO_CHALLENGES, errorMsg ); throw new PwmUnrecoverableException( errorInformation ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) ); } @@ -731,14 +731,14 @@ static void initForgottenPasswordBean( throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTRUDER_LDAP ) ); } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "error checking user '" + userInfo.getUserIdentity() + "' ldap intruder lock status: " + e.getMessage() ); LOGGER.error( sessionLabel, errorInformation ); throw new PwmUnrecoverableException( errorInformation ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) ); } @@ -795,7 +795,7 @@ static List figureAttributeForm( LOGGER.trace( commonValues.getSessionLabel(), () -> "excluding optional required attribute(" + formItem.getName() + "), user has no value" ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_NO_CHALLENGES, "unexpected error reading value for attribute " + formItem.getName() ) ); } diff --git a/server/src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java b/server/src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java index 1cbb4bcd7..fce568148 100644 --- a/server/src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java +++ b/server/src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java @@ -164,12 +164,12 @@ private void sendRemoteRequest( final Map userResponses ) throws final String responseBodyStr = response.getBody(); this.lastResponse = JsonUtil.deserialize( responseBodyStr, RemoteVerificationResponseBean.class ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( sessionLabel, e.getErrorInformation() ); throw new PwmUnrecoverableException( e.getErrorInformation() ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error reading remote responses web service response: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg ); diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java index f63bdf7c1..4b72f94c7 100644 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java @@ -138,7 +138,7 @@ static HelpdeskDetailInfoBean makeHelpdeskDetailInfo( pwmRequest.getLocale() ); builder.userHistory( userHistory ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "unexpected error reading userHistory for user '" + userIdentity + "', " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java index cde07e43a..89262f5d4 100644 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java @@ -299,7 +299,7 @@ private ProcessStatus processExecuteActionRequest( pwmRequest.outputJsonResult( restResultBean ); return ProcessStatus.Halt; } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( pwmRequest, e.getErrorInformation().toDebugStr() ); final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest ); @@ -344,7 +344,7 @@ private ProcessStatus restDeleteUserRequest( pwmRequest.getLocale() ); userID = deletedUserInfo.getUsername(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.warn( pwmSession, "unable to read username of deleted user while creating audit record" ); } @@ -359,7 +359,7 @@ private ProcessStatus restDeleteUserRequest( { provider.deleteEntry( userIdentity.getUserDN() ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error while attempting to delete user " + userIdentity.toString() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -442,7 +442,7 @@ private ProcessStatus restSearchRequest( { searchResultsBean = searchImpl( pwmRequest, helpdeskProfile, searchRequest ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } @@ -617,7 +617,7 @@ private ProcessStatus restUnlockIntruder( pwmRequest.getPwmApplication().getAuditManager().submit( auditRecord ); } } - catch ( ChaiPasswordPolicyException e ) + catch ( final ChaiPasswordPolicyException e ) { final ChaiError passwordError = e.getErrorCode(); final PwmError pwmError = PwmError.forChaiError( passwordError ); @@ -625,7 +625,7 @@ private ProcessStatus restUnlockIntruder( LOGGER.trace( pwmRequest, () -> "ChaiPasswordPolicyException was thrown while resetting password: " + e.toString() ); return ProcessStatus.Halt; } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final PwmError returnMsg = PwmError.forChaiError( e.getErrorCode() ) == null ? PwmError.ERROR_INTERNAL : PwmError.forChaiError( e.getErrorCode() ); final ErrorInformation error = new ErrorInformation( returnMsg, e.getMessage() ); @@ -717,7 +717,7 @@ private ProcessStatus restValidateOtpCodeRequest( return outputVerificationResponseBean( pwmRequest, passed, verificationStateBean ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); } @@ -800,7 +800,7 @@ private ProcessStatus restSendVerificationTokenRequest( .build() ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( pwmRequest, e.getErrorInformation() ); pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); @@ -948,7 +948,7 @@ private ProcessStatus restClearOtpSecret( pwmRequest.getPwmApplication().getAuditManager().submit( auditRecord ); } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final PwmError returnMsg = e.getError(); final ErrorInformation error = new ErrorInformation( returnMsg, e.getMessage() ); @@ -1013,7 +1013,7 @@ private ProcessStatus restShowVerifications( final PwmRequest pwmRequest ) { results.put( "records", state.asViewableValidationRecords( pwmRequest.getPwmApplication(), pwmRequest.getLocale() ) ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -1062,7 +1062,7 @@ private ProcessStatus restValidateAttributes( final PwmRequest pwmRequest ) successCount++; } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { LOGGER.error( pwmRequest, "error comparing ldap attribute during verification " + e.getMessage() ); } @@ -1138,7 +1138,7 @@ private ProcessStatus restClearResponsesHandler( final PwmRequest pwmRequest ) { userIdentity = readUserKeyRequestParameter( pwmRequest ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation() ) ); return ProcessStatus.Halt; @@ -1319,7 +1319,7 @@ private ProcessStatus processSetPasswordAction( final PwmRequest pwmRequest ) th newPassword ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( "error during set password REST operation: " + e.getMessage() ); pwmRequest.outputJsonResult( RestResultBean.fromError( e.getErrorInformation(), pwmRequest ) ); diff --git a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationOptionsBean.java b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationOptionsBean.java index 71ae42785..bb291c4af 100644 --- a/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationOptionsBean.java +++ b/server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskVerificationOptionsBean.java @@ -116,7 +116,7 @@ static HelpdeskVerificationOptionsBean makeBean( testSetting ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.trace( () -> "error while calculating available token methods: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java b/server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java index 751a8ca3a..59a0e912f 100644 --- a/server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java @@ -251,7 +251,7 @@ protected void nextStep( final PwmRequest pwmRequest ) { verifyForm( pwmRequest, newUserBean.getNewUserForm(), false ); } - catch ( PwmDataValidationException e ) + catch ( final PwmDataValidationException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } @@ -293,7 +293,7 @@ protected void nextStep( final PwmRequest pwmRequest ) newUserBean.setCreateStartTime( Instant.now() ); forwardToWait( pwmRequest, newUserProfile ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( pwmRequest, "error during user creation: " + e.getMessage() ); if ( newUserProfile.readSettingAsBoolean( PwmSetting.NEWUSER_DELETE_ON_FAIL ) ) @@ -383,7 +383,7 @@ private ProcessStatus restValidateForm( final RestResultBean restResultBean = RestResultBean.withData( jsonData ); pwmRequest.outputJsonResult( restResultBean ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final RestResultBean restResultBean = RestResultBean.fromError( e.getErrorInformation(), pwmRequest ); LOGGER.debug( pwmRequest, () -> "error while validating new user form: " + e.getMessage() ); @@ -475,7 +475,7 @@ private ProcessStatus handleEnterCodeRequest( final PwmRequest pwmRequest ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.debug( pwmRequest, () -> "error while checking entered token: " ); errorInformation = e.getErrorInformation(); @@ -503,7 +503,7 @@ private ProcessStatus handleEnterCodeRequest( final PwmRequest pwmRequest ) newUserBean.getCompletedTokenFields().addAll( newUserTokenData.getCompletedTokenFields() ); newUserBean.setCurrentTokenField( newUserTokenData.getCurrentTokenField() ); } - catch ( PwmUnrecoverableException | PwmOperationalException e ) + catch ( final PwmUnrecoverableException | PwmOperationalException e ) { LOGGER.error( pwmRequest, "while reading stored form data in token payload, form validation error occurred: " + e.getMessage() ); errorInformation = e.getErrorInformation(); @@ -519,7 +519,7 @@ else if ( tokenType == TokenDestinationItem.Type.sms ) } } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { errorInformation = e.getErrorInformation(); } @@ -597,7 +597,7 @@ private ProcessStatus handleProcessFormRequest( final PwmRequest pwmRequest ) newUserBean.setNewUserForm( newUserForm ); newUserBean.setFormPassed( true ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { setLastError( pwmRequest, e.getErrorInformation() ); forwardToFormPage( pwmRequest, newUserBean ); diff --git a/server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java b/server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java index 865e50b7a..b12d3ec83 100644 --- a/server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java +++ b/server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java @@ -174,7 +174,7 @@ static void createUser( NewUserUtils.LOGGER.info( pwmSession, () -> "created user entry: " + newUserDN ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String userMessage = "unexpected ldap error creating user entry: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, @@ -214,7 +214,7 @@ static void createUser( proxiedUser.setPassword( temporaryPassword.getStringValue() ); NewUserUtils.LOGGER.debug( pwmSession, () -> "set temporary password for new user entry: " + newUserDN ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String userMessage = "unexpected ldap error setting temporary password for new user entry: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, @@ -231,7 +231,7 @@ static void createUser( "setting userAccountControl attribute to enable account " + theUser.getEntryDN() ); theUser.writeStringAttribute( "userAccountControl", "512" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error enabling AD account when writing userAccountControl attribute: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, @@ -255,7 +255,7 @@ static void createUser( NewUserUtils.LOGGER.debug( pwmSession, () -> "changed to user requested password for new user entry: " + newUserDN ); bindAsProvider.close(); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String userMessage = "unexpected ldap error setting user password for new user entry: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, @@ -271,7 +271,7 @@ static void createUser( theUser.setPassword( userPassword.getStringValue() ); NewUserUtils.LOGGER.debug( pwmSession, () -> "set user requested password for new user entry: " + newUserDN ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String userMessage = "unexpected ldap error setting password for new user entry: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, @@ -286,7 +286,7 @@ static void createUser( { theUser.writeStringAttribute( "userAccountControl", "512" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error enabling AD account when writing userAccountControl attribute: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, @@ -349,7 +349,7 @@ static void deleteUserAccount( pwmRequest.getConfig().getDefaultLdapProfile().getProxyChaiProvider( pwmRequest.getPwmApplication() ).deleteEntry( userDN ); NewUserUtils.LOGGER.warn( pwmRequest, "ldap user account " + userDN + " has been deleted" ); } - catch ( ChaiUnavailableException | ChaiOperationException e ) + catch ( final ChaiUnavailableException | ChaiOperationException e ) { NewUserUtils.LOGGER.error( pwmRequest, "error deleting ldap user account " + userDN + ", " + e.getMessage() ); } @@ -447,7 +447,7 @@ private static boolean testIfEntryNameExists( searchConfiguration, 2, Collections.emptyList(), pwmRequest.getSessionLabel() ); return results != null && !results.isEmpty(); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String msg = "ldap error while searching for duplicate entry names: " + e.getMessage(); NewUserUtils.LOGGER.error( pwmRequest, msg ); diff --git a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java index b299527d6..bc530e9f1 100644 --- a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java @@ -187,7 +187,7 @@ protected void processAction( final PwmRequest pwmRequest ) JavaHelper.unhandledSwitchStatement( oAuthUseCaseCase ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "unexpected error redirecting user to oauth page: " + e.toString(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, errorMsg ); @@ -204,7 +204,7 @@ protected void processAction( final PwmRequest pwmRequest ) { resolveResults = oAuthMachine.makeOAuthResolveRequest( pwmRequest, requestCodeStr ); } - catch ( PwmException e ) + catch ( final PwmException e ) { final String errorMsg = "unexpected error communicating with oauth server: " + e.toString(); final ErrorInformation errorInformation = new ErrorInformation( e.getError(), errorMsg ); @@ -269,7 +269,7 @@ protected void processAction( final PwmRequest pwmRequest ) return; } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorMsg = "error while examining incoming oauth code for already authenticated session: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, errorMsg ); @@ -295,7 +295,7 @@ protected void processAction( final PwmRequest pwmRequest ) LOGGER.debug( pwmSession, () -> "oauth authentication completed, redirecting to originally requested URL: " + nextUrl ); pwmRequest.sendRedirect( nextUrl ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( pwmSession, "error during OAuth authentication attempt: " + e.getMessage() ); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, e.getMessage() ); diff --git a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java index af1c232f2..13f6a2737 100644 --- a/server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java +++ b/server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java @@ -144,7 +144,7 @@ public void redirectUserToOAuthServer( pwmRequest.getPwmSession().getSessionStateBean().setOauthInProgress( true ); LOGGER.debug( sessionLabel, () -> "redirecting user to oauth id server, url: " + redirectUrl ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "unexpected error redirecting user to oauth page: " + e.toString(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -302,7 +302,7 @@ private static PwmHttpClientResponse makeHttpRequest( final PwmHttpClient pwmHttpClient = pwmRequest.getPwmApplication().getHttpClientService().getPwmHttpClient( config ); pwmHttpClientResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest, pwmRequest.getSessionLabel() ); } - catch ( PwmException e ) + catch ( final PwmException e ) { final String errorMsg = "error during " + debugText + " http request to oauth server, remote error: " + e.getErrorInformation().toDebugStr(); throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_OAUTH_ERROR, errorMsg ) ); @@ -353,7 +353,7 @@ else if ( siteURL != null && !siteURL.trim().isEmpty() ) + pwmRequest.getContextPath() + PwmServletDefinition.OAuthConsumer.servletUrl(); } - catch ( URISyntaxException e ) + catch ( final URISyntaxException e ) { throw new IllegalStateException( "unable to parse inbound request uri while generating oauth redirect: " + e.getMessage() ); } @@ -400,7 +400,7 @@ public boolean checkOAuthExpiration( } } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( sessionLabel, "error while processing oauth token refresh: " + e.getMessage() ); } @@ -534,7 +534,7 @@ public String readAttributeFromBodyMap( } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( sessionLabel, () -> "unexpected error parsing json response: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java index ac6d9a2ec..d5396ffa0 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java @@ -300,7 +300,7 @@ private List makeUserDetailLinks( final UserIdentity actorIde { linkMap = JsonUtil.deserializeStringMap( userLinksStr ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( pwmRequest, "error de-serializing configured app property json for detail links: " + e.getMessage() ); return Collections.emptyList(); @@ -345,14 +345,14 @@ private List readUserMultiAttributeValues( } return Collections.unmodifiableList( returnObj ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, "error reading attribute value '" + attributeName + "', error:" + e.getMessage() ) ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } @@ -413,14 +413,14 @@ private List readUserDNAttributeValues( { ldapValues = chaiUser.readMultiStringAttribute( attributeName ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, "error reading attribute value '" + attributeName + "', error:" + e.getMessage() ) ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } @@ -729,7 +729,7 @@ private UserSearchResults doDetailLookup( false ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { LOGGER.error( "unexpected error during detail lookup of '" + userIdentity + "', error: " + e.getMessage() ); throw PwmUnrecoverableException.fromChaiException( e ); @@ -820,7 +820,7 @@ private SearchResultBean makeSearchResultsImpl( results = userSearchEngine.performMultiUserSearchFromForm( locale, searchConfiguration, maxResults, searchForm, pwmRequest.getSessionLabel() ); sizeExceeded = results.isSizeExceeded(); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final ErrorInformation errorInformation = e.getErrorInformation(); LOGGER.error( pwmRequest.getSessionLabel(), errorInformation.toDebugStr() ); @@ -877,12 +877,12 @@ private String readUserAttribute( { return getChaiUser( userIdentity ).readStringAttribute( attribute ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.trace( pwmRequest, () -> "error reading attribute for user '" + userIdentity.toDisplayString() + "', error: " + e.getMessage() ); return null; } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -990,7 +990,7 @@ public void run() { doJob(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "error exporting csv row data: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java index d87c59f0d..6842b78b0 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java @@ -195,7 +195,7 @@ private ProcessStatus restOrgChartData( pwmRequest.outputJsonResult( RestResultBean.withData( orgChartData ) ); StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_ORGCHART ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( pwmRequest, "error generating user detail object: " + e.getMessage() ); pwmRequest.respondWithError( e.getErrorInformation() ); diff --git a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java index b93ef90ef..1a7a348bf 100644 --- a/server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java +++ b/server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java @@ -252,7 +252,7 @@ private Optional readPhotoDataFromHTTP() } return Optional.empty(); } - catch ( Exception e ) + catch ( final Exception e ) { final String msg = "error reading remote http photo data: " + JavaHelper.readHostileExceptionMessage( e ); throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_SERVICE_UNREACHABLE, msg ) ); @@ -300,7 +300,7 @@ public static void servletRespondWithPhoto( } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( pwmRequest, () -> "error reading user photo data: " + e.getMessage() ); if ( !pwmRequest.getPwmResponse().isCommitted() ) diff --git a/server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java b/server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java index faa691e2d..b1a9420f5 100644 --- a/server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java @@ -82,7 +82,7 @@ protected void doGet( final HttpServletRequest req, final HttpServletResponse re { pwmRequest = PwmRequest.forRequest( req, resp ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "unable to satisfy request using standard mechanism, reverting to raw resource server" ); } @@ -93,7 +93,7 @@ protected void doGet( final HttpServletRequest req, final HttpServletResponse re { processAction( pwmRequest ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmRequest, "error during resource servlet request processing: " + e.getMessage() ); } @@ -104,7 +104,7 @@ protected void doGet( final HttpServletRequest req, final HttpServletResponse re { rawRequestProcessor( req, resp ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error serving raw resource request: " + e.getMessage() ); } @@ -150,14 +150,14 @@ protected void processAction( final PwmRequest pwmRequest ) { file = resourceFileRequest.getRequestedFileResource(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { pwmRequest.getPwmResponse().getHttpServletResponse().sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage() ); try { pwmRequest.debugHttpRequestToLog( "returning HTTP 500 status" ); } - catch ( PwmUnrecoverableException e2 ) + catch ( final PwmUnrecoverableException e2 ) { /* noop */ } @@ -171,7 +171,7 @@ protected void processAction( final PwmRequest pwmRequest ) { pwmRequest.debugHttpRequestToLog( "returning HTTP 404 status" ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { /* noop */ } @@ -203,7 +203,7 @@ protected void processAction( final PwmRequest pwmRequest ) fromCache = handleCacheableResponse( resourceFileRequest, response, resourceService.getCacheMap() ); debugText = makeDebugText( fromCache, acceptsGzip, false ); } - catch ( UncacheableResourceException e ) + catch ( final UncacheableResourceException e ) { handleUncachedResponse( response, file, acceptsGzip ); debugText = makeDebugText( fromCache, acceptsGzip, true ); @@ -215,7 +215,7 @@ protected void processAction( final PwmRequest pwmRequest ) StatisticsManager.incrementStat( pwmApplication, Statistic.HTTP_RESOURCE_REQUESTS ); cacheHitRatio.update( fromCache ? 1 : 0 ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "error fulfilling response for url '" + requestURI + "', error: " + e.getMessage() ); } @@ -374,7 +374,7 @@ private boolean respondWithNotModified( final PwmRequest pwmRequest, final Resou { pwmRequest.debugHttpRequestToLog( "returning HTTP 304 status" ); } - catch ( PwmUnrecoverableException e2 ) + catch ( final PwmUnrecoverableException e2 ) { /* noop */ } diff --git a/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java b/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java index fb31d581f..778b6078f 100644 --- a/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java +++ b/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java @@ -101,7 +101,7 @@ private ResourceServletConfiguration( final PwmApplication pwmApplication ) zipResources.put( ResourceFileServlet.RESOURCE_PATH + configuredZipFileResource.getUrl(), zipFile ); LOGGER.debug( () -> "registered resource-zip file " + configuredZipFileResource.getZipFile() + " at path " + zipFileFile.getAbsolutePath() ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.warn( "unable to resource-zip file " + configuredZipFileResource + ", error: " + e.getMessage() ); } @@ -127,7 +127,7 @@ private ResourceServletConfiguration( final PwmApplication pwmApplication ) customFileBundle.clear(); customFileBundle.putAll( customFiles ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.error( "error assembling memory file map zip bundle: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java b/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java index e055c837c..f56225eea 100644 --- a/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java +++ b/server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java @@ -31,6 +31,7 @@ import password.pwm.health.HealthRecord; import password.pwm.http.PwmRequest; import password.pwm.svc.PwmService; +import password.pwm.util.java.ClosableIterator; import password.pwm.util.java.FileSystemUtility; import password.pwm.util.java.JavaHelper; import password.pwm.util.java.MovingAverage; @@ -129,7 +130,7 @@ public void init( final PwmApplication pwmApplication ) throws PwmException status = STATUS.OPEN; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error during cache initialization, will remain closed; error: " + e.getMessage() ); status = STATUS.CLOSED; @@ -140,7 +141,7 @@ public void init( final PwmApplication pwmApplication ) throws PwmException { resourceNonce = makeResourcePathNonce(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error during nonce generation, will remain closed; error: " + e.getMessage() ); status = STATUS.CLOSED; @@ -277,15 +278,21 @@ private static void checksumResourceFilePath( final PwmApplication pwmApplicatio final File resourcePath = new File( basePath.getAbsolutePath() + File.separator + "public" + File.separator + "resources" ); if ( resourcePath.exists() ) { - for ( final FileSystemUtility.FileSummaryInformation fileSummaryInformation : FileSystemUtility.readFileInformation( resourcePath ) ) + try ( ClosableIterator iter = + FileSystemUtility.readFileInformation( Collections.singletonList( resourcePath ) ) ) { - checksumStream.write( JavaHelper.longToBytes( fileSummaryInformation.getChecksum() ) ); + while ( iter.hasNext() ) + { + final FileSystemUtility.FileSummaryInformation fileSummaryInformation = iter.next(); + checksumStream.write( JavaHelper.longToBytes( fileSummaryInformation.getChecksum() ) ); + } + } } } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unable to generate resource path nonce: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/servlet/updateprofile/UpdateProfileServlet.java b/server/src/main/java/password/pwm/http/servlet/updateprofile/UpdateProfileServlet.java index c10928c3d..a0598de28 100644 --- a/server/src/main/java/password/pwm/http/servlet/updateprofile/UpdateProfileServlet.java +++ b/server/src/main/java/password/pwm/http/servlet/updateprofile/UpdateProfileServlet.java @@ -179,7 +179,7 @@ ProcessStatus handleEnterCodeRequest( TokenService.TokenEntryType.authenticated ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.debug( pwmRequest, () -> "error while checking entered token: " ); errorInformation = e.getErrorInformation(); @@ -229,7 +229,7 @@ ProcessStatus restValidateForm( updateProfileBean.getFormData().putAll( FormUtility.asStringMap( formValues ) ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { success = false; userMessage = e.getErrorInformation().toUserStr( pwmRequest.getPwmSession(), pwmRequest.getPwmApplication() ); @@ -318,7 +318,7 @@ ProcessStatus handleUpdateProfileRequest( { readFormParametersFromRequest( pwmRequest, updateProfileProfile, updateProfileBean ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( pwmRequest, e.getMessage() ); setLastError( pwmRequest, e.getErrorInformation() ); @@ -381,7 +381,7 @@ protected void nextStep( final PwmRequest pwmRequest ) final Map formValues = FormUtility.readFormValuesFromMap( updateProfileBean.getFormData(), formFields, pwmRequest.getLocale() ); UpdateProfileUtil.verifyFormAttributes( pwmRequest.getPwmApplication(), pwmRequest.getUserInfoIfLoggedIn(), pwmRequest.getLocale(), formValues, true ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( pwmSession, e.getMessage() ); setLastError( pwmRequest, e.getErrorInformation() ); @@ -433,12 +433,12 @@ protected void nextStep( final PwmRequest pwmRequest ) pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_UpdateProfile ); return; } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( pwmSession, e.getMessage() ); setLastError( pwmRequest, e.getErrorInformation() ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UPDATE_ATTRS_FAILURE, e.toString() ); LOGGER.error( pwmSession, errorInformation.toDebugStr() ); diff --git a/server/src/main/java/password/pwm/http/state/CryptoCookieBeanImpl.java b/server/src/main/java/password/pwm/http/state/CryptoCookieBeanImpl.java index b3c9a86cd..3a6a003a6 100644 --- a/server/src/main/java/password/pwm/http/state/CryptoCookieBeanImpl.java +++ b/server/src/main/java/password/pwm/http/state/CryptoCookieBeanImpl.java @@ -27,7 +27,6 @@ import password.pwm.http.PwmRequest; import password.pwm.http.PwmRequestAttribute; import password.pwm.http.bean.PwmSessionBean; -import password.pwm.util.PasswordData; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; @@ -69,7 +68,7 @@ public E getSessionBean( final PwmRequest pwmRequest, return cookieBean; } } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.debug( pwmRequest, () -> "ignoring existing existing " + cookieName + " cookie bean due to error: " + e.getMessage() ); } @@ -155,7 +154,7 @@ public void saveSessionBeans( final PwmRequest pwmRequest ) } } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmRequest, "error writing cookie bean to response: " + e.getMessage(), e ); } @@ -196,8 +195,9 @@ public String getSessionStateInfo( final PwmRequest pwmRequest ) throws PwmUnrec private PwmSecurityKey keyForSession( final PwmRequest pwmRequest ) throws PwmUnrecoverableException { - final PasswordData configKey = pwmRequest.getConfig().readSettingAsPassword( PwmSetting.PWM_SECURITY_KEY ); + final PwmSecurityKey pwmSecurityKey = pwmRequest.getConfig().getSecurityKey(); + final String keyHash = pwmSecurityKey.keyHash( pwmRequest.getPwmApplication().getSecureService() ); final String userGuid = pwmRequest.getPwmSession().getLoginInfoBean().getGuid(); - return new PwmSecurityKey( configKey.getStringValue() + userGuid ); + return new PwmSecurityKey( keyHash + userGuid ); } } diff --git a/server/src/main/java/password/pwm/http/state/CryptoCookieLoginImpl.java b/server/src/main/java/password/pwm/http/state/CryptoCookieLoginImpl.java index 23fd19966..2fd04ac0b 100644 --- a/server/src/main/java/password/pwm/http/state/CryptoCookieLoginImpl.java +++ b/server/src/main/java/password/pwm/http/state/CryptoCookieLoginImpl.java @@ -84,7 +84,7 @@ public void saveLoginSessionState( final PwmRequest pwmRequest ) LOGGER.trace( pwmRequest, () -> "wrote LoginInfoBean=" + debugTxt ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "unexpected error writing login cookie to response: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -100,7 +100,7 @@ public void readLoginSessionState( final PwmRequest pwmRequest ) throws PwmUnrec { remoteLoginCookie = pwmRequest.readEncryptedCookie( cookieName, LoginInfoBean.class ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "unexpected error reading login cookie, will clear and ignore; error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); @@ -117,7 +117,7 @@ public void readLoginSessionState( final PwmRequest pwmRequest ) throws PwmUnrec { checkIfRemoteLoginCookieIsValid( pwmRequest, remoteLoginCookie ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.debug( pwmRequest, () -> e.getErrorInformation().toDebugStr() ); clearLoginSession( pwmRequest ); @@ -135,7 +135,7 @@ public void readLoginSessionState( final PwmRequest pwmRequest ) throws PwmUnrec importRemoteCookie( pwmRequest, remoteLoginCookie ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error authenticating using crypto session cookie: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -202,7 +202,7 @@ private static void importRemoteCookie( remoteLoginCookie.getAuthFlags().add( AuthenticationType.AUTH_FROM_REQ_COOKIE ); LOGGER.debug( pwmRequest, () -> "logged in using encrypted request cookie = " + JsonUtil.serialize( remoteLoginCookie ) ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error reading session cookie: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); diff --git a/server/src/main/java/password/pwm/http/state/LocalSessionBeanImpl.java b/server/src/main/java/password/pwm/http/state/LocalSessionBeanImpl.java index d432ca10b..2a631407c 100644 --- a/server/src/main/java/password/pwm/http/state/LocalSessionBeanImpl.java +++ b/server/src/main/java/password/pwm/http/state/LocalSessionBeanImpl.java @@ -47,7 +47,7 @@ public E getSessionBean( final PwmRequest pwmRequest, final Object newBean = SessionStateService.newBean( null, theClass ); sessionBeans.put( theClass, ( PwmSessionBean ) newBean ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error trying to instantiate bean class " + theClass.getName() + ": " + e.getMessage(), e ); } diff --git a/server/src/main/java/password/pwm/http/state/SessionStateService.java b/server/src/main/java/password/pwm/http/state/SessionStateService.java index 2848d0806..3b57d973d 100644 --- a/server/src/main/java/password/pwm/http/state/SessionStateService.java +++ b/server/src/main/java/password/pwm/http/state/SessionStateService.java @@ -192,7 +192,7 @@ private boolean beanSupportsMode( final Class theClass { return theClass.newInstance().supportedModes().contains( mode ); } - catch ( InstantiationException | IllegalAccessException e ) + catch ( final InstantiationException | IllegalAccessException e ) { e.printStackTrace(); } @@ -208,7 +208,7 @@ static E newBean( final String sessionGuid, final Cla newBean.setTimestamp( Instant.now() ); return newBean; } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error trying to instantiate bean class " + theClass.getName() + ": " + e.getMessage(); LOGGER.error( errorMsg, e ); diff --git a/server/src/main/java/password/pwm/http/tag/CurrentUrlTag.java b/server/src/main/java/password/pwm/http/tag/CurrentUrlTag.java index fdc1ef467..5910767b0 100644 --- a/server/src/main/java/password/pwm/http/tag/CurrentUrlTag.java +++ b/server/src/main/java/password/pwm/http/tag/CurrentUrlTag.java @@ -42,13 +42,13 @@ public int doEndTag( ) final String currentUrl = pwmRequest.getURLwithoutQueryString(); pageContext.getOut().write( StringUtil.escapeHtml( currentUrl ) ); } - catch ( Exception e ) + catch ( final Exception e ) { try { pageContext.getOut().write( "errorGeneratingPwmFormID" ); } - catch ( IOException e1 ) + catch ( final IOException e1 ) { /* ignore */ } diff --git a/server/src/main/java/password/pwm/http/tag/DisplayTag.java b/server/src/main/java/password/pwm/http/tag/DisplayTag.java index 01aeda3f8..3949deab8 100644 --- a/server/src/main/java/password/pwm/http/tag/DisplayTag.java +++ b/server/src/main/java/password/pwm/http/tag/DisplayTag.java @@ -120,7 +120,7 @@ public int doEndTag( ) { pwmRequest = PwmRequest.forRequest( ( HttpServletRequest ) pageContext.getRequest(), ( HttpServletResponse ) pageContext.getResponse() ); } - catch ( PwmException e ) + catch ( final PwmException e ) { /* noop */ } @@ -138,14 +138,14 @@ public int doEndTag( ) pageContext.getOut().write( displayMessage ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { { LOGGER.debug( () -> "error while executing jsp display tag: " + e.getMessage() ); return EVAL_PAGE; } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "error while executing jsp display tag: " + e.getMessage(), e ); throw new JspTagException( e.getMessage(), e ); @@ -164,7 +164,7 @@ private Class readBundle( ) { return Class.forName( bundle ); } - catch ( ClassNotFoundException e ) + catch ( final ClassNotFoundException e ) { /* no op */ } @@ -173,7 +173,7 @@ private Class readBundle( ) { return Class.forName( Display.class.getPackage().getName() + "." + bundle ); } - catch ( ClassNotFoundException e ) + catch ( final ClassNotFoundException e ) { /* no op */ } @@ -198,7 +198,7 @@ private String figureDisplayMessage( final Locale locale, final Configuration co } ); } - catch ( MissingResourceException e ) + catch ( final MissingResourceException e ) { if ( !displayIfMissing ) { diff --git a/server/src/main/java/password/pwm/http/tag/ErrorMessageTag.java b/server/src/main/java/password/pwm/http/tag/ErrorMessageTag.java index f6b68032b..e59a5b604 100644 --- a/server/src/main/java/password/pwm/http/tag/ErrorMessageTag.java +++ b/server/src/main/java/password/pwm/http/tag/ErrorMessageTag.java @@ -55,7 +55,7 @@ public int doEndTag( ) { pwmApplication = ContextManager.getPwmApplication( pageContext.getRequest() ); } - catch ( PwmException e ) + catch ( final PwmException e ) { /* noop */ } @@ -93,11 +93,11 @@ public int doEndTag( ) pageContext.getOut().write( outputMsg ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { /* app not running */ } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error executing error message tag: " + e.getMessage(), e ); throw new JspTagException( e.getMessage() ); diff --git a/server/src/main/java/password/pwm/http/tag/JspThrowableHandlerTag.java b/server/src/main/java/password/pwm/http/tag/JspThrowableHandlerTag.java index 69f700055..edbda6550 100644 --- a/server/src/main/java/password/pwm/http/tag/JspThrowableHandlerTag.java +++ b/server/src/main/java/password/pwm/http/tag/JspThrowableHandlerTag.java @@ -61,13 +61,13 @@ public int doEndTag( ) final String jspOutout = jspOutput( errorHash ); pageContext.getOut().write( jspOutout ); } - catch ( Exception e ) + catch ( final Exception e ) { try { pageContext.getOut().write( "" ); } - catch ( IOException e1 ) + catch ( final IOException e1 ) { /* ignore */ } @@ -86,7 +86,7 @@ private String jspOutput( final String errorReference ) userLocale = pwmRequest.getLocale(); configuration = pwmRequest.getConfig(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error during pwmFormIDTag output of pwmFormID: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/tag/PasswordRequirementsTag.java b/server/src/main/java/password/pwm/http/tag/PasswordRequirementsTag.java index 816e0d878..9bd9f9b49 100644 --- a/server/src/main/java/password/pwm/http/tag/PasswordRequirementsTag.java +++ b/server/src/main/java/password/pwm/http/tag/PasswordRequirementsTag.java @@ -491,7 +491,7 @@ private static String getLocalString( final Message message, final int size, fin { return Message.getLocalizedMessage( policyValues.getLocale(), effectiveMessage, policyValues.getConfig(), String.valueOf( size ) ); } - catch ( MissingResourceException e ) + catch ( final MissingResourceException e ) { LOGGER.error( "unable to display requirement tag for message '" + message.toString() + "': " + e.getMessage() ); } @@ -504,7 +504,7 @@ private static String getLocalString( final Message message, final String field, { return Message.getLocalizedMessage( policyValues.getLocale(), message, policyValues.getConfig(), field ); } - catch ( MissingResourceException e ) + catch ( final MissingResourceException e ) { LOGGER.error( "unable to display requirement tag for message '" + message.toString() + "': " + e.getMessage() ); } @@ -589,7 +589,7 @@ public int doEndTag( ) pageContext.getOut().write( requirementsText.toString() ); } } - catch ( IOException | PwmException e ) + catch ( final IOException | PwmException e ) { LOGGER.error( "unexpected error during password requirements generation: " + e.getMessage(), e ); throw new JspTagException( e.getMessage() ); diff --git a/server/src/main/java/password/pwm/http/tag/PwmAutofocusTag.java b/server/src/main/java/password/pwm/http/tag/PwmAutofocusTag.java index cfb5afc39..0ffa4302c 100644 --- a/server/src/main/java/password/pwm/http/tag/PwmAutofocusTag.java +++ b/server/src/main/java/password/pwm/http/tag/PwmAutofocusTag.java @@ -39,7 +39,7 @@ public int doEndTag( ) req.setAttribute( "autoFocusHasBeenSet", true ); } } - catch ( Exception e ) + catch ( final Exception e ) { throw new JspTagException( e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/tag/PwmContextTag.java b/server/src/main/java/password/pwm/http/tag/PwmContextTag.java index 65a25b3ce..fe9c02446 100644 --- a/server/src/main/java/password/pwm/http/tag/PwmContextTag.java +++ b/server/src/main/java/password/pwm/http/tag/PwmContextTag.java @@ -35,7 +35,7 @@ public int doEndTag( ) final HttpServletRequest req = ( HttpServletRequest ) pageContext.getRequest(); pageContext.getOut().write( req.getContextPath() ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new JspTagException( e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/tag/PwmFormIDTag.java b/server/src/main/java/password/pwm/http/tag/PwmFormIDTag.java index 79ee53457..58dc89a83 100644 --- a/server/src/main/java/password/pwm/http/tag/PwmFormIDTag.java +++ b/server/src/main/java/password/pwm/http/tag/PwmFormIDTag.java @@ -77,13 +77,13 @@ public int doEndTag( ) pageContext.getOut().write( pwmFormID ); } - catch ( Exception e ) + catch ( final Exception e ) { try { pageContext.getOut().write( "errorGeneratingPwmFormID" ); } - catch ( IOException e1 ) + catch ( final IOException e1 ) { /* ignore */ } diff --git a/server/src/main/java/password/pwm/http/tag/PwmMacroTag.java b/server/src/main/java/password/pwm/http/tag/PwmMacroTag.java index 0b2f89a74..8693d3c2d 100644 --- a/server/src/main/java/password/pwm/http/tag/PwmMacroTag.java +++ b/server/src/main/java/password/pwm/http/tag/PwmMacroTag.java @@ -56,11 +56,11 @@ public int doEndTag( ) final String outputValue = macroMachine.expandMacros( value ); pageContext.getOut().write( outputValue ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error while processing PwmMacroTag: " + e.getMessage() ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new JspTagException( e.getMessage(), e ); } diff --git a/server/src/main/java/password/pwm/http/tag/PwmScriptRefTag.java b/server/src/main/java/password/pwm/http/tag/PwmScriptRefTag.java index 9ed0a28b9..980336da2 100644 --- a/server/src/main/java/password/pwm/http/tag/PwmScriptRefTag.java +++ b/server/src/main/java/password/pwm/http/tag/PwmScriptRefTag.java @@ -60,7 +60,7 @@ public int doEndTag( ) final String output = ""; pageContext.getOut().write( output ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error during scriptRef output of pwmFormID: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/tag/PwmScriptTag.java b/server/src/main/java/password/pwm/http/tag/PwmScriptTag.java index 0aedc943b..b1f2eb606 100644 --- a/server/src/main/java/password/pwm/http/tag/PwmScriptTag.java +++ b/server/src/main/java/password/pwm/http/tag/PwmScriptTag.java @@ -62,11 +62,11 @@ public int doAfterBody( ) getPreviousOut().write( output ); } } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.error( "IO error while processing PwmScriptTag: " + e.getMessage() ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error while processing PwmScriptTag: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/tag/SuccessMessageTag.java b/server/src/main/java/password/pwm/http/tag/SuccessMessageTag.java index b74e3f038..1f98c848a 100644 --- a/server/src/main/java/password/pwm/http/tag/SuccessMessageTag.java +++ b/server/src/main/java/password/pwm/http/tag/SuccessMessageTag.java @@ -65,7 +65,7 @@ public int doEndTag( ) pageContext.getOut().write( outputMsg ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new JspTagException( e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/tag/UserInfoTag.java b/server/src/main/java/password/pwm/http/tag/UserInfoTag.java index 8ffed0cc3..a49847090 100644 --- a/server/src/main/java/password/pwm/http/tag/UserInfoTag.java +++ b/server/src/main/java/password/pwm/http/tag/UserInfoTag.java @@ -53,7 +53,7 @@ public int doEndTag( ) pageContext.getOut().write( StringUtil.escapeHtml( ldapValue == null ? "" : ldapValue ) ); } } - catch ( Exception e ) + catch ( final Exception e ) { throw new JspTagException( e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/tag/conditional/PwmIfTag.java b/server/src/main/java/password/pwm/http/tag/conditional/PwmIfTag.java index 59015bff5..7d837c5f3 100644 --- a/server/src/main/java/password/pwm/http/tag/conditional/PwmIfTag.java +++ b/server/src/main/java/password/pwm/http/tag/conditional/PwmIfTag.java @@ -95,7 +95,7 @@ public int doStartTag( ) final PwmIfOptions options = new PwmIfOptions( negate, permission, setting, requestFlag ); showBody = testEnum.passed( pwmRequest, options ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { LOGGER.error( "error testing jsp if '" + testEnum.toString() + "', error: " + e.getMessage() ); } @@ -106,7 +106,7 @@ public int doStartTag( ) LOGGER.warn( pwmSession, errorMsg ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error executing PwmIfTag for test '" + test + "', error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/tag/url/PwmUrlTag.java b/server/src/main/java/password/pwm/http/tag/url/PwmUrlTag.java index 3385916dd..04a03517a 100644 --- a/server/src/main/java/password/pwm/http/tag/url/PwmUrlTag.java +++ b/server/src/main/java/password/pwm/http/tag/url/PwmUrlTag.java @@ -66,7 +66,7 @@ public int doEndTag( ) { pwmRequest = PwmRequest.forRequest( ( HttpServletRequest ) pageContext.getRequest(), ( HttpServletResponse ) pageContext.getResponse() ); } - catch ( PwmException e ) + catch ( final PwmException e ) { /* noop */ } @@ -97,7 +97,7 @@ public int doEndTag( ) { pageContext.getOut().write( outputURL ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new JspTagException( e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/http/tag/value/PwmValue.java b/server/src/main/java/password/pwm/http/tag/value/PwmValue.java index 04bb0a89a..67daef43b 100644 --- a/server/src/main/java/password/pwm/http/tag/value/PwmValue.java +++ b/server/src/main/java/password/pwm/http/tag/value/PwmValue.java @@ -118,7 +118,7 @@ public String valueOutput( final PwmRequest pwmRequest, final PageContext pageCo pwmRequest.getPwmApplication() ); outputURL = macroMachine.expandMacros( outputURL ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmRequest, "error expanding macros in homeURL: " + e.getMessage() ); } @@ -163,7 +163,7 @@ public String valueOutput( final PwmRequest pwmRequest, final PageContext pageCo final String expandedScript = macroMachine.expandMacros( customScript ); return expandedScript; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmRequest, "error while expanding customJavascript macros: " + e.getMessage() ); return customScript; diff --git a/server/src/main/java/password/pwm/http/tag/value/PwmValueTag.java b/server/src/main/java/password/pwm/http/tag/value/PwmValueTag.java index 43e27acd7..8797f4f00 100644 --- a/server/src/main/java/password/pwm/http/tag/value/PwmValueTag.java +++ b/server/src/main/java/password/pwm/http/tag/value/PwmValueTag.java @@ -73,16 +73,16 @@ public int doEndTag( ) pageContext.getOut().write( escapedOutput ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { LOGGER.error( "can't output requested value name '" + getName() + "'" ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error while processing PwmValueTag: " + e.getMessage() ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new JspTagException( e.getMessage(), e ); } @@ -102,7 +102,7 @@ public String calcValue( { return value.getValueOutput().valueOutput( pwmRequest, pageContext ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error executing value tag option '" + value.toString() + "', error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/i18n/Display.java b/server/src/main/java/password/pwm/i18n/Display.java index bb7152e50..db5d473aa 100644 --- a/server/src/main/java/password/pwm/i18n/Display.java +++ b/server/src/main/java/password/pwm/i18n/Display.java @@ -332,7 +332,5 @@ public String getKey( ) return this.toString(); } - - } diff --git a/server/src/main/java/password/pwm/i18n/PwmLocaleBundle.java b/server/src/main/java/password/pwm/i18n/PwmLocaleBundle.java index 0dd3a58de..e0df20cba 100644 --- a/server/src/main/java/password/pwm/i18n/PwmLocaleBundle.java +++ b/server/src/main/java/password/pwm/i18n/PwmLocaleBundle.java @@ -21,35 +21,43 @@ package password.pwm.i18n; import password.pwm.PwmConstants; +import password.pwm.util.java.JavaHelper; +import password.pwm.util.java.StringUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.List; +import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; public enum PwmLocaleBundle { - DISPLAY( Display.class, false ), - ERRORS( Error.class, false ), - MESSAGE( Message.class, false ), + DISPLAY( Display.class ), + ERRORS( Error.class ), + MESSAGE( Message.class ), - CONFIG( Config.class, true ), - ADMIN( Admin.class, true ), - HEALTH( Health.class, true ),; + CONFIG( Config.class, Flag.AdminOnly ), + ADMIN( Admin.class, Flag.AdminOnly ), + HEALTH( Health.class, Flag.AdminOnly ), + CONFIG_GUIDE( ConfigGuide.class, Flag.AdminOnly ),; private final Class theClass; - private final boolean adminOnly; - private Set keys; - PwmLocaleBundle( final Class theClass, final boolean adminOnly ) + enum Flag + { + AdminOnly, + } + + private final Flag[] flags; + + PwmLocaleBundle( final Class theClass, final Flag... flags ) { this.theClass = theClass; - this.adminOnly = adminOnly; + this.flags = flags; } public Class getTheClass( ) @@ -59,17 +67,40 @@ public Class getTheClass( ) public boolean isAdminOnly( ) { - return adminOnly; + return JavaHelper.enumArrayContainsValue( flags, Flag.AdminOnly ); } - public Set getKeys( ) + public static Optional forKey( final String key ) { - if ( keys == null ) + for ( final PwmLocaleBundle pwmLocaleBundle : PwmLocaleBundle.values() ) { - final ResourceBundle defaultBundle = ResourceBundle.getBundle( this.getTheClass().getName(), PwmConstants.DEFAULT_LOCALE ); - keys = Collections.unmodifiableSet( new HashSet<>( defaultBundle.keySet() ) ); + if ( StringUtil.caseIgnoreContains( pwmLocaleBundle.getLegacyKeys(), key ) ) + { + return Optional.of( pwmLocaleBundle ); + } } - return keys; + + return Optional.empty(); + } + + public String getKey() + { + return getTheClass().getSimpleName(); + } + + public Set getLegacyKeys() + { + return Collections.unmodifiableSet( new HashSet<>( Arrays.asList( + this.getTheClass().getSimpleName(), + this.getTheClass().getName(), + "password.pwm." + this.getTheClass().getSimpleName() + ) ) ); + } + + public Set getDisplayKeys( ) + { + final ResourceBundle defaultBundle = ResourceBundle.getBundle( this.getTheClass().getName(), PwmConstants.DEFAULT_LOCALE ); + return Collections.unmodifiableSet( new HashSet<>( defaultBundle.keySet() ) ); } public static Collection allValues( ) @@ -80,13 +111,7 @@ public static Collection allValues( ) public static Collection userFacingValues( ) { final List returnValue = new ArrayList<>( allValues() ); - for ( final Iterator iter = returnValue.iterator(); iter.hasNext(); ) - { - if ( iter.next().isAdminOnly() ) - { - iter.remove(); - } - } + returnValue.removeIf( PwmLocaleBundle::isAdminOnly ); return Collections.unmodifiableList( returnValue ); } } diff --git a/server/src/main/java/password/pwm/ldap/LdapBrowser.java b/server/src/main/java/password/pwm/ldap/LdapBrowser.java index 0f9c5be0f..5f2bfdbfc 100644 --- a/server/src/main/java/password/pwm/ldap/LdapBrowser.java +++ b/server/src/main/java/password/pwm/ldap/LdapBrowser.java @@ -32,7 +32,7 @@ import password.pwm.AppProperty; import password.pwm.config.Configuration; import password.pwm.config.profile.LdapProfile; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmUnrecoverableException; @@ -51,14 +51,14 @@ public class LdapBrowser { private static final PwmLogger LOGGER = PwmLogger.forClass( LdapBrowser.class ); - private final StoredConfigurationImpl storedConfiguration; + private final StoredConfiguration storedConfiguration; private final ChaiProviderFactory chaiProviderFactory; private final Map providerCache = new HashMap<>(); public LdapBrowser( final ChaiProviderFactory chaiProviderFactory, - final StoredConfigurationImpl storedConfiguration + final StoredConfiguration storedConfiguration ) throws PwmUnrecoverableException { @@ -72,11 +72,11 @@ public LdapBrowseResult doBrowse( final String profile, final String dn ) throws { return doBrowseImpl( figureLdapProfileID( profile ), dn ); } - catch ( ChaiUnavailableException | ChaiOperationException e ) + catch ( final ChaiUnavailableException | ChaiOperationException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_LDAP_DATA_ERROR, e.getMessage() ) ); } @@ -228,7 +228,7 @@ private Map getChildEntries( final Map> subSearchResults = chaiProvider.search( resultDN, searchHelper ); hasSubs = !subSearchResults.isEmpty(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "error during subordinate entry count of " + dn + ", error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/ldap/LdapConnectionService.java b/server/src/main/java/password/pwm/ldap/LdapConnectionService.java index 46c6db764..76aa1ff43 100644 --- a/server/src/main/java/password/pwm/ldap/LdapConnectionService.java +++ b/server/src/main/java/password/pwm/ldap/LdapConnectionService.java @@ -98,7 +98,7 @@ public void close( ) { chaiProviderFactory.close(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error closing ldap proxy connection: " + e.getMessage(), e ); } @@ -189,12 +189,12 @@ private ChaiProvider getNewProxyChaiProvider( final LdapProfile ldapProfile ) return newProvider; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { setLastLdapFailure( ldapProfile, e.getErrorInformation() ); throw e; } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error creating new proxy ldap connection: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -241,7 +241,7 @@ private static Map readLastLdapFailure( final PwmAppli return returnMap; } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error loading cached lastLdapFailure statuses: " + e.getMessage() + ", input=" + lastLdapFailureStr ); } diff --git a/server/src/main/java/password/pwm/ldap/LdapDebugDataGenerator.java b/server/src/main/java/password/pwm/ldap/LdapDebugDataGenerator.java index 7b5f9e12d..88a99de3a 100644 --- a/server/src/main/java/password/pwm/ldap/LdapDebugDataGenerator.java +++ b/server/src/main/java/password/pwm/ldap/LdapDebugDataGenerator.java @@ -120,7 +120,7 @@ public static List makeLdapDebugInfos( returnList.add( ldapDebugInfo ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error during output of ldap profile debug data profile: " + ldapProfile + ", error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java b/server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java index 4ea07e5a3..6bee0de56 100644 --- a/server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java +++ b/server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java @@ -107,7 +107,7 @@ public static void addConfiguredUserObjectClass( final ChaiUser theUser = chaiProvider.getEntryFactory().newChaiUser( userIdentity.getUserDN() ); addUserObjectClass( sessionLabel, theUser, newObjClasses ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -134,7 +134,7 @@ private static void addUserObjectClass( LOGGER.info( sessionLabel, () -> "added objectclass '" + finalAuxClass + "' to user " + theUser.getEntryDN() ); } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final StringBuilder errorMsg = new StringBuilder(); @@ -183,7 +183,7 @@ static ChaiProvider openProxyChaiProvider( { return createChaiProvider( chaiProviderFactory, sessionLabel, ldapProfile, config, proxyDN, proxyPW ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { if ( statisticsManager != null ) { @@ -302,7 +302,7 @@ public static void writeFormValuesToLdap( { newBytes = StringUtil.base64Decode( sValue ); } - catch ( IOException e ) + catch ( final IOException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "error processing binary form value: " + e.getMessage() ); } @@ -322,7 +322,7 @@ public static void writeFormValuesToLdap( existingBytes = null; } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error reading existing values on user " + theUser.getEntryDN() + " prior to replacing values, error: " + e.getMessage(); @@ -341,7 +341,7 @@ public static void writeFormValuesToLdap( { theUser.writeBinaryAttribute( attrName, newBytes ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error setting '" + attrName + "' attribute on user " + theUser.getEntryDN() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_LDAP_DATA_ERROR, errorMsg ); @@ -358,7 +358,7 @@ else if ( existingBytes != null && existingBytes.length > 0 ) theUser.deleteAttribute( attrName, null ); LOGGER.info( () -> "deleted binary attribute value on user " + theUser.getEntryDN() + " (" + attrName + ")" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error removing '" + attrName + "' attribute value on user " + theUser.getEntryDN() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_LDAP_DATA_ERROR, errorMsg ); @@ -383,7 +383,7 @@ else if ( existingBytes != null && existingBytes.length > 0 ) { currentValue = theUser.readStringAttribute( attrName ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error reading existing values on user " + theUser.getEntryDN() + " prior to replacing values, error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_LDAP_DATA_ERROR, errorMsg ); @@ -400,7 +400,7 @@ else if ( existingBytes != null && existingBytes.length > 0 ) final String finalAttrValue = attrValue; LOGGER.info( () -> "set attribute on user " + theUser.getEntryDN() + " (" + attrName + "=" + finalAttrValue + ")" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error setting '" + attrName + "' attribute on user " + theUser.getEntryDN() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_LDAP_DATA_ERROR, errorMsg ); @@ -416,7 +416,7 @@ else if ( existingBytes != null && existingBytes.length > 0 ) theUser.deleteAttribute( attrName, null ); LOGGER.info( () -> "deleted attribute value on user " + theUser.getEntryDN() + " (" + attrName + ")" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error removing '" + attrName + "' attribute value on user " + theUser.getEntryDN() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_LDAP_DATA_ERROR, errorMsg ); @@ -468,7 +468,7 @@ private static String readExistingGuidValue( } return guidValue; } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error while reading vendor GUID value for user " + theUser.getEntryDN() + ", error: " + e.getMessage(); return processError( errorMsg, throwExceptionOnError ); @@ -479,13 +479,13 @@ private static String readExistingGuidValue( { return theUser.readStringAttribute( guidAttributeName ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "unexpected error while reading attribute GUID value for user " + userIdentity + " from '" + guidAttributeName + "', error: " + e.getMessage(); return processError( errorMsg, throwExceptionOnError ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -527,7 +527,7 @@ private static boolean searchForExistingGuidValue( final UserIdentity result = userSearchEngine.performSingleUserSearch( searchConfiguration, sessionLabel ); exists = result != null; } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { if ( e.getError() != PwmError.ERROR_CANT_MATCH_USER ) { @@ -578,7 +578,7 @@ private static String assignGuidToUser( LOGGER.info( sessionLabel, () -> "added GUID value '" + finalNewGuid + "' to user " + userIdentity ); return newGuid; } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "unable to write GUID value to user attribute " + guidAttributeName + " : " + e.getMessage() + ", cannot write GUID value to user " + userIdentity; @@ -586,7 +586,7 @@ private static String assignGuidToUser( LOGGER.error( errorInformation.toDebugStr() ); throw new PwmUnrecoverableException( errorInformation ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -727,7 +727,7 @@ public static ChaiConfiguration createChaiConfiguration( final Answer.FormatType formatType = Answer.FormatType.valueOf( storageMethodString ); configBuilder.setSetting( ChaiSetting.CR_DEFAULT_FORMAT_TYPE, formatType.toString() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "unknown CR storage format type '" + storageMethodString + "' " ); } @@ -831,7 +831,7 @@ public static boolean updateLastPasswordUpdateAttribute( LOGGER.debug( sessionLabel, () -> "wrote pwdLastModified update attribute for " + theUser.getEntryDN() ); success = true; } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.debug( sessionLabel, () -> "error writing update attribute for user '" + theUser.getEntryDN() + "' " + e.getMessage() ); } @@ -915,7 +915,7 @@ public static Instant readPasswordExpirationTime( final ChaiUser theUser ) } return ldapPasswordExpirationTime; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "error reading password expiration time: " + e.getMessage() ); } @@ -951,7 +951,7 @@ public static PasswordData readLdapPassword( LOGGER.debug( sessionLabel, () -> "successfully retrieved user's current password from ldap, now conducting standard authentication" ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( sessionLabel, () -> "unable to retrieve user password from ldap: " + e.getMessage() ); } @@ -996,11 +996,11 @@ public static Optional readPhotoDataFromLdap( photoData = photoAttributeData[ 0 ]; mimeType = URLConnection.guessContentTypeFromStream( new ByteArrayInputStream( photoData ) ); } - catch ( IOException | ChaiOperationException e ) + catch ( final IOException | ChaiOperationException e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_INTERNAL, "error reading user photo ldap attribute: " + e.getMessage() ) ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -1032,7 +1032,7 @@ public static Locale readLdapStoredLanguage( return LocaleHelper.parseLocaleString( storedValue ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -1068,7 +1068,7 @@ public static void processAutoUpdateLanguageAttribute( user.writeStringAttribute( languageAttr, languageCodeValue ); LOGGER.debug( sessionLabel, () -> "wrote current browser session language value '" + languageCodeValue + "' to user attribute " + languageAttr ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { LOGGER.error( sessionLabel, "error writing language value to language attribute '" + languageAttr + "', error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/ldap/LdapPermissionTester.java b/server/src/main/java/password/pwm/ldap/LdapPermissionTester.java index da2155e83..4b5f006d0 100644 --- a/server/src/main/java/password/pwm/ldap/LdapPermissionTester.java +++ b/server/src/main/java/password/pwm/ldap/LdapPermissionTester.java @@ -166,7 +166,7 @@ public static boolean testGroupMatch( result = true; } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { LOGGER.warn( pwmSession, "LDAP error during group for " + userIdentity + " using " + filterString + ", error:" + e.getMessage() ); } @@ -222,7 +222,7 @@ else if ( "(objectClass=*)".equalsIgnoreCase( filterString ) || "objectClass=*". result = true; } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { LOGGER.warn( pwmSession, "LDAP error during check for " + userIdentity + " using " + filterString + ", error:" + e.getMessage() ); } @@ -265,7 +265,7 @@ public static Map> discoverMatchingUsers( sessionLabel ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error reading matching users: " + e.getMessage() ); throw new PwmOperationalException( e.getErrorInformation() ); diff --git a/server/src/main/java/password/pwm/ldap/PasswordChangeProgressChecker.java b/server/src/main/java/password/pwm/ldap/PasswordChangeProgressChecker.java index 7cddf8334..708f88efe 100644 --- a/server/src/main/java/password/pwm/ldap/PasswordChangeProgressChecker.java +++ b/server/src/main/java/password/pwm/ldap/PasswordChangeProgressChecker.java @@ -383,7 +383,7 @@ else if ( TimeDuration.fromCurrent( tracker.lastReplicaCheckTime ).isShorterThan return progressRecord; } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmSession, "error during password replication status check: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/ldap/PwmLdapVendor.java b/server/src/main/java/password/pwm/ldap/PwmLdapVendor.java index aa210505e..f9fcacd5a 100644 --- a/server/src/main/java/password/pwm/ldap/PwmLdapVendor.java +++ b/server/src/main/java/password/pwm/ldap/PwmLdapVendor.java @@ -47,7 +47,7 @@ public static PwmLdapVendor fromString( final String input ) return null; } - for ( PwmLdapVendor vendor : PwmLdapVendor.values() ) + for ( final PwmLdapVendor vendor : PwmLdapVendor.values() ) { if ( vendor.name().equals( input ) ) { @@ -71,7 +71,7 @@ public static PwmLdapVendor fromString( final String input ) public static PwmLdapVendor fromChaiVendor( final DirectoryVendor directoryVendor ) { - for ( PwmLdapVendor vendor : PwmLdapVendor.values() ) + for ( final PwmLdapVendor vendor : PwmLdapVendor.values() ) { if ( vendor.chaiVendor == directoryVendor ) { diff --git a/server/src/main/java/password/pwm/ldap/UserInfoFactory.java b/server/src/main/java/password/pwm/ldap/UserInfoFactory.java index b36b322c8..a5c75007a 100644 --- a/server/src/main/java/password/pwm/ldap/UserInfoFactory.java +++ b/server/src/main/java/password/pwm/ldap/UserInfoFactory.java @@ -108,7 +108,7 @@ public static UserInfo newUserInfo( { return makeUserInfoImpl( pwmApplication, sessionLabel, userLocale, userIdentity, provider, null ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } @@ -128,7 +128,7 @@ public static UserInfo newUserInfo( { return makeUserInfoImpl( pwmApplication, sessionLabel, userLocale, userIdentity, provider, currentPassword ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } diff --git a/server/src/main/java/password/pwm/ldap/UserInfoReader.java b/server/src/main/java/password/pwm/ldap/UserInfoReader.java index c9a93cbae..5b4e4360f 100644 --- a/server/src/main/java/password/pwm/ldap/UserInfoReader.java +++ b/server/src/main/java/password/pwm/ldap/UserInfoReader.java @@ -160,11 +160,11 @@ public Instant getLastLdapLoginTime( ) throws PwmUnrecoverableException { return chaiUser.readLastLoginTime(); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.warn( sessionLabel, "error reading user's last ldap login time: " + e.getMessage() ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -232,13 +232,13 @@ public PasswordStatus getPasswordStatus( ) throws PwmUnrecoverableException final PwmPasswordRuleValidator passwordRuleValidator = new PwmPasswordRuleValidator( pwmApplication, passwordPolicy ); passwordRuleValidator.testPassword( currentPassword, null, selfCachedReference, chaiUser ); } - catch ( PwmDataValidationException | PwmUnrecoverableException e ) + catch ( final PwmDataValidationException | PwmUnrecoverableException e ) { LOGGER.debug( sessionLabel, () -> "user " + userDN + " password does not conform to current password policy (" + e.getMessage() + "), marking as requiring change." ); passwordStatusBuilder.violatesPolicy( true ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -259,11 +259,11 @@ public PasswordStatus getPasswordStatus( ) throws PwmUnrecoverableException LOGGER.trace( sessionLabel, () -> "password for " + userDN + " does not appear to be expired" ); } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.info( sessionLabel, () -> "error reading LDAP attributes for " + userDN + " while reading isPasswordExpired(): " + e.getMessage() ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -369,7 +369,7 @@ public boolean isAccountEnabled( ) throws PwmUnrecoverableException { return chaiUser.isAccountEnabled(); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -382,7 +382,7 @@ public boolean isAccountExpired( ) throws PwmUnrecoverableException { return chaiUser.isAccountExpired(); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -395,7 +395,7 @@ public boolean isPasswordLocked( ) throws PwmUnrecoverableException { return chaiUser.isPasswordLocked(); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -414,7 +414,7 @@ public boolean isRequiresResponseConfig( ) throws PwmUnrecoverableException selfCachedReference.getChallengeProfile().getChallengeSet(), selfCachedReference.getResponseInfoBean() ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -512,12 +512,12 @@ public boolean isRequiresUpdateProfile( ) throws PwmUnrecoverableException LOGGER.debug( sessionLabel, () -> "checkProfile: " + userIdentity + " has value for attributes, update profile will not be required" ); return false; } - catch ( PwmDataValidationException e ) + catch ( final PwmDataValidationException e ) { LOGGER.debug( sessionLabel, () -> "checkProfile: " + userIdentity + " does not have good attributes (" + e.getMessage() + "), update profile will be required" ); return true; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { e.printStackTrace(); } @@ -532,7 +532,7 @@ public Instant getPasswordLastModifiedTime( ) throws PwmUnrecoverableException { return PasswordUtility.determinePwdLastModified( pwmApplication, sessionLabel, userIdentity ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -600,7 +600,7 @@ public ResponseInfoBean getResponseInfoBean( ) throws PwmUnrecoverableException { return crService.readUserResponseInfo( sessionLabel, getUserIdentity(), chaiUser ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -616,7 +616,7 @@ public OTPUserRecord getOtpUserRecord( ) throws PwmUnrecoverableException { return otpService.readOTPUserConfiguration( sessionLabel, userIdentity ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -631,11 +631,11 @@ public Instant getAccountExpirationTime( ) throws PwmUnrecoverableException { return chaiUser.readAccountExpirationDate(); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.warn( sessionLabel, "error reading user's account expiration time: " + e.getMessage() ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -713,7 +713,7 @@ public byte[] readBinaryAttribute( return value[0]; } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -729,7 +729,7 @@ public Instant readDateAttribute( final String attribute ) { return chaiUser.readDateAttribute( attribute ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -789,13 +789,13 @@ private Map> readMultiStringAttributesImpl( SearchScope.BASE ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String msg = "ldap operational error while reading user data" + e.getMessage(); LOGGER.error( sessionLabel, msg ); throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_LDAP_DATA_ERROR, msg ) ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } diff --git a/server/src/main/java/password/pwm/ldap/auth/AuthenticationUtility.java b/server/src/main/java/password/pwm/ldap/auth/AuthenticationUtility.java index 5e8d584d8..55bacb52f 100644 --- a/server/src/main/java/password/pwm/ldap/auth/AuthenticationUtility.java +++ b/server/src/main/java/password/pwm/ldap/auth/AuthenticationUtility.java @@ -40,7 +40,7 @@ public static void checkIfUserEligibleToAuthentication( { checkIfUserEligibleToAuthenticationImpl( pwmApplication, userIdentity ); } - catch ( ChaiOperationException | ChaiUnavailableException e ) + catch ( final ChaiOperationException | ChaiUnavailableException e ) { throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) ); } diff --git a/server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java b/server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java index 347de07e9..c78992767 100644 --- a/server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java +++ b/server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java @@ -158,7 +158,7 @@ public AuthenticationResult authUsingUnknownPw( ) { return authenticateUserImpl( userPassword ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { if ( strategy == AuthenticationStrategy.READ_THEN_BIND ) { @@ -243,7 +243,7 @@ private AuthenticationResult authenticateUserImpl( { testCredentials( userIdentity, password ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { boolean permitAuthDespiteError = false; final DirectoryVendor vendor = pwmApplication.getProxyChaiProvider( @@ -427,7 +427,7 @@ private void testCredentials( bindSucceeded = true; } - catch ( ChaiException e ) + catch ( final ChaiException e ) { if ( e.getErrorCode() != null && e.getErrorCode() == ChaiError.INTRUDER_LOCKOUT ) { @@ -458,7 +458,7 @@ private void testCredentials( userProvider.close(); userProvider = null; } - catch ( Throwable e ) + catch ( final Throwable e ) { log( PwmLogLevel.ERROR, () -> "unexpected error closing invalid ldap connection after failed login attempt: " + e.getMessage() ); } @@ -516,7 +516,7 @@ private PasswordData setTempUserPassword( log( PwmLogLevel.DEBUG, () -> "user " + userIdentity + " password has been set to random value to use for user authentication" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorStr = "error setting random password for user " + userIdentity + " " + e.getMessage(); log( PwmLogLevel.ERROR, () -> errorStr ); diff --git a/server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java b/server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java index d033b3709..7772f5037 100644 --- a/server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java +++ b/server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java @@ -105,7 +105,7 @@ public void searchAndAuthenticateUser( final AuthenticationResult authResult = authEngine.authenticateUser( password ); postAuthenticationSequence( userIdentity, authResult ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { postFailureSequence( e, username, userIdentity ); @@ -146,7 +146,7 @@ private Set readHiddenErrorTypes( ) returnSet.add( pwmError ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( pwmSession, "error parsing app property " + AppProperty.SECURITY_LOGIN_HIDDEN_ERROR_TYPES.getKey() + ", error: " + e.getMessage() ); @@ -174,11 +174,11 @@ public void authenticateUser( final AuthenticationResult authResult = authEngine.authenticateUser( password ); postAuthenticationSequence( userIdentity, authResult ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { postFailureSequence( e, null, userIdentity ); throw e; @@ -210,11 +210,11 @@ public void authUserWithUnknownPassword( final AuthenticationResult authResult = authEngine.authUsingUnknownPw(); postAuthenticationSequence( userIdentity, authResult ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { postFailureSequence( e, username, userIdentity ); throw e; @@ -239,7 +239,7 @@ public void authUserWithUnknownPassword( final AuthenticationResult authResult = authEngine.authUsingUnknownPw(); postAuthenticationSequence( userIdentity, authResult ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -301,7 +301,7 @@ public static void simulateBadPassword( final CommonValues commonValues, final U LOGGER.debug( sessionLabel, () -> "bad-password login attempt succeeded for " + userIdentity ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { if ( e.getErrorCode() == ChaiError.PASSWORD_BADPASSWORD ) { @@ -320,7 +320,7 @@ public static void simulateBadPassword( final CommonValues commonValues, final U { provider.close(); } - catch ( Throwable e ) + catch ( final Throwable e ) { LOGGER.error( sessionLabel, "unexpected error closing invalid ldap connection after simulated bad-password failed login attempt: " + e.getMessage() ); diff --git a/server/src/main/java/password/pwm/ldap/auth/SimpleLdapAuthenticator.java b/server/src/main/java/password/pwm/ldap/auth/SimpleLdapAuthenticator.java index 8733e0316..6212f5c0b 100644 --- a/server/src/main/java/password/pwm/ldap/auth/SimpleLdapAuthenticator.java +++ b/server/src/main/java/password/pwm/ldap/auth/SimpleLdapAuthenticator.java @@ -64,11 +64,11 @@ public static AuthenticationResult authenticateUser( { authResult = authEngine.authenticateUser( password ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } diff --git a/server/src/main/java/password/pwm/ldap/schema/EdirSchemaExtender.java b/server/src/main/java/password/pwm/ldap/schema/EdirSchemaExtender.java index d08ef69f0..8f3fb3bd2 100644 --- a/server/src/main/java/password/pwm/ldap/schema/EdirSchemaExtender.java +++ b/server/src/main/java/password/pwm/ldap/schema/EdirSchemaExtender.java @@ -58,7 +58,7 @@ public void init( final ChaiProvider chaiProvider ) throws PwmUnrecoverableExcep { schemaEntry = chaiProvider.getEntryFactory().newChaiEntry( LDAP_SCHEMA_DN ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } @@ -119,11 +119,11 @@ private void execute( final boolean readOnly ) throws PwmUnrecoverableException } } } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, e.getMessage() ) ); } @@ -151,7 +151,7 @@ private void checkObjectclass( final boolean readOnly, final SchemaDefinition sc logActivity( "+ objectclass '" + name + "' has been modified" ); stateMap.put( name, SchemaDefinition.State.correct ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { logActivity( "error while updating objectclass definition '" + name + "', error: " + e.getMessage() ); } @@ -170,7 +170,7 @@ private void checkObjectclass( final boolean readOnly, final SchemaDefinition sc logActivity( "+ objectclass '" + name + "' has been added" ); stateMap.put( name, SchemaDefinition.State.correct ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { logActivity( "error while updating objectclass definition '" + name + "', error: " + e.getMessage() ); } @@ -197,7 +197,7 @@ private void checkAttribute( final boolean readOnly, final SchemaDefinition sche logActivity( "+ attribute '" + name + "' has been modified" ); stateMap.put( name, SchemaDefinition.State.correct ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { logActivity( "error while updating attribute definition '" + name + "', error: " + e.getMessage() ); } @@ -216,7 +216,7 @@ private void checkAttribute( final boolean readOnly, final SchemaDefinition sche logActivity( "+ attribute '" + name + "' has been added" ); stateMap.put( name, SchemaDefinition.State.correct ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { logActivity( "error while adding attribute definition '" + name + "', error: " + e.getMessage() ); } @@ -253,7 +253,7 @@ private boolean checkObjectclassCorrectness( final SchemaDefinition schemaDefini } } } - catch ( IOException e ) + catch ( final IOException e ) { e.printStackTrace(); } @@ -286,7 +286,7 @@ private boolean checkAttributeCorrectness( final SchemaDefinition schemaDefiniti } } } - catch ( IOException e ) + catch ( final IOException e ) { e.printStackTrace(); } @@ -306,7 +306,7 @@ private Map readSchemaAttributes( ) throws ChaiUnavailable { schemaParser = new SchemaParser( key ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error parsing schema attribute definition: " + e.getMessage() ); } @@ -333,7 +333,7 @@ private Map readSchemaObjectclasses( ) throws ChaiUnavaila { schemaParser = new SchemaParser( key ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error parsing schema objectclasses definition: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/ldap/schema/SchemaManager.java b/server/src/main/java/password/pwm/ldap/schema/SchemaManager.java index a8e60acf8..3f79b76d3 100644 --- a/server/src/main/java/password/pwm/ldap/schema/SchemaManager.java +++ b/server/src/main/java/password/pwm/ldap/schema/SchemaManager.java @@ -73,11 +73,11 @@ protected static SchemaExtender implForChaiProvider( final ChaiProvider chaiProv schemaExtenderImpl.init( chaiProvider ); return schemaExtenderImpl; } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error instantiating schema extender: " + e.getMessage(); LOGGER.error( errorMsg ); diff --git a/server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java b/server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java index 7a5f45632..671b1c587 100644 --- a/server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java +++ b/server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java @@ -152,7 +152,7 @@ public UserIdentity resolveUsername( { inputIdentity = UserIdentity.fromKey( username, pwmApplication ); } - catch ( PwmException e ) + catch ( final PwmException e ) { /* input is not a userIdentity */ } @@ -169,11 +169,11 @@ public UserIdentity resolveUsername( return new UserIdentity( canonicalDN, inputIdentity.getLdapProfileID() ); } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_CANT_MATCH_USER, e.getMessage() ) ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -203,7 +203,7 @@ public UserIdentity resolveUsername( return performSingleUserSearch( searchConfiguration, sessionLabel ); } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_CANT_MATCH_USER, @@ -211,7 +211,7 @@ public UserIdentity resolveUsername( e.getErrorInformation().getFieldValues() ) ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -355,7 +355,7 @@ public Map> performMultiUserSearch( returnAttributes ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { if ( e.getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE ) { @@ -521,11 +521,11 @@ private Map> executeSearch( { results = userSearchJob.getChaiProvider().search( userSearchJob.getContext(), searchHelper ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { throw new PwmOperationalException( PwmError.forChaiError( e.getErrorCode() ), "ldap error during searchID=" + searchID + ", error=" + e.getMessage() ); @@ -643,7 +643,7 @@ private UserIdentity resolveUserDN( { return new UserIdentity( user.readCanonicalDN(), ldapProfile.getIdentifier() ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.error( "unexpected error reading canonical userDN for '" + userDN + "', error: " + e.getMessage() ); } @@ -663,7 +663,7 @@ private Map> executeSearchJobs( final List jobs = new ArrayList<>(); { int jobID = 0; - for ( UserSearchJob userSearchJob : userSearchJobs ) + for ( final UserSearchJob userSearchJob : userSearchJobs ) { final int loopJobID = jobID++; @@ -698,7 +698,7 @@ private Map> executeSearchJobs( submittedToExecutor = true; backgroundJobCounter.incrementAndGet(); } - catch ( RejectedExecutionException e ) + catch ( final RejectedExecutionException e ) { // executor is full, so revert to running locally rejectionJobCounter.incrementAndGet(); @@ -712,7 +712,7 @@ private Map> executeSearchJobs( jobInfo.getFutureTask().run(); foregroundJobCounter.incrementAndGet(); } - catch ( Throwable t ) + catch ( final Throwable t ) { log( PwmLogLevel.ERROR, sessionLabel, searchID, jobInfo.getJobID(), "unexpected error running job in local thread: " + t.getMessage() ); } @@ -739,14 +739,14 @@ private Map> executeSearchJobs( { results.putAll( jobInfo.getFutureTask().get( maxWaitTime, TimeUnit.MILLISECONDS ) ); } - catch ( InterruptedException e ) + catch ( final InterruptedException e ) { final String errorMsg = "unexpected interruption during search job execution: " + e.getMessage(); log( PwmLogLevel.WARN, sessionLabel, searchID, jobInfo.getJobID(), errorMsg ); LOGGER.error( sessionLabel, errorMsg, e ); throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) ); } - catch ( ExecutionException e ) + catch ( final ExecutionException e ) { final Throwable t = e.getCause(); final ErrorInformation errorInformation; @@ -764,7 +764,7 @@ private Map> executeSearchJobs( log( PwmLogLevel.WARN, sessionLabel, searchID, jobInfo.getJobID(), "error during user search: " + errorInformation.toDebugStr() ); throw new PwmUnrecoverableException( errorInformation ); } - catch ( TimeoutException e ) + catch ( final TimeoutException e ) { final String errorMsg = "background search job timeout after " + jobInfo.getUserSearchJob().getTimeoutMs() + "ms, to ldapProfile '" diff --git a/server/src/main/java/password/pwm/svc/PwmServiceManager.java b/server/src/main/java/password/pwm/svc/PwmServiceManager.java index efc3fdd41..4c03f0e46 100644 --- a/server/src/main/java/password/pwm/svc/PwmServiceManager.java +++ b/server/src/main/java/password/pwm/svc/PwmServiceManager.java @@ -102,7 +102,7 @@ private PwmService initService( final Class serviceClass ) final Object newInstance = serviceClass.newInstance(); newServiceInstance = ( PwmService ) newInstance; } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error instantiating service class '" + serviceName + "', error: " + e.toString(); LOGGER.fatal( errorMsg, e ); @@ -116,11 +116,11 @@ private PwmService initService( final Class serviceClass ) final TimeDuration startupDuration = TimeDuration.fromCurrent( startTime ); LOGGER.debug( () -> "completed initialization of service " + serviceName + " in " + startupDuration.asCompactString() + ", status=" + newServiceInstance.status() ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.warn( "error instantiating service class '" + serviceName + "', service will remain unavailable, error: " + e.getMessage() ); } - catch ( Exception e ) + catch ( final Exception e ) { String errorMsg = "unexpected error instantiating service class '" + serviceName + "', cannot load, error: " + e.getMessage(); if ( e.getCause() != null ) @@ -170,7 +170,7 @@ private void shutDownService( final Class serviceClass ) final TimeDuration timeDuration = TimeDuration.fromCurrent( startTime ); LOGGER.trace( () -> "successfully closed service " + serviceClass.getName() + " (" + timeDuration.asCompactString() + ")" ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error closing " + loopService.getClass().getSimpleName() + ": " + e.getMessage(), e ); } diff --git a/server/src/main/java/password/pwm/svc/cache/MemoryCacheStore.java b/server/src/main/java/password/pwm/svc/cache/MemoryCacheStore.java index 14726b7f2..3207fede5 100644 --- a/server/src/main/java/password/pwm/svc/cache/MemoryCacheStore.java +++ b/server/src/main/java/password/pwm/svc/cache/MemoryCacheStore.java @@ -131,7 +131,7 @@ public int itemCount( ) public List getCacheDebugItems( ) { final List items = new ArrayList<>(); - for ( Map.Entry entry : memoryStore.asMap().entrySet() ) + for ( final Map.Entry entry : memoryStore.asMap().entrySet() ) { final CacheKey cacheKey = entry.getKey(); final CacheValueWrapper cacheValueWrapper = entry.getValue(); @@ -184,7 +184,7 @@ Map storedClassHistogram( final String prefix ) public long byteCount() { long byteCount = 0; - for ( Map.Entry entry : memoryStore.asMap().entrySet() ) + for ( final Map.Entry entry : memoryStore.asMap().entrySet() ) { final CacheKey cacheKey = entry.getKey(); final UserIdentity userIdentity = cacheKey.getUserIdentity(); diff --git a/server/src/main/java/password/pwm/svc/email/EmailServerUtil.java b/server/src/main/java/password/pwm/svc/email/EmailServerUtil.java index cb1fbd53b..9f296df5c 100644 --- a/server/src/main/java/password/pwm/svc/email/EmailServerUtil.java +++ b/server/src/main/java/password/pwm/svc/email/EmailServerUtil.java @@ -186,7 +186,7 @@ private static Properties makeJavaMailProps( properties.put( "mail.smtp.starttls.enable", useStartTls ); properties.put( "mail.smtp.starttls.required", useStartTls ); } - catch ( Exception e ) + catch ( final Exception e ) { final String msg = "unable to create message transport properties: " + e.getMessage(); throw new PwmUnrecoverableException( PwmError.CONFIG_FORMAT_ERROR, msg ); @@ -222,7 +222,7 @@ private static InternetAddress makeInternetAddress( final String input ) { address.setPersonal( splitString[ 0 ].trim(), PwmConstants.DEFAULT_CHARSET.toString() ); } - catch ( UnsupportedEncodingException e ) + catch ( final UnsupportedEncodingException e ) { LOGGER.error( "unsupported encoding error while parsing internet address '" + input + "', error: " + e.getMessage() ); } @@ -400,7 +400,7 @@ public static List readCertificates( final Configuration config { return certReaderTm.getCertificates(); } - catch ( Exception e ) + catch ( final Exception e ) { final String exceptionMessage = JavaHelper.readHostileExceptionMessage( e ); final String errorMsg = "error connecting to secure server while reading SMTP certificates: " + exceptionMessage; diff --git a/server/src/main/java/password/pwm/svc/email/EmailService.java b/server/src/main/java/password/pwm/svc/email/EmailService.java index 72faf0bf8..c5f9ed383 100644 --- a/server/src/main/java/password/pwm/svc/email/EmailService.java +++ b/server/src/main/java/password/pwm/svc/email/EmailService.java @@ -92,7 +92,7 @@ public void init( final PwmApplication pwmApplication ) { servers.addAll( EmailServerUtil.makeEmailServersMap( pwmApplication.getConfig() ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { startupError = e.getErrorInformation(); LOGGER.error( "unable to startup email service: " + e.getMessage() ); @@ -329,7 +329,7 @@ private void submitEmailImpl( workQueueProcessor.submit( finalBean ); } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.warn( "unable to add email to queue: " + e.getMessage() ); } @@ -403,7 +403,7 @@ private WorkQueueProcessor.ProcessResult sendItem( final EmailItemBean emailItem StatisticsManager.incrementStat( pwmApplication, Statistic.EMAIL_SEND_SUCCESSES ); return WorkQueueProcessor.ProcessResult.SUCCESS; } - catch ( MessagingException | PwmException e ) + catch ( final MessagingException | PwmException e ) { final ErrorInformation errorInformation; @@ -466,7 +466,7 @@ private EmailConnection getSmtpTransport( ) serverErrors.remove( server ); return new EmailConnection( server, transport ); } - catch ( Exception e ) + catch ( final Exception e ) { final String exceptionMsg = JavaHelper.readHostileExceptionMessage( e ); final String msg = "unable to connect to email server '" + server.toDebugString() + "', error: " + exceptionMsg; diff --git a/server/src/main/java/password/pwm/svc/event/AuditRecordFactory.java b/server/src/main/java/password/pwm/svc/event/AuditRecordFactory.java index c02b34eb3..95f1fc3dd 100644 --- a/server/src/main/java/password/pwm/svc/event/AuditRecordFactory.java +++ b/server/src/main/java/password/pwm/svc/event/AuditRecordFactory.java @@ -240,7 +240,7 @@ private AuditUserDefinition userIdentityToUserDefinition( final UserIdentity use ); userID = userInfo.getUsername(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "unable to read userID for " + userIdentity + ", error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/event/AuditService.java b/server/src/main/java/password/pwm/svc/event/AuditService.java index 69585c8c4..38b578443 100644 --- a/server/src/main/java/password/pwm/svc/event/AuditService.java +++ b/server/src/main/java/password/pwm/svc/event/AuditService.java @@ -116,7 +116,7 @@ public void init( final PwmApplication pwmApplication ) throws PwmException { syslogManager = new SyslogAuditService( pwmApplication ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SYSLOG_WRITE_ERROR, "startup error: " + e.getMessage() ); LOGGER.error( errorInformation.toDebugStr() ); @@ -375,7 +375,7 @@ public void submit( final AuditRecord auditRecord ) { auditVault.add( auditRecord ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.warn( "discarding audit event due to storage error: " + e.getMessage() ); } @@ -408,7 +408,7 @@ public void submit( final AuditRecord auditRecord ) { syslogManager.add( auditRecord ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { lastError = e.getErrorInformation(); } diff --git a/server/src/main/java/password/pwm/svc/event/DatabaseUserHistory.java b/server/src/main/java/password/pwm/svc/event/DatabaseUserHistory.java index ec65fccf5..5decf9c07 100644 --- a/server/src/main/java/password/pwm/svc/event/DatabaseUserHistory.java +++ b/server/src/main/java/password/pwm/svc/event/DatabaseUserHistory.java @@ -76,7 +76,7 @@ public void updateUserHistory( final UserAuditRecord auditRecord ) throws PwmUnr storedHistory.getRecords().add( auditRecord ); writeStoredHistory( guid, storedHistory ); } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, e.getMessage() ) ); } @@ -90,7 +90,7 @@ public List readUserHistory( final UserInfo userInfo ) throws P { return readStoredHistory( userGuid ).getRecords(); } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, e.getMessage() ) ); } diff --git a/server/src/main/java/password/pwm/svc/event/LdapXmlUserHistory.java b/server/src/main/java/password/pwm/svc/event/LdapXmlUserHistory.java index 41ccd92ff..a6ede7d13 100644 --- a/server/src/main/java/password/pwm/svc/event/LdapXmlUserHistory.java +++ b/server/src/main/java/password/pwm/svc/event/LdapXmlUserHistory.java @@ -82,7 +82,7 @@ public void updateUserHistory( final UserAuditRecord auditRecord ) { updateUserHistoryImpl( auditRecord ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) ); } @@ -124,7 +124,7 @@ private void updateUserHistoryImpl( final UserAuditRecord auditRecord ) { corList = ConfigObjectRecord.readRecordFromLDAP( theUser, corAttribute, corRecordIdentifer, null, null ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error reading LDAP user event history for user " + userIdentity.toDisplayString() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -145,7 +145,7 @@ private void updateUserHistoryImpl( final UserAuditRecord auditRecord ) storedHistory = StoredHistory.fromXml( theCor.getPayload() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "ldap error writing user event log: " + e.getMessage() ); return; @@ -164,7 +164,7 @@ private void updateUserHistoryImpl( final UserAuditRecord auditRecord ) { theCor.updatePayload( storedHistory.toXml() ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.error( "ldap error writing user event log: " + e.getMessage() ); } @@ -179,7 +179,7 @@ public List readUserHistory( final UserInfo userInfo ) final StoredHistory storedHistory = readUserHistory( pwmApplication, userInfo.getUserIdentity(), theUser ); return storedHistory.asAuditRecords( userInfo ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) ); } @@ -211,7 +211,7 @@ private StoredHistory readUserHistory( return StoredHistory.fromXml( theCor.getPayload() ); } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.error( "ldap error reading user event log: " + e.getMessage() ); } @@ -305,7 +305,7 @@ static StoredHistory fromXml( final String input ) returnHistory.addEvent( storedEvent ); } } - catch ( JDOMException | IOException e ) + catch ( final JDOMException | IOException e ) { LOGGER.error( "error parsing user event history record: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/event/LocalDbAuditVault.java b/server/src/main/java/password/pwm/svc/event/LocalDbAuditVault.java index 3c8093e52..1a6d36155 100644 --- a/server/src/main/java/password/pwm/svc/event/LocalDbAuditVault.java +++ b/server/src/main/java/password/pwm/svc/event/LocalDbAuditVault.java @@ -162,7 +162,7 @@ private static AuditRecord deSerializeRecord( final String input ) { event = AuditEvent.valueOf( eventCode ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { errorMsg = "error de-serializing audit record: " + e.getMessage(); LOGGER.error( errorMsg ); @@ -174,7 +174,7 @@ private static AuditRecord deSerializeRecord( final String input ) } } } - catch ( Exception e ) + catch ( final Exception e ) { errorMsg = e.getMessage(); } diff --git a/server/src/main/java/password/pwm/svc/event/SyslogAuditService.java b/server/src/main/java/password/pwm/svc/event/SyslogAuditService.java index 7f4f4ce53..25ec501f3 100644 --- a/server/src/main/java/password/pwm/svc/event/SyslogAuditService.java +++ b/server/src/main/java/password/pwm/svc/event/SyslogAuditService.java @@ -102,7 +102,7 @@ public class SyslogAuditService final List syslogConfigStringArray = configuration.readSettingAsStringArray( PwmSetting.AUDIT_SYSLOG_SERVERS ); try { - for ( String entry : syslogConfigStringArray ) + for ( final String entry : syslogConfigStringArray ) { final SyslogConfig syslogCfg = SyslogConfig.fromConfigString( entry ); final SyslogIF syslogInstance = makeSyslogInstance( syslogCfg ); @@ -110,7 +110,7 @@ public class SyslogAuditService } LOGGER.trace( () -> "queued service running for syslog entries" ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { LOGGER.error( "error parsing syslog configuration for syslogConfigStrings ERROR: " + e.getMessage() ); } @@ -215,7 +215,7 @@ public void add( final AuditRecord event ) throws PwmOperationalException { syslogMsg = auditFormatter.convertAuditRecordToMessage( pwmApplication, event ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String msg = "error generating syslog message text: " + e.getMessage(); final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_SYSLOG_WRITE_ERROR, msg ); @@ -228,7 +228,7 @@ public void add( final AuditRecord event ) throws PwmOperationalException { workQueueProcessor.submit( syslogMsg ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.warn( "unable to add syslog message to queue: " + e.getMessage() ); } @@ -252,7 +252,7 @@ public List healthCheck( ) private WorkQueueProcessor.ProcessResult processEvent( final String auditRecord ) { - for ( SyslogIF syslogInstance : syslogInstances ) + for ( final SyslogIF syslogInstance : syslogInstances ) { try { @@ -262,7 +262,7 @@ private WorkQueueProcessor.ProcessResult processEvent( final String auditRecord StatisticsManager.incrementStat( this.pwmApplication, Statistic.SYSLOG_MESSAGES_SENT ); return WorkQueueProcessor.ProcessResult.SUCCESS; } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error while sending syslog message to remote service: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SYSLOG_WRITE_ERROR, errorMsg, new String[] @@ -319,7 +319,7 @@ public static SyslogConfig fromConfigString( final String input ) throws Illegal { protocol = Protocol.valueOf( parts[ 0 ] ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { throw new IllegalArgumentException( "unknown protocol '" + parts[ 0 ] + "'" ); } @@ -329,7 +329,7 @@ public static SyslogConfig fromConfigString( final String input ) throws Illegal { port = Integer.parseInt( parts[ 2 ] ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { throw new IllegalArgumentException( "invalid port number '" + parts[ 2 ] + "'" ); } @@ -370,7 +370,7 @@ protected SocketFactory obtainSocketFactory( ) new java.security.SecureRandom() ); return sc.getSocketFactory(); } - catch ( NoSuchAlgorithmException | KeyManagementException e ) + catch ( final NoSuchAlgorithmException | KeyManagementException e ) { LOGGER.error( "unexpected error loading syslog certificates: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java b/server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java index f965a6866..19f9eedd9 100644 --- a/server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java +++ b/server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java @@ -85,7 +85,7 @@ public void close() { pwmHttpClient.close(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "error closing pwmHttpClient instance: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/httpclient/PwmHttpClient.java b/server/src/main/java/password/pwm/svc/httpclient/PwmHttpClient.java index 52ff99e2a..e5b5684fa 100644 --- a/server/src/main/java/password/pwm/svc/httpclient/PwmHttpClient.java +++ b/server/src/main/java/password/pwm/svc/httpclient/PwmHttpClient.java @@ -174,7 +174,7 @@ private static CloseableHttpClient makeHttpClient( clientBuilder.setSSLSocketFactory( sslConnectionFactory ); clientBuilder.setConnectionManager( ccm ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "unexpected error creating promiscuous https client: " + e.getMessage() ) ); } @@ -321,7 +321,7 @@ public PwmHttpClientResponse makeRequest( { return makeRequestImpl( clientRequest, sessionLabel ); } - catch ( IOException e ) + catch ( final IOException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_UNREACHABLE, "error while making http request: " + e.getMessage() ), e ); } @@ -407,7 +407,7 @@ private HttpResponse executeRequest( final PwmHttpClientRequest clientRequest ) ( ( HttpPost ) httpRequest ).setEntity( new StringEntity( requestBody, PwmConstants.DEFAULT_CHARSET ) ); } } - catch ( URISyntaxException e ) + catch ( final URISyntaxException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "malformed url: " + clientRequest.getUrl() + ", error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/intruder/DataStoreRecordStore.java b/server/src/main/java/password/pwm/svc/intruder/DataStoreRecordStore.java index c036d0ff0..544ec90d4 100644 --- a/server/src/main/java/password/pwm/svc/intruder/DataStoreRecordStore.java +++ b/server/src/main/java/password/pwm/svc/intruder/DataStoreRecordStore.java @@ -66,7 +66,7 @@ public IntruderRecord read( final String key ) { value = dataStore.get( key ); } - catch ( PwmDataStoreException e ) + catch ( final PwmDataStoreException e ) { LOGGER.error( "error reading stored intruder record: " + e.getMessage() ); if ( e.getError() == PwmError.ERROR_DB_UNAVAILABLE ) @@ -85,7 +85,7 @@ public IntruderRecord read( final String key ) { return JsonUtil.deserialize( value, IntruderRecord.class ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error decoding IntruderRecord:" + e.getMessage() ); } @@ -95,7 +95,7 @@ public IntruderRecord read( final String key ) { dataStore.remove( key ); } - catch ( PwmDataStoreException e ) + catch ( final PwmDataStoreException e ) { /*noop*/ } @@ -111,7 +111,7 @@ public void write( final String key, final IntruderRecord record ) throws PwmOpe { dataStore.put( key, jsonRecord ); } - catch ( PwmDataStoreException e ) + catch ( final PwmDataStoreException e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_LOCALDB_UNAVAILABLE, "error writing to LocalDB: " + e.getMessage() ) ); } @@ -124,7 +124,7 @@ public ClosableIterator iterator( ) throws PwmOperationalExcepti { return new RecordIterator( dataStore.iterator() ); } - catch ( PwmDataStoreException e ) + catch ( final PwmDataStoreException e ) { throw new PwmOperationalException( PwmError.ERROR_INTERNAL, "iterator unavailable:" + e.getMessage() ); } @@ -153,7 +153,7 @@ public IntruderRecord next( ) { return read( key ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { throw new IllegalStateException( e ); } @@ -202,7 +202,7 @@ public void cleanup( final TimeDuration maxRecordAge ) dataStore.remove( key ); } } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( "unable to perform removal of identified stale records: " + e.getMessage() ); } @@ -239,7 +239,7 @@ private List discoverPurgableKeys( final TimeDuration maxRecordAge ) } } } - catch ( PwmDataStoreException | PwmUnrecoverableException e ) + catch ( final PwmDataStoreException | PwmUnrecoverableException e ) { LOGGER.error( "unable to perform intruder table cleanup: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/intruder/IntruderManager.java b/server/src/main/java/password/pwm/svc/intruder/IntruderManager.java index a067fddd7..27acd937c 100644 --- a/server/src/main/java/password/pwm/svc/intruder/IntruderManager.java +++ b/server/src/main/java/password/pwm/svc/intruder/IntruderManager.java @@ -182,7 +182,7 @@ public void run( ) { recordStore.cleanup( TimeDuration.of( maxRecordAge, TimeDuration.Unit.MILLISECONDS ) ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error cleaning recordStore: " + e.getMessage(), e ); } @@ -195,7 +195,7 @@ public void run( ) initializeRecordManagers( config, recordStore ); status = STATUS.OPEN; } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "unexpected error starting intruder manager: " + e.getMessage() ); LOGGER.error( errorInformation.toDebugStr() ); @@ -371,7 +371,7 @@ public void mark( final RecordType recordType, final String subject, final Sessi return; } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error examining address: " + subject ); } @@ -405,7 +405,7 @@ public void mark( final RecordType recordType, final String subject, final Sessi { check( recordType, subject ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { if ( !manager.isAlerted( subject ) ) { @@ -487,7 +487,7 @@ private void sendAlert( final IntruderRecord intruderRecord, final SessionLabel final UserIdentity identity = UserIdentity.fromDelimitedKey( intruderRecord.getSubject() ); sendIntruderNoticeEmail( pwmApplication, sessionLabel, identity ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "unable to send intruder mail, can't read userDN/ldapProfile from stored record: " + e.getMessage() ); } @@ -518,7 +518,7 @@ public List> getRecords( final RecordType recordType, final check( recordType, intruderRecord.getSubject() ); rowData.put( "status", "watching" ); } - catch ( PwmException e ) + catch ( final PwmException e ) { rowData.put( "status", "locked" ); } @@ -707,7 +707,7 @@ private static void sendIntruderNoticeEmail( pwmApplication.getEmailQueue().submitEmail( configuredEmailSetting, userInfo, macroMachine ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error reading user info while sending intruder notice for user " + userIdentity + ", error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/intruder/RecordManagerImpl.java b/server/src/main/java/password/pwm/svc/intruder/RecordManagerImpl.java index 7fb451196..bc61a7740 100644 --- a/server/src/main/java/password/pwm/svc/intruder/RecordManagerImpl.java +++ b/server/src/main/java/password/pwm/svc/intruder/RecordManagerImpl.java @@ -139,7 +139,7 @@ public IntruderRecord readIntruderRecord( final String subject ) { return recordStore.read( makeKey( subject ) ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( "unable to read read intruder record from storage: " + e.getMessage() ); } @@ -152,7 +152,7 @@ private void writeIntruderRecord( final IntruderRecord intruderRecord ) { recordStore.write( makeKey( intruderRecord.getSubject() ), intruderRecord ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.warn( "unexpected error attempting to write intruder record " + JsonUtil.serialize( intruderRecord ) + ", error: " + e.getMessage() ); } @@ -165,7 +165,7 @@ private String makeKey( final String subject ) throws PwmOperationalException { hash = SecureEngine.hash( subject, KEY_HASH_ALG ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { throw new PwmOperationalException( PwmError.ERROR_INTERNAL, "error generating md5sum for intruder record: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/node/DatabaseNodeDataService.java b/server/src/main/java/password/pwm/svc/node/DatabaseNodeDataService.java index c2c9d6401..97eac1162 100644 --- a/server/src/main/java/password/pwm/svc/node/DatabaseNodeDataService.java +++ b/server/src/main/java/password/pwm/svc/node/DatabaseNodeDataService.java @@ -92,7 +92,7 @@ public Map readStoredData( ) } } } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { throw new PwmUnrecoverableException( PwmError.ERROR_DB_UNAVAILABLE, "unexpected database error reading cluster node status: " + e.getMessage() ); } @@ -109,7 +109,7 @@ public void writeNodeStatus( final StoredNodeData storedNodeData ) throws PwmUnr final String value = JsonUtil.serialize( storedNodeData ); databaseAccessor.put( TABLE, key, value ); } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { throw new PwmUnrecoverableException( PwmError.ERROR_DB_UNAVAILABLE, "unexpected database error writing cluster node status: " + e.getMessage() ); } @@ -141,7 +141,7 @@ public int purgeOutdatedNodes( final TimeDuration maxNodeAge ) } } } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { throw new PwmUnrecoverableException( PwmError.ERROR_DB_UNAVAILABLE, "unexpected database error writing cluster node status: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/node/LDAPNodeDataService.java b/server/src/main/java/password/pwm/svc/node/LDAPNodeDataService.java index a45ff66d0..aca59fd82 100644 --- a/server/src/main/java/password/pwm/svc/node/LDAPNodeDataService.java +++ b/server/src/main/java/password/pwm/svc/node/LDAPNodeDataService.java @@ -82,7 +82,7 @@ public Map readStoredData( ) throws PwmUnrecoverableExce } } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw new PwmUnrecoverableException( PwmError.ERROR_LDAP_DATA_ERROR, "error reading node service data " + ldapHelper.debugInfo() + ", error: " + e.getMessage() ); @@ -113,7 +113,7 @@ public void writeNodeStatus( final StoredNodeData storedNodeData ) throws PwmUnr ldapHelper.getChaiUser().addAttribute( ldapHelper.getAttr(), newRawValue ); } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw new PwmUnrecoverableException( PwmError.ERROR_LDAP_DATA_ERROR, "error writing node service data " + ldapHelper.debugInfo() + ", error: " + e.getMessage() ); @@ -146,7 +146,7 @@ public int purgeOutdatedNodes( final TimeDuration maxNodeAge ) throws PwmUnrecov ldapHelper.getChaiUser().deleteAttribute( ldapHelper.getAttr(), oldRawValue ); nodesPurged++; } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw new PwmUnrecoverableException( PwmError.ERROR_LDAP_DATA_ERROR, "error purging node service data " + ldapHelper.debugInfo() + ", error: " + e.getMessage() ); diff --git a/server/src/main/java/password/pwm/svc/node/NodeMachine.java b/server/src/main/java/password/pwm/svc/node/NodeMachine.java index 3ba771c3e..ab0d392a5 100644 --- a/server/src/main/java/password/pwm/svc/node/NodeMachine.java +++ b/server/src/main/java/password/pwm/svc/node/NodeMachine.java @@ -78,7 +78,7 @@ public void close( ) public List nodes( ) throws PwmUnrecoverableException { final Map returnObj = new TreeMap<>(); - final String configHash = pwmApplication.getConfig().configurationHash(); + final String configHash = pwmApplication.getConfig().configurationHash( pwmApplication.getSecureService() ); for ( final StoredNodeData storedNodeData : knownNodes.values() ) { final boolean configMatch = configHash.equals( storedNodeData.getConfigHash() ); @@ -175,7 +175,7 @@ void writeNodeStatus( ) clusterDataServiceProvider.writeNodeStatus( storedNodeData ); nodeServiceStatistics.getClusterWrites().incrementAndGet(); } - catch ( PwmException e ) + catch ( final PwmException e ) { final String errorMsg = "error writing node service heartbeat: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NODE_SERVICE_ERROR, errorMsg ); @@ -192,7 +192,7 @@ void readNodeStatuses( ) knownNodes.putAll( readNodeData ); nodeServiceStatistics.getClusterReads().incrementAndGet(); } - catch ( PwmException e ) + catch ( final PwmException e ) { final String errorMsg = "error reading node statuses: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NODE_SERVICE_ERROR, errorMsg ); @@ -208,7 +208,7 @@ void purgeOutdatedNodes( ) final int purges = clusterDataServiceProvider.purgeOutdatedNodes( settings.getNodePurgeInterval() ); nodeServiceStatistics.getNodePurges().addAndGet( purges ); } - catch ( PwmException e ) + catch ( final PwmException e ) { final String errorMsg = "error purging outdated node reference: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NODE_SERVICE_ERROR, errorMsg ); diff --git a/server/src/main/java/password/pwm/svc/node/NodeService.java b/server/src/main/java/password/pwm/svc/node/NodeService.java index aa4b5b064..255338c09 100644 --- a/server/src/main/java/password/pwm/svc/node/NodeService.java +++ b/server/src/main/java/password/pwm/svc/node/NodeService.java @@ -108,12 +108,12 @@ public void init( final PwmApplication pwmApplication ) throws PwmException return; } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { startupError = e.getErrorInformation(); LOGGER.error( "error starting up node service: " + e.getMessage() ); } - catch ( Exception e ) + catch ( final Exception e ) { startupError = new ErrorInformation( PwmError.ERROR_NODE_SERVICE_ERROR, "error starting up node service: " + e.getMessage() ); LOGGER.error( startupError ); diff --git a/server/src/main/java/password/pwm/svc/node/StoredNodeData.java b/server/src/main/java/password/pwm/svc/node/StoredNodeData.java index fe6ca555f..2883206c0 100644 --- a/server/src/main/java/password/pwm/svc/node/StoredNodeData.java +++ b/server/src/main/java/password/pwm/svc/node/StoredNodeData.java @@ -47,7 +47,7 @@ static StoredNodeData makeNew( final PwmApplication pwmApplication ) pwmApplication.getStartupTime(), pwmApplication.getInstanceID(), pwmApplication.getRuntimeNonce(), - pwmApplication.getConfig().configurationHash() + pwmApplication.getConfig().configurationHash( pwmApplication.getSecureService() ) ); } } diff --git a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyDbStorageService.java b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyDbStorageService.java index 4e0380176..8ecf087ea 100644 --- a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyDbStorageService.java +++ b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyDbStorageService.java @@ -71,7 +71,7 @@ public Optional readStoredUserState( { rawDbValue = pwmApplication.getDatabaseAccessor().get( TABLE, guid ); } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, e.getMessage() ) ); } @@ -103,7 +103,7 @@ public void writeStoredUserState( { pwmApplication.getDatabaseAccessor().put( TABLE, guid, rawDbValue ); } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, e.getMessage() ) ); } @@ -122,7 +122,7 @@ public PwNotifyStoredJobState readStoredJobState() } return JsonUtil.deserialize( strValue, PwNotifyStoredJobState.class ); } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, e.getMessage() ) ); } @@ -137,7 +137,7 @@ public void writeStoredJobState( final PwNotifyStoredJobState pwNotifyStoredJobS final String strValue = JsonUtil.serialize( pwNotifyStoredJobState ); pwmApplication.getDatabaseService().getAccessor().put( DatabaseTable.PW_NOTIFY, DB_STATE_STRING, strValue ); } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, e.getMessage() ) ); } diff --git a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java index af99a3b09..cf0de6256 100644 --- a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java +++ b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java @@ -183,7 +183,7 @@ void executeJob( ) + ", sent " + noticeCount + " notices." ); } - catch ( PwmUnrecoverableException | PwmOperationalException e ) + catch ( final PwmUnrecoverableException | PwmOperationalException e ) { log( "error while executing job: " + e.getMessage() ); throw e; @@ -219,7 +219,7 @@ public void run() processUserIdentity( userIdentity ); debugOutputTask.conditionallyExecuteTask(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.trace( () -> "unexpected error processing user '" + userIdentity.toDisplayString() + "', error: " + e.getMessage() ); } @@ -356,7 +356,7 @@ private void log( final String output ) debugWriter.append( msg ); debugWriter.flush(); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.warn( SessionLabel.PWNOTIFY_SESSION_LABEL, "unexpected IO error writing to debugWriter: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyLdapStorageService.java b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyLdapStorageService.java index d7c47f096..0dc838ade 100644 --- a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyLdapStorageService.java +++ b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyLdapStorageService.java @@ -124,13 +124,13 @@ public void writeStoredUserState( { configObjectRecord.updatePayload( payload ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String msg = "error writing user pwNotifyStatus attribute '" + getLdapUserAttribute( userIdentity ) + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_LDAP_DATA_ERROR, msg ); throw new PwmUnrecoverableException( errorInformation ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -163,13 +163,13 @@ public void writeStoredJobState( final PwNotifyStoredJobState pwNotifyStoredJobS { configObjectRecord.updatePayload( payload ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String msg = "error writing user pwNotifyStatus attribute on proxy user '" + getLdapUserAttribute( proxyUser ) + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_LDAP_DATA_ERROR, msg ); throw new PwmUnrecoverableException( errorInformation ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -197,7 +197,7 @@ private ConfigObjectRecord getUserCOR( final UserIdentity userIdentity, final Co return list.iterator().next(); } } - catch ( ChaiUnavailableException | ChaiOperationException e ) + catch ( final ChaiUnavailableException | ChaiOperationException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } diff --git a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java index 38621a1f3..2bafcfbd9 100644 --- a/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java +++ b/server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java @@ -146,7 +146,7 @@ public void init( final PwmApplication pwmApplication ) throws PwmException setStatus( STATUS.OPEN ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { setStatus( STATUS.CLOSED ); LOGGER.trace( SessionLabel.PWNOTIFY_SESSION_LABEL, () -> "will remain closed, pw notify feature is not enabled due to error: " + e.getMessage() ); @@ -166,7 +166,7 @@ private void scheduleNextJobExecution() nextExecutionTime = figureNextJobExecutionTime(); LOGGER.debug( SessionLabel.PWNOTIFY_SESSION_LABEL, () -> "scheduled next job execution at " + nextExecutionTime.toString() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( SessionLabel.PWNOTIFY_SESSION_LABEL, "error calculating next job execution time: " + e.getMessage() ); } @@ -232,7 +232,7 @@ protected List serviceHealthCheck( ) } } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( SessionLabel.PWNOTIFY_SESSION_LABEL, "error while generating health information: " + e.getMessage() ); } @@ -294,7 +294,7 @@ public void run( ) doJob(); scheduleNextJobExecution(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( SessionLabel.PWNOTIFY_SESSION_LABEL, "unexpected error running job: " + e.getMessage() ); } @@ -315,7 +315,7 @@ private void doJob( ) final PwNotifyStoredJobState pwNotifyStoredJobState = new PwNotifyStoredJobState( start, finish, pwmApplication.getInstanceID(), null, true ); storageService.writeStoredJobState( pwNotifyStoredJobState ); } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation; if ( e instanceof PwmException ) @@ -335,7 +335,7 @@ private void doJob( ) { storageService.writeStoredJobState( pwNotifyStoredJobState ); } - catch ( Exception e2 ) + catch ( final Exception e2 ) { //no hope } diff --git a/server/src/main/java/password/pwm/svc/report/ReportService.java b/server/src/main/java/password/pwm/svc/report/ReportService.java index 5acc2d2f9..f625927f2 100644 --- a/server/src/main/java/password/pwm/svc/report/ReportService.java +++ b/server/src/main/java/password/pwm/svc/report/ReportService.java @@ -128,7 +128,7 @@ public void init( final PwmApplication pwmApplication ) userCacheService = new UserCacheService(); userCacheService.init( pwmApplication ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( SessionLabel.REPORTING_SESSION_LABEL, "unable to init cache service" ); status = STATUS.CLOSED; @@ -170,7 +170,7 @@ private void writeReportStatus( ) { pwmApplication.writeAppAttribute( PwmApplication.AppAttribute.REPORT_STATUS, reportStatus.get() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( SessionLabel.REPORTING_SESSION_LABEL, "error writing cached report dredge info into memory: " + e.getMessage() ); } @@ -295,7 +295,7 @@ public UserCacheRecord next( ) } } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected iterator traversal error while reading LocalDB: " + e.getMessage() ); } @@ -340,7 +340,7 @@ public void run( ) readUserListFromLdap(); executorService.execute( new ProcessWorkQueueTask() ); } - catch ( Exception e ) + catch ( final Exception e ) { boolean errorProcessed = false; if ( e instanceof PwmException ) @@ -437,7 +437,7 @@ public void run( ) writeReportStatus(); } } - catch ( PwmException e ) + catch ( final PwmException e ) { if ( e.getErrorInformation().getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE ) { @@ -529,7 +529,7 @@ private void processWorkQueue( ) TimeDuration.of( avgTracker.avgAsLong(), TimeDuration.Unit.MILLISECONDS ).pause(); } } - catch ( Exception e ) + catch ( final Exception e ) { String errorMsg = "error while updating report cache for " + userIdentity.toString() + ", cause: "; errorMsg += e instanceof PwmException ? ( ( PwmException ) e ).getErrorInformation().toDebugStr() : e.getMessage(); @@ -625,7 +625,7 @@ public void run( ) initTempData(); LOGGER.debug( SessionLabel.REPORTING_SESSION_LABEL, () -> "report service initialized: " + JsonUtil.serialize( reportStatus.get() ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( SessionLabel.REPORTING_SESSION_LABEL, "error during initialization: " + e.getMessage() ); status = STATUS.CLOSED; @@ -648,7 +648,7 @@ private void initTempData( ) final ReportStatusInfo localReportStatus = pwmApplication.readAppAttribute( PwmApplication.AppAttribute.REPORT_STATUS, ReportStatusInfo.class ); reportStatus.set( localReportStatus ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( SessionLabel.REPORTING_SESSION_LABEL, "error loading cached report status info into memory: " + e.getMessage() ); } @@ -698,7 +698,7 @@ public void run( ) { doClear(); } - catch ( LocalDBException | PwmUnrecoverableException e ) + catch ( final LocalDBException | PwmUnrecoverableException e ) { LOGGER.error( SessionLabel.REPORTING_SESSION_LABEL, "error during clear operation: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/report/ReportSettings.java b/server/src/main/java/password/pwm/svc/report/ReportSettings.java index 85de4d2a3..3a43f031f 100644 --- a/server/src/main/java/password/pwm/svc/report/ReportSettings.java +++ b/server/src/main/java/password/pwm/svc/report/ReportSettings.java @@ -23,7 +23,6 @@ import lombok.Builder; import lombok.Value; import password.pwm.AppProperty; -import password.pwm.PwmConstants; import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.value.data.UserPermission; @@ -31,6 +30,7 @@ import password.pwm.util.java.JsonUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; +import password.pwm.util.secure.PwmHashAlgorithm; import password.pwm.util.secure.SecureEngine; import java.io.Serializable; @@ -129,7 +129,7 @@ private static List parseDayIntervalStr( final Configuration configurat final int dayValue = Integer.parseInt( splitDay ); returnValue.add( dayValue ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { LOGGER.error( "error parsing reporting summary day value '" + splitDay + "', error: " + e.getMessage() ); } @@ -141,6 +141,6 @@ private static List parseDayIntervalStr( final Configuration configurat String getSettingsHash( ) throws PwmUnrecoverableException { - return SecureEngine.hash( JsonUtil.serialize( this ), PwmConstants.SETTING_CHECKSUM_HASH_METHOD ); + return SecureEngine.hash( JsonUtil.serialize( this ), PwmHashAlgorithm.SHA512 ); } } diff --git a/server/src/main/java/password/pwm/svc/report/UserCacheService.java b/server/src/main/java/password/pwm/svc/report/UserCacheService.java index 86dd7a449..73177bc59 100755 --- a/server/src/main/java/password/pwm/svc/report/UserCacheService.java +++ b/server/src/main/java/password/pwm/svc/report/UserCacheService.java @@ -68,7 +68,7 @@ UserCacheRecord updateUserCache( final UserInfo userInfo ) store( userCacheRecord ); return userCacheRecord; } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { LOGGER.error( "unable to store user status cache to localdb: " + e.getMessage() ); } @@ -104,7 +104,7 @@ public UserStatusCacheBeanIterator iterator( ) { return new UserStatusCacheBeanIterator<>(); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { LOGGER.error( "unexpected error generating user status iterator: " + e.getMessage() ); return null; @@ -239,7 +239,7 @@ private UserCacheRecord read( final StorageKey key ) { return JsonUtil.deserialize( jsonValue, UserCacheRecord.class ); } - catch ( JsonSyntaxException e ) + catch ( final JsonSyntaxException e ) { LOGGER.error( "error reading record from cache store for key=" + key.getKey() + ", error: " + e.getMessage() ); localDB.remove( DB, key.getKey() ); @@ -266,7 +266,7 @@ private long size( ) { return localDB.size( DB ); } - catch ( Exception e ) + catch ( final Exception e ) { return 0; } diff --git a/server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java b/server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java index 12a5fe9e0..89e277e24 100644 --- a/server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java +++ b/server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java @@ -134,7 +134,7 @@ public Map getDebugData( ) sizeTotal += pwmSession.size(); sessionCounter++; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error during session size calculation: " + e.getMessage() ); } @@ -146,7 +146,7 @@ public Map getDebugData( ) sessionCounter < 1 ? "0" : String.valueOf( ( int ) ( sizeTotal / sessionCounter ) ) ); return returnMap; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error during session debug generation: " + e.getMessage() ); } @@ -278,7 +278,7 @@ private static SessionStateInfoBean infoBeanFromPwmSession( final PwmSession loo ? loopUiBean.getUsername() : "" ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "unexpected error reading username: " + e.getMessage(), e ); } diff --git a/server/src/main/java/password/pwm/svc/sessiontrack/UserAgentUtils.java b/server/src/main/java/password/pwm/svc/sessiontrack/UserAgentUtils.java index b9c039217..672bf75ed 100644 --- a/server/src/main/java/password/pwm/svc/sessiontrack/UserAgentUtils.java +++ b/server/src/main/java/password/pwm/svc/sessiontrack/UserAgentUtils.java @@ -49,7 +49,7 @@ private static UserAgentParser loadUserAgentParser( ) { return new UserAgentService().loadParser(); } - catch ( IOException | ParseException e ) + catch ( final IOException | ParseException e ) { final String msg = "error loading user-agent parser: " + e.getMessage(); LOGGER.error( msg, e ); @@ -90,7 +90,7 @@ public static void checkIfPreIE11( final PwmRequest pwmRequest ) throws PwmUnrec badBrowser = true; } } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { LOGGER.error( "error parsing user-agent major version" + e.getMessage(), e ); } diff --git a/server/src/main/java/password/pwm/svc/shorturl/UrlShortenerService.java b/server/src/main/java/password/pwm/svc/shorturl/UrlShortenerService.java index 7980eab9c..900d33a69 100644 --- a/server/src/main/java/password/pwm/svc/shorturl/UrlShortenerService.java +++ b/server/src/main/java/password/pwm/svc/shorturl/UrlShortenerService.java @@ -81,15 +81,15 @@ public void init( final PwmApplication pwmApplication ) throws PwmUnrecoverableE theShortener = ( BasicUrlShortener ) theClass.newInstance(); theShortener.setConfiguration( sConfig ); } - catch ( java.lang.IllegalAccessException e ) + catch ( final java.lang.IllegalAccessException e ) { LOGGER.error( "Illegal access to class " + classNameString + ": " + e.toString() ); } - catch ( java.lang.InstantiationException e ) + catch ( final java.lang.InstantiationException e ) { LOGGER.error( "Cannot instantiate class " + classNameString + ": " + e.toString() ); } - catch ( java.lang.ClassNotFoundException e ) + catch ( final java.lang.ClassNotFoundException e ) { LOGGER.error( "Class " + classNameString + " not found: " + e.getMessage() ); } @@ -154,7 +154,7 @@ public String shortenUrlInText( final String text ) throws PwmUnrecoverableExcep return result.toString(); } } - catch ( PatternSyntaxException e ) + catch ( final PatternSyntaxException e ) { LOGGER.error( "Error compiling pattern: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/stats/StatisticsManager.java b/server/src/main/java/password/pwm/svc/stats/StatisticsManager.java index 0e7d95362..538386330 100644 --- a/server/src/main/java/password/pwm/svc/stats/StatisticsManager.java +++ b/server/src/main/java/password/pwm/svc/stats/StatisticsManager.java @@ -179,7 +179,7 @@ public StatisticsBundle getStatBundleForKey( final String key ) cachedStoredStats.put( key, returnBundle ); return returnBundle; } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { LOGGER.error( "error retrieving stored stat for " + key + ": " + e.getMessage() ); } @@ -250,7 +250,7 @@ public void init( final PwmApplication pwmApplication ) throws PwmException { statsCummulative = StatisticsBundle.input( storedCumulativeBundleSir ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "error loading saved stored cumulative statistics: " + e.getMessage() ); } @@ -278,7 +278,7 @@ public void init( final PwmApplication pwmApplication ) throws PwmException { localDB.put( LocalDB.DB.PWM_STATS, DB_KEY_TEMP, JavaHelper.toIsoDate( Instant.now() ) ); } - catch ( IllegalStateException e ) + catch ( final IllegalStateException e ) { LOGGER.error( "unable to write to localDB, will remain closed, error: " + e.getMessage() ); status = STATUS.CLOSED; @@ -309,7 +309,7 @@ private void writeDbValues( ) dbData.put( currentDailyKey.toString(), statsDaily.output() ); localDB.putAll( LocalDB.DB.PWM_STATS, dbData ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { LOGGER.error( "error outputting pwm statistics: " + e.getMessage() ); } @@ -348,7 +348,7 @@ public void close( ) { writeDbValues(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error closing: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/telemetry/FtpTelemetrySender.java b/server/src/main/java/password/pwm/svc/telemetry/FtpTelemetrySender.java index c0d74fbfc..c1557753e 100644 --- a/server/src/main/java/password/pwm/svc/telemetry/FtpTelemetrySender.java +++ b/server/src/main/java/password/pwm/svc/telemetry/FtpTelemetrySender.java @@ -99,7 +99,7 @@ private void ftpPut( final TelemetryPublishBean telemetryPublishBean ) throws Pw LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, () -> "connected to " + settings.getHost() ); } - catch ( IOException e ) + catch ( final IOException e ) { disconnectFtpClient( ftpClient ); final String msg = "unable to connect to " + settings.getHost() + ", error: " + e.getMessage(); @@ -120,7 +120,7 @@ private void ftpPut( final TelemetryPublishBean telemetryPublishBean ) throws Pw throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_TELEMETRY_SEND_ERROR, msg ) ); } } - catch ( IOException e ) + catch ( final IOException e ) { disconnectFtpClient( ftpClient ); final String msg = "unable to connect to " + settings.getHost() + ", error: " + e.getMessage(); @@ -142,7 +142,7 @@ private void ftpPut( final TelemetryPublishBean telemetryPublishBean ) throws Pw LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, () -> "authenticated to " + settings.getHost() + " as " + settings.getUsername() ); } - catch ( IOException e ) + catch ( final IOException e ) { disconnectFtpClient( ftpClient ); final String msg = "error authenticating as " + settings.getUsername() + " to " + settings.getHost() + ", error: " + e.getMessage(); @@ -172,7 +172,7 @@ private void ftpPut( final TelemetryPublishBean telemetryPublishBean ) throws Pw LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, () -> "completed transfer of " + fileBytes.length + " in " + TimeDuration.compactFromCurrent( startTime ) ); } - catch ( IOException e ) + catch ( final IOException e ) { disconnectFtpClient( ftpClient ); final String msg = "error uploading file to " + settings.getHost() + ", error: " + e.getMessage(); @@ -189,7 +189,7 @@ private void disconnectFtpClient( final FTPClient ftpClient ) ftpClient.disconnect(); LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, () -> "disconnected" ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, () -> "error while disconnecting ftp client: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java b/server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java index 70d70e9ba..b1d14c52d 100644 --- a/server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java +++ b/server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java @@ -127,7 +127,7 @@ public void init( final PwmApplication pwmApplication ) throws PwmException { initSender(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, () -> "will remain closed, unable to init sender: " + e.getMessage() ); status = STATUS.CLOSED; @@ -164,7 +164,7 @@ private void initSender( ) throws PwmUnrecoverableException final Class theClass = Class.forName( senderClass ); telemetrySender = ( TelemetrySender ) theClass.newInstance(); } - catch ( Exception e ) + catch ( final Exception e ) { final String msg = "unable to load implementation class: " + e.getMessage(); throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, msg ) ); @@ -175,7 +175,7 @@ private void initSender( ) throws PwmUnrecoverableException final String macrodSettings = MacroMachine.forNonUserSpecific( pwmApplication, null ).expandMacros( settings.getSenderSettings() ); telemetrySender.init( pwmApplication, macrodSettings ); } - catch ( Exception e ) + catch ( final Exception e ) { final String msg = "unable to init implementation class: " + e.getMessage(); throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, msg ) ); @@ -198,7 +198,7 @@ private void executePublishJob( ) throws PwmUnrecoverableException, IOException, sender.publish( telemetryPublishBean ); LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, () -> "sent telemetry data: " + JsonUtil.serialize( telemetryPublishBean ) ); } - catch ( PwmException e ) + catch ( final PwmException e ) { lastError = e.getErrorInformation(); LOGGER.error( SessionLabel.TELEMETRY_SESSION_LABEL, "error sending telemetry data: " + e.getMessage() ); @@ -226,11 +226,11 @@ public void run( ) { executePublishJob(); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( e.getErrorInformation() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error during telemetry publish job: " + e.getMessage() ); } @@ -298,7 +298,7 @@ public TelemetryPublishBean generatePublishableBean( ) ldapVendorName = pwmLdapVendor.name(); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.trace( SessionLabel.TELEMETRY_SESSION_LABEL, () -> "unable to read ldap vendor type for stats publication: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java b/server/src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java index 7f4885ec2..2e4b86ae8 100644 --- a/server/src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java +++ b/server/src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java @@ -95,7 +95,7 @@ private void purgeOutdatedTokens( ) throws retrieveToken( loopKey ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error while cleaning expired stored tokens: " + e.getMessage() ); } @@ -148,7 +148,7 @@ public TokenPayload retrieveToken( final TokenKey tokenKey ) { tokenPayload = tokenService.fromEncryptedString( storedRawValue ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.trace( () -> "error while trying to decrypted stored token payload for key '" + storedHash + "', will purge record, error: " + e.getMessage() ); dataStore.remove( storedHash ); diff --git a/server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java b/server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java index bd9ccc804..41ecd3c93 100644 --- a/server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java +++ b/server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java @@ -107,7 +107,7 @@ public TokenPayload retrieveToken( final TokenKey tokenKey ) return tokenService.fromEncryptedString( splitString[ 1 ] ); } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { if ( e.getError() == PwmError.ERROR_CANT_MATCH_USER ) { @@ -115,7 +115,7 @@ public TokenPayload retrieveToken( final TokenKey tokenKey ) } throw e; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "unexpected ldap error searching for token: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ); @@ -136,7 +136,7 @@ public void storeToken( final TokenKey tokenKey, final TokenPayload tokenPayload final ChaiUser chaiUser = pwmApplication.getProxiedChaiUser( userIdentity ); chaiUser.writeStringAttribute( tokenAttribute, md5sumToken + KEY_VALUE_DELIMITER + encodedTokenPayload ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final String errorMsg = "unexpected ldap error saving token: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -156,7 +156,7 @@ public void removeToken( final TokenKey tokenKey ) final ChaiUser chaiUser = pwmApplication.getProxiedChaiUser( userIdentity ); chaiUser.deleteAttribute( tokenAttribute, null ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final String errorMsg = "unexpected ldap error removing token: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); diff --git a/server/src/main/java/password/pwm/svc/token/TokenService.java b/server/src/main/java/password/pwm/svc/token/TokenService.java index 605be3e2d..0e7b321f1 100644 --- a/server/src/main/java/password/pwm/svc/token/TokenService.java +++ b/server/src/main/java/password/pwm/svc/token/TokenService.java @@ -182,7 +182,7 @@ public void init( final PwmApplication pwmApplication ) } serviceInfo = new ServiceInfoBean( Collections.singletonList( usedStorageMethod ) ); } - catch ( PwmException e ) + catch ( final PwmException e ) { final String errorMsg = "unable to start token manager: " + e.getErrorInformation().getDetailedErrorMsg(); final ErrorInformation newErrorInformation = new ErrorInformation( e.getError(), errorMsg ); @@ -224,7 +224,7 @@ public String generateNewToken( final TokenPayload tokenPayload, final SessionLa tokenKey = tokenMachine.generateToken( sessionLabel, tokenPayload ); tokenMachine.storeToken( tokenMachine.keyFromKey( tokenKey ), tokenPayload ); } - catch ( PwmException e ) + catch ( final PwmException e ) { final String errorMsg = "unexpected error trying to store token in datastore: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( e.getError(), errorMsg ); @@ -265,7 +265,7 @@ private void markTokenAsClaimed( LOGGER.trace( sessionLabel, () -> "removing claimed token: " + tokenPayload.toDebugString() ); tokenMachine.removeToken( tokenKey ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { LOGGER.error( sessionLabel, "error clearing claimed token: " + e.getMessage() ); } @@ -301,7 +301,7 @@ public TokenPayload retrieveTokenData( final SessionLabel sessionLabel, final St return storedToken; } } - catch ( PwmException e ) + catch ( final PwmException e ) { if ( e.getError() == PwmError.ERROR_TOKEN_EXPIRED || e.getError() == PwmError.ERROR_TOKEN_INCORRECT || e.getError() == PwmError.ERROR_TOKEN_MISSING_CONTACT ) { @@ -402,7 +402,7 @@ public void run( ) { tokenMachine.cleanup(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "unexpected error while cleaning expired stored tokens: " + e.getMessage(), e ); } @@ -428,7 +428,7 @@ public long size( ) { return tokenMachine.size(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error reading size of token storage table: " + e.getMessage() ); } @@ -521,7 +521,7 @@ TokenPayload fromEncryptedString( final String inputString ) final String decryptedString = pwmApplication.getSecureService().decryptStringValue( deWhiteSpacedToken ); return JsonUtil.deserialize( decryptedString, TokenPayload.class ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "unable to decrypt token payload: " + e.getErrorInformation().toDebugStr(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ); @@ -559,7 +559,7 @@ public TokenPayload processUserEnteredCode( markTokenAsClaimed( tokenMachine.keyFromKey( userEnteredCode ), sessionLabel, tokenPayload ); return tokenPayload; } - catch ( Exception e ) + catch ( final Exception e ) { final ErrorInformation errorInformation; if ( e instanceof PwmException ) @@ -596,7 +596,7 @@ private TokenPayload processUserEnteredCodeImpl( { tokenPayload = pwmApplication.getTokenService().retrieveTokenData( sessionLabel, userEnteredCode ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorMsg = "unexpected error attempting to read token from storage: " + e.getErrorInformation().toDebugStr(); throw new PwmOperationalException( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ); @@ -666,7 +666,7 @@ private TokenPayload processUserEnteredCodeImpl( } } } - catch ( ChaiUnavailableException | PwmUnrecoverableException e ) + catch ( final ChaiUnavailableException | PwmUnrecoverableException e ) { final String errorMsg = "unexpected error reading user's last password change time while validating token: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ); @@ -780,7 +780,7 @@ static TimeDuration maxTokenAge( final Configuration configuration ) long maxValue = 0; maxValue = Math.max( maxValue, configuration.readSettingAsLong( PwmSetting.TOKEN_LIFETIME ) ); maxValue = Math.max( maxValue, configuration.readSettingAsLong( PwmSetting.TOKEN_LIFETIME ) ); - for ( NewUserProfile newUserProfile : configuration.getNewUserProfiles().values() ) + for ( final NewUserProfile newUserProfile : configuration.getNewUserProfiles().values() ) { maxValue = Math.max( maxValue, newUserProfile.readSettingAsLong( PwmSetting.NEWUSER_TOKEN_LIFETIME_EMAIL ) ); maxValue = Math.max( maxValue, newUserProfile.readSettingAsLong( PwmSetting.NEWUSER_TOKEN_LIFETIME_SMS ) ); diff --git a/server/src/main/java/password/pwm/svc/token/TokenUtil.java b/server/src/main/java/password/pwm/svc/token/TokenUtil.java index 03e2259ab..6047ff31c 100644 --- a/server/src/main/java/password/pwm/svc/token/TokenUtil.java +++ b/server/src/main/java/password/pwm/svc/token/TokenUtil.java @@ -198,7 +198,7 @@ public static TokenPayload checkEnteredCode( return tokenPayload; } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorMsg = "token incorrect: " + e.getMessage(); throw PwmUnrecoverableException.newException( PwmError.ERROR_TOKEN_INCORRECT, errorMsg ); @@ -270,7 +270,7 @@ else if ( tokenInitAndSendRequest.getUserInfo() != null ) ); tokenKey = commonValues.getPwmApplication().getTokenService().generateNewToken( tokenPayload, commonValues.getSessionLabel() ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } diff --git a/server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java b/server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java index 0ea02529f..a881f7c2d 100644 --- a/server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java +++ b/server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java @@ -37,6 +37,7 @@ import password.pwm.util.java.JavaHelper; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.Percent; +import password.pwm.util.java.PwmCallable; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; @@ -245,7 +246,7 @@ public long size( ) { return wordlistBucket.size(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { getLogger().error( "error reading size: " + e.getMessage() ); } @@ -392,11 +393,6 @@ public void populate( final InputStream inputStream ) throws PwmUnrecoverableExc executorService.execute( new InspectorJob() ); } - private interface PwmCallable - { - void call() throws PwmUnrecoverableException; - } - private void cancelBackgroundAndRunImmediate( final PwmCallable runnable ) throws PwmUnrecoverableException { inhibitBackgroundImportFlag.set( true ); @@ -434,7 +430,7 @@ public void run() activity = Wordlist.Activity.Idle; } } - catch ( Throwable t ) + catch ( final Throwable t ) { getLogger().error( "error running InspectorJob: " + t.getMessage(), t ); throw t; diff --git a/server/src/main/java/password/pwm/svc/wordlist/AbstractWordlistBucket.java b/server/src/main/java/password/pwm/svc/wordlist/AbstractWordlistBucket.java index 686866410..677e5678a 100644 --- a/server/src/main/java/password/pwm/svc/wordlist/AbstractWordlistBucket.java +++ b/server/src/main/java/password/pwm/svc/wordlist/AbstractWordlistBucket.java @@ -125,7 +125,7 @@ public String randomSeed() throws PwmUnrecoverableException return getValue( seedlistLongToKey( randomKey ) ); } } - catch ( Exception e ) + catch ( final Exception e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "error while generating random word: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/wordlist/LocalDBWordlistBucket.java b/server/src/main/java/password/pwm/svc/wordlist/LocalDBWordlistBucket.java index 3147abcbd..55e7403fc 100644 --- a/server/src/main/java/password/pwm/svc/wordlist/LocalDBWordlistBucket.java +++ b/server/src/main/java/password/pwm/svc/wordlist/LocalDBWordlistBucket.java @@ -53,7 +53,7 @@ void putValues( final Map values ) { localDB.putAll( db, values ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_LOCALDB_UNAVAILABLE, "error while writing words to wordlist: " + e.getMessage() ); } @@ -67,7 +67,7 @@ String getValue( final String key ) { return pwmApplication.getLocalDB().get( db, key ); } - catch ( Exception e ) + catch ( final Exception e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "error while generating random word: " + e.getMessage() ); } @@ -81,7 +81,7 @@ boolean containsKey( final String key ) { return pwmApplication.getLocalDB().contains( db, key ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_LOCALDB_UNAVAILABLE, e.getMessage() ); } @@ -94,7 +94,7 @@ public long size() throws PwmUnrecoverableException { return localDB.size( db ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_LOCALDB_UNAVAILABLE, e.getMessage() ); } @@ -108,7 +108,7 @@ public void clear() throws PwmUnrecoverableException { localDB.truncate( db ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_LOCALDB_UNAVAILABLE, e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java b/server/src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java index a10d93f17..69c05d88e 100644 --- a/server/src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java +++ b/server/src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java @@ -120,7 +120,7 @@ public boolean containsWord( final String word ) } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "error checking global history list: " + e.getMessage() ); } @@ -151,7 +151,7 @@ public long size( ) { return localDB.size( WORDS_DB ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error checking wordlist size: " + e.getMessage() ); return 0; @@ -196,7 +196,7 @@ private void init( final PwmApplication pwmApplication, final long maxAgeMs ) { checkDbVersion(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error checking db version", e ); status = STATUS.CLOSED; @@ -218,7 +218,7 @@ private void init( final PwmApplication pwmApplication, final long maxAgeMs ) LOGGER.trace( () -> "oldest timestamp loaded from localDB, age is " + TimeDuration.fromCurrent( oldestEntry ).asCompactString() ); } } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { LOGGER.error( "unexpected error loading oldest-entry meta record, will remain closed: " + e.getMessage(), e ); status = STATUS.CLOSED; @@ -233,7 +233,7 @@ private void init( final PwmApplication pwmApplication, final long maxAgeMs ) + ", maxAgeMs=" + TimeDuration.of( maxAgeMs, TimeDuration.Unit.MILLISECONDS ).asCompactString() + ", oldestEntry=" + TimeDuration.fromCurrent( oldestEntry ).asCompactString() ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { LOGGER.error( "unexpected error examining size of DB, will remain closed: " + e.getMessage(), e ); status = STATUS.CLOSED; @@ -302,7 +302,7 @@ public synchronized void addWord( + " (" + TimeDuration.compactFromCurrent( startTime ) + ")" + " (" + this.size() + " total words)" ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( sessionLabel, "error adding word to global history list: " + e.getMessage() ); } @@ -335,7 +335,7 @@ public void run( ) { reduceWordDB(); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { LOGGER.error( "error during old record purge: " + e.getMessage() ); } @@ -406,7 +406,7 @@ private void reduceWordDB( ) keyIterator.close(); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "error returning LocalDB iterator: " + e.getMessage() ); } @@ -479,7 +479,7 @@ public void init( final PwmApplication pwmApplication ) { localDB.truncate( WORDS_DB ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error during wordlist truncate", e ); } diff --git a/server/src/main/java/password/pwm/svc/wordlist/WordlistConfiguration.java b/server/src/main/java/password/pwm/svc/wordlist/WordlistConfiguration.java index 4883653ed..156fda52e 100644 --- a/server/src/main/java/password/pwm/svc/wordlist/WordlistConfiguration.java +++ b/server/src/main/java/password/pwm/svc/wordlist/WordlistConfiguration.java @@ -164,7 +164,7 @@ private static String readAutoImportUrl( { return SecureEngine.hash( JsonUtil.serialize( WordlistConfiguration.this ), HASH_ALGORITHM ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { throw new IllegalStateException( "unexpected error generating wordlist-config hash: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/wordlist/WordlistImporter.java b/server/src/main/java/password/pwm/svc/wordlist/WordlistImporter.java index e116c3650..eea991277 100644 --- a/server/src/main/java/password/pwm/svc/wordlist/WordlistImporter.java +++ b/server/src/main/java/password/pwm/svc/wordlist/WordlistImporter.java @@ -124,7 +124,7 @@ public void run() { doImport(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { errorMsg = "error during import: " + e.getErrorInformation().getDetailedErrorMsg(); } @@ -353,7 +353,7 @@ private void skipForward( final long previousBytesRead ) getLogger().debug( () -> "will skip forward " + StringUtil.formatDiskSizeforDebug( previousBytesRead ) + " in wordlist that has been previously imported" ); - while ( !cancelFlag.getAsBoolean() && bytesSkipped < ( previousBytesRead + 1024 ) ) + while ( !cancelFlag.getAsBoolean() && bytesSkipped < previousBytesRead ) { zipFileReader.nextLine(); bytesSkipped = zipFileReader.getByteCount(); @@ -399,7 +399,7 @@ private Map makeStatValues() } } } - catch ( Exception e ) + catch ( final Exception e ) { getLogger().error( "error calculating import statistics: " + e.getMessage() ); @@ -443,7 +443,7 @@ private Map makeStatValues() { stats.put( DebugKey.WordsImported, PwmNumberFormat.forDefaultLocale().format( wordlistBucket.size() ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { getLogger().debug( () -> "error while calculating wordsImported stat during wordlist import: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/svc/wordlist/WordlistInspector.java b/server/src/main/java/password/pwm/svc/wordlist/WordlistInspector.java index cd22e947b..1c7038a23 100644 --- a/server/src/main/java/password/pwm/svc/wordlist/WordlistInspector.java +++ b/server/src/main/java/password/pwm/svc/wordlist/WordlistInspector.java @@ -55,7 +55,7 @@ public void run() { checkPopulation(); } - catch ( Exception e ) + catch ( final Exception e ) { getLogger().error( "unexpected error running population worker: " + e.getMessage(), e ); } @@ -96,7 +96,7 @@ private void checkPopulation( ) { checkAutoPopulation( existingStatus ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { getLogger().error( "error importing auto-import wordlist: " + e.getMessage() ); rootWordlist.setAutoImportError( e.getErrorInformation() ); @@ -274,7 +274,7 @@ private boolean checkIfExistingOkay( { testWordlistSource.readRemoteWordlistInfo( pwmApplication, cancelFlag, getLogger() ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { rootWordlist.setAutoImportError( e.getErrorInformation() ); getLogger().debug( () -> "existing stored list is not type AutoImport but auto-import is configured" @@ -330,7 +330,7 @@ private void checkAutoPopulation( getLogger().debug( () -> "auto-import url remote hash does not equal currently stored hash, will start auto-import" ); needsAutoImport = true; } - else if ( remoteInfo.getBytes() > existingStatus.getBytes() || !existingStatus.isCompleted() ) + else if ( !existingStatus.isCompleted() ) { getLogger().debug( () -> "auto-import did not previously complete, will continue previous import" ); needsAutoImport = true; diff --git a/server/src/main/java/password/pwm/svc/wordlist/WordlistSource.java b/server/src/main/java/password/pwm/svc/wordlist/WordlistSource.java index 2df2767fc..a7c075a7e 100644 --- a/server/src/main/java/password/pwm/svc/wordlist/WordlistSource.java +++ b/server/src/main/java/password/pwm/svc/wordlist/WordlistSource.java @@ -92,7 +92,7 @@ static WordlistSource forAutoImport( final PwmApplication pwmApplication, final final URL url = new URL( importUrl ); return url.openStream(); } - catch ( IOException e ) + catch ( final IOException e ) { final String msg = "unable to open auto-import URL: " + e.getMessage(); throw PwmUnrecoverableException.newException( PwmError.ERROR_WORDLIST_IMPORT_ERROR, msg ); @@ -166,15 +166,16 @@ WordlistSourceInfo readRemoteWordlistInfo( new ConditionalTaskExecutor.TimeDurationPredicate( AbstractWordlist.DEBUG_OUTPUT_FREQUENCY ) ); - bytes = JavaHelper.copyWhilePredicate( + JavaHelper.copyWhilePredicate( countingInputStream, new NullOutputStream(), WordlistConfiguration.STREAM_BUFFER_SIZE, o -> !cancelFlag.getAsBoolean(), debugOutputter ); + bytes = countingInputStream.getByteCount(); hash = JavaHelper.byteArrayToHexString( checksumInputStream.getMessageDigest().digest() ); } - catch ( IOException e ) + catch ( final IOException e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_WORDLIST_IMPORT_ERROR, diff --git a/server/src/main/java/password/pwm/svc/wordlist/WordlistStatus.java b/server/src/main/java/password/pwm/svc/wordlist/WordlistStatus.java index e689db6ce..b778e73ef 100644 --- a/server/src/main/java/password/pwm/svc/wordlist/WordlistStatus.java +++ b/server/src/main/java/password/pwm/svc/wordlist/WordlistStatus.java @@ -32,7 +32,7 @@ @Builder( toBuilder = true ) public class WordlistStatus implements Serializable { - public static final int CURRENT_VERSION = 7; + public static final int CURRENT_VERSION = 8; @Builder.Default private int version = CURRENT_VERSION; diff --git a/server/src/main/java/password/pwm/svc/wordlist/WordlistZipReader.java b/server/src/main/java/password/pwm/svc/wordlist/WordlistZipReader.java index b6b00c1a8..c6b9b5365 100644 --- a/server/src/main/java/password/pwm/svc/wordlist/WordlistZipReader.java +++ b/server/src/main/java/password/pwm/svc/wordlist/WordlistZipReader.java @@ -80,7 +80,7 @@ private void nextZipEntry( ) reader = new BufferedReader( new InputStreamReader( zipStream, PwmConstants.DEFAULT_CHARSET ) ); } } - catch ( IOException e ) + catch ( final IOException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_WORDLIST_IMPORT_ERROR, "error reading wordlist zip: " + e.getMessage() ); } @@ -92,7 +92,7 @@ public void close( ) { zipStream.close(); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.debug( () -> "error closing zip stream: " + e.getMessage() ); } @@ -101,7 +101,7 @@ public void close( ) { reader.close(); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.debug( () -> "error closing zip stream: " + e.getMessage() ); } @@ -123,7 +123,7 @@ String nextLine( ) { line = reader.readLine(); } - catch ( IOException e ) + catch ( final IOException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_WORDLIST_IMPORT_ERROR, "error reading zip wordlist file: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/BasicAuthInfo.java b/server/src/main/java/password/pwm/util/BasicAuthInfo.java index abda2e5bc..0f25c6b17 100644 --- a/server/src/main/java/password/pwm/util/BasicAuthInfo.java +++ b/server/src/main/java/password/pwm/util/BasicAuthInfo.java @@ -87,7 +87,7 @@ public static BasicAuthInfo parseAuthHeader( // "cn=user,o=company:chpass" or "user:chpass" return parseHeaderString( decoded ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.debug( () -> "error decoding auth header" + e.getMessage() ); } @@ -117,7 +117,7 @@ public static BasicAuthInfo parseHeaderString( final String input ) return new BasicAuthInfo( input, null ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error decoding auth header: " + e.getMessage() ); throw new IllegalArgumentException( "invalid basic authentication input string: " + e.getMessage(), e ); diff --git a/server/src/main/java/password/pwm/util/CaptchaUtility.java b/server/src/main/java/password/pwm/util/CaptchaUtility.java index 8c118cc73..59857f2cc 100644 --- a/server/src/main/java/password/pwm/util/CaptchaUtility.java +++ b/server/src/main/java/password/pwm/util/CaptchaUtility.java @@ -169,7 +169,7 @@ public static boolean verifyReCaptcha( } } } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error during reCaptcha API execution: " + e.getMessage(); LOGGER.error( errorMsg, e ); @@ -351,7 +351,7 @@ private static boolean checkIfCaptchaParamPresent( final PwmRequest pwmRequest ) } else { - LOGGER.error( pwmRequest, "skipCaptcha value is in request, however value '" + requestValue + "' does not match configured value" ); + LOGGER.debug( pwmRequest, () -> "skipCaptcha value is in request, however value '" + requestValue + "' does not match configured value" ); } } } diff --git a/server/src/main/java/password/pwm/util/DailySummaryJob.java b/server/src/main/java/password/pwm/util/DailySummaryJob.java index 97775fd75..fe27e719f 100644 --- a/server/src/main/java/password/pwm/util/DailySummaryJob.java +++ b/server/src/main/java/password/pwm/util/DailySummaryJob.java @@ -62,7 +62,7 @@ public void run() { alertDailyStats(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error while generating daily alert statistics: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/IPMatcher.java b/server/src/main/java/password/pwm/util/IPMatcher.java index 2a7c2ae1b..69425c8ba 100644 --- a/server/src/main/java/password/pwm/util/IPMatcher.java +++ b/server/src/main/java/password/pwm/util/IPMatcher.java @@ -57,7 +57,7 @@ public IPMatcher( final String ipSpec ) throws IPMatcherException { network = Inet6Address.getByName( parts[ 0 ] ).getAddress(); } - catch ( UnknownHostException e ) + catch ( final UnknownHostException e ) { throw new IPMatcherException( "Malformed IP range specification " + ipSpec, e ); @@ -73,7 +73,7 @@ public IPMatcher( final String ipSpec ) throws IPMatcherException { maskBits = Integer.parseInt( parts[ 1 ] ); } - catch ( NumberFormatException nfe ) + catch ( final NumberFormatException nfe ) { throw new IPMatcherException( "Malformed IP range specification " + ipSpec, nfe ); @@ -132,7 +132,7 @@ public IPMatcher( final String ipSpec ) throws IPMatcherException { x = Integer.parseInt( maskParts[ 0 ] ); } - catch ( NumberFormatException nfe ) + catch ( final NumberFormatException nfe ) { throw new IPMatcherException( "Malformed IP range specification " + ipSpec, nfe ); @@ -223,7 +223,7 @@ private static int ipToBytes( final String ip, final byte[] bytes, final boolean bytes[ i ] = ( byte ) ( p < 128 ? p : p - 256 ); } } - catch ( NumberFormatException nfe ) + catch ( final NumberFormatException nfe ) { throw new IPMatcherException( "Malformed IP specification " + ip, nfe ); @@ -258,7 +258,7 @@ public boolean match( final String ipIn ) throws IPMatcherException { candidate = Inet6Address.getByName( ipIn ).getAddress(); } - catch ( UnknownHostException e ) + catch ( final UnknownHostException e ) { throw new IPMatcherException( "Malformed IPv6 address ", e ); } diff --git a/server/src/main/java/password/pwm/util/LDAPPermissionCalculator.java b/server/src/main/java/password/pwm/util/LDAPPermissionCalculator.java index f2fb10ba4..621e27845 100644 --- a/server/src/main/java/password/pwm/util/LDAPPermissionCalculator.java +++ b/server/src/main/java/password/pwm/util/LDAPPermissionCalculator.java @@ -30,7 +30,7 @@ import password.pwm.config.PwmSettingTemplateSet; import password.pwm.config.option.DataStorageMethod; import password.pwm.config.profile.LdapProfile; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.config.value.data.ActionConfiguration; import password.pwm.config.value.data.FormConfiguration; @@ -59,11 +59,11 @@ public class LDAPPermissionCalculator implements Serializable { private static final PwmLogger LOGGER = PwmLogger.forClass( LDAPPermissionCalculator.class ); - private final transient StoredConfigurationImpl storedConfiguration; + private final transient StoredConfiguration storedConfiguration; private final transient Configuration configuration; private final Collection permissionRecords; - public LDAPPermissionCalculator( final StoredConfigurationImpl storedConfiguration ) throws PwmUnrecoverableException + public LDAPPermissionCalculator( final StoredConfiguration storedConfiguration ) throws PwmUnrecoverableException { this.storedConfiguration = storedConfiguration; this.configuration = new Configuration( storedConfiguration ); @@ -96,7 +96,7 @@ public Map>> getPe return Collections.unmodifiableMap( returnObj ); } - private Collection figureRecords( final StoredConfigurationImpl storedConfiguration ) throws PwmUnrecoverableException + private Collection figureRecords( final StoredConfiguration storedConfiguration ) throws PwmUnrecoverableException { final List permissionRecords = new ArrayList<>(); @@ -241,7 +241,7 @@ private Collection figurePermissionInfos( final PwmSetting p { case PEOPLE_SEARCH: { - if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.PEOPLE_SEARCH_ENABLE ).toNativeObject() ) + if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.PEOPLE_SEARCH_ENABLE, null ).toNativeObject() ) { return Collections.emptyList(); } @@ -268,7 +268,7 @@ private Collection figurePermissionInfos( final PwmSetting p case GUEST: { - if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.GUEST_ENABLE ).toNativeObject() ) + if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.GUEST_ENABLE, null ).toNativeObject() ) { return Collections.emptyList(); } @@ -279,7 +279,7 @@ private Collection figurePermissionInfos( final PwmSetting p case UPDATE_PROFILE: case UPDATE_SETTINGS: { - if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.UPDATE_PROFILE_ENABLE ).toNativeObject() ) + if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.UPDATE_PROFILE_ENABLE, null ).toNativeObject() ) { return Collections.emptyList(); } @@ -288,7 +288,7 @@ private Collection figurePermissionInfos( final PwmSetting p case FORGOTTEN_USERNAME: { - if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.FORGOTTEN_USERNAME_ENABLE ).toNativeObject() ) + if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.FORGOTTEN_USERNAME_ENABLE, null ).toNativeObject() ) { return Collections.emptyList(); } @@ -299,7 +299,7 @@ private Collection figurePermissionInfos( final PwmSetting p case NEWUSER_PROFILE: case NEWUSER_SETTINGS: { - if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.NEWUSER_ENABLE ).toNativeObject() ) + if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.NEWUSER_ENABLE, null ).toNativeObject() ) { return Collections.emptyList(); } @@ -308,7 +308,7 @@ private Collection figurePermissionInfos( final PwmSetting p case ACTIVATION: { - if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.ACTIVATE_USER_ENABLE ).toNativeObject() ) + if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.ACTIVATE_USER_ENABLE, null ).toNativeObject() ) { return Collections.emptyList(); } @@ -317,7 +317,7 @@ private Collection figurePermissionInfos( final PwmSetting p case HELPDESK_PROFILE: { - if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.HELPDESK_ENABLE ).toNativeObject() ) + if ( !( Boolean ) storedConfiguration.readSetting( PwmSetting.HELPDESK_ENABLE, null ).toNativeObject() ) { return Collections.emptyList(); } diff --git a/server/src/main/java/password/pwm/util/MBeanUtility.java b/server/src/main/java/password/pwm/util/MBeanUtility.java index a77b7813f..4f4d4b17d 100644 --- a/server/src/main/java/password/pwm/util/MBeanUtility.java +++ b/server/src/main/java/password/pwm/util/MBeanUtility.java @@ -62,7 +62,7 @@ public static void registerMBean( final PwmApplication pwmApplication ) mbs.registerMBean( mbean, name ); mbs.setAttributes( name, attributeList ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "error registering mbean: " + e.getMessage() ); } @@ -75,7 +75,7 @@ public static void unregisterMBean( final PwmApplication pwmApplication ) final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); mbs.unregisterMBean( figureMBeanName( pwmApplication ) ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "error unregistering mbean: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/PasswordData.java b/server/src/main/java/password/pwm/util/PasswordData.java index abb5e2d14..0380872fa 100644 --- a/server/src/main/java/password/pwm/util/PasswordData.java +++ b/server/src/main/java/password/pwm/util/PasswordData.java @@ -70,7 +70,7 @@ public class PasswordData implements Serializable newKey = new PwmSecurityKey( randomBytes ); newKeyHash = SecureEngine.hash( randomBytes, PwmHashAlgorithm.SHA512 ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.fatal( "can't initialize PasswordData handler: " + e.getMessage(), e ); e.printStackTrace(); @@ -172,7 +172,7 @@ private boolean equals( final Object obj, final boolean ignoreCase ) final String objValue = ( ( PasswordData ) obj ).getStringValue(); return ignoreCase ? strValue.equalsIgnoreCase( objValue ) : strValue.equals( objValue ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { e.printStackTrace(); } diff --git a/server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java b/server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java index 3be581f3a..da997be55 100644 --- a/server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java +++ b/server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java @@ -23,7 +23,10 @@ import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; import password.pwm.config.stored.ConfigurationProperty; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationFactory; +import password.pwm.config.stored.StoredConfigurationModifier; +import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.config.value.BooleanValue; import password.pwm.config.value.PasswordValue; import password.pwm.config.value.StringArrayValue; @@ -109,13 +112,13 @@ private void readInputFile( final InputStream propertiesInput ) this.inputMap = inputMap; } - public StoredConfigurationImpl readConfiguration( final InputStream propertiesInput ) + public StoredConfiguration readConfiguration( final InputStream propertiesInput ) throws PwmException, IOException { readInputFile( propertiesInput ); - final StoredConfigurationImpl storedConfiguration = StoredConfigurationImpl.newStoredConfiguration( ); - storedConfiguration.initNewRandomSecurityKey( ); + final StoredConfigurationModifier storedConfiguration = StoredConfigurationModifier.newModifier( StoredConfigurationFactory.newConfig( ) ); + StoredConfigurationUtil.initNewRandomSecurityKey( storedConfiguration ); storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( false ) ); storedConfiguration.writeConfigProperty( @@ -124,25 +127,25 @@ public StoredConfigurationImpl readConfiguration( final InputStream propertiesIn ConfigurationProperty.IMPORT_LDAP_CERTIFICATES, Boolean.toString( true ) ); // static values - storedConfiguration.writeSetting( PwmSetting.TEMPLATE_LDAP, new StringValue( + storedConfiguration.writeSetting( PwmSetting.TEMPLATE_LDAP, null, new StringValue( inputMap.getOrDefault( PropertyKey.TEMPLATE_LDAP.name( ), PropertyKey.TEMPLATE_LDAP.getDefaultValue() ) ), null ); if ( inputMap.containsKey( PropertyKey.DISPLAY_THEME.name( ) ) ) { - storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_SOURCE, new StringValue( + storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_SOURCE, null, new StringValue( inputMap.get( PropertyKey.DISPLAY_THEME.name( ) ) ), null ); } - storedConfiguration.writeSetting( PwmSetting.DISPLAY_HOME_BUTTON, new BooleanValue( false ), null ); - storedConfiguration.writeSetting( PwmSetting.LOGOUT_AFTER_PASSWORD_CHANGE, new BooleanValue( false ), null ); - storedConfiguration.writeSetting( PwmSetting.PASSWORD_REQUIRE_CURRENT, new BooleanValue( false ), null ); - storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_SOURCE, new StringValue( "LDAP" ), null ); - storedConfiguration.writeSetting( PwmSetting.CERTIFICATE_VALIDATION_MODE, new StringValue( "CA_ONLY" ), null ); + storedConfiguration.writeSetting( PwmSetting.DISPLAY_HOME_BUTTON, null, new BooleanValue( false ), null ); + storedConfiguration.writeSetting( PwmSetting.LOGOUT_AFTER_PASSWORD_CHANGE, null, new BooleanValue( false ), null ); + storedConfiguration.writeSetting( PwmSetting.PASSWORD_REQUIRE_CURRENT, null, new BooleanValue( false ), null ); + storedConfiguration.writeSetting( PwmSetting.PASSWORD_POLICY_SOURCE, null, new StringValue( "LDAP" ), null ); + storedConfiguration.writeSetting( PwmSetting.CERTIFICATE_VALIDATION_MODE, null, new StringValue( "CA_ONLY" ), null ); { final String notes = "Configuration generated via properties import at " + JavaHelper.toIsoDate( Instant.now( ) ); - storedConfiguration.writeSetting( PwmSetting.NOTES, new StringValue( notes ), null ); + storedConfiguration.writeSetting( PwmSetting.NOTES, null, new StringValue( notes ), null ); } // ldap server @@ -155,23 +158,23 @@ public StoredConfigurationImpl readConfiguration( final InputStream propertiesIn new StringArrayValue( Collections.singletonList( inputMap.get( PropertyKey.USER_CONTAINER.name( ) ) ) ), null ); // oauth - storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_LOGIN_URL, new StringValue( makeOAuthBaseUrl( ) + "/grant" ), null ); - storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CODERESOLVE_URL, new StringValue( makeOAuthBaseUrl( ) + "/authcoderesolve" ), null ); - storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_ATTRIBUTES_URL, new StringValue( makeOAuthBaseUrl( ) + "/getattributes" ), null ); - storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CLIENTNAME, new StringValue( "sspr" ), null ); - storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_DN_ATTRIBUTE_NAME, new StringValue( "name" ), null ); - storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_SECRET, + storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_LOGIN_URL, null, new StringValue( makeOAuthBaseUrl( ) + "/grant" ), null ); + storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CODERESOLVE_URL, null, new StringValue( makeOAuthBaseUrl( ) + "/authcoderesolve" ), null ); + storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_ATTRIBUTES_URL, null, new StringValue( makeOAuthBaseUrl( ) + "/getattributes" ), null ); + storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CLIENTNAME, null, new StringValue( "sspr" ), null ); + storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_DN_ATTRIBUTE_NAME, null, new StringValue( "name" ), null ); + storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_SECRET, null, new PasswordValue( PasswordData.forStringValue( inputMap.get( PropertyKey.SSO_SERVICE_PWD.name( ) ) ) ), null ); //urls - storedConfiguration.writeSetting( PwmSetting.URL_FORWARD, makeForwardUrl( ), null ); - storedConfiguration.writeSetting( PwmSetting.URL_LOGOUT, makeLogoutUrl( ), null ); - storedConfiguration.writeSetting( PwmSetting.PWM_SITE_URL, makeSelfUrl( ), null ); - storedConfiguration.writeSetting( PwmSetting.SECURITY_REDIRECT_WHITELIST, makeWhitelistUrl( ), null ); + storedConfiguration.writeSetting( PwmSetting.URL_FORWARD, null, makeForwardUrl( ), null ); + storedConfiguration.writeSetting( PwmSetting.URL_LOGOUT, null, makeLogoutUrl( ), null ); + storedConfiguration.writeSetting( PwmSetting.PWM_SITE_URL, null, makeSelfUrl( ), null ); + storedConfiguration.writeSetting( PwmSetting.SECURITY_REDIRECT_WHITELIST, null, makeWhitelistUrl( ), null ); // admin settings - storedConfiguration.writeSetting( PwmSetting.QUERY_MATCH_PWM_ADMIN, makeAdminPermissions( ), null ); - storedConfiguration.setPassword( inputMap.get( PropertyKey.CONFIGURATION_PWD.name( ) ) ); + storedConfiguration.writeSetting( PwmSetting.QUERY_MATCH_PWM_ADMIN, null, makeAdminPermissions( ), null ); + StoredConfigurationUtil.setPassword( storedConfiguration, inputMap.get( PropertyKey.CONFIGURATION_PWD.name( ) ) ); // certificates { @@ -185,19 +188,19 @@ public StoredConfigurationImpl readConfiguration( final InputStream propertiesIn final Optional> optionalCert = readCertificate( PropertyKey.AUDIT_SERVERCERTS ); if ( optionalCert.isPresent( ) ) { - storedConfiguration.writeSetting( PwmSetting.AUDIT_SYSLOG_CERTIFICATES, new X509CertificateValue( optionalCert.get( ) ), null ); + storedConfiguration.writeSetting( PwmSetting.AUDIT_SYSLOG_CERTIFICATES, null, new X509CertificateValue( optionalCert.get( ) ), null ); } } { final Optional> optionalCert = readCertificate( PropertyKey.OAUTH_IDSERVER_SERVERCERTS ); if ( optionalCert.isPresent( ) ) { - storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CERTIFICATE, new X509CertificateValue( optionalCert.get( ) ), null ); + storedConfiguration.writeSetting( PwmSetting.OAUTH_ID_CERTIFICATE, null, new X509CertificateValue( optionalCert.get( ) ), null ); } } - return storedConfiguration; + return storedConfiguration.newStoredConfiguration(); } private String makeOAuthBaseUrl( ) @@ -259,7 +262,12 @@ private StoredValue makeAdminPermissions( ) final String value = inputMap.get( propertyKey.name() ); if ( !StringUtil.isEmpty( value ) ) { - permissions.add( new UserPermission( UserPermission.Type.ldapQuery, LDAP_PROFILE, filter, value ) ); + permissions.add( UserPermission.builder() + .type( UserPermission.Type.ldapQuery ) + .ldapProfileID( LDAP_PROFILE ) + .ldapQuery( filter ) + .ldapBase( value ) + .build() ); } } @@ -292,7 +300,7 @@ private Optional> readCertificate( } } - catch ( Exception e ) + catch ( final Exception e ) { throw new IOException( "error importing key " + propertyKey.name() + ", error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/PwmScheduler.java b/server/src/main/java/password/pwm/util/PwmScheduler.java index 398761785..41c3b0cf2 100644 --- a/server/src/main/java/password/pwm/util/PwmScheduler.java +++ b/server/src/main/java/password/pwm/util/PwmScheduler.java @@ -284,10 +284,9 @@ public void run() else { hasFailed = true; - LOGGER.trace( () -> "skipping scheduled job " + runnable + " on shutdown executor + " + executor ); } } - catch ( Throwable t ) + catch ( final Throwable t ) { LOGGER.error( "unexpected error running scheduled job: " + t.getMessage(), t ); hasFailed = true; diff --git a/server/src/main/java/password/pwm/util/ServletUtility.java b/server/src/main/java/password/pwm/util/ServletUtility.java index 3ceb8b257..5996572f3 100644 --- a/server/src/main/java/password/pwm/util/ServletUtility.java +++ b/server/src/main/java/password/pwm/util/ServletUtility.java @@ -51,7 +51,7 @@ public static String readRequestBodyAsString( final HttpServletRequest req, fina { IOUtils.copy( readerStream, stringWriter ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error reading request body stream: " + e.getMessage(); throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) ); diff --git a/server/src/main/java/password/pwm/util/Validator.java b/server/src/main/java/password/pwm/util/Validator.java index 2374cbbde..3cd1f0dab 100644 --- a/server/src/main/java/password/pwm/util/Validator.java +++ b/server/src/main/java/password/pwm/util/Validator.java @@ -107,7 +107,7 @@ public static void validatePwmRequestCounter( final PwmRequest pwmRequest ) throw new PwmOperationalException( PwmError.ERROR_INCORRECT_REQ_SEQUENCE, debugMsg ); } } - catch ( StringIndexOutOfBoundsException | NumberFormatException e ) + catch ( final StringIndexOutOfBoundsException | NumberFormatException e ) { throw new PwmOperationalException( PwmError.ERROR_INCORRECT_REQ_SEQUENCE ); } diff --git a/server/src/main/java/password/pwm/util/cli/MainClass.java b/server/src/main/java/password/pwm/util/cli/MainClass.java index 1d27fe332..78fdfb25c 100644 --- a/server/src/main/java/password/pwm/util/cli/MainClass.java +++ b/server/src/main/java/password/pwm/util/cli/MainClass.java @@ -24,7 +24,6 @@ import org.apache.log4j.EnhancedPatternLayout; import org.apache.log4j.Layout; import org.apache.log4j.Logger; -import org.apache.log4j.varia.NullAppender; import password.pwm.AppProperty; import password.pwm.PwmApplication; import password.pwm.PwmApplicationMode; @@ -269,7 +268,7 @@ public static Map parseCommandOptions( } returnObj.put( option.getName(), theFile ); } - catch ( Exception e ) + catch ( final Exception e ) { if ( e instanceof CliException ) { @@ -290,7 +289,7 @@ public static Map parseCommandOptions( } returnObj.put( option.getName(), theFile ); } - catch ( Exception e ) + catch ( final Exception e ) { if ( e instanceof CliException ) { @@ -369,7 +368,7 @@ private static void executeCommand( { cliEnvironment = createEnv( command.getCliParameters(), argList ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unable to establish operating environment: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_ENVIRONMENT_ERROR, errorMsg ); @@ -383,7 +382,7 @@ private static void executeCommand( { command.execute( commandStr, cliEnvironment ); } - catch ( Exception e ) + catch ( final Exception e ) { System.out.println( e.getMessage() ); //System.exit(-1); @@ -396,7 +395,7 @@ private static void executeCommand( { cliEnvironment.getPwmApplication().shutdown(); } - catch ( Exception e ) + catch ( final Exception e ) { out( "error closing operating environment: " + e.getMessage() ); e.printStackTrace(); @@ -408,7 +407,7 @@ private static void executeCommand( { cliEnvironment.getLocalDB().close(); } - catch ( Exception e ) + catch ( final Exception e ) { out( "error closing LocalDB environment: " + e.getMessage() ); } @@ -423,9 +422,7 @@ private static void initLog4j( final PwmLogLevel logLevel ) { if ( logLevel == null ) { - Logger.getRootLogger().removeAllAppenders(); - Logger.getRootLogger().addAppender( new NullAppender() ); - PwmLogger.markInitialized(); + PwmLogger.disableAllLogging(); return; } diff --git a/server/src/main/java/password/pwm/util/cli/MainOptions.java b/server/src/main/java/password/pwm/util/cli/MainOptions.java index a00c314b8..28414ed4f 100644 --- a/server/src/main/java/password/pwm/util/cli/MainOptions.java +++ b/server/src/main/java/password/pwm/util/cli/MainOptions.java @@ -122,7 +122,7 @@ public static MainOptions parseMainCommandLineOptions( { pwmLogLevel = PwmLogLevel.valueOf( levelStr.toUpperCase() ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { out( debugWriter, " unknown log level value: " + levelStr ); System.exit( -1 ); @@ -182,7 +182,7 @@ static void out( final Writer debugWriter, final CharSequence out ) debugWriter.append( "\n" ); debugWriter.flush(); } - catch ( IOException e ) + catch ( final IOException e ) { e.printStackTrace(); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/AbstractCliCommand.java b/server/src/main/java/password/pwm/util/cli/commands/AbstractCliCommand.java index 77a9542ca..610f1628e 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/AbstractCliCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/AbstractCliCommand.java @@ -47,7 +47,7 @@ void out( final CharSequence out ) cliEnvironment.getDebugWriter().append( "\n" ); cliEnvironment.getDebugWriter().flush(); } - catch ( IOException e ) + catch ( final IOException e ) { e.printStackTrace(); } @@ -66,7 +66,7 @@ public void execute( { doCommand(); } - catch ( Exception e ) + catch ( final Exception e ) { e.printStackTrace(); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ConfigLockCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ConfigLockCommand.java index bc4cbecd6..e18e44c7d 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ConfigLockCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ConfigLockCommand.java @@ -23,24 +23,29 @@ import password.pwm.bean.SessionLabel; import password.pwm.config.stored.ConfigurationProperty; import password.pwm.config.stored.ConfigurationReader; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.util.cli.CliParameters; +import java.util.Optional; + public class ConfigLockCommand extends AbstractCliCommand { public void doCommand( ) throws Exception { final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader(); - final StoredConfigurationImpl storedConfiguration = configurationReader.getStoredConfiguration(); - if ( !Boolean.parseBoolean( storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE ) ) ) + final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration(); + final Optional configIsEditable = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE ); + if ( configIsEditable.isPresent() && !Boolean.parseBoolean( configIsEditable.get() ) ) { out( "configuration is already locked" ); return; } - storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( false ) ); - configurationReader.saveConfiguration( storedConfiguration, cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL ); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); + modifier.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( false ) ); + configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL ); out( "success: configuration has been locked" ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ConfigNewCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ConfigNewCommand.java index 19f77a5f5..5c3cc2e48 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ConfigNewCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ConfigNewCommand.java @@ -20,8 +20,8 @@ package password.pwm.util.cli.commands; -import password.pwm.config.stored.ConfigurationProperty; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationFactory; import password.pwm.util.cli.CliParameters; import java.io.File; @@ -33,18 +33,13 @@ public class ConfigNewCommand extends AbstractCliCommand public void doCommand( ) throws Exception { - final StoredConfigurationImpl storedConfiguration = StoredConfigurationImpl.newStoredConfiguration(); - storedConfiguration.initNewRandomSecurityKey(); - storedConfiguration.writeConfigProperty( - ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) ); - storedConfiguration.writeConfigProperty( - ConfigurationProperty.CONFIG_EPOCH, String.valueOf( 0 ) ); + final StoredConfiguration storedConfiguration = StoredConfigurationFactory.newConfig(); final File outputFile = ( File ) cliEnvironment.getOptions().get( CliParameters.REQUIRED_NEW_OUTPUT_FILE.getName() ); try ( FileOutputStream fileOutputStream = new FileOutputStream( outputFile, false ) ) { - storedConfiguration.toXml( fileOutputStream ); + StoredConfigurationFactory.toXml( storedConfiguration, fileOutputStream ); } out( "success: created new configuration" ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ConfigResetHttpsCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ConfigResetHttpsCommand.java index 2ecb9a397..b7c334ce6 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ConfigResetHttpsCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ConfigResetHttpsCommand.java @@ -24,7 +24,8 @@ import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingCategory; import password.pwm.config.stored.ConfigurationReader; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.util.cli.CliParameters; import java.io.File; @@ -48,13 +49,14 @@ public void doCommand( ) } final ConfigurationReader configurationReader = new ConfigurationReader( cliEnvironment.getConfigurationFile() ); - final StoredConfigurationImpl storedConfiguration = configurationReader.getStoredConfiguration(); + final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration(); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); for ( final PwmSetting setting : PwmSettingCategory.HTTPS_SERVER.getSettings() ) { - storedConfiguration.resetSetting( setting, null, null ); + modifier.resetSetting( setting, null, null ); } - configurationReader.saveConfiguration( storedConfiguration, cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL ); + configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL ); out( "success" ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ConfigSetPasswordCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ConfigSetPasswordCommand.java index 3ab3a1dc3..c1338d67d 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ConfigSetPasswordCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ConfigSetPasswordCommand.java @@ -22,7 +22,9 @@ import password.pwm.bean.SessionLabel; import password.pwm.config.stored.ConfigurationReader; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationModifier; +import password.pwm.config.stored.StoredConfigurationUtil; import password.pwm.util.cli.CliParameters; import java.util.Collections; @@ -34,10 +36,11 @@ public void doCommand( ) throws Exception { final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader(); - final StoredConfigurationImpl storedConfiguration = configurationReader.getStoredConfiguration(); + final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration(); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); final String password = getOptionalPassword(); - storedConfiguration.setPassword( password ); - configurationReader.saveConfiguration( storedConfiguration, cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL ); + StoredConfigurationUtil.setPassword( modifier, password ); + configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL ); out( "success: new password has been set" ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ConfigUnlockCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ConfigUnlockCommand.java index c717d0e6e..ac23bd979 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ConfigUnlockCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ConfigUnlockCommand.java @@ -23,24 +23,30 @@ import password.pwm.bean.SessionLabel; import password.pwm.config.stored.ConfigurationProperty; import password.pwm.config.stored.ConfigurationReader; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.util.cli.CliParameters; +import java.util.Optional; + public class ConfigUnlockCommand extends AbstractCliCommand { public void doCommand( ) throws Exception { final ConfigurationReader configurationReader = cliEnvironment.getConfigurationReader(); - final StoredConfigurationImpl storedConfiguration = configurationReader.getStoredConfiguration(); - if ( Boolean.parseBoolean( storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE ) ) ) + final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration(); + + final Optional configIsEditable = storedConfiguration.readConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE ); + if ( configIsEditable.isPresent() && Boolean.parseBoolean( configIsEditable.get() ) ) { out( "configuration is already unlocked" ); return; } - storedConfiguration.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) ); - configurationReader.saveConfiguration( storedConfiguration, cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL ); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); + modifier.writeConfigProperty( ConfigurationProperty.CONFIG_IS_EDITABLE, Boolean.toString( true ) ); + configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL ); out( "success: configuration has been unlocked" ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ExportHttpsTomcatConfigCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ExportHttpsTomcatConfigCommand.java index 20ab39c41..fca57552d 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ExportHttpsTomcatConfigCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ExportHttpsTomcatConfigCommand.java @@ -57,7 +57,7 @@ void doCommand( ) throws Exception fileOutputStream ); } - catch ( IOException e ) + catch ( final IOException e ) { out( "error during tomcat config file export: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ExportLocalDBCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ExportLocalDBCommand.java index 6d7140ee9..5295196f6 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ExportLocalDBCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ExportLocalDBCommand.java @@ -49,7 +49,7 @@ void doCommand( ) { localDBUtility.exportLocalDB( fileOutputStream, System.out ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { out( "error during export: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ExportWordlistCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ExportWordlistCommand.java index 5de6e6fbe..f43571199 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ExportWordlistCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ExportWordlistCommand.java @@ -49,7 +49,7 @@ void doCommand( ) { localDBUtility.exportWordlist( fileOutputStream, System.out ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { out( "error during export: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ImportHttpsKeyStoreCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ImportHttpsKeyStoreCommand.java index e582b0b9e..f2aad8fa6 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ImportHttpsKeyStoreCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ImportHttpsKeyStoreCommand.java @@ -22,7 +22,8 @@ import password.pwm.bean.SessionLabel; import password.pwm.config.stored.ConfigurationReader; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.util.PasswordData; import password.pwm.util.cli.CliParameters; import password.pwm.util.java.StringUtil; @@ -55,7 +56,7 @@ void doCommand( ) { format = HttpsServerCertificateManager.KeyStoreFormat.valueOf( formatString ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { out( "unknown format '" + formatString + "', must be one of " + StringUtil.join( HttpsServerCertificateManager.KeyStoreFormat.values(), "," ) ); return; @@ -64,25 +65,26 @@ void doCommand( ) final String inputAliasName = ( String ) cliEnvironment.getOptions().get( ALIAS_OPTIONNAME ); final ConfigurationReader configurationReader = new ConfigurationReader( cliEnvironment.getConfigurationFile() ); - final StoredConfigurationImpl storedConfiguration = configurationReader.getStoredConfiguration(); + final StoredConfiguration storedConfiguration = configurationReader.getStoredConfiguration(); + final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration ); try ( FileInputStream fileInputStream = new FileInputStream( inputFile ) ) { HttpsServerCertificateManager.importKey( - storedConfiguration, + modifier, format, fileInputStream, new PasswordData( keyStorePassword ), inputAliasName ); } - catch ( Exception e ) + catch ( final Exception e ) { out( "unable to load configured https certificate: " + e.getMessage() ); return; } - configurationReader.saveConfiguration( storedConfiguration, cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL ); + configurationReader.saveConfiguration( modifier.newStoredConfiguration(), cliEnvironment.getPwmApplication(), SessionLabel.CLI_SESSION_LABEL ); out( "success: keystore has been imported" ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ImportLocalDBCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ImportLocalDBCommand.java index 1a51638e7..a9addb50b 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ImportLocalDBCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ImportLocalDBCommand.java @@ -52,7 +52,7 @@ void doCommand( ) { pwmDBUtility.importLocalDB( inputFile, System.out ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { out( "error during import: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ImportPropertyConfigCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ImportPropertyConfigCommand.java index fd7fee78f..4617a4bb4 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ImportPropertyConfigCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ImportPropertyConfigCommand.java @@ -20,7 +20,8 @@ package password.pwm.util.cli.commands; -import password.pwm.config.stored.StoredConfigurationImpl; +import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationFactory; import password.pwm.util.PropertyConfigurationImporter; import password.pwm.util.cli.CliParameters; @@ -28,7 +29,6 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.OutputStream; - import java.util.Collections; /** @@ -53,15 +53,15 @@ void doCommand( ) try { final PropertyConfigurationImporter importer = new PropertyConfigurationImporter(); - final StoredConfigurationImpl storedConfiguration = importer.readConfiguration( new FileInputStream( inputFile ) ); + final StoredConfiguration storedConfiguration = importer.readConfiguration( new FileInputStream( inputFile ) ); try ( OutputStream outputStream = new FileOutputStream( configFile ) ) { - storedConfiguration.toXml( outputStream ); + StoredConfigurationFactory.toXml( storedConfiguration, outputStream ); out( "output configuration file " + configFile.getAbsolutePath() ); } } - catch ( Exception e ) + catch ( final Exception e ) { out( "error during import: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java index 5c8d49106..64c468d03 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java @@ -76,7 +76,7 @@ void doCommand( ) final ResponseInfoBean responseInfoBean = inputData.toResponseInfoBean( PwmConstants.DEFAULT_LOCALE, challengeSet.getIdentifier() ); pwmApplication.getCrService().writeResponses( userIdentity, user, userGuid, responseInfoBean ); } - catch ( Exception e ) + catch ( final Exception e ) { out( "error writing responses to user '" + user.getEntryDN() + "', error: " + e.getMessage() ); return; diff --git a/server/src/main/java/password/pwm/util/cli/commands/ShellCommand.java b/server/src/main/java/password/pwm/util/cli/commands/ShellCommand.java index 7540bd700..d0a50c714 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/ShellCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/ShellCommand.java @@ -106,7 +106,7 @@ final void processCommand( final String commandLine ) { executeCommand( cliEnvironment, cliCommand, commandLine ); } - catch ( CliException e ) + catch ( final CliException e ) { out( "error executing command: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java b/server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java index dcfff90e9..de58d9800 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java @@ -46,7 +46,7 @@ public void doCommand( ) { tokenPayload = tokenService.retrieveTokenData( SessionLabel.TOKEN_SESSION_LABEL, tokenKey ); } - catch ( Exception e ) + catch ( final Exception e ) { lookupError = e; } diff --git a/server/src/main/java/password/pwm/util/cli/commands/UserReportCommand.java b/server/src/main/java/password/pwm/util/cli/commands/UserReportCommand.java index e7afde19e..006d6f368 100644 --- a/server/src/main/java/password/pwm/util/cli/commands/UserReportCommand.java +++ b/server/src/main/java/password/pwm/util/cli/commands/UserReportCommand.java @@ -72,7 +72,7 @@ void doCommand( ) final ReportCsvUtility reportCsvUtility = new ReportCsvUtility( pwmApplication ); reportCsvUtility.outputToCsv( outputFileStream, true, PwmConstants.DEFAULT_LOCALE ); } - catch ( IOException e ) + catch ( final IOException e ) { out( "unable to open file '" + outputFile.getAbsolutePath() + "' for writing" ); System.exit( -1 ); diff --git a/server/src/main/java/password/pwm/util/db/DatabaseAccessorImpl.java b/server/src/main/java/password/pwm/util/db/DatabaseAccessorImpl.java index 666fd3a24..3e13ed7c2 100644 --- a/server/src/main/java/password/pwm/util/db/DatabaseAccessorImpl.java +++ b/server/src/main/java/password/pwm/util/db/DatabaseAccessorImpl.java @@ -107,7 +107,7 @@ public boolean put( { exists = containsImpl( table, key ); } - catch ( SQLException e ) + catch ( final SQLException e ) { processSqlException( debugInfo, e ); } @@ -222,7 +222,7 @@ public String get( } } } - catch ( SQLException e ) + catch ( final SQLException e ) { processSqlException( debugInfo, e ); } @@ -289,7 +289,7 @@ public int size( final DatabaseTable table ) } } } - catch ( SQLException e ) + catch ( final SQLException e ) { processSqlException( debugInfo, e ); } @@ -322,7 +322,7 @@ boolean isValid( ) } } - catch ( SQLException e ) + catch ( final SQLException e ) { LOGGER.debug( () -> "error while checking connection validity: " + e.getMessage() ); } @@ -362,7 +362,7 @@ private void init( ) throws DatabaseException resultSet = statement.executeQuery(); connection.commit(); } - catch ( SQLException e ) + catch ( final SQLException e ) { processSqlException( null, e ); } @@ -404,7 +404,7 @@ private void getNextItem( ) close(); } } - catch ( SQLException e ) + catch ( final SQLException e ) { finished = true; LOGGER.warn( "unexpected error during result set iteration: " + e.getMessage() ); @@ -430,7 +430,7 @@ public void close( ) resultSet.close(); resultSet = null; } - catch ( SQLException e ) + catch ( final SQLException e ) { LOGGER.error( "error closing inner resultSet in iterator: " + e.getMessage() ); } @@ -443,7 +443,7 @@ public void close( ) statement.close(); statement = null; } - catch ( SQLException e ) + catch ( final SQLException e ) { LOGGER.error( "error closing inner statement in iterator: " + e.getMessage() ); } @@ -546,7 +546,7 @@ void close( ) iterator.close(); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "error while closing connection: " + e.getMessage() ); } @@ -555,7 +555,7 @@ void close( ) { connection.close(); } - catch ( SQLException e ) + catch ( final SQLException e ) { LOGGER.warn( "error while closing connection: " + e.getMessage() ); } @@ -602,7 +602,7 @@ private void executeUpdate( final String sqlStatement, final DatabaseUtil.DebugI } statement.executeUpdate(); } - catch ( SQLException e ) + catch ( final SQLException e ) { processSqlException( debugInfo, e ); } @@ -622,7 +622,7 @@ public boolean isConnected() { return connection.isValid( 5000 ); } - catch ( SQLException e ) + catch ( final SQLException e ) { LOGGER.error( "error while checking database connection: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/db/DatabaseService.java b/server/src/main/java/password/pwm/util/db/DatabaseService.java index 3630c7dba..fe5ed9158 100644 --- a/server/src/main/java/password/pwm/util/db/DatabaseService.java +++ b/server/src/main/java/password/pwm/util/db/DatabaseService.java @@ -176,7 +176,7 @@ private synchronized void init( ) status = STATUS.OPEN; initialized = true; } - catch ( Throwable t ) + catch ( final Throwable t ) { final String errorMsg = "exception initializing database service: " + t.getMessage(); LOGGER.warn( errorMsg ); @@ -202,7 +202,7 @@ public void close( ) { driver = null; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "error while de-registering driver: " + e.getMessage() ); } @@ -216,7 +216,7 @@ public void close( ) private void clearCurrentAccessors( ) { - for ( DatabaseAccessorImpl accessor : accessors.values() ) + for ( final DatabaseAccessorImpl accessor : accessors.values() ) { accessor.close(); } @@ -245,7 +245,7 @@ public List healthCheck( ) final DatabaseAccessor accessor = getAccessor(); accessor.put( DatabaseTable.PWM_META, KEY_TEST, JsonUtil.serializeMap( tempMap ) ); } - catch ( PwmException e ) + catch ( final PwmException e ) { returnRecords.add( new HealthRecord( HealthStatus.WARN, HealthTopic.Database, "Error writing to database: " + e.getErrorInformation().toDebugStr() ) ); return returnRecords; @@ -358,7 +358,7 @@ private Connection openConnection( final DBConfiguration dbConfiguration ) connection.setAutoCommit( false ); return connection; } - catch ( Throwable e ) + catch ( final Throwable e ) { final String errorMsg = "error connecting to database: " + JavaHelper.readHostileExceptionMessage( e ); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, errorMsg ); @@ -413,7 +413,7 @@ private void updateDebugProperties( final Connection connection ) debugInfo.clear(); debugInfo.putAll( Collections.unmodifiableMap( returnObj ) ); } - catch ( SQLException e ) + catch ( final SQLException e ) { LOGGER.error( "error reading jdbc meta data: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/db/DatabaseUtil.java b/server/src/main/java/password/pwm/util/db/DatabaseUtil.java index 23c164ba8..e7a49d0e0 100644 --- a/server/src/main/java/password/pwm/util/db/DatabaseUtil.java +++ b/server/src/main/java/password/pwm/util/db/DatabaseUtil.java @@ -57,7 +57,7 @@ static void close( final Statement statement ) throws DatabaseException { statement.close(); } - catch ( SQLException e ) + catch ( final SQLException e ) { LOGGER.error( "unexpected error during close statement object " + e.getMessage(), e ); throw new DatabaseException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, "statement close failure: " + e.getMessage() ) ); @@ -73,7 +73,7 @@ static void close( final ResultSet resultSet ) throws DatabaseException { resultSet.close(); } - catch ( SQLException e ) + catch ( final SQLException e ) { LOGGER.error( "unexpected error during close resultSet object " + e.getMessage(), e ); throw new DatabaseException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, "resultset close failure: " + e.getMessage() ) ); @@ -88,7 +88,7 @@ static void commit( final Connection connection ) { connection.commit(); } - catch ( SQLException e ) + catch ( final SQLException e ) { LOGGER.warn( "database commit failed: " + e.getMessage() ); throw new DatabaseException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, "commit failure: " + e.getMessage() ) ); @@ -119,7 +119,7 @@ static void initTable( LOGGER.trace( () -> "table " + table + " appears to exist" ); tableExists = true; } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { // assume error was due to table missing; LOGGER.trace( () -> "error while checking for table: " + e.getMessage() + ", assuming due to table non-existence" ); @@ -155,7 +155,7 @@ private static void createTable( connection.commit(); LOGGER.debug( () -> "created table " + table.toString() ); } - catch ( SQLException ex ) + catch ( final SQLException ex ) { final String errorMsg = "error creating new table " + table.toString() + ": " + ex.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, errorMsg ); @@ -184,7 +184,7 @@ private static void createTable( connection.commit(); LOGGER.debug( () -> "created index " + indexName ); } - catch ( SQLException ex ) + catch ( final SQLException ex ) { final String errorMsg = "error creating new index " + indexName + ": " + ex.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, errorMsg ); @@ -218,7 +218,7 @@ private static void checkIfTableExists( { resultSet = statement.executeQuery( sqlText ); } - catch ( SQLException e ) + catch ( final SQLException e ) { rollbackTransaction( connection ); throw DatabaseUtil.convertSqlException( debugInfo, e ); @@ -235,7 +235,7 @@ static void rollbackTransaction( final Connection connection ) throws DatabaseEx { connection.rollback(); } - catch ( SQLException e1 ) + catch ( final SQLException e1 ) { final String errorMsg = "error during transaction rollback: " + e1.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, errorMsg ); diff --git a/server/src/main/java/password/pwm/util/db/JDBCDriverLoader.java b/server/src/main/java/password/pwm/util/db/JDBCDriverLoader.java index a2ba12ee2..3677d3318 100644 --- a/server/src/main/java/password/pwm/util/db/JDBCDriverLoader.java +++ b/server/src/main/java/password/pwm/util/db/JDBCDriverLoader.java @@ -74,7 +74,7 @@ static DriverWrapper loadDriver( return new DriverWrapper( driver, loader ); } } - catch ( PwmUnrecoverableException | DatabaseException e ) + catch ( final PwmUnrecoverableException | DatabaseException e ) { errorMsgs.add( strategy + " error: " + e.getMessage() ); } @@ -108,7 +108,7 @@ private DriverLoader getJdbcDriverDriverLoader( ) constructor.setAccessible( true ); return constructor.newInstance(); } - catch ( InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e ) + catch ( final InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "unable to load jdbc driver loader: " + e.getMessage() ); } @@ -141,7 +141,7 @@ public Driver loadDriver( final PwmApplication pwmApplication, final DBConfigura LOGGER.debug( () -> "successfully loaded JDBC database driver from classpath: " + jdbcClassName ); return driver; } - catch ( Throwable e ) + catch ( final Throwable e ) { final String errorMsg = e.getClass().getName() + " error loading JDBC database driver from classpath: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, errorMsg ); @@ -186,7 +186,7 @@ public Driver loadDriver( final PwmApplication pwmApplication, final DBConfigura return driver; } - catch ( Throwable e ) + catch ( final Throwable e ) { final String errorMsg = "error registering JDBC database driver stored in configuration: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, errorMsg ); @@ -257,7 +257,7 @@ public Driver loadDriver( final PwmApplication pwmApplication, final DBConfigura return driver; } - catch ( Throwable e ) + catch ( final Throwable e ) { final String errorMsg = "error registering JDBC database driver stored in configuration: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, errorMsg ); @@ -310,7 +310,7 @@ public Driver loadDriver( final PwmApplication pwmApplication, final DBConfigura { jdbcDriverHash = pwmApplication.getSecureService().hash( jdbcDriverBytes ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { throw new DatabaseException( e.getErrorInformation() ); } @@ -336,7 +336,7 @@ public Driver loadDriver( final PwmApplication pwmApplication, final DBConfigura ); driverCache.put( jdbcDriverHash, urlClassLoader ); } - catch ( Throwable e ) + catch ( final Throwable e ) { final String errorMsg = "error establishing classloader for driver: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, errorMsg ); @@ -352,7 +352,7 @@ public Driver loadDriver( final PwmApplication pwmApplication, final DBConfigura LOGGER.debug( () -> "successfully loaded JDBC database driver '" + jdbcClassName + "' from application configuration" ); return driver; } - catch ( Throwable e ) + catch ( final Throwable e ) { final String errorMsg = "error registering JDBC database driver stored in configuration: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, errorMsg ); diff --git a/server/src/main/java/password/pwm/util/form/FormUtility.java b/server/src/main/java/password/pwm/util/form/FormUtility.java index 87203b4ec..3a1a87b19 100644 --- a/server/src/main/java/password/pwm/util/form/FormUtility.java +++ b/server/src/main/java/password/pwm/util/form/FormUtility.java @@ -368,7 +368,7 @@ public static void validateFormValueUniqueness( final ChaiUser theUser = pwmApplication.getProxiedChaiUser( userIdentity ); compareResult = theUser.compareStringAttribute( name, value ); } - catch ( ChaiOperationException | ChaiUnavailableException e ) + catch ( final ChaiOperationException | ChaiUnavailableException e ) { final PwmError error = PwmError.forChaiError( e.getErrorCode() ); throw new PwmUnrecoverableException( error.toInfo() ); @@ -392,7 +392,7 @@ public static void validateFormValueUniqueness( throw new PwmDataValidationException( error ); } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { if ( cacheService != null ) { @@ -557,7 +557,7 @@ else if ( formConfiguration.getType() == FormConfiguration.Type.photo ) } } } - catch ( Exception e ) + catch ( final Exception e ) { PwmError error = null; if ( e instanceof ChaiException ) diff --git a/server/src/main/java/password/pwm/util/i18n/LocaleHelper.java b/server/src/main/java/password/pwm/util/i18n/LocaleHelper.java index 43eefce1f..64fd706a8 100644 --- a/server/src/main/java/password/pwm/util/i18n/LocaleHelper.java +++ b/server/src/main/java/password/pwm/util/i18n/LocaleHelper.java @@ -75,7 +75,7 @@ public static Class classForShortName( final String shortName ) { return Class.forName( className ); } - catch ( ClassNotFoundException e ) + catch ( final ClassNotFoundException e ) { return null; } @@ -123,7 +123,9 @@ public static String getLocalizedMessage( String returnValue = null; if ( config != null ) { - final Map configuredBundle = config.readLocalizedBundle( bundleClass.getName(), key ); + final PwmLocaleBundle pwmLocaleBundle = PwmLocaleBundle.forKey( bundleClass.getName() ) + .orElseThrow( () -> new IllegalStateException( "unknown locale bundle name '" + bundleClass.getName() + "'" ) ); + final Map configuredBundle = config.readLocalizedBundle( pwmLocaleBundle, key ); if ( configuredBundle != null ) { final Locale resolvedLocale = localeResolver( locale, configuredBundle.keySet() ); @@ -144,7 +146,7 @@ public static String getLocalizedMessage( { returnValue = bundle.getString( key ); } - catch ( MissingResourceException e ) + catch ( final MissingResourceException e ) { final String errorMsg = "missing key '" + key + "' for " + bundleClass.getName(); if ( config != null && config.isDevDebugMode() ) @@ -385,7 +387,7 @@ public static Map>> getModifiedKeysInC final Map>> returnObj = new LinkedHashMap<>(); for ( final PwmLocaleBundle pwmLocaleBundle : PwmLocaleBundle.values() ) { - for ( final String key : pwmLocaleBundle.getKeys() ) + for ( final String key : pwmLocaleBundle.getDisplayKeys() ) { for ( final Locale locale : configuration.getKnownLocales() ) { @@ -395,11 +397,11 @@ public static Map>> getModifiedKeysInC { if ( !returnObj.containsKey( pwmLocaleBundle ) ) { - returnObj.put( pwmLocaleBundle, new LinkedHashMap>() ); + returnObj.put( pwmLocaleBundle, new LinkedHashMap<>() ); } if ( !returnObj.get( pwmLocaleBundle ).containsKey( key ) ) { - returnObj.get( pwmLocaleBundle ).put( key, new ArrayList() ); + returnObj.get( pwmLocaleBundle ).put( key, new ArrayList<>() ); } returnObj.get( pwmLocaleBundle ).get( key ).add( locale ); diff --git a/server/src/main/java/password/pwm/util/i18n/LocaleStats.java b/server/src/main/java/password/pwm/util/i18n/LocaleStats.java index a367c286f..7b2b0bc24 100644 --- a/server/src/main/java/password/pwm/util/i18n/LocaleStats.java +++ b/server/src/main/java/password/pwm/util/i18n/LocaleStats.java @@ -144,7 +144,7 @@ private static LocaleStats createLocaleStatsForBundle( ) { final List knownLocales = LocaleHelper.knownBuiltInLocales(); - final int totalKeysInBundle = pwmLocaleBundle.getKeys().size(); + final int totalKeysInBundle = pwmLocaleBundle.getDisplayKeys().size(); int totalSlots = 0; int missingSlots = 0; @@ -204,13 +204,13 @@ public static List missingKeysForBundleAndLocale( { LOGGER.trace( () -> "missing resource bundle: bundle=" + pwmLocaleBundle.getTheClass().getName() + ", locale=" + locale.toString() ); } - returnList.addAll( pwmLocaleBundle.getKeys() ); + returnList.addAll( pwmLocaleBundle.getDisplayKeys() ); } else { LOGGER.trace( () -> "checking file " + bundleFilename ); checkProperties.load( stream ); - for ( final String key : pwmLocaleBundle.getKeys() ) + for ( final String key : pwmLocaleBundle.getDisplayKeys() ) { if ( !checkProperties.containsKey( key ) ) { @@ -223,7 +223,7 @@ public static List missingKeysForBundleAndLocale( } } } - catch ( IOException e ) + catch ( final IOException e ) { if ( DEBUG_FLAG ) { diff --git a/server/src/main/java/password/pwm/util/java/CachingProxyWrapper.java b/server/src/main/java/password/pwm/util/java/CachingProxyWrapper.java index 183a9037e..c5f50574a 100644 --- a/server/src/main/java/password/pwm/util/java/CachingProxyWrapper.java +++ b/server/src/main/java/password/pwm/util/java/CachingProxyWrapper.java @@ -76,7 +76,7 @@ public static T create( final Class proxiedClass, final T innerInstance ) cache.put( methodSignature, Optional.ofNullable( result ) ); return result; } - catch ( InvocationTargetException e ) + catch ( final InvocationTargetException e ) { throw e.getTargetException(); } diff --git a/server/src/main/java/password/pwm/util/java/ConcurrentClosableIteratorWrapper.java b/server/src/main/java/password/pwm/util/java/ConcurrentClosableIteratorWrapper.java new file mode 100644 index 000000000..a1d8d9e8f --- /dev/null +++ b/server/src/main/java/password/pwm/util/java/ConcurrentClosableIteratorWrapper.java @@ -0,0 +1,103 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2019 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.Iterator; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public class ConcurrentClosableIteratorWrapper implements Iterator, ClosableIterator +{ + private final AtomicReference nextReference = new AtomicReference<>(); + private final AtomicBoolean completed = new AtomicBoolean( false ); + + private final Supplier> nextSupplier; + private final Runnable closeFunction; + + public ConcurrentClosableIteratorWrapper( final Supplier> nextFunction, final Runnable closeFunction ) + { + this.nextSupplier = nextFunction; + this.closeFunction = closeFunction; + } + + @Override + public synchronized boolean hasNext() + { + if ( completed.get() ) + { + return false; + } + work(); + return nextReference.get() != null; + } + + @Override + public synchronized T next() + { + if ( completed.get() ) + { + throw new NoSuchElementException( ); + } + work(); + final T fileSummaryInformation = nextReference.get(); + if ( fileSummaryInformation == null ) + { + throw new NoSuchElementException( ); + } + nextReference.set( null ); + return fileSummaryInformation; + } + + @Override + public synchronized void close() + { + closeImpl(); + } + + private void closeImpl() + { + if ( !completed.get() ) + { + completed.set( true ); + nextReference.set( null ); + closeFunction.run(); + } + } + + private void work() + { + if ( nextReference.get() == null && !completed.get() ) + { + final Optional next = nextSupplier.get(); + if ( next.isPresent() ) + { + nextReference.set( next.get() ); + } + else + { + closeImpl(); + } + } + } +} diff --git a/server/src/main/java/password/pwm/util/java/ConditionalTaskExecutor.java b/server/src/main/java/password/pwm/util/java/ConditionalTaskExecutor.java index ca213be41..ef3555367 100644 --- a/server/src/main/java/password/pwm/util/java/ConditionalTaskExecutor.java +++ b/server/src/main/java/password/pwm/util/java/ConditionalTaskExecutor.java @@ -57,7 +57,7 @@ public void conditionallyExecuteTask( ) { task.run(); } - catch ( Throwable t ) + catch ( final Throwable t ) { LOGGER.warn( "unexpected error executing conditional task: " + t.getMessage(), t ); } diff --git a/server/src/main/java/password/pwm/util/java/FileSystemUtility.java b/server/src/main/java/password/pwm/util/java/FileSystemUtility.java index 743dac930..a6d736e3b 100644 --- a/server/src/main/java/password/pwm/util/java/FileSystemUtility.java +++ b/server/src/main/java/password/pwm/util/java/FileSystemUtility.java @@ -26,7 +26,6 @@ import password.pwm.util.logging.PwmLogger; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Serializable; @@ -35,14 +34,19 @@ import java.nio.channels.FileChannel; import java.nio.file.Files; import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.RecursiveTask; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.zip.CRC32; @@ -51,90 +55,133 @@ public class FileSystemUtility { private static final PwmLogger LOGGER = PwmLogger.forClass( FileSystemUtility.class ); + private static final int CRC_BUFFER_SIZE = 60 * 1024; + private static final AtomicLoopIntIncrementer OP_COUNTER = new AtomicLoopIntIncrementer(); - public static List readFileInformation( final File rootFile ) + + public static ClosableIterator readFileInformation( final List rootFiles ) { final Instant startTime = Instant.now(); final int operation = OP_COUNTER.next(); - LOGGER.trace( () -> "begin file summary load for file '" + rootFile.getAbsolutePath() + ", operation=" + operation ); - final ForkJoinPool pool = new ForkJoinPool(); - final RecursiveFileReaderTask task = new RecursiveFileReaderTask( rootFile ); - final List fileSummaryInformations = pool.invoke( task ); - final AtomicLong byteCount = new AtomicLong( 0 ); - final AtomicInteger fileCount = new AtomicInteger( 0 ); - fileSummaryInformations.forEach( fileSummaryInformation -> byteCount.addAndGet( fileSummaryInformation.getSize() ) ); - fileSummaryInformations.forEach( fileSummaryInformation -> fileCount.incrementAndGet() ); - final Map debugInfo = new LinkedHashMap<>(); - debugInfo.put( "operation", Integer.toString( operation ) ); - debugInfo.put( "bytes", StringUtil.formatDiskSizeforDebug( byteCount.get() ) ); - debugInfo.put( "files", Integer.toString( fileCount.get() ) ); - debugInfo.put( "duration", TimeDuration.compactFromCurrent( startTime ) ); - LOGGER.trace( () -> "completed file summary load for file '" + rootFile.getAbsolutePath() + ", " + StringUtil.mapToString( debugInfo ) ); - return fileSummaryInformations; + + final int cpus = Runtime.getRuntime().availableProcessors(); + final BlockingQueue workQueue = new LinkedBlockingQueue<>( ); + final ExecutorService executor = new ThreadPoolExecutor( cpus, cpus, Long.MAX_VALUE, TimeUnit.MILLISECONDS, workQueue ); + final TaskData taskData = new TaskData( executor ); + + for ( final File rootFile : rootFiles ) + { + LOGGER.trace( () -> "begin file summary load for file '" + rootFile.getAbsolutePath() + ", operation=" + operation ); + executor.execute( new RecursiveFileReaderTask( rootFile, taskData ) ); + } + + return new ConcurrentClosableIteratorWrapper<>( () -> + { + while ( taskData.getWorkInProgress().get() > 0 ) + { + final FileSummaryInformation next = taskData.getOutputQueue().poll(); + + if ( next == null ) + { + TimeDuration.of( 20, TimeDuration.Unit.MILLISECONDS ).pause(); + } + else + { + return Optional.of( next ); + } + } + return Optional.empty(); + }, + () -> + { + executor.shutdown(); + final Map debugInfo = new LinkedHashMap<>(); + debugInfo.put( "bytes", StringUtil.formatDiskSizeforDebug( taskData.getByteCount().get() ) ); + debugInfo.put( "files", Integer.toString( taskData.getFileCount().get() ) ); + debugInfo.put( "duration", TimeDuration.compactFromCurrent( startTime ) ); + LOGGER.trace( () -> "completed file summary load for operation '" + operation + ", " + StringUtil.mapToString( debugInfo ) ); + } ); } - private static class RecursiveFileReaderTask extends RecursiveTask> + @Value + private static class TaskData + { + private final AtomicLong byteCount = new AtomicLong( 0 ); + private final AtomicInteger fileCount = new AtomicInteger( 0 ); + private final AtomicInteger workInProgress = new AtomicInteger( 0 ); + private final Queue outputQueue = new ConcurrentLinkedQueue<>(); + + private Executor executor; + + TaskData( final Executor executor ) + { + this.executor = executor; + } + } + + private static class RecursiveFileReaderTask implements Runnable { private final File theFile; + private final TaskData taskData; - RecursiveFileReaderTask( final File theFile ) + RecursiveFileReaderTask( final File theFile, final TaskData taskData ) { Objects.requireNonNull( theFile ); + Objects.requireNonNull( taskData ); this.theFile = theFile; + this.taskData = taskData; + this.taskData.getWorkInProgress().incrementAndGet(); } @Override - protected List compute() + public void run() { - final List results = new ArrayList<>(); - - if ( theFile.isDirectory() ) + try { - final File[] subFiles = theFile.listFiles(); - if ( subFiles != null ) + if ( theFile.isDirectory() ) { - final List tasks = new ArrayList<>(); - for ( final File file : subFiles ) + final File[] subFiles = theFile.listFiles(); + if ( subFiles != null ) { - final RecursiveFileReaderTask newTask = new RecursiveFileReaderTask( file ); - newTask.fork(); - tasks.add( newTask ); + for ( final File file : subFiles ) + { + final RecursiveFileReaderTask newTask = new RecursiveFileReaderTask( file, taskData ); + taskData.getExecutor().execute( newTask ); + } } - tasks.forEach( recursiveFileReaderTask -> results.addAll( recursiveFileReaderTask.join() ) ); } - } - else - { - try + else { - results.add( fileInformationForFile( theFile ) ); - } - catch ( Exception e ) - { - LOGGER.debug( () -> "error executing file summary reader: " + e.getMessage() ); + try + { + if ( theFile.exists() ) + { + final FileSummaryInformation fileSummaryInformation = new FileSummaryInformation( + theFile.getName(), + theFile.getParentFile().getAbsolutePath(), + Instant.ofEpochMilli( theFile.lastModified() ), + theFile.length(), + crc32( theFile ) + ); + taskData.getByteCount().addAndGet( fileSummaryInformation.getSize() ); + taskData.getFileCount().incrementAndGet(); + taskData.getOutputQueue().offer( fileSummaryInformation ); + } + } + catch ( final Exception e ) + { + LOGGER.debug( () -> "error executing file summary reader: " + e.getMessage() ); + } } } - - return Collections.unmodifiableList( results ); + finally + { + this.taskData.getWorkInProgress().decrementAndGet(); + } } } - private static FileSummaryInformation fileInformationForFile( final File file ) - throws IOException - { - if ( file == null || !file.exists() ) - { - return null; - } - return new FileSummaryInformation( - file.getName(), - file.getParentFile().getAbsolutePath(), - Instant.ofEpochMilli( file.lastModified() ), - file.length(), - crc32( file ) - ); - } public static long getFileDirectorySize( final File dir ) { @@ -145,7 +192,7 @@ public static long getFileDirectorySize( final File dir ) .mapToLong( path -> path.toFile().length() ) .sum(); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.error( "error calculating disk size of '" + dir.getAbsolutePath() + "', error: " + e.getMessage(), e ); } @@ -249,7 +296,7 @@ private static void deleteDirectoryContents( final File path, final boolean dele { Files.delete( path.toPath() ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.warn( "error deleting temporary file '" + path.getAbsolutePath() + "', error: " + e.getMessage() ); } @@ -260,9 +307,9 @@ private static long crc32( final File file ) throws IOException { final CRC32 crc32 = new CRC32(); - final FileInputStream fileInputStream = new FileInputStream( file ); - final FileChannel fileChannel = fileInputStream.getChannel(); - final ByteBuffer byteBuffer = ByteBuffer.allocateDirect( 1024 ); + final FileChannel fileChannel = FileChannel.open( file.toPath() ); + final int bufferSize = (int) Math.min( file.length(), CRC_BUFFER_SIZE ); + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect( bufferSize ); while ( fileChannel.read( byteBuffer ) > 0 ) { diff --git a/server/src/main/java/password/pwm/util/java/JavaHelper.java b/server/src/main/java/password/pwm/util/java/JavaHelper.java index 4a833263f..b26bdf769 100644 --- a/server/src/main/java/password/pwm/util/java/JavaHelper.java +++ b/server/src/main/java/password/pwm/util/java/JavaHelper.java @@ -164,32 +164,37 @@ public static > List readEnumListFromStringCollection( fina } public static > E readEnumFromString( final Class enumClass, final E defaultValue, final String input ) + { + return readEnumFromString( enumClass, input ).orElse( defaultValue ); + } + + public static > Optional readEnumFromString( final Class enumClass, final String input ) { if ( StringUtil.isEmpty( input ) ) { - return defaultValue; + return Optional.empty(); } if ( enumClass == null || !enumClass.isEnum() ) { - return defaultValue; + return Optional.empty(); } try { - return Enum.valueOf( enumClass, input ); + return Optional.of( Enum.valueOf( enumClass, input ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { /* noop */ //LOGGER.trace("input=" + input + " does not exist in enumClass=" + enumClass.getSimpleName()); } - catch ( Throwable e ) + catch ( final Throwable e ) { LOGGER.warn( "unexpected error translating input=" + input + " to enumClass=" + enumClass.getSimpleName() + ", error: " + e.getMessage() ); } - return defaultValue; + return Optional.empty(); } public static String throwableToString( final Throwable throwable ) @@ -343,7 +348,7 @@ public static void closeAndWaitExecutor( final ExecutorService executor, final T { executor.awaitTermination( timeDuration.asMillis(), TimeUnit.MILLISECONDS ); } - catch ( InterruptedException e ) + catch ( final InterruptedException e ) { LOGGER.warn( "unexpected error shutting down executor service " + executor.getClass().toString() + " error: " + e.getMessage() ); } @@ -426,7 +431,7 @@ public static String threadInfoToString( final ThreadInfo threadInfo ) } } - for ( MonitorInfo mi : threadInfo.getLockedMonitors() ) + for ( final MonitorInfo mi : threadInfo.getLockedMonitors() ) { if ( mi.getLockedStackDepth() == counter ) { @@ -446,7 +451,7 @@ public static String threadInfoToString( final ThreadInfo threadInfo ) { sb.append( "\n\tNumber of locked synchronizers = " + locks.length ); sb.append( '\n' ); - for ( LockInfo li : locks ) + for ( final LockInfo li : locks ) { sb.append( "\t- " + li ); sb.append( '\n' ); @@ -544,7 +549,7 @@ public static long sizeof( final Serializable object ) out.flush(); return byteArrayOutputStream.toByteArray().length; } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.debug( () -> "exception while estimating session size: " + e.getMessage() ); return 0; @@ -574,7 +579,7 @@ public static Optional deriveLocalServerHostname( final Configuration co return Optional.ofNullable( uriHost ); } } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { LOGGER.trace( () -> " error parsing siteURL hostname: " + e.getMessage() ); } @@ -608,7 +613,7 @@ public static int silentParseInt( final String input, final int defaultValue ) { return Integer.parseInt( input ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { return defaultValue; } @@ -620,7 +625,7 @@ public static long silentParseLong( final String input, final long defaultValue { return Long.parseLong( input ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { return defaultValue; } diff --git a/server/src/main/java/password/pwm/util/java/JsonUtil.java b/server/src/main/java/password/pwm/util/java/JsonUtil.java index 4a038e620..d9c8fd55a 100644 --- a/server/src/main/java/password/pwm/util/java/JsonUtil.java +++ b/server/src/main/java/password/pwm/util/java/JsonUtil.java @@ -164,7 +164,7 @@ public synchronized JsonElement serialize( final X509Certificate cert, final Typ { return new JsonPrimitive( StringUtil.base64Encode( cert.getEncoded() ) ); } - catch ( CertificateEncodingException e ) + catch ( final CertificateEncodingException e ) { throw new IllegalStateException( "unable to json-encode certificate: " + e.getMessage() ); } @@ -179,7 +179,7 @@ public X509Certificate deserialize( final JsonElement jsonElement, final Type ty return ( X509Certificate ) certificateFactory.generateCertificate( new ByteArrayInputStream( StringUtil.base64Decode( jsonElement.getAsString() ) ) ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new JsonParseException( "unable to parse x509certificate: " + e.getMessage() ); } @@ -218,7 +218,7 @@ public synchronized Date deserialize( final JsonElement jsonElement, final Type { return ISO_DATE_FORMAT.parse( jsonElement.getAsString() ); } - catch ( ParseException e ) + catch ( final ParseException e ) { /* noop */ } @@ -228,7 +228,7 @@ public synchronized Date deserialize( final JsonElement jsonElement, final Type { return GSON_DATE_FORMAT.parse( jsonElement.getAsString() ); } - catch ( ParseException e ) + catch ( final ParseException e ) { LOGGER.debug( () -> "unable to parse stored json Date.class timestamp '" + jsonElement.getAsString() + "' error: " + e.getMessage() ); throw new JsonParseException( e ); @@ -256,7 +256,7 @@ public synchronized Instant deserialize( final JsonElement jsonElement, final Ty { return JavaHelper.parseIsoToInstant( jsonElement.getAsString() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "unable to parse stored json Instant.class timestamp '" + jsonElement.getAsString() + "' error: " + e.getMessage() ); throw new JsonParseException( e ); @@ -276,7 +276,7 @@ public synchronized ChallengeSet deserialize( final JsonElement jsonElement, fin { return null; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "unable to parse stored json ChallengeSet.class timestamp '" + jsonElement.getAsString() + "' error: " + e.getMessage() ); throw new JsonParseException( e ); @@ -292,7 +292,7 @@ public byte[] deserialize( final JsonElement json, final Type typeOfT, final Jso { return StringUtil.base64Decode( json.getAsString() ); } - catch ( IOException e ) + catch ( final IOException e ) { final String errorMsg = "io stream error while de-serializing byte array: " + e.getMessage(); LOGGER.error( errorMsg ); @@ -306,7 +306,7 @@ public JsonElement serialize( final byte[] src, final Type typeOfSrc, final Json { return new JsonPrimitive( StringUtil.base64Encode( src, StringUtil.Base64Options.GZIP ) ); } - catch ( IOException e ) + catch ( final IOException e ) { final String errorMsg = "io stream error while serializing byte array: " + e.getMessage(); LOGGER.error( errorMsg ); @@ -323,7 +323,7 @@ public PasswordData deserialize( final JsonElement json, final Type typeOfT, fin { return new PasswordData( json.getAsString() ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "error while deserializing password data: " + e.getMessage(); LOGGER.error( errorMsg ); @@ -337,7 +337,7 @@ public JsonElement serialize( final PasswordData src, final Type typeOfSrc, fina { return new JsonPrimitive( src.getStringValue() ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "error while serializing password data: " + e.getMessage(); LOGGER.error( errorMsg ); diff --git a/server/src/main/java/password/pwm/util/java/LicenseInfoReader.java b/server/src/main/java/password/pwm/util/java/LicenseInfoReader.java index 4d0d81ad0..cde06efd0 100644 --- a/server/src/main/java/password/pwm/util/java/LicenseInfoReader.java +++ b/server/src/main/java/password/pwm/util/java/LicenseInfoReader.java @@ -28,6 +28,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; public class LicenseInfoReader { @@ -61,14 +62,17 @@ public static List getLicenseInfos() throws PwmUnrecoverableExce final List licenseInfos = new ArrayList<>(); { - final XmlElement licenses = dependency.getChild( "licenses" ); - final List licenseList = licenses.getChildren( "license" ); - for ( final XmlElement license : licenseList ) + final Optional licenses = dependency.getChild( "licenses" ); + if ( licenses.isPresent() ) { - final String licenseUrl = license.getChildText( "url" ); - final String licenseName = license.getChildText( "name" ); - final LicenseInfo licenseInfo = new LicenseInfo( licenseUrl, licenseName ); - licenseInfos.add( licenseInfo ); + final List licenseList = licenses.get().getChildren( "license" ); + for ( final XmlElement license : licenseList ) + { + final String licenseUrl = license.getChildText( "url" ); + final String licenseName = license.getChildText( "name" ); + final LicenseInfo licenseInfo = new LicenseInfo( licenseUrl, licenseName ); + licenseInfos.add( licenseInfo ); + } } } diff --git a/server/src/main/java/password/pwm/config/stored/StorageEngine.java b/server/src/main/java/password/pwm/util/java/PwmCallable.java similarity index 55% rename from server/src/main/java/password/pwm/config/stored/StorageEngine.java rename to server/src/main/java/password/pwm/util/java/PwmCallable.java index 77edae5c0..f1f7ad861 100644 --- a/server/src/main/java/password/pwm/config/stored/StorageEngine.java +++ b/server/src/main/java/password/pwm/util/java/PwmCallable.java @@ -18,24 +18,11 @@ * limitations under the License. */ -package password.pwm.config.stored; +package password.pwm.util.java; -import password.pwm.bean.UserIdentity; -import password.pwm.config.StoredValue; +import password.pwm.error.PwmUnrecoverableException; -public interface StorageEngine +public interface PwmCallable { - StoredValue read( StoredConfigReference storedConfigReference ); - - void write( StoredConfigReference storedConfigReference, StoredValue value, UserIdentity userIdentity ); - - void reset( StoredConfigReference storedConfigReference, UserIdentity userIdentity ); - - boolean isWriteLocked( ); - - void writeLock( ); - - ValueMetaData readMetaData( StoredConfigReference storedConfigReference ); - - ConfigChangeLog changeLog( ); + void call() throws PwmUnrecoverableException; } diff --git a/server/src/main/java/password/pwm/util/java/PwmExceptionLoggingConsumer.java b/server/src/main/java/password/pwm/util/java/PwmExceptionLoggingConsumer.java new file mode 100644 index 000000000..a4ee8cce9 --- /dev/null +++ b/server/src/main/java/password/pwm/util/java/PwmExceptionLoggingConsumer.java @@ -0,0 +1,49 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2019 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 password.pwm.error.PwmException; +import password.pwm.util.logging.PwmLogger; + +import java.util.function.Consumer; + +@FunctionalInterface +public interface PwmExceptionLoggingConsumer +{ + static Consumer wrapConsumer( final PwmExceptionLoggingConsumer throwingConsumer ) + { + return t -> + { + try + { + throwingConsumer.accept( t ); + } + catch ( final Exception e ) + { + final PwmLogger pwmLogger = PwmLogger.forClass( throwingConsumer.getClass() ); + pwmLogger.error( "unexpected error running '" + throwingConsumer.getClass().getName() + + "' consumer: " + e.getMessage(), e ); + } + }; + } + + void accept( T t ) throws PwmException; +} diff --git a/server/src/main/java/password/pwm/util/java/PwmSupplier.java b/server/src/main/java/password/pwm/util/java/PwmSupplier.java new file mode 100644 index 000000000..b184d99f5 --- /dev/null +++ b/server/src/main/java/password/pwm/util/java/PwmSupplier.java @@ -0,0 +1,28 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2019 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 password.pwm.error.PwmUnrecoverableException; + +public interface PwmSupplier +{ + T get() throws PwmUnrecoverableException; +} diff --git a/server/src/main/java/password/pwm/util/java/StringUtil.java b/server/src/main/java/password/pwm/util/java/StringUtil.java index 2159b721e..c4a42531c 100644 --- a/server/src/main/java/password/pwm/util/java/StringUtil.java +++ b/server/src/main/java/password/pwm/util/java/StringUtil.java @@ -40,7 +40,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; public abstract class StringUtil @@ -232,9 +234,7 @@ public static boolean nullSafeEqualsIgnoreCase( final String value1, final Strin public static boolean nullSafeEquals( final String value1, final String value2 ) { - return value1 == null - ? value2 == null - : value1.equals( value2 ); + return Objects.equals( value1, value2 ); } public enum Base64Options @@ -289,7 +289,7 @@ public static String urlEncode( final String input ) { return URLEncoder.encode( input, PwmConstants.DEFAULT_CHARSET.toString() ); } - catch ( UnsupportedEncodingException e ) + catch ( final UnsupportedEncodingException e ) { LOGGER.error( "unexpected error during url encoding: " + e.getMessage() ); return input; @@ -302,7 +302,7 @@ public static String urlDecode( final String input ) { return URLDecoder.decode( input, PwmConstants.DEFAULT_CHARSET.toString() ); } - catch ( UnsupportedEncodingException e ) + catch ( final UnsupportedEncodingException e ) { LOGGER.error( "unexpected error during url decoding: " + e.getMessage() ); return input; @@ -527,9 +527,93 @@ public static int convertStrToInt( final String string, final int defaultValue ) { return Integer.parseInt( string ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { return defaultValue; } } + + public static String stripAllWhitespace( final String input ) + { + return stripAllChars( input, Character::isWhitespace ); + } + + public static String stripAllChars( final String input, final Predicate stripPredicate ) + { + final StringBuilder sb = new StringBuilder( input ); + int index = 0; + while ( index < sb.length() ) + { + final char loopChar = sb.charAt( index ); + if ( stripPredicate.test( loopChar ) ) + { + sb.deleteCharAt( index ); + } + else + { + index++; + } + } + return sb.toString(); + } + + public static String insertRepeatedLineBreaks( final String input, final int periodicity ) + { + final String lineSeparator = System.lineSeparator(); + return repeatedInsert( input, periodicity, lineSeparator ); + } + + public static String repeatedInsert( final String input, final int periodicity, final String insertValue ) + { + if ( StringUtil.isEmpty( input ) ) + { + return ""; + } + + if ( StringUtil.isEmpty( insertValue ) ) + { + return input; + } + + final int inputLength = input.length(); + final StringBuilder output = new StringBuilder( inputLength + ( periodicity * insertValue.length() ) ); + + int index = 0; + while ( index < inputLength ) + { + final int endIndex = Math.min( index + periodicity, inputLength ); + output.append( input, index, endIndex ); + output.append( insertValue ); + index += periodicity; + } + return output.toString(); + } + + public static boolean caseIgnoreContains( final Collection collection, final String value ) + { + if ( value == null || collection == null ) + { + return false; + } + + if ( collection.contains( value ) ) + { + return true; + } + + final String lcaseValue = value.toLowerCase(); + for ( final String item : collection ) + { + if ( item != null ) + { + final String lcaseItem = item.toLowerCase(); + if ( lcaseItem.equalsIgnoreCase( lcaseValue ) ) + { + return true; + } + } + } + + return false; + } } diff --git a/server/src/main/java/password/pwm/util/java/TimeDuration.java b/server/src/main/java/password/pwm/util/java/TimeDuration.java index cfb371ef3..8333f65be 100644 --- a/server/src/main/java/password/pwm/util/java/TimeDuration.java +++ b/server/src/main/java/password/pwm/util/java/TimeDuration.java @@ -481,7 +481,7 @@ public TimeDuration pause( { Thread.sleep( pauseTime ); } - catch ( InterruptedException e ) + catch ( final InterruptedException e ) { // ignore } diff --git a/server/src/main/java/password/pwm/util/java/XmlDocument.java b/server/src/main/java/password/pwm/util/java/XmlDocument.java index 56a38226e..60f7e990b 100644 --- a/server/src/main/java/password/pwm/util/java/XmlDocument.java +++ b/server/src/main/java/password/pwm/util/java/XmlDocument.java @@ -33,12 +33,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; public interface XmlDocument { XmlElement getRootElement(); - XmlElement evaluateXpathToElement( String xpathExpression ); + Optional evaluateXpathToElement( String xpathExpression ); List evaluateXpathToElements( String xpathExpression ); @@ -60,14 +61,16 @@ public XmlElement getRootElement() } @Override - public XmlElement evaluateXpathToElement( + public Optional evaluateXpathToElement( final String xpathExpression ) { final XPathFactory xpfac = XPathFactory.instance(); final XPathExpression xp = xpfac.compile( xpathExpression, Filters.element() ); final Element element = xp.evaluateFirst( document ); - return element == null ? null : new XmlElement.XmlElementJDOM( element ); + return element == null + ? Optional.empty() + : Optional.of( new XmlElement.XmlElementJDOM( element ) ); } @Override @@ -109,16 +112,16 @@ public XmlElement getRootElement() } @Override - public XmlElement evaluateXpathToElement( + public Optional evaluateXpathToElement( final String xpathExpression ) { final List elements = evaluateXpathToElements( xpathExpression ); if ( JavaHelper.isEmpty( elements ) ) { - return null; + return Optional.empty(); } - return elements.iterator().next(); + return Optional.of( elements.iterator().next() ); } @Override @@ -133,7 +136,7 @@ public List evaluateXpathToElements( final NodeList nodeList = (NodeList) expression.evaluate( document, XPathConstants.NODESET ); return XmlFactory.XmlFactoryW3c.nodeListToElementList( nodeList ); } - catch ( XPathExpressionException e ) + catch ( final XPathExpressionException e ) { throw new IllegalStateException( "error evaluating xpath expression: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/java/XmlElement.java b/server/src/main/java/password/pwm/util/java/XmlElement.java index 158e6bd2a..596bfec26 100644 --- a/server/src/main/java/password/pwm/util/java/XmlElement.java +++ b/server/src/main/java/password/pwm/util/java/XmlElement.java @@ -24,7 +24,6 @@ import org.jdom2.Content; import org.jdom2.Element; import org.jdom2.Text; -import org.jdom2.input.DOMBuilder; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -32,10 +31,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; public interface XmlElement { - XmlElement getChild( String elementName ); + Optional getChild( String elementName ); String getAttributeValue( String attribute ); @@ -47,8 +47,6 @@ public interface XmlElement String getChildText( String elementName ); - org.jdom2.Element asJdomElement(); - String getName(); void setAttribute( String name, String value ); @@ -69,6 +67,10 @@ public interface XmlElement List getChildren(); + XmlElement copy(); + + XmlElement parent(); + class XmlElementJDOM implements XmlElement { private final Element element; @@ -85,14 +87,14 @@ public String getName() } @Override - public XmlElement getChild( final String elementName ) + public Optional getChild( final String elementName ) { final List children = getChildren( elementName ); if ( JavaHelper.isEmpty( children ) ) { - return null; + return Optional.empty(); } - return children.iterator().next(); + return Optional.of( children.iterator().next() ); } @Override @@ -141,18 +143,8 @@ public String getTextTrim() @Override public String getChildText( final String elementName ) { - final XmlElement child = getChild( elementName ); - if ( child == null ) - { - return null; - } - return child.getText(); - } - - @Override - public Element asJdomElement() - { - return element; + final Optional child = getChild( elementName ); + return child.map( XmlElement::getText ).orElse( null ); } @Override @@ -219,6 +211,18 @@ public void setComment( final List textLines ) element.addContent( 0, new Comment( text ) ); } } + + @Override + public XmlElement copy() + { + return new XmlElementJDOM( this.element.clone() ); + } + + @Override + public XmlElement parent() + { + return new XmlElementJDOM( this.element.getParentElement() ); + } } class XmlElementW3c implements XmlElement @@ -237,14 +241,14 @@ public String getName() } @Override - public XmlElement getChild( final String elementName ) + public Optional getChild( final String elementName ) { final List children = getChildren( elementName ); if ( JavaHelper.isEmpty( children ) ) { - return null; + return Optional.empty(); } - return children.iterator().next(); + return Optional.of( children.iterator().next() ); } @Override @@ -285,19 +289,8 @@ public String getTextTrim() @Override public String getChildText( final String elementName ) { - final XmlElement child = getChild( elementName ); - if ( child == null ) - { - return null; - } - return child.getText(); - } - - @Override - public Element asJdomElement() - { - final DOMBuilder domBuilder = new DOMBuilder(); - return domBuilder.build( element ); + final Optional child = getChild( elementName ); + return child.map( XmlElement::getText ).orElse( null ); } @Override @@ -390,5 +383,19 @@ public void setComment( final List textLines ) } } + + @Override + public XmlElement copy() + { + final Node newNode = this.element.cloneNode( true ); + this.element.getOwnerDocument().adoptNode( newNode ); + return new XmlElementW3c( (org.w3c.dom.Element ) newNode ); + } + + @Override + public XmlElement parent() + { + return new XmlElementW3c( ( org.w3c.dom.Element ) this.element.getParentNode() ); + } } } diff --git a/server/src/main/java/password/pwm/util/java/XmlFactory.java b/server/src/main/java/password/pwm/util/java/XmlFactory.java index 295dba31e..533578abd 100644 --- a/server/src/main/java/password/pwm/util/java/XmlFactory.java +++ b/server/src/main/java/password/pwm/util/java/XmlFactory.java @@ -48,6 +48,7 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public interface XmlFactory { @@ -103,13 +104,15 @@ class XmlFactoryJDOM implements XmlFactory public XmlDocument parseXml( final InputStream inputStream ) throws PwmUnrecoverableException { + Objects.requireNonNull( inputStream ); + final SAXBuilder builder = getBuilder(); final Document inputDocument; try { inputDocument = builder.build( inputStream ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { @@ -190,7 +193,7 @@ public XmlDocument parseXml( final InputStream inputStream ) final DocumentBuilder builder = getBuilder(); inputDocument = builder.parse( inputStream ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, null, new String[] { @@ -213,7 +216,7 @@ static DocumentBuilder getBuilder() dbFactory.setExpandEntityReferences( false ); return dbFactory.newDocumentBuilder(); } - catch ( ParserConfigurationException e ) + catch ( final ParserConfigurationException e ) { throw new IllegalArgumentException( "unable to generate dom xml builder: " + e.getMessage() ); } @@ -231,7 +234,7 @@ public void outputDocument( final XmlDocument document, final OutputStream outpu tr.setOutputProperty( OutputKeys.ENCODING, STORAGE_CHARSET.toString() ); tr.transform( new DOMSource( ( ( XmlDocument.XmlDocumentW3c ) document ).document ), new StreamResult( outputStream ) ); } - catch ( TransformerException e ) + catch ( final TransformerException e ) { throw new IOException( "error loading xml transformer: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/localdb/AbstractJDBCLocalDB.java b/server/src/main/java/password/pwm/util/localdb/AbstractJDBCLocalDB.java index f99852633..054b6cc0e 100644 --- a/server/src/main/java/password/pwm/util/localdb/AbstractJDBCLocalDB.java +++ b/server/src/main/java/password/pwm/util/localdb/AbstractJDBCLocalDB.java @@ -675,7 +675,7 @@ public void remove( ) { AbstractJDBCLocalDB.this.remove( db, currentItem ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new RuntimeException( e ); } diff --git a/server/src/main/java/password/pwm/util/localdb/DerbyLocalDB.java b/server/src/main/java/password/pwm/util/localdb/DerbyLocalDB.java index fb0b3214e..9655326a8 100644 --- a/server/src/main/java/password/pwm/util/localdb/DerbyLocalDB.java +++ b/server/src/main/java/password/pwm/util/localdb/DerbyLocalDB.java @@ -77,7 +77,7 @@ String getDriverClasspath( ) } } - catch ( SQLException e ) + catch ( final SQLException e ) { if ( "XJ015".equals( e.getSQLState() ) ) { @@ -96,7 +96,7 @@ String getDriverClasspath( ) DriverManager.deregisterDriver( driver ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error while de-registering derby driver: " + e.getMessage() ); } @@ -107,7 +107,7 @@ String getDriverClasspath( ) { connection.close(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error while closing derby connection: " + e.getMessage() ); } @@ -138,7 +138,7 @@ Connection openConnection( return connection; } - catch ( Throwable e ) + catch ( final Throwable e ) { final String errorMsg; if ( e instanceof SQLException ) @@ -216,7 +216,7 @@ private void reclaimSpace( final Connection dbConnection, final LocalDB.DB db ) statement.setShort( 5, ( short ) 1 ); statement.execute(); } - catch ( SQLException ex ) + catch ( final SQLException ex ) { LOGGER.error( "error reclaiming space in table " + db.toString() + ": " + ex.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/localdb/LocalDB.java b/server/src/main/java/password/pwm/util/localdb/LocalDB.java index 9fa19f2a3..365e4f280 100644 --- a/server/src/main/java/password/pwm/util/localdb/LocalDB.java +++ b/server/src/main/java/password/pwm/util/localdb/LocalDB.java @@ -21,6 +21,7 @@ package password.pwm.util.localdb; import password.pwm.util.java.ClosableIterator; +import password.pwm.util.java.JavaHelper; import java.io.File; import java.io.Serializable; @@ -116,35 +117,39 @@ enum DB /** * Used for various pwm operational data. */ - PWM_META( true ), - SHAREDHISTORY_META( true ), - SHAREDHISTORY_WORDS( true ), + PWM_META( Flag.Backup ), + SHAREDHISTORY_META( Flag.Backup ), + SHAREDHISTORY_WORDS( Flag.Backup ), // WORDLIST_META(true), // @deprecated - WORDLIST_WORDS( true ), + WORDLIST_WORDS( Flag.Backup ), // SEEDLIST_META(true), // @deprecated - SEEDLIST_WORDS( true ), - PWM_STATS( true ), - EVENTLOG_EVENTS( true ), - EMAIL_QUEUE( true ), - SMS_QUEUE( true ), - RESPONSE_STORAGE( true ), - OTP_SECRET( true ), - TOKENS( true ), - INTRUDER( true ), - AUDIT_QUEUE( true ), - AUDIT_EVENTS( true ), - USER_CACHE( true ), - TEMP( false ), - SYSLOG_QUEUE( true ), - CACHE( false ), - - REPORT_QUEUE( false ),; + SEEDLIST_WORDS( Flag.Backup ), + PWM_STATS( Flag.Backup ), + EVENTLOG_EVENTS( Flag.Backup ), + EMAIL_QUEUE( Flag.Backup ), + SMS_QUEUE( Flag.Backup ), + RESPONSE_STORAGE( Flag.Backup ), + OTP_SECRET( Flag.Backup ), + TOKENS( Flag.Backup ), + INTRUDER( Flag.Backup ), + AUDIT_QUEUE( Flag.Backup ), + AUDIT_EVENTS( Flag.Backup ), + USER_CACHE( Flag.Backup ), + TEMP( ), + SYSLOG_QUEUE( Flag.Backup ), + CACHE( ), + REPORT_QUEUE( ),; private final boolean backup; - DB( final boolean backup ) + private enum Flag { - this.backup = backup; + Backup, + } + + DB( final Flag... flag ) + { + this.backup = JavaHelper.enumArrayContainsValue( flag, Flag.Backup ); } public boolean isBackup( ) diff --git a/server/src/main/java/password/pwm/util/localdb/LocalDBAdaptor.java b/server/src/main/java/password/pwm/util/localdb/LocalDBAdaptor.java index 7aa9b4117..f79fb81f6 100644 --- a/server/src/main/java/password/pwm/util/localdb/LocalDBAdaptor.java +++ b/server/src/main/java/password/pwm/util/localdb/LocalDBAdaptor.java @@ -110,11 +110,11 @@ public void putAll( final DB db, final Map keyValueMap ) throws ParameterValidator.validateKeyValue( loopKey ); ParameterValidator.validateValueValue( loopValue ); } - catch ( NullPointerException e ) + catch ( final NullPointerException e ) { throw new NullPointerException( e.getMessage() + " for transaction record: '" + loopKey + "'" ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { throw new IllegalArgumentException( e.getMessage() + " for transaction record: '" + loopKey + "'" ); } @@ -171,11 +171,11 @@ public void removeAll( final DB db, final Collection keys ) throws Local { ParameterValidator.validateValueValue( loopKey ); } - catch ( NullPointerException e ) + catch ( final NullPointerException e ) { throw new NullPointerException( e.getMessage() + " for transaction record: '" + loopKey + "'" ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { throw new IllegalArgumentException( e.getMessage() + " for transaction record: '" + loopKey + "'" ); } diff --git a/server/src/main/java/password/pwm/util/localdb/LocalDBFactory.java b/server/src/main/java/password/pwm/util/localdb/LocalDBFactory.java index e82e7fb26..f712e32c5 100644 --- a/server/src/main/java/password/pwm/util/localdb/LocalDBFactory.java +++ b/server/src/main/java/password/pwm/util/localdb/LocalDBFactory.java @@ -127,7 +127,7 @@ private static LocalDBProvider createInstance( final String className ) } localDB = ( LocalDBProvider ) impl; } - catch ( Throwable e ) + catch ( final Throwable e ) { final String errorMsg = "error creating new LocalDB instance: " + e.getClass().getName() + ":" + e.getMessage(); LOGGER.error( errorMsg, e ); @@ -156,7 +156,7 @@ private static void initInstance( pwmDBProvider.init( dbFileLocation, initParameters, parameters ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error creating new LocalDB instance: " + e.getClass().getName() + ":" + e.getMessage(); LOGGER.error( errorMsg, e ); diff --git a/server/src/main/java/password/pwm/util/localdb/LocalDBStoredQueue.java b/server/src/main/java/password/pwm/util/localdb/LocalDBStoredQueue.java index 23852ca3c..1bf370ae8 100644 --- a/server/src/main/java/password/pwm/util/localdb/LocalDBStoredQueue.java +++ b/server/src/main/java/password/pwm/util/localdb/LocalDBStoredQueue.java @@ -84,7 +84,7 @@ public static synchronized LocalDBStoredQueue createLocalDBStoredQueue( { developerDebug = pwmApplication.getConfig().isDevDebugMode(); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( () -> "can't read app property for developerDebug mode: " + e.getMessage() ); } @@ -109,7 +109,7 @@ public void removeLast( final int removalCount ) { internalQueue.removeLast( removalCount, false ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected localDB error while modifying queue: " + e.getMessage(), e ); } @@ -121,7 +121,7 @@ public void removeFirst( final int removalCount ) { internalQueue.removeFirst( removalCount, false ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected localDB error while modifying queue: " + e.getMessage(), e ); } @@ -133,7 +133,7 @@ public boolean isEmpty( ) { return internalQueue.size() == 0; } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( e ); } @@ -180,7 +180,7 @@ public boolean addAll( final Collection c ) internalQueue.addFirst( stringCollection ); return true; } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected LocalDB error while modifying queue: " + e.getMessage(), e ); } @@ -198,7 +198,7 @@ public boolean add( final String s ) internalQueue.addFirst( Collections.singletonList( s ) ); return true; } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected LocalDB error while modifying queue: " + e.getMessage(), e ); } @@ -215,7 +215,7 @@ public void clear( ) { internalQueue.clear(); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected LocalDB error while modifying queue: " + e.getMessage(), e ); } @@ -237,7 +237,7 @@ public int size( ) { return internalQueue.size(); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( e ); } @@ -249,7 +249,7 @@ public void addFirst( final String s ) { internalQueue.addFirst( Collections.singletonList( s ) ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected LocalDB error while modifying queue: " + e.getMessage(), e ); } @@ -261,7 +261,7 @@ public void addLast( final String s ) { internalQueue.addLast( Collections.singletonList( s ) ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected LocalDB error while modifying queue: " + e.getMessage(), e ); } @@ -274,7 +274,7 @@ public boolean offerFirst( final String s ) internalQueue.addFirst( Collections.singletonList( s ) ); return true; } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected localDB error while modifying queue: " + e.getMessage(), e ); } @@ -287,7 +287,7 @@ public boolean offerLast( final String s ) internalQueue.addLast( Collections.singletonList( s ) ); return true; } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected localDB error while modifying queue: " + e.getMessage(), e ); } @@ -324,7 +324,7 @@ public String pollFirst( ) } return values.get( 0 ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected localDB error while modifying queue: " + e.getMessage(), e ); } @@ -341,7 +341,7 @@ public String pollLast( ) } return values.get( 0 ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected localDB error while modifying queue: " + e.getMessage(), e ); } @@ -378,7 +378,7 @@ public String peekFirst( ) } return values.get( 0 ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected localDB error while modifying queue: " + e.getMessage(), e ); } @@ -395,7 +395,7 @@ public String peekLast( ) } return values.get( 0 ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected localDB error while modifying queue: " + e.getMessage(), e ); } @@ -432,7 +432,7 @@ public Iterator descendingIterator( ) { return new InnerIterator( internalQueue, false ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( e ); } @@ -444,7 +444,7 @@ public Iterator iterator( ) { return new InnerIterator( internalQueue, true ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( e ); } @@ -467,7 +467,7 @@ public String poll( ) { return this.removeFirst(); } - catch ( NoSuchElementException e ) + catch ( final NoSuchElementException e ) { return null; } @@ -535,7 +535,7 @@ public String next( ) } return nextValue; } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( "unexpected localDB error while iterating queue: " + e.getMessage(), e ); } @@ -1052,7 +1052,7 @@ void debugOutput( final String input ) } } } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { LOGGER.error( "error generating logMsg: " + e.getMessage() ); } @@ -1087,7 +1087,7 @@ public void run( ) + ", size=" + dbSize + ", head=" + headPosition.toString() + ", tail=" + tailPosition.toString() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error during output of debug message during stored queue repair operation: " + e.getMessage(), e ); } diff --git a/server/src/main/java/password/pwm/util/localdb/LocalDBUtility.java b/server/src/main/java/password/pwm/util/localdb/LocalDBUtility.java index c9c397e50..95df493ac 100644 --- a/server/src/main/java/password/pwm/util/localdb/LocalDBUtility.java +++ b/server/src/main/java/password/pwm/util/localdb/LocalDBUtility.java @@ -134,7 +134,7 @@ public void exportLocalDB( final OutputStream outputStream, final Appendable deb } csvPrinter.printComment( "export completed at " + JavaHelper.toIsoDate( new Date() ) ); } - catch ( IOException e ) + catch ( final IOException e ) { writeStringToOut( debugOutput, "IO error during localDB export: " + e.getMessage() ); } @@ -176,7 +176,7 @@ public void exportWordlist( final OutputStream outputStream, final Appendable de } } } - catch ( IOException e ) + catch ( final IOException e ) { writeStringToOut( debugOutput, "IO error during localDB export: " + e.getMessage() ); } @@ -216,7 +216,7 @@ private static void writeStringToOut( final Appendable out, final String string { out.append( msg ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.error( "error writing to output appender while performing operation: " + e.getMessage() ); } @@ -435,7 +435,7 @@ public static Map dbStats( } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error while examining LocalDB: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java b/server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java index b6eb097e8..d8c5540ea 100644 --- a/server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java +++ b/server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java @@ -191,7 +191,7 @@ public void submit( final W workItem ) executorService.execute( ( ) -> sendAndQueueIfNecessary( itemWrapper ) ); preQueueSubmit.incrementAndGet(); } - catch ( RejectedExecutionException e ) + catch ( final RejectedExecutionException e ) { submitToQueue( itemWrapper ); preQueueBypass.incrementAndGet(); @@ -219,13 +219,13 @@ else if ( processResult == ProcessResult.RETRY || processResult == ProcessResult { submitToQueue( itemWrapper ); } - catch ( Exception e ) + catch ( final Exception e ) { logger.error( "error submitting to work queue after executor returned retry status: " + e.getMessage() ); } } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { logger.error( "unexpected error while processing itemWrapper: " + e.getMessage(), e ); } @@ -281,7 +281,7 @@ private String makeDebugText( final ItemWrapper itemWrapper ) { itemMsg = itemWrapper.toDebugString( itemProcessor ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { itemMsg = "error"; } @@ -315,7 +315,7 @@ public void run( ) waitForWork(); } } - catch ( Throwable t ) + catch ( final Throwable t ) { logger.error( "unexpected error processing work item queue: " + JavaHelper.readHostileExceptionMessage( t ), t ); } @@ -334,7 +334,7 @@ public void run( ) processNextItem(); } } - catch ( Throwable t ) + catch ( final Throwable t ) { logger.error( "unexpected error processing work item queue: " + JavaHelper.readHostileExceptionMessage( t ), t ); } @@ -412,7 +412,7 @@ void processNextItem( ) return; } } - catch ( Throwable e ) + catch ( final Throwable e ) { removeQueueTop(); logger.warn( "discarding stored record due to parsing error: " + e.getMessage() + ", record=" + nextStrValue ); @@ -463,7 +463,7 @@ void processNextItem( ) } } } - catch ( Throwable e ) + catch ( final Throwable e ) { if ( !shutdownFlag.get() ) { @@ -516,7 +516,7 @@ W getWorkItem( ) throws PwmOperationalException final Object o = JsonUtil.deserialize( item, clazz ); return ( W ) o; } - catch ( Exception e ) + catch ( final Exception e ) { throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_INTERNAL, "unexpected error deserializing work queue item: " + e.getMessage() ) ); } diff --git a/server/src/main/java/password/pwm/util/localdb/XodusLocalDB.java b/server/src/main/java/password/pwm/util/localdb/XodusLocalDB.java index c9a99bce9..0f2d8f7bb 100644 --- a/server/src/main/java/password/pwm/util/localdb/XodusLocalDB.java +++ b/server/src/main/java/password/pwm/util/localdb/XodusLocalDB.java @@ -119,6 +119,29 @@ public void init( final EnvironmentConfig environmentConfig = makeEnvironmentConfig( initParameters ); + if ( Files.exists( getDirtyFile().toPath() ) ) + { + environmentConfig.setGcUtilizationFromScratch( true ); + LOGGER.warn( "environment not closed cleanly, will re-calculate GC" ); + } + else + { + LOGGER.debug( () -> "environment was closed cleanly" ); + } + + try + { + if ( !getDirtyFile().exists() ) + { + Files.createFile( getDirtyFile().toPath() ); + LOGGER.trace( () -> "created openLock file" ); + } + } + catch ( final IOException e ) + { + LOGGER.error( "error creating openLock file: " + e.getMessage() ); + } + { final boolean compressionEnabled = initParameters.containsKey( Property.Compression_Enabled.getKeyName() ) ? Boolean.parseBoolean( initParameters.get( Property.Compression_Enabled.getKeyName() ) ) @@ -166,6 +189,17 @@ public void close( ) throws LocalDBException { environment.close(); } + + try + { + Files.deleteIfExists( getDirtyFile().toPath() ); + LOGGER.trace( () -> "deleted openLock file" ); + } + catch ( final IOException e ) + { + LOGGER.error( "error creating openLock file: " + e.getMessage() ); + } + status = LocalDB.Status.CLOSED; LOGGER.debug( () -> "closed (" + TimeDuration.compactFromCurrent( startTime ) + ")" ); } @@ -176,7 +210,6 @@ private EnvironmentConfig makeEnvironmentConfig( final Map initP environmentConfig.setEnvCloseForcedly( true ); environmentConfig.setMemoryUsage( 50 * 1024 * 1024 ); environmentConfig.setEnvGatherStatistics( true ); - environmentConfig.setGcUtilizationFromScratch( true ); for ( final Map.Entry entry : initParameters.entrySet() ) { @@ -188,7 +221,7 @@ private EnvironmentConfig makeEnvironmentConfig( final Map initP environmentConfig.setSettings( singleMap ); LOGGER.trace( () -> "set env setting from appProperty: " + key + "=" + value ); } - catch ( InvalidSettingException e ) + catch ( final InvalidSettingException e ) { LOGGER.warn( "problem setting configured env settings: " + e.getMessage() ); } @@ -258,7 +291,7 @@ private void doNext( ) { checkStatus( false ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { throw new IllegalStateException( e ); } @@ -288,7 +321,7 @@ private void doNext( ) } nextValue = decodedValue; } - catch ( Exception e ) + catch ( final Exception e ) { e.printStackTrace(); throw e; @@ -500,7 +533,7 @@ public Map debugInfo( ) outputStats.put( "size." + db.name(), this.size( db ) ); } } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { LOGGER.debug( () -> "error while calculating sizes for localDB debug output: " + e.getMessage() ); } @@ -583,7 +616,7 @@ static byte[] compressData( final byte[] data ) deflaterOutputStream.write( data ); deflaterOutputStream.close(); } - catch ( IOException e ) + catch ( final IOException e ) { throw new IllegalStateException( "unexpected exception compressing data stream: " + e.getMessage(), e ); } @@ -599,7 +632,7 @@ static byte[] decompressData( final byte[] data ) inflaterOutputStream.write( data ); inflaterOutputStream.close(); } - catch ( IOException e ) + catch ( final IOException e ) { throw new IllegalStateException( "unexpected exception decompressing data stream: " + e.getMessage(), e ); } @@ -622,9 +655,14 @@ private static void outputReadme( final File xodusPath ) final byte[] byteContents = contents.getBytes( PwmConstants.DEFAULT_CHARSET ); Files.write( xodusPath.toPath(), byteContents, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.error( "error writing LocalDB readme file: " + e.getMessage() ); } } + + private File getDirtyFile() + { + return new File( this.getFileLocation().getAbsolutePath() + File.separator + FILE_SUB_PATH + File.separator + "xodus.open" ); + } } diff --git a/server/src/main/java/password/pwm/util/logging/LocalDBLogger.java b/server/src/main/java/password/pwm/util/logging/LocalDBLogger.java index 54bc41432..1aa9aa534 100644 --- a/server/src/main/java/password/pwm/util/logging/LocalDBLogger.java +++ b/server/src/main/java/password/pwm/util/logging/LocalDBLogger.java @@ -163,7 +163,7 @@ public Instant getTailDate( ) } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unexpected error attempting to determine tail event timestamp: " + e.getMessage() ); } @@ -267,7 +267,7 @@ PwmLogEvent readEvent( final String value ) { return PwmLogEvent.fromEncodedString( value ); } - catch ( Throwable e ) + catch ( final Throwable e ) { if ( !hasShownReadError ) { @@ -306,7 +306,7 @@ boolean checkEventForParams( pattern = Pattern.compile( searchParameters.getUsername() ); } } - catch ( PatternSyntaxException e ) + catch ( final PatternSyntaxException e ) { LOGGER.trace( () -> "invalid regex syntax for " + searchParameters.getUsername() + ", reverting to plaintext search" ); } @@ -403,7 +403,7 @@ private void flushEvents( ) { localBuffer.add( pwmLogEvent.toEncodedString() ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.warn( "error flushing events to localDB: " + e.getMessage(), e ); } @@ -417,7 +417,7 @@ private void flushEvents( ) } localDBListQueue.addAll( localBuffer ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error writing to localDBLogger: " + e.getMessage(), e ); } @@ -435,7 +435,7 @@ public void run( ) flushEvents(); } } - catch ( Throwable t ) + catch ( final Throwable t ) { LOGGER.fatal( "localDBLogger flush thread has failed: " + t.getMessage(), t ); } @@ -463,7 +463,7 @@ public void run( ) } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.fatal( "unexpected error during LocalDBLogger log event cleanup: " + e.getMessage(), e ); } diff --git a/server/src/main/java/password/pwm/util/logging/PwmLogEvent.java b/server/src/main/java/password/pwm/util/logging/PwmLogEvent.java index 25c94b443..0f137e451 100644 --- a/server/src/main/java/password/pwm/util/logging/PwmLogEvent.java +++ b/server/src/main/java/password/pwm/util/logging/PwmLogEvent.java @@ -135,7 +135,7 @@ private static String makeSrcString( final SessionLabel sessionLabel ) } return from.toString(); } - catch ( NullPointerException e ) + catch ( final NullPointerException e ) { return ""; } diff --git a/server/src/main/java/password/pwm/util/logging/PwmLogManager.java b/server/src/main/java/password/pwm/util/logging/PwmLogManager.java index 1cc1f237f..6ee5426ef 100644 --- a/server/src/main/java/password/pwm/util/logging/PwmLogManager.java +++ b/server/src/main/java/password/pwm/util/logging/PwmLogManager.java @@ -33,6 +33,7 @@ import password.pwm.PwmApplicationMode; import password.pwm.PwmConstants; import password.pwm.config.Configuration; +import password.pwm.config.stored.StoredConfigurationFactory; import password.pwm.util.java.FileSystemUtility; import password.pwm.util.localdb.LocalDB; import password.pwm.util.localdb.LocalDBException; @@ -96,7 +97,7 @@ public static void initializeLogger( LOGGER.debug( () -> "successfully initialized log4j using file " + log4jConfigFile.getAbsolutePath() ); return; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error loading log4jconfig file '" + log4jConfigFile + "' error: " + e.getMessage() ); } @@ -112,6 +113,20 @@ public static void initializeLogger( java.util.logging.Logger.getLogger( "org.glassfish.jersey" ).setLevel( java.util.logging.Level.SEVERE ); } + + public static void preInitConsoleLogLevel( final String pwmLogLevel ) + { + try + { + initConsoleLogger( new Configuration( StoredConfigurationFactory.newConfig() ), pwmLogLevel ); + } + catch ( final Exception e ) + { + final String msg = "error pre-initializing logger: " + e.getMessage(); + System.err.println( msg ); + } + } + private static void initConsoleLogger( final Configuration config, final String consoleLogLevel @@ -192,7 +207,7 @@ private static void initFileLogger( } LOGGER.debug( () -> "successfully initialized default file log4j config at log level " + level.toString() ); } - catch ( IOException e ) + catch ( final IOException e ) { LOGGER.debug( () -> "error initializing RollingFileAppender: " + e.getMessage() ); } @@ -220,7 +235,7 @@ public static LocalDBLogger initializeLocalDBLogger( final PwmApplication pwmApp PwmLogger.setLocalDBLogger( localDBLogLevel, localDBLogger ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "unable to initialize localDBLogger: " + e.getMessage() ); return null; @@ -241,7 +256,7 @@ public static LocalDBLogger initializeLocalDBLogger( final PwmApplication pwmApp } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "unable to initialize localDBLogger/extraAppender: " + e.getMessage() ); } @@ -259,7 +274,7 @@ static LocalDBLogger initLocalDBLogger( final LocalDBLoggerSettings settings = LocalDBLoggerSettings.fromConfiguration( pwmApplication.getConfig() ); return new LocalDBLogger( pwmApplication, pwmDB, settings ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { //nothing to do; } diff --git a/server/src/main/java/password/pwm/util/logging/PwmLogger.java b/server/src/main/java/password/pwm/util/logging/PwmLogger.java index 650e540e4..3ffc84cf1 100644 --- a/server/src/main/java/password/pwm/util/logging/PwmLogger.java +++ b/server/src/main/java/password/pwm/util/logging/PwmLogger.java @@ -20,7 +20,9 @@ package password.pwm.util.logging; +import org.apache.log4j.Logger; import org.apache.log4j.RollingFileAppender; +import org.apache.log4j.varia.NullAppender; import password.pwm.PwmApplication; import password.pwm.PwmConstants; import password.pwm.bean.LoginInfoBean; @@ -140,7 +142,7 @@ private void doPwmSessionLogEvent( final PwmLogLevel level, final PwmSession pwm final CharSequence cleanedString = PwmLogger.removeUserDataFromString( pwmSession.getLoginInfoBean(), message.get() ); cleanedMessage = () -> cleanedString; } - catch ( PwmUnrecoverableException e1 ) + catch ( final PwmUnrecoverableException e1 ) { /* can't be logged */ } @@ -204,7 +206,7 @@ private void doLogEvent( final PwmLogEvent logEvent ) } } } - catch ( Exception e2 ) + catch ( final Exception e2 ) { //nothing can be done about it now; } @@ -601,5 +603,12 @@ public boolean isEnabled( final PwmLogLevel pwmLogLevel ) && minimumDbLogLevel.compareTo( pwmLogLevel ) <= 0 ); } + + public static void disableAllLogging() + { + Logger.getRootLogger().removeAllAppenders(); + Logger.getRootLogger().addAppender( new NullAppender() ); + PwmLogger.markInitialized(); + } } diff --git a/server/src/main/java/password/pwm/util/macro/ExternalRestMacro.java b/server/src/main/java/password/pwm/util/macro/ExternalRestMacro.java index 27262b709..0f277595e 100644 --- a/server/src/main/java/password/pwm/util/macro/ExternalRestMacro.java +++ b/server/src/main/java/password/pwm/util/macro/ExternalRestMacro.java @@ -103,7 +103,7 @@ public String replaceValue( return ""; } } - catch ( PwmException e ) + catch ( final PwmException e ) { final String errorMsg = "error while executing external macro '" + matchValue + "', error: " + e.getMessage(); LOGGER.error( errorMsg ); diff --git a/server/src/main/java/password/pwm/util/macro/InternalMacros.java b/server/src/main/java/password/pwm/util/macro/InternalMacros.java index f463b89c1..33d36a94d 100644 --- a/server/src/main/java/password/pwm/util/macro/InternalMacros.java +++ b/server/src/main/java/password/pwm/util/macro/InternalMacros.java @@ -294,7 +294,7 @@ private String doHash( final String input, final PwmHashAlgorithm pwmHashAlgorit { hashOutput = SecureEngine.hash( inputBytes, pwmHashAlgorithm ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { throw new MacroParseException( "error during hash operation: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/macro/MacroMachine.java b/server/src/main/java/password/pwm/util/macro/MacroMachine.java index abe52ceb6..0f4ca3b8f 100644 --- a/server/src/main/java/password/pwm/util/macro/MacroMachine.java +++ b/server/src/main/java/password/pwm/util/macro/MacroMachine.java @@ -93,7 +93,7 @@ private static Map> } map.get( scope ).put( pattern, macroImplementation ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unable to load macro class " + macroClass.getName() + ", error: " + e.getMessage() ); } @@ -243,7 +243,7 @@ private String doReplace( { replaceStr = macroImplementation.replaceValue( matchedStr, macroRequestInfo ); } - catch ( MacroParseException e ) + catch ( final MacroParseException e ) { LOGGER.debug( sessionLabel, () -> "macro parse error replacing macro '" + matchedStr + "', error: " + e.getMessage() ); if ( pwmApplication != null ) @@ -255,7 +255,7 @@ private String doReplace( replaceStr = "[" + e.getErrorInformation().toUserStr( PwmConstants.DEFAULT_LOCALE, null ) + "]"; } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( sessionLabel, "error while replacing macro '" + matchedStr + "', error: " + e.getMessage() ); } @@ -271,7 +271,7 @@ private String doReplace( { replaceStr = stringReplacer.replace( matchedStr, replaceStr ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( sessionLabel, "unexpected error while executing '" + matchedStr + "' during StringReplacer.replace(), error: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/macro/StandardMacros.java b/server/src/main/java/password/pwm/util/macro/StandardMacros.java index 5a964875a..3c693304f 100644 --- a/server/src/main/java/password/pwm/util/macro/StandardMacros.java +++ b/server/src/main/java/password/pwm/util/macro/StandardMacros.java @@ -128,7 +128,7 @@ public String replaceValue( { length = Integer.parseInt( parameters.get( 1 ) ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { throw new MacroParseException( "error parsing length parameter: " + e.getMessage() ); } @@ -174,7 +174,7 @@ else if ( length <= 0 ) { ldapValue = userInfo.readStringAttribute( ldapAttr ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.trace( () -> "could not replace value for '" + matchValue + "', ldap error: " + e.getMessage() ); return ""; @@ -259,7 +259,7 @@ public String replaceValue( { dateFormat = new SimpleDateFormat( parameters.get( 0 ) ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { throw new MacroParseException( e.getMessage() ); } @@ -363,7 +363,7 @@ public String replaceValue( { pwdExpirationTime = userInfo.getPasswordExpirationTime(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error reading pwdExpirationTime during macro replacement: " + e.getMessage() ); return ""; @@ -382,7 +382,7 @@ public String replaceValue( final DateFormat dateFormat = new SimpleDateFormat( datePattern ); return dateFormat.format( pwdExpirationTime ); } - catch ( IllegalArgumentException e ) + catch ( final IllegalArgumentException e ) { throw new MacroParseException( e.getMessage() ); } @@ -423,7 +423,7 @@ public String replaceValue( return JavaHelper.toIsoDate( pwdExpirationTime ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error reading pwdExpirationTime during macro replacement: " + e.getMessage() ); return ""; @@ -460,7 +460,7 @@ public String replaceValue( final long daysUntilExpiration = timeUntilExpiration.as( TimeDuration.Unit.DAYS ); return String.valueOf( daysUntilExpiration ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error reading pwdExpirationTime during macro replacement: " + e.getMessage() ); return ""; @@ -493,7 +493,7 @@ public String replaceValue( return userInfo.getUsername(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error reading username during macro replacement: " + e.getMessage() ); return ""; @@ -555,7 +555,7 @@ public String replaceValue( return userInfo.getUserEmailAddress(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error reading user email address during macro replacement: " + e.getMessage() ); return ""; @@ -588,7 +588,7 @@ public String replaceValue( return loginInfoBean.getUserCurrentPassword().getStringValue(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error decrypting in memory password during macro replacement: " + e.getMessage() ); return ""; @@ -660,7 +660,7 @@ public String replaceValue( final URL url = new URL( siteUrl ); return url.getHost(); } - catch ( MalformedURLException e ) + catch ( final MalformedURLException e ) { LOGGER.error( "unable to parse configured/detected site URL: " + e.getMessage() ); } @@ -705,7 +705,7 @@ else if ( length <= 0 ) throw new MacroParseException( "length of RandomChar (" + maxLengthPermitted + ") must be greater than zero" ); } } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { throw new MacroParseException( "error parsing length parameter of RandomChar: " + e.getMessage() ); } @@ -756,7 +756,7 @@ public String replaceValue( { min = Integer.parseInt( parameters.get( 0 ) ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { throw new MacroParseException( "error parsing minimum value parameter of RandomNumber: " + e.getMessage() ); } @@ -765,7 +765,7 @@ public String replaceValue( { max = Integer.parseInt( parameters.get( 1 ) ); } - catch ( NumberFormatException e ) + catch ( final NumberFormatException e ) { throw new MacroParseException( "error parsing maximum value parameter of RandomNumber: " + e.getMessage() ); } @@ -817,7 +817,7 @@ public String replaceValue( final String matchValue, final MacroRequestInfo macr return JavaHelper.toIsoDate( userInfo.getOtpUserRecord().getTimestamp() ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error reading otp setup time during macro replacement: " + e.getMessage() ); } @@ -845,7 +845,7 @@ public String replaceValue( final String matchValue, final MacroRequestInfo macr return JavaHelper.toIsoDate( userInfo.getResponseInfoBean().getTimestamp() ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error reading response setup time macro replacement: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/operations/ActionExecutor.java b/server/src/main/java/password/pwm/util/operations/ActionExecutor.java index 32e6eb072..1f3ce604a 100644 --- a/server/src/main/java/password/pwm/util/operations/ActionExecutor.java +++ b/server/src/main/java/password/pwm/util/operations/ActionExecutor.java @@ -145,7 +145,7 @@ private void executeLdapAction( settings.getMacroMachine() ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -242,7 +242,7 @@ private void executeWebserviceAction( } } - catch ( PwmException e ) + catch ( final PwmException e ) { if ( e instanceof PwmOperationalException ) { @@ -284,7 +284,7 @@ private static void writeLdapAttribute( theUser.writeStringAttribute( attrName, effectiveAttrValue ); LOGGER.info( sessionLabel, () -> "replaced attribute on user " + theUser.getEntryDN() + " (" + attrName + "=" + effectiveAttrValue + ")" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error setting '" + attrName + "' attribute on user " + theUser.getEntryDN() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -302,7 +302,7 @@ private static void writeLdapAttribute( theUser.addAttribute( attrName, effectiveAttrValue ); LOGGER.info( sessionLabel, () -> "added attribute on user " + theUser.getEntryDN() + " (" + attrName + "=" + effectiveAttrValue + ")" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error adding '" + attrName + "' attribute value from user " + theUser.getEntryDN() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -321,7 +321,7 @@ private static void writeLdapAttribute( theUser.deleteAttribute( attrName, effectiveAttrValue ); LOGGER.info( sessionLabel, () -> "deleted attribute value on user " + theUser.getEntryDN() + " (" + attrName + ")" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error deleting '" + attrName + "' attribute value on user " + theUser.getEntryDN() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); diff --git a/server/src/main/java/password/pwm/util/operations/CrService.java b/server/src/main/java/password/pwm/util/operations/CrService.java index b357a9f2c..39bfeca2e 100644 --- a/server/src/main/java/password/pwm/util/operations/CrService.java +++ b/server/src/main/java/password/pwm/util/operations/CrService.java @@ -181,7 +181,7 @@ public ChallengeProfile readUserChallengeProfile( } } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { LOGGER.error( sessionLabel, "error reading nmas c/r policy for user " + theUser.getEntryDN() + ": " + e.getMessage() ); } @@ -224,7 +224,7 @@ private static ChallengeSet applyPwmPolicyToNmasChallenges( final ChallengeSet c challengeSet.getIdentifier() ); } - catch ( ChaiValidationException e ) + catch ( final ChaiValidationException e ) { final String errorMsg = "unexpected error applying policies to nmas challengeset: " + e.getMessage(); LOGGER.error( errorMsg, e ); @@ -262,7 +262,7 @@ protected static String determineChallengeProfileForUser( return profile; } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( sessionLabel, "unexpected error while testing password policy profile '" + profile + "', error: " + e.getMessage() ); } @@ -514,7 +514,7 @@ public void writeResponses( errorMessages.put( loopWriteMethod, "Success" ); successes++; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "error saving responses via " + loopWriteMethod + ", error: " + e.getMessage(); errorMessages.put( loopWriteMethod, errorMsg ); @@ -564,7 +564,7 @@ public void clearResponses( operatorMap.get( loopWriteMethod ).clearResponses( userIdentity, theUser, userGUID ); successes++; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( sessionLabel, "error clearing responses via " + loopWriteMethod + ", error: " + e.getMessage() ); } @@ -656,7 +656,7 @@ public boolean checkIfResponseConfigNeeded( LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: " + userIdentity + " has good responses" ); return false; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: " + userIdentity + " does not have good responses: " + e.getMessage() ); return true; diff --git a/server/src/main/java/password/pwm/util/operations/OtpService.java b/server/src/main/java/password/pwm/util/operations/OtpService.java index 48a8091ea..952cfbc1f 100644 --- a/server/src/main/java/password/pwm/util/operations/OtpService.java +++ b/server/src/main/java/password/pwm/util/operations/OtpService.java @@ -126,7 +126,7 @@ public boolean validateToken( throw new UnsupportedOperationException( "OTP type not supported: " + otpUserRecord.getType() ); } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( sessionLabel, "error checking otp secret: " + e.getMessage() ); } @@ -150,7 +150,7 @@ public boolean validateToken( { pwmApplication.getOtpService().writeOTPUserConfiguration( null, userIdentity, otpUserRecord ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_WRITING_OTP_SECRET, e.getMessage() ) ); } @@ -274,7 +274,7 @@ public String doRecoveryHash( { md = MessageDigest.getInstance( algorithm ); } - catch ( NoSuchAlgorithmException e ) + catch ( final NoSuchAlgorithmException e ) { throw new IllegalStateException( "unable to load " + algorithm + " message digest algorithm: " + e.getMessage() ); } @@ -341,7 +341,7 @@ public OTPUserRecord readOTPUserConfiguration( { otpConfig = operator.readOtpUserConfiguration( userIdentity, userGUID ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( sessionLabel, "unexpected error reading stored otp configuration from " + location + " for user " + userIdentity + ", error: " + e.getMessage() ); @@ -396,7 +396,7 @@ public void writeOTPUserConfiguration( operator.writeOtpUserConfiguration( pwmSession, userIdentity, userGUID, otp ); successes++; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmSession, "error writing to " + otpSecretStorageLocation + ", error: " + e.getMessage() ); errorMsgs.append( otpSecretStorageLocation ).append( " error: " ).append( e.getMessage() ); @@ -456,7 +456,7 @@ public void clearOTPUserConfiguration( operator.clearOtpUserConfiguration( pwmSession, userIdentity, chaiUser, userGUID ); successes++; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmSession, "error clearing " + otpSecretStorageLocation + ", error: " + e.getMessage() ); errorMsgs.append( otpSecretStorageLocation ).append( " error: " ).append( e.getMessage() ); diff --git a/server/src/main/java/password/pwm/util/operations/cr/CrOperator.java b/server/src/main/java/password/pwm/util/operations/cr/CrOperator.java index 6e288599d..8cf38aed9 100644 --- a/server/src/main/java/password/pwm/util/operations/cr/CrOperator.java +++ b/server/src/main/java/password/pwm/util/operations/cr/CrOperator.java @@ -99,7 +99,7 @@ static ResponseInfoBean convertToNoAnswerInfoBean( final ResponseSet responseSet } } } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "unable to determine formatType of stored responses: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/operations/cr/DbCrOperator.java b/server/src/main/java/password/pwm/util/operations/cr/DbCrOperator.java index cf200567d..fdfa0d122 100644 --- a/server/src/main/java/password/pwm/util/operations/cr/DbCrOperator.java +++ b/server/src/main/java/password/pwm/util/operations/cr/DbCrOperator.java @@ -85,13 +85,13 @@ public ResponseSet readResponseSet( LOGGER.trace( () -> "user guid for " + theUser.getEntryDN() + " not found in remote database (key=" + userGUID + ")" ); } } - catch ( ChaiValidationException e ) + catch ( final ChaiValidationException e ) { final String errorMsg = "unexpected error reading responses for " + theUser.getEntryDN() + " from remote database: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); throw new PwmUnrecoverableException( errorInformation ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorMsg = "unexpected error reading responses for " + theUser.getEntryDN() + " from remote database: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( e.getErrorInformation().getError(), errorMsg ); @@ -108,7 +108,7 @@ public ResponseInfoBean readResponseInfo( final ChaiUser theUser, final UserIden final ResponseSet responseSet = readResponseSet( theUser, userIdentity, userGUID ); return responseSet == null ? null : CrOperators.convertToNoAnswerInfoBean( responseSet, DataStorageMethod.DB ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_RESPONSES_NORESPONSES, @@ -134,7 +134,7 @@ public void clearResponses( final UserIdentity userIdentity, final ChaiUser theU databaseAccessor.remove( DatabaseTable.PWM_RESPONSES, userGUID ); LOGGER.info( () -> "cleared responses for user " + theUser.getEntryDN() + " in remote database" ); } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_CLEARING_RESPONSES, @@ -180,11 +180,11 @@ public void writeResponses( databaseAccessor.put( DatabaseTable.PWM_RESPONSES, userGUID, responseSet.stringValue() ); LOGGER.info( () -> "saved responses for " + theUser.getEntryDN() + " in remote database (key=" + userGUID + ")" ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } - catch ( DatabaseException e ) + catch ( final DatabaseException e ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_RESPONSES, diff --git a/server/src/main/java/password/pwm/util/operations/cr/LdapCrOperator.java b/server/src/main/java/password/pwm/util/operations/cr/LdapCrOperator.java index cb146fdf1..197f7fd0e 100644 --- a/server/src/main/java/password/pwm/util/operations/cr/LdapCrOperator.java +++ b/server/src/main/java/password/pwm/util/operations/cr/LdapCrOperator.java @@ -62,7 +62,7 @@ public ResponseSet readResponseSet( final ChaiUser theUser, final UserIdentity u { return ChaiCrFactory.readChaiResponseSet( theUser ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { LOGGER.debug( () -> "ldap error reading response set: " + e.getMessage(), e ); } @@ -77,7 +77,7 @@ public ResponseInfoBean readResponseInfo( final ChaiUser theUser, final UserIden final ResponseSet responseSet = readResponseSet( theUser, userIdentity, userGUID ); return responseSet == null ? null : CrOperators.convertToNoAnswerInfoBean( responseSet, DataStorageMethod.LDAP ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final String errorMsg = "unexpected error reading response info " + e.getMessage(); throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_RESPONSES_NORESPONSES, errorMsg ) ); @@ -105,7 +105,7 @@ public void clearResponses( final UserIdentity userIdentity, final ChaiUser theU } LOGGER.info( () -> "cleared responses for user to chai-ldap format" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg; if ( e.getErrorCode() == ChaiError.NO_ACCESS ) @@ -123,7 +123,7 @@ public void clearResponses( final UserIdentity userIdentity, final ChaiUser theU pwmOE.initCause( e ); throw pwmOE; } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } @@ -153,7 +153,7 @@ public void writeResponses( final UserIdentity userIdentity, final ChaiUser theU ChaiCrFactory.writeChaiResponseSet( responseSet, theUser ); LOGGER.info( () -> "saved responses for user to chai-ldap format" ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final String errorMsg; if ( e.getErrorCode() == ChaiError.NO_ACCESS ) diff --git a/server/src/main/java/password/pwm/util/operations/cr/LocalDbCrOperator.java b/server/src/main/java/password/pwm/util/operations/cr/LocalDbCrOperator.java index 8bc07059e..148ce6e9c 100644 --- a/server/src/main/java/password/pwm/util/operations/cr/LocalDbCrOperator.java +++ b/server/src/main/java/password/pwm/util/operations/cr/LocalDbCrOperator.java @@ -81,13 +81,13 @@ public ResponseSet readResponseSet( return userResponseSet; } } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { final String errorMsg = "unexpected LocalDB error reading responses: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); throw new PwmUnrecoverableException( errorInformation ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final String errorMsg = "unexpected chai error reading responses from LocalDB: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -104,7 +104,7 @@ public ResponseInfoBean readResponseInfo( final ChaiUser theUser, final UserIden final ResponseSet responseSet = readResponseSet( theUser, userIdentity, userGUID ); return responseSet == null ? null : CrOperators.convertToNoAnswerInfoBean( responseSet, DataStorageMethod.LOCALDB ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_RESPONSES_NORESPONSES, "unexpected error reading response info " + e.getMessage() ) ); } @@ -129,7 +129,7 @@ public void clearResponses( final UserIdentity userIdentity, final ChaiUser theU localDB.remove( LocalDB.DB.RESPONSE_STORAGE, userGUID ); LOGGER.info( () -> "cleared responses for user " + theUser.getEntryDN() + " in local LocalDB" ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_CLEARING_RESPONSES, "unexpected LocalDB error clearing responses: " + e.getMessage() ); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException( errorInfo ); @@ -167,14 +167,14 @@ public void writeResponses( final UserIdentity userIdentity, final ChaiUser theU localDB.put( LocalDB.DB.RESPONSE_STORAGE, userGUID, responseSet.stringValue() ); LOGGER.info( () -> "saved responses for user in LocalDB" ); } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_RESPONSES, "unexpected LocalDB error saving responses to localDB: " + e.getMessage() ); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException( errorInfo ); pwmOE.initCause( e ); throw pwmOE; } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_RESPONSES, "unexpected error saving responses to localDB: " + e.getMessage() ); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException( errorInfo ); diff --git a/server/src/main/java/password/pwm/util/operations/cr/NMASCrOperator.java b/server/src/main/java/password/pwm/util/operations/cr/NMASCrOperator.java index c27e75380..f6d212dc0 100644 --- a/server/src/main/java/password/pwm/util/operations/cr/NMASCrOperator.java +++ b/server/src/main/java/password/pwm/util/operations/cr/NMASCrOperator.java @@ -195,7 +195,7 @@ private void registerSaslProvider( ) } LOGGER.trace( () -> "initialized security provider " + saslProvider.getClass().getName() ); } - catch ( Throwable t ) + catch ( final Throwable t ) { LOGGER.warn( "unable to create SASL provider, error: " + t.getMessage(), t ); } @@ -206,7 +206,7 @@ private void registerSaslProvider( ) { Security.addProvider( saslProvider ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "error registering security provider" ); } @@ -222,7 +222,7 @@ private void unregisterSaslProvider( ) { Security.removeProvider( NMASCrPwmSaslProvider.SASL_PROVIDER_NAME ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.warn( "error removing provider " + NMASCrPwmSaslProvider.SASL_PROVIDER_NAME + ", error: " + e.getMessage() ); } @@ -292,11 +292,11 @@ public ResponseSet readResponseSet( } return responseSet; } - catch ( PwmException e ) + catch ( final PwmException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error loading NMAS responses: " + e.getMessage(); throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_RESPONSES_NORESPONSES, errorMsg ) ); @@ -324,7 +324,7 @@ public ResponseInfoBean readResponseInfo( final ChaiUser theUser, final UserIden responseInfoBean.setTimestamp( null ); return responseInfoBean; } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_RESPONSES_NORESPONSES, "unexpected error reading response info " + e.getMessage() ) ); } @@ -344,7 +344,7 @@ public void clearResponses( LOGGER.info( () -> "cleared responses for user " + theUser.getEntryDN() + " using NMAS method " ); } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final String errorMsg = "error clearing responses from nmas: " + e.getMessage(); final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_CLEARING_RESPONSES, errorMsg ); @@ -377,7 +377,7 @@ public void writeResponses( LOGGER.info( () -> "saved responses for user using NMAS method " ); } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final String errorMsg = "error writing responses to nmas: " + e.getMessage(); final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_RESPONSES, errorMsg ); @@ -492,7 +492,7 @@ else if ( !theUser.isAccountEnabled() ) throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_RESPONSES_NORESPONSES, "nmas account is disabled" ) ); } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_RESPONSES_NORESPONSES, "unable to evaluate nmas account status attributes" ) ); } @@ -575,7 +575,7 @@ public boolean test( final Map challengeStringMap ) { passed = ldapChallengeSession.testAnswers( answers ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error testing responses: " + e.getMessage() ); } @@ -591,7 +591,7 @@ public boolean test( final Map challengeStringMap ) throw new ChaiUnavailableException( errorMsg, ChaiError.UNKNOWN ); } } - catch ( PwmException e ) + catch ( final PwmException e ) { final String errorMsg = "error reading next challenges after testing responses: " + e.getMessage(); LOGGER.error( "error reading next challenges after testing responses: " + e.getMessage() ); @@ -599,7 +599,7 @@ public boolean test( final Map challengeStringMap ) chaiUnavailableException.initCause( e ); throw chaiUnavailableException; } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error reading next challenges after testing responses: " + e.getMessage(); LOGGER.error( "error reading next challenges after testing responses: " + e.getMessage() ); @@ -703,7 +703,7 @@ public void close( ) { this.ldapConnection.disconnect(); } - catch ( LDAPException e ) + catch ( final LDAPException e ) { LOGGER.error( "error closing ldap connection: " + e.getMessage(), e ); } @@ -740,7 +740,7 @@ else if ( NMASCallback.class.getName().equals( callbackClassname ) ) { handleNMASCallback( ( NMASCallback ) callback ); } - catch ( com.novell.security.nmas.client.InvalidNMASCallbackException e ) + catch ( final com.novell.security.nmas.client.InvalidNMASCallbackException e ) { LOGGER.error( "error processing NMASCallback: " + e.getMessage(), e ); } @@ -752,7 +752,7 @@ else if ( LCMUserPromptCallback.class.getName().equals( callbackClassname ) ) { handleLCMUserPromptCallback( ( LCMUserPromptCallback ) callback ); } - catch ( LCMUserPromptException e ) + catch ( final LCMUserPromptException e ) { LOGGER.error( "error processing LCMUserPromptCallback: " + e.getMessage(), e ); } @@ -851,7 +851,7 @@ public final synchronized com.novell.security.nmas.client.NMASLoginResult getLog { wait(); } - catch ( Exception localException ) + catch ( final Exception localException ) { /* noop */ } @@ -934,7 +934,7 @@ private void doLoginSequence( ) this.callbackHandler ); } - catch ( NullPointerException e ) + catch ( final NullPointerException e ) { LOGGER.error( "NullPointer error during CallBackHandler-NMASCR-bind; " + "this is usually the result of an ldap disconnection, thread=" + this.toDebugString() ); @@ -953,7 +953,7 @@ private void doLoginSequence( ) setLoginResult( new com.novell.security.nmas.client.NMASLoginResult( this.callbackHandler.awaitRetCode(), this.ldapConn ) ); lastActivityTimestamp = Instant.now(); } - catch ( LDAPException e ) + catch ( final LDAPException e ) { if ( loginState == NMASThreadState.ABORTED ) { @@ -985,7 +985,7 @@ public void abort( ) { this.notify(); } - catch ( Exception e ) + catch ( final Exception e ) { /* ignore */ } @@ -994,7 +994,7 @@ public void abort( ) { this.nmasResponseSession.lcmEnv.setUserResponse( null ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.trace( () -> "error during NMASResponseSession abort: " + e.getMessage() ); } @@ -1084,7 +1084,7 @@ public Object run( ) final String saslFactoryName = password.pwm.util.operations.cr.NMASCrOperator.NMASCrPwmSaslFactory.class.getName(); thisInstance.put( "SaslClientFactory." + SASL_PROVIDER_NAME, saslFactoryName ); } - catch ( SecurityException e ) + catch ( final SecurityException e ) { LOGGER.warn( "error registering " + NMASCrPwmSaslProvider.class.getSimpleName() + " SASL Provider, error: " + e.getMessage(), e ); } @@ -1126,7 +1126,7 @@ public SaslClient createSaslClient( final SaslClientFactory realFactory = getRealSaslClientFactory(); return realFactory.createSaslClient( mechanisms, authorizationId, protocol, serverName, props, cbh ); } - catch ( Throwable t ) + catch ( final Throwable t ) { LOGGER.error( "error creating backing sasl factory: " + t.getMessage(), t ); } @@ -1149,7 +1149,7 @@ public String[] getMechanismNames( final Map props ) final SaslClientFactory realFactory = getRealSaslClientFactory(); return realFactory.getMechanismNames( props ); } - catch ( Throwable t ) + catch ( final Throwable t ) { LOGGER.error( "error creating backing sasl factory: " + t.getMessage(), t ); } diff --git a/server/src/main/java/password/pwm/util/operations/otp/AbstractOtpOperator.java b/server/src/main/java/password/pwm/util/operations/otp/AbstractOtpOperator.java index 6dbe433ce..d8d3a8321 100644 --- a/server/src/main/java/password/pwm/util/operations/otp/AbstractOtpOperator.java +++ b/server/src/main/java/password/pwm/util/operations/otp/AbstractOtpOperator.java @@ -137,7 +137,7 @@ public OTPUserRecord decomposeOtpAttribute( final String value ) LOGGER.debug( () -> "detected JSON format - returning" ); return otpconfig; } - catch ( JsonSyntaxException ex ) + catch ( final JsonSyntaxException ex ) { LOGGER.debug( () -> "no JSON format detected - returning" ); /* So, it's not JSON, try something else */ diff --git a/server/src/main/java/password/pwm/util/operations/otp/DbOtpOperator.java b/server/src/main/java/password/pwm/util/operations/otp/DbOtpOperator.java index 9b00456d7..16c2e883d 100644 --- a/server/src/main/java/password/pwm/util/operations/otp/DbOtpOperator.java +++ b/server/src/main/java/password/pwm/util/operations/otp/DbOtpOperator.java @@ -86,7 +86,7 @@ public OTPUserRecord readOtpUserConfiguration( final UserIdentity theUser, final } } } - catch ( PwmException e ) + catch ( final PwmException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } @@ -123,7 +123,7 @@ public void writeOtpUserConfiguration( databaseAccessor.put( DatabaseTable.OTP, userGUID, value ); LOGGER.debug( pwmSession, () -> "saved OTP secret for " + theUser + " in remote database (key=" + userGUID + ")" ); } - catch ( PwmOperationalException ex ) + catch ( final PwmOperationalException ex ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_OTP_SECRET, "unexpected error saving otp to db: " + ex.getMessage() ); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException( errorInfo ); @@ -158,7 +158,7 @@ public void clearOtpUserConfiguration( databaseAccessor.remove( DatabaseTable.OTP, userGUID ); LOGGER.info( () -> "cleared OTP secret for " + theUser + " in remote database (key=" + userGUID + ")" ); } - catch ( DatabaseException ex ) + catch ( final DatabaseException ex ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_OTP_SECRET, diff --git a/server/src/main/java/password/pwm/util/operations/otp/LdapOtpOperator.java b/server/src/main/java/password/pwm/util/operations/otp/LdapOtpOperator.java index eeca4fe32..429e71081 100644 --- a/server/src/main/java/password/pwm/util/operations/otp/LdapOtpOperator.java +++ b/server/src/main/java/password/pwm/util/operations/otp/LdapOtpOperator.java @@ -82,13 +82,13 @@ public OTPUserRecord readOtpUserConfiguration( otp = decomposeOtpAttribute( value ); } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "unexpected LDAP error reading responses: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); throw new PwmUnrecoverableException( errorInformation ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { final String errorMsg = "unexpected LDAP error reading responses: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -133,7 +133,7 @@ public void writeOtpUserConfiguration( theUser.writeStringAttribute( ldapStorageAttribute, value ); LOGGER.info( () -> "saved OTP secret for user to chai-ldap format" ); } - catch ( ChaiException ex ) + catch ( final ChaiException ex ) { final String errorMsg; if ( ex.getErrorCode() == ChaiError.NO_ACCESS ) @@ -177,7 +177,7 @@ public void clearOtpUserConfiguration( chaiUser.deleteAttribute( ldapStorageAttribute, null ); LOGGER.info( () -> "cleared OTP secret for user to chai-ldap format" ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg; if ( e.getErrorCode() == ChaiError.NO_ACCESS ) @@ -195,7 +195,7 @@ public void clearOtpUserConfiguration( pwmOE.initCause( e ); throw pwmOE; } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage() ) ); } diff --git a/server/src/main/java/password/pwm/util/operations/otp/LocalDbOtpOperator.java b/server/src/main/java/password/pwm/util/operations/otp/LocalDbOtpOperator.java index 16fdc6e6f..e40ce3cdd 100644 --- a/server/src/main/java/password/pwm/util/operations/otp/LocalDbOtpOperator.java +++ b/server/src/main/java/password/pwm/util/operations/otp/LocalDbOtpOperator.java @@ -93,13 +93,13 @@ public OTPUserRecord readOtpUserConfiguration( final UserIdentity theUser, final } } } - catch ( LocalDBException e ) + catch ( final LocalDBException e ) { final String errorMsg = "unexpected LocalDB error reading otp: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); throw new PwmUnrecoverableException( errorInformation ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorMsg = "unexpected error reading otp: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -143,14 +143,14 @@ public void writeOtpUserConfiguration( localDB.put( LocalDB.DB.OTP_SECRET, userGUID, value ); LOGGER.info( pwmSession, () -> "saved OTP secret for user in LocalDB" ); } - catch ( LocalDBException ex ) + catch ( final LocalDBException ex ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_OTP_SECRET, "unexpected LocalDB error saving otp to localDB: " + ex.getMessage() ); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException( errorInfo ); pwmOE.initCause( ex ); throw pwmOE; } - catch ( PwmOperationalException ex ) + catch ( final PwmOperationalException ex ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_OTP_SECRET, "unexpected error saving otp to localDB: " + ex.getMessage() ); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException( errorInfo ); @@ -186,7 +186,7 @@ public void clearOtpUserConfiguration( localDB.remove( LocalDB.DB.OTP_SECRET, userGUID ); LOGGER.info( pwmSession, () -> "cleared OTP secret for user in LocalDB" ); } - catch ( LocalDBException ex ) + catch ( final LocalDBException ex ) { final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_OTP_SECRET, "unexpected error saving otp to localDB: " + ex.getMessage() ); final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException( errorInfo ); diff --git a/server/src/main/java/password/pwm/util/operations/otp/PasscodeGenerator.java b/server/src/main/java/password/pwm/util/operations/otp/PasscodeGenerator.java index 73930c67b..f4e782a5d 100644 --- a/server/src/main/java/password/pwm/util/operations/otp/PasscodeGenerator.java +++ b/server/src/main/java/password/pwm/util/operations/otp/PasscodeGenerator.java @@ -171,7 +171,7 @@ private int hashToInt( final byte[] bytes, final int start ) { val = input.readInt(); } - catch ( IOException e ) + catch ( final IOException e ) { throw new IllegalStateException( e ); } diff --git a/server/src/main/java/password/pwm/util/password/PasswordRuleReaderHelper.java b/server/src/main/java/password/pwm/util/password/PasswordRuleReaderHelper.java index c02bd18cc..5ced70c2c 100644 --- a/server/src/main/java/password/pwm/util/password/PasswordRuleReaderHelper.java +++ b/server/src/main/java/password/pwm/util/password/PasswordRuleReaderHelper.java @@ -172,7 +172,7 @@ public List readRegExSetting( final PwmPasswordRule rule, final MacroMa final Pattern loopPattern = Pattern.compile( valueToCompile ); patterns.add( loopPattern ); } - catch ( PatternSyntaxException e ) + catch ( final PatternSyntaxException e ) { LOGGER.warn( "reading password rule value '" + valueToCompile + "' for rule " + rule.getKey() + " is not a valid regular expression " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/password/PasswordUtility.java b/server/src/main/java/password/pwm/util/password/PasswordUtility.java index 83c133648..98db3dd7e 100644 --- a/server/src/main/java/password/pwm/util/password/PasswordUtility.java +++ b/server/src/main/java/password/pwm/util/password/PasswordUtility.java @@ -275,7 +275,7 @@ public static void setActorPassword( final PwmPasswordRuleValidator pwmPasswordRuleValidator = new PwmPasswordRuleValidator( pwmApplication, userInfo.getPasswordPolicy() ); pwmPasswordRuleValidator.testPassword( newPassword, null, userInfo, pwmSession.getSessionManager().getActor( pwmApplication ) ); } - catch ( PwmDataValidationException e ) + catch ( final PwmDataValidationException e ) { final String errorMsg = "attempt to setActorPassword, but password does not pass local policy validator"; final ErrorInformation errorInformation = new ErrorInformation( e.getErrorInformation().getError(), errorMsg ); @@ -403,11 +403,11 @@ public static void setPassword( pwmPasswordRuleValidator.testPassword( newPassword, null, userInfo, theUser ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } - catch ( PwmException e ) + catch ( final PwmException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } @@ -439,21 +439,21 @@ public static void setPassword( + AppProperty.LDAP_PASSWORD_CHANGE_SELF_ENABLE.getKey() + "=false" ); } } - catch ( ChaiPasswordPolicyException e ) + catch ( final ChaiPasswordPolicyException e ) { final String errorMsg = "error setting password for user '" + userIdentity.toDisplayString() + "'' " + e.toString(); final PwmError pwmError = PwmError.forChaiError( e.getErrorCode() ); final ErrorInformation error = new ErrorInformation( pwmError == null ? PwmError.PASSWORD_UNKNOWN_VALIDATION : pwmError, errorMsg ); throw new PwmOperationalException( error ); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { final String errorMsg = "error setting password for user '" + userIdentity.toDisplayString() + "'' " + e.getMessage(); final PwmError pwmError = PwmError.forChaiError( e.getErrorCode() ) == null ? PwmError.ERROR_INTERNAL : PwmError.forChaiError( e.getErrorCode() ); final ErrorInformation error = new ErrorInformation( pwmError, errorMsg ); throw new PwmOperationalException( error ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } @@ -587,7 +587,7 @@ public static void helpdeskSetUserPassword( { proxiedUser.expirePassword(); } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.warn( pwmSession, "error while forcing password expiration for user " + userIdentity.toDisplayString() + ", error: " + e.getMessage() ); } @@ -637,7 +637,7 @@ public static Map readIndividualReplicaLastPasswordTimes( final Instant lastModifiedDate = determinePwdLastModified( pwmApplication, sessionLabel, userIdentity ); returnValue.put( loopReplicaUrl, lastModifiedDate ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { LOGGER.error( sessionLabel, "unreachable server during replica password sync check" ); e.printStackTrace(); @@ -650,7 +650,7 @@ public static Map readIndividualReplicaLastPasswordTimes( { loopProvider.close(); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error closing loopProvider to " + loopReplicaUrl + " while checking individual password sync status"; LOGGER.error( sessionLabel, errorMsg ); @@ -709,7 +709,7 @@ private static void executePostActionMethods( actionExecutor.executeActions( configValues, pwmRequest.getSessionLabel() ); } } - catch ( PwmException e ) + catch ( final PwmException e ) { final ErrorInformation info = new ErrorInformation( PwmError.ERROR_SERVICE_UNREACHABLE, @@ -812,17 +812,21 @@ public static int judgePasswordStrengthUsingZxcvbnAlgorithm( final String password ) { + final int maxTestLength = 100; + if ( StringUtil.isEmpty( password ) ) { return Integer.parseInt( configuration.readAppProperty( AppProperty.PASSWORD_STRENGTH_THRESHOLD_VERY_WEAK ) ); } + final String testPassword = StringUtil.truncate( password, maxTestLength ); + final Zxcvbn zxcvbn = new Zxcvbn(); - final Strength strength = zxcvbn.measure( password ); + final Strength strength = zxcvbn.measure( testPassword ); final int zxcvbnScore = strength.getScore(); - // zxcvbn returns a score of 0-4 (see: https://github.com/dropbox/zxcvbn) + // zxcvbn returns a score of 0-4 (see: https://github.com/nulab/zxcvbn4j) switch ( zxcvbnScore ) { case 4: @@ -964,7 +968,7 @@ public static PwmPasswordPolicy determineConfiguredPolicyProfileForUser( return loopPolicy; } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( pwmSession, "unexpected error while testing password policy profile '" + profile + "', error: " + e.getMessage() ); } @@ -987,7 +991,7 @@ public static PwmPasswordPolicy readLdapPasswordPolicy( { chaiPolicy = theUser.getPasswordPolicy(); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) ); } @@ -1009,7 +1013,7 @@ public static PwmPasswordPolicy readLdapPasswordPolicy( return PwmPasswordPolicy.createPwmPasswordPolicy( ruleMap, chaiPolicy ); } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.warn( "error reading password policy for user " + theUser.getEntryDN() + ", error: " + e.getMessage() ); } @@ -1093,7 +1097,7 @@ public static PasswordCheckInfo checkEnteredPassword( } } } - catch ( PwmDataValidationException e ) + catch ( final PwmDataValidationException e ) { errorCode = e.getError().getErrorCode(); userMessage = e.getErrorInformation().toUserStr( locale, pwmApplication.getConfig() ); @@ -1267,7 +1271,7 @@ private static Instant determinePwdLastModified( return chaiReadDate; } } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.error( sessionLabel, "unexpected error reading password last modified timestamp: " + e.getMessage() ); } @@ -1282,7 +1286,7 @@ private static Instant determinePwdLastModified( LOGGER.trace( sessionLabel, () -> "read pwmPasswordChangeTime as: " + ( pwmPwdLastModified == null ? "n/a" : JavaHelper.toIsoDate( pwmPwdLastModified ) ) ); return pwmPwdLastModified; } - catch ( ChaiOperationException e ) + catch ( final ChaiOperationException e ) { LOGGER.error( sessionLabel, "error parsing password last modified PWM password value for user " + theUser.getEntryDN() + "; error: " + e.getMessage() ); } @@ -1363,7 +1367,7 @@ public static boolean isPasswordWithinMinimumLifetimeImpl( return false; } } - catch ( ChaiException e ) + catch ( final ChaiException e ) { LOGGER.debug( sessionLabel, () -> "unexpected error reading OracleDS password allow modification time: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/password/PwmPasswordRuleValidator.java b/server/src/main/java/password/pwm/util/password/PwmPasswordRuleValidator.java index 01053e3cb..b0a917954 100644 --- a/server/src/main/java/password/pwm/util/password/PwmPasswordRuleValidator.java +++ b/server/src/main/java/password/pwm/util/password/PwmPasswordRuleValidator.java @@ -119,17 +119,17 @@ public boolean testPassword( LOGGER.trace( () -> "calling chai directory password validation checker" ); user.testPasswordPolicy( password.getStringValue() ); } - catch ( UnsupportedOperationException e ) + catch ( final UnsupportedOperationException e ) { LOGGER.trace( () -> "Unsupported operation was thrown while validating password: " + e.toString() ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { pwmApplication.getStatisticsManager().incrementValue( Statistic.LDAP_UNAVAILABLE_COUNT ); LOGGER.warn( "ChaiUnavailableException was thrown while validating password: " + e.toString() ); throw e; } - catch ( ChaiPasswordPolicyException e ) + catch ( final ChaiPasswordPolicyException e ) { final ChaiError passwordError = e.getErrorCode(); final PwmError pwmError = PwmError.forChaiError( passwordError ); @@ -273,7 +273,7 @@ public List invokeExternalRuleMethods( } } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorMsg = "error executing external rule REST call: " + e.getMessage(); LOGGER.error( errorMsg ); diff --git a/server/src/main/java/password/pwm/util/queue/SmsQueueManager.java b/server/src/main/java/password/pwm/util/queue/SmsQueueManager.java index 87d15218a..e90959182 100644 --- a/server/src/main/java/password/pwm/util/queue/SmsQueueManager.java +++ b/server/src/main/java/password/pwm/util/queue/SmsQueueManager.java @@ -155,7 +155,7 @@ public WorkQueueProcessor.ProcessResult process( final SmsItemBean workItem ) StatisticsManager.incrementStat( pwmApplication, Statistic.SMS_SEND_SUCCESSES ); lastError = null; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { StatisticsManager.incrementStat( pwmApplication, Statistic.SMS_SEND_DISCARDS ); StatisticsManager.incrementStat( pwmApplication, Statistic.SMS_SEND_FAILURES ); @@ -163,7 +163,7 @@ public WorkQueueProcessor.ProcessResult process( final SmsItemBean workItem ) lastError = e.getErrorInformation(); return WorkQueueProcessor.ProcessResult.FAILED; } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { StatisticsManager.incrementStat( pwmApplication, Statistic.SMS_SEND_FAILURES ); lastError = e.getErrorInformation(); @@ -197,7 +197,7 @@ public void addSmsToQueue( final SmsItemBean smsItem ) { workQueueProcessor.submit( shortenedBean ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error writing to LocalDB queue, discarding sms send request: " + e.getMessage() ); } @@ -525,7 +525,7 @@ protected void sendSms( final String to, final String message, final SessionLabe determineIfResultSuccessful( config, resultCode, responseBody ); LOGGER.debug( () -> "SMS send successful, HTTP status: " + resultCode ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SMS_SEND_ERROR, @@ -559,7 +559,7 @@ private String makeRequestData( final String gatewayStrPass = gatewayPass == null ? null : gatewayPass.getStringValue(); requestData = requestData.replace( TOKEN_PASS, smsDataEncode( gatewayStrPass, encoding ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "unable to read sms password while reading configuration" ); } diff --git a/server/src/main/java/password/pwm/util/secure/BeanCryptoMachine.java b/server/src/main/java/password/pwm/util/secure/BeanCryptoMachine.java index ea343bc28..ee6b6e433 100644 --- a/server/src/main/java/password/pwm/util/secure/BeanCryptoMachine.java +++ b/server/src/main/java/password/pwm/util/secure/BeanCryptoMachine.java @@ -95,7 +95,7 @@ public Optional decryprt( this.key = key; return Optional.of( ( T ) bean ); } - catch ( ClassNotFoundException e ) + catch ( final ClassNotFoundException e ) { final String msg = "error clasting return bean class"; throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, msg ) ); diff --git a/server/src/main/java/password/pwm/util/secure/HmacAlgorithm.java b/server/src/main/java/password/pwm/util/secure/HmacAlgorithm.java index caaa9c2a7..f54111bc5 100644 --- a/server/src/main/java/password/pwm/util/secure/HmacAlgorithm.java +++ b/server/src/main/java/password/pwm/util/secure/HmacAlgorithm.java @@ -20,7 +20,7 @@ package password.pwm.util.secure; -enum HmacAlgorithm +public enum HmacAlgorithm { HMAC_SHA_256( "HmacSHA256", PwmSecurityKey.Type.HMAC_256, 32 ), HMAC_SHA_512( "HmacSHA512", PwmSecurityKey.Type.HMAC_512, 64 ),; diff --git a/server/src/main/java/password/pwm/util/secure/HttpsServerCertificateManager.java b/server/src/main/java/password/pwm/util/secure/HttpsServerCertificateManager.java index 5d15e38a1..dd7d86fe2 100644 --- a/server/src/main/java/password/pwm/util/secure/HttpsServerCertificateManager.java +++ b/server/src/main/java/password/pwm/util/secure/HttpsServerCertificateManager.java @@ -40,7 +40,7 @@ import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.StoredValue; -import password.pwm.config.stored.StoredConfiguration; +import password.pwm.config.stored.StoredConfigurationModifier; import password.pwm.config.value.PrivateKeyValue; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; @@ -141,7 +141,7 @@ private static KeyStore exportKey( ); return keyStore; } - catch ( Exception e ) + catch ( final Exception e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "error generating keystore file;: " + e.getMessage() ) ); } @@ -157,7 +157,7 @@ private static KeyStore makeSelfSignedCert( final PwmApplication pwmApplication, final SelfCertGenerator selfCertGenerator = new SelfCertGenerator( configuration ); return selfCertGenerator.makeSelfSignedCert( pwmApplication, password, alias ); } - catch ( Exception e ) + catch ( final Exception e ) { throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_CERTIFICATE_ERROR, "unable to generate self signed certificate: " + e.getMessage() ) ); } @@ -264,7 +264,7 @@ public String makeSubjectName( ) cnName = uri.getHost(); } } - catch ( URISyntaxException e ) + catch ( final URISyntaxException e ) { // disregard } @@ -353,12 +353,13 @@ public enum KeyStoreFormat } public static void importKey( - final StoredConfiguration storedConfiguration, + final StoredConfigurationModifier storedConfiguration, final KeyStoreFormat keyStoreFormat, final InputStream inputStream, final PasswordData password, final String alias - ) throws PwmUnrecoverableException + ) + throws PwmUnrecoverableException { final char[] charPassword = password == null ? new char[ 0 ] : password.getStringValue().toCharArray(); final PrivateKeyCertificate privateKeyCertificate; @@ -399,7 +400,7 @@ public static void importKey( LOGGER.debug( () -> "importing certificate chain: " + JsonUtil.serializeCollection( X509Utils.makeDebugInfoMap( certificates ) ) ); privateKeyCertificate = new PrivateKeyCertificate( certificates, key ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unable to load configured https certificate: " + e.getMessage(); final String[] errorDetail = new String[] @@ -410,7 +411,7 @@ public static void importKey( } final StoredValue storedValue = new PrivateKeyValue( privateKeyCertificate ); - storedConfiguration.writeSetting( PwmSetting.HTTPS_CERT, storedValue, null ); + storedConfiguration.writeSetting( PwmSetting.HTTPS_CERT, null, storedValue, null ); } } diff --git a/server/src/main/java/password/pwm/util/secure/PwmSecurityKey.java b/server/src/main/java/password/pwm/util/secure/PwmSecurityKey.java index 67fe187d8..8b929b565 100644 --- a/server/src/main/java/password/pwm/util/secure/PwmSecurityKey.java +++ b/server/src/main/java/password/pwm/util/secure/PwmSecurityKey.java @@ -68,7 +68,7 @@ private byte[] stringToKeyData( final String input ) throws PwmUnrecoverableExce { return input.getBytes( "iso-8859-1" ); } - catch ( UnsupportedEncodingException e ) + catch ( final UnsupportedEncodingException e ) { final String errorMsg = "unexpected error converting input text to crypto key bytes: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); @@ -124,7 +124,7 @@ private SecretKey shaBasedKey( final String keySpecName, final PwmHashAlgorithm final byte[] key = Arrays.copyOfRange( sha1Hash, 0, keyLength ); return new SecretKeySpec( key, keySpecName ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error generating simple crypto key: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); diff --git a/server/src/main/java/password/pwm/util/secure/SecureEngine.java b/server/src/main/java/password/pwm/util/secure/SecureEngine.java index b4a4f617c..d6e23df83 100644 --- a/server/src/main/java/password/pwm/util/secure/SecureEngine.java +++ b/server/src/main/java/password/pwm/util/secure/SecureEngine.java @@ -20,7 +20,6 @@ package password.pwm.util.secure; -import org.apache.commons.io.IOUtils; import password.pwm.PwmConstants; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; @@ -37,7 +36,6 @@ import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Writer; @@ -89,7 +87,7 @@ public static String encryptToString( ? StringUtil.base64Encode( encrypted, StringUtil.Base64Options.URL_SAFE, StringUtil.Base64Options.GZIP ) : StringUtil.base64Encode( encrypted ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error b64 encoding crypto result: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); @@ -155,7 +153,7 @@ public static byte[] encryptToBytes( return output; } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error performing simple crypt operation: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); @@ -185,7 +183,7 @@ public static String decryptStringValue( : StringUtil.base64Decode( value ); return decryptBytes( decoded, key, blockAlgorithm ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error performing simple decrypt operation: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); @@ -252,7 +250,7 @@ public static String decryptBytes( final byte[] decrypted = cipher.doFinal( workingValue ); return new String( decrypted, PwmConstants.DEFAULT_CHARSET ); } - catch ( GeneralSecurityException e ) + catch ( final GeneralSecurityException e ) { final String errorMsg = "unexpected error performing simple decrypt operation: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); @@ -279,13 +277,12 @@ public static String hash( ) throws PwmUnrecoverableException { - FileInputStream fileInputStream = null; try { final MessageDigest messageDigest = MessageDigest.getInstance( hashAlgorithm.getAlgName() ); - fileInputStream = new FileInputStream( file ); - final FileChannel fileChannel = fileInputStream.getChannel(); - final ByteBuffer byteBuffer = ByteBuffer.allocateDirect( HASH_FILE_BUFFER_SIZE ); + final int bufferSize = (int) Math.min( file.length(), HASH_FILE_BUFFER_SIZE ); + final FileChannel fileChannel = FileChannel.open( file.toPath() ); + final ByteBuffer byteBuffer = ByteBuffer.allocateDirect( bufferSize ); while ( fileChannel.read( byteBuffer ) > 0 ) { @@ -301,16 +298,12 @@ public static String hash( return JavaHelper.byteArrayToHexString( messageDigest.digest() ); } - catch ( NoSuchAlgorithmException | IOException e ) + catch ( final NoSuchAlgorithmException | IOException e ) { final String errorMsg = "unexpected error during file hash operation: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); throw new PwmUnrecoverableException( errorInformation ); } - finally - { - IOUtils.closeQuietly( fileInputStream ); - } } public static String hash( @@ -335,7 +328,17 @@ public static String hash( return JavaHelper.byteArrayToHexString( computeHashToBytes( is, algorithm ) ); } - private static byte[] computeHmacToBytes( + public static String hmac( + final HmacAlgorithm hmacAlgorithm, + final PwmSecurityKey pwmSecurityKey, + final String input + ) + throws PwmUnrecoverableException + { + return JavaHelper.byteArrayToHexString( computeHmacToBytes( hmacAlgorithm, pwmSecurityKey, input.getBytes( PwmConstants.DEFAULT_CHARSET ) ) ); + } + + public static byte[] computeHmacToBytes( final HmacAlgorithm hmacAlgorithm, final PwmSecurityKey pwmSecurityKey, final byte[] input @@ -350,7 +353,7 @@ private static byte[] computeHmacToBytes( mac.init( secretKey ); return mac.doFinal( input ); } - catch ( GeneralSecurityException e ) + catch ( final GeneralSecurityException e ) { final String errorMsg = "error during hmac operation: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); @@ -373,7 +376,7 @@ public static byte[] computeHashToBytes( { messageDigest = MessageDigest.getInstance( algorithm.getAlgName() ); } - catch ( NoSuchAlgorithmException e ) + catch ( final NoSuchAlgorithmException e ) { final String errorMsg = "missing hash algorithm: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); @@ -397,7 +400,7 @@ public static byte[] computeHashToBytes( return messageDigest.digest(); } - catch ( IOException e ) + catch ( final IOException e ) { final String errorMsg = "unexpected error during hash operation: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_CRYPT_ERROR, errorMsg ); diff --git a/server/src/main/java/password/pwm/util/secure/SecureService.java b/server/src/main/java/password/pwm/util/secure/SecureService.java index dd863ca61..de20d2d7a 100644 --- a/server/src/main/java/password/pwm/util/secure/SecureService.java +++ b/server/src/main/java/password/pwm/util/secure/SecureService.java @@ -191,7 +191,7 @@ public DigestInputStream digestInputStream( final MessageDigest messageDigest = MessageDigest.getInstance( pwmHashAlgorithm.getAlgName() ); return new DigestInputStream( inputStream, messageDigest ); } - catch ( NoSuchAlgorithmException e ) + catch ( final NoSuchAlgorithmException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_CRYPT_ERROR, "can't create digest inputstream: " + e.getMessage() ); } diff --git a/server/src/main/java/password/pwm/util/secure/X509Utils.java b/server/src/main/java/password/pwm/util/secure/X509Utils.java index e3ee72353..8da0b4a84 100644 --- a/server/src/main/java/password/pwm/util/secure/X509Utils.java +++ b/server/src/main/java/password/pwm/util/secure/X509Utils.java @@ -115,7 +115,7 @@ public static List readRemoteCertificates( sslSock.close(); LOGGER.debug( () -> "ServerCertReader: certificate information read from host=" + host + ", port=" + port ); } - catch ( Exception e ) + catch ( final Exception e ) { final StringBuilder errorMsg = new StringBuilder(); errorMsg.append( "unable to read server certificates from host=" ); @@ -170,7 +170,7 @@ public static List readRemoteHttpCertificates( { pwmHttpClient.makeRequest( request, sessionLabel ); } - catch ( PwmException e ) + catch ( final PwmException e ) { requestError = e.getErrorInformation(); } @@ -223,7 +223,7 @@ public static boolean testIfLdapServerCertsInDefaultKeystore( final URI serverUR sslSock.close(); return true; } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.trace( () -> "exception while testing ldap server cert validity against default keystore: " + e.getMessage() ); } @@ -300,7 +300,7 @@ private void logMsg( final X509Certificate[] certs, final String authType ) { LOGGER.debug( () -> "promiscuous trusting certificate during authType=" + authType + ", subject=" + cert.getSubjectDN().toString() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( "error while decoding certificate: " + e.getMessage() ); throw new IllegalStateException( e ); @@ -505,7 +505,7 @@ public static Map makeDebugInfoMap( final X509Certificate cert, returnMap.put( CertDebugInfoKey.detail.toString(), X509Utils.makeDetailText( cert ) ); } } - catch ( PwmUnrecoverableException | CertificateEncodingException e ) + catch ( final PwmUnrecoverableException | CertificateEncodingException e ) { LOGGER.warn( "error generating hash for certificate: " + e.getMessage() ); } @@ -577,7 +577,7 @@ public static TrustManager[] getDefaultJavaTrustManager( final Configuration con tmf.init( (KeyStore) null ); return tmf.getTrustManagers(); } - catch ( GeneralSecurityException e ) + catch ( final GeneralSecurityException e ) { final String errorMsg = "unexpected error loading default java TrustManager: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -592,7 +592,7 @@ public static String hash( final X509Certificate certificate, final PwmHashAlgor { return SecureEngine.hash( new ByteArrayInputStream( certificate.getEncoded() ), pwmHashAlgorithm ); } - catch ( CertificateEncodingException e ) + catch ( final CertificateEncodingException e ) { throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "unexpected error encoding certificate: " + e.getMessage() ); } @@ -614,7 +614,7 @@ public static Set readCertsForListOfLdapUrls( final List "beginning token destination rest client call to " + configuredUrl ); return invoke( sessionLabel, tokenDestinationData, userIdentity, configuredUrl, locale ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "error making token destination rest client call; error: " + e.getMessage(); LOGGER.error( sessionLabel, errorMsg ); diff --git a/server/src/main/java/password/pwm/ws/client/rest/form/RestFormDataClient.java b/server/src/main/java/password/pwm/ws/client/rest/form/RestFormDataClient.java index 01a37c0f2..ab2f94413 100644 --- a/server/src/main/java/password/pwm/ws/client/rest/form/RestFormDataClient.java +++ b/server/src/main/java/password/pwm/ws/client/rest/form/RestFormDataClient.java @@ -126,7 +126,7 @@ public FormDataResponseBean invoke( final FormDataResponseBean formDataResponseBean = JsonUtil.deserialize( responseBody, FormDataResponseBean.class ); return formDataResponseBean; } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { final String errorMsg = "http response error while executing external rest call, error: " + e.getMessage(); LOGGER.error( errorMsg ); diff --git a/server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java b/server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java index ada07566c..e26a778a0 100644 --- a/server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java +++ b/server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java @@ -203,7 +203,7 @@ private UserIdentity readLdapUserIdentity( ) throws PwmUnrecoverableException { return userSearchEngine.resolveUsername( basicAuthInfo.getUsername(), null, null, sessionLabel ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { throw new PwmUnrecoverableException( e.getErrorInformation().wrapWithNewErrorCode( PwmError.ERROR_WRONGPASSWORD ) ); } diff --git a/server/src/main/java/password/pwm/ws/server/RestServlet.java b/server/src/main/java/password/pwm/ws/server/RestServlet.java index a06c80175..b9e4e1088 100644 --- a/server/src/main/java/password/pwm/ws/server/RestServlet.java +++ b/server/src/main/java/password/pwm/ws/server/RestServlet.java @@ -82,7 +82,7 @@ protected void service( final HttpServletRequest req, final HttpServletResponse { pwmApplication = ContextManager.getContextManager( req.getServletContext() ).getPwmApplication(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { outputRestResultBean( restResultBean, req, resp ); return; @@ -106,7 +106,7 @@ protected void service( final HttpServletRequest req, final HttpServletResponse RequestInitializationFilter.readUserHostname( req, pwmApplication.getConfig() ) ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { restResultBean = RestResultBean.fromError( e.getErrorInformation(), @@ -128,7 +128,7 @@ protected void service( final HttpServletRequest req, final HttpServletResponse LOGGER.trace( sessionLabel, () -> "incoming HTTP REST request: " + debutTxt ); } } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { LOGGER.error( "error while trying to log HTTP request data " + e.getMessage(), e ); } @@ -162,7 +162,7 @@ protected void service( final HttpServletRequest req, final HttpServletResponse restResultBean = invokeWebService( restRequest ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { restResultBean = RestResultBean.fromError( e.getErrorInformation(), @@ -172,7 +172,7 @@ protected void service( final HttpServletRequest req, final HttpServletResponse pwmApplication.determineIfDetailErrorMsgShown() ); } - catch ( Throwable e ) + catch ( final Throwable e ) { final String errorMsg = "internal error during rest service invocation: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -196,7 +196,7 @@ private RestResultBean invokeWebService( final RestRequest restRequest ) throws { return ( RestResultBean ) interestedMethod.invoke( this, restRequest ); } - catch ( InvocationTargetException e ) + catch ( final InvocationTargetException e ) { final Throwable rootException = e.getTargetException(); if ( rootException instanceof PwmUnrecoverableException ) @@ -206,7 +206,7 @@ private RestResultBean invokeWebService( final RestRequest restRequest ) throws LOGGER.error( restRequest.getSessionLabel(), "internal error executing rest request: " + e.getMessage(), e ); throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, e.getMessage() ); } - catch ( IllegalAccessException e ) + catch ( final IllegalAccessException e ) { LOGGER.error( restRequest.getSessionLabel(), "internal error executing rest request: " + e.getMessage(), e ); throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, e.getMessage() ); @@ -235,7 +235,7 @@ private Method discoverMethodForAction( final Class clazz, final RestRequest res final MethodMatcher anyMatch = new MethodMatcher(); final Collection methods = JavaHelper.getAllMethodsForClass( clazz ); - for ( Method method : methods ) + for ( final Method method : methods ) { final RestMethodHandler annotation = method.getAnnotation( RestMethodHandler.class ); final MethodMatcher loopMatch = new MethodMatcher(); @@ -453,7 +453,7 @@ public ChaiUser getChaiUser( ) throws PwmUnrecoverableException { return getChaiProvider().getEntryFactory().newChaiUser( userIdentity.getUserDN() ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } diff --git a/server/src/main/java/password/pwm/ws/server/RestUtility.java b/server/src/main/java/password/pwm/ws/server/RestUtility.java index b5535c730..db699efa5 100644 --- a/server/src/main/java/password/pwm/ws/server/RestUtility.java +++ b/server/src/main/java/password/pwm/ws/server/RestUtility.java @@ -54,7 +54,7 @@ public static T deserializeJsonBody( final RestRequest restRequest, final Cl } return jsonData; } - catch ( Exception e ) + catch ( final Exception e ) { if ( e.getCause() instanceof MalformedJsonException ) { @@ -144,7 +144,7 @@ public static RestServlet.TargetUserIdentity resolveRequestedUsername( return new RestServlet.TargetUserIdentity( restRequest, userIdentity, false ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { throw new PwmUnrecoverableException( e.getErrorInformation() ); } diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java index 15aa443e9..62d38a960 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java @@ -234,7 +234,7 @@ public RestResultBean doFormGetChallengeData( final RestRequest restRequest ) StatisticsManager.incrementStat( restRequest.getPwmApplication(), Statistic.REST_CHALLENGES ); return RestResultBean.withData( jsonData ); } - catch ( ChaiException e ) + catch ( final ChaiException e ) { final String errorMsg = "unexpected error building json response: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -291,7 +291,7 @@ public RestResultBean doSetChallengeDataJson( final RestRequest restRequest ) return RestResultBean.forSuccessMessage( restRequest, Message.Success_SetupResponse ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error reading json input: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -339,7 +339,7 @@ private RestResultBean doDeleteChallengeData( final RestRequest restRequest, fin return RestResultBean.forSuccessMessage( restRequest, Message.Success_Unknown ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error delete responses: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java index 292ea37c3..830be451a 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java @@ -212,12 +212,12 @@ public RestResultBean doOperation( final RestRequest restRequest, final JsonInpu LOGGER.trace( restRequest.getSessionLabel(), () -> "REST /checkpassword response (" + timeDuration.asCompactString() + "): " + JsonUtil.serialize( jsonOutput ) ); return restResultBean; } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.debug( restRequest.getSessionLabel(), () -> "REST /checkpassword error during execution: " + e.getMessage() ); return RestResultBean.fromError( restRequest, e.getErrorInformation() ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMessage = "unexpected error executing web service: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMessage ); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestForgottenPasswordServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestForgottenPasswordServer.java index 23ccf87ec..dc718ac83 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestForgottenPasswordServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestForgottenPasswordServer.java @@ -123,7 +123,7 @@ public RestResultBean doRestForgottenPasswordService( final RestRequest restRequ stateMachine.nextStage(); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { return RestResultBean.fromError( e.getErrorInformation() ); } @@ -135,7 +135,7 @@ public RestResultBean doRestForgottenPasswordService( final RestRequest restRequ { stateMachine.applyFormValues( jsonInput.getForm() ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { errorInformation = e.getErrorInformation(); } @@ -146,7 +146,7 @@ public RestResultBean doRestForgottenPasswordService( final RestRequest restRequ { jsonResponse = JsonResponse.makeResponse( beanBeanCryptoMachine, stateMachine ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { errorInformation = e.getErrorInformation(); } diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestFormSigningServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestFormSigningServer.java index d613126e1..c372262f8 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestFormSigningServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestFormSigningServer.java @@ -103,7 +103,7 @@ private RestResultBean handleRestPostRequest( } throw PwmUnrecoverableException.newException( PwmError.ERROR_MISSING_PARAMETER, "POST body should be a json object" ); } - catch ( Exception e ) + catch ( final Exception e ) { if ( e instanceof PwmUnrecoverableException ) { diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestHealthServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestHealthServer.java index 194edae90..b3855fa1c 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestHealthServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestHealthServer.java @@ -70,7 +70,7 @@ private RestResultBean doPwmHealthPlainGet( final RestRequest restRequest ) StatisticsManager.incrementStat( restRequest.getPwmApplication(), Statistic.REST_HEALTH ); return RestResultBean.withData( resultString ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMessage = "unexpected error executing web service: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMessage ); @@ -91,16 +91,16 @@ public static HealthData processGetHealthCheckData( final PwmApplication pwmApplication, final Locale locale ) - throws IOException, PwmUnrecoverableException { final HealthMonitor healthMonitor = pwmApplication.getHealthMonitor(); final List healthRecords = new ArrayList<>( healthMonitor.getHealthRecords() ); final List healthRecordBeans = HealthRecord.fromHealthRecords( healthRecords, locale, pwmApplication.getConfig() ); - final HealthData healthData = new HealthData(); - healthData.timestamp = healthMonitor.getLastHealthCheckTime(); - healthData.overall = healthMonitor.getMostSevereHealthStatus().toString(); - healthData.records = healthRecordBeans; - return healthData; + return HealthData.builder() + .timestamp( healthMonitor.getLastHealthCheckTime() ) + .overall( healthMonitor.getMostSevereHealthStatus().toString() ) + .records( healthRecordBeans ) + .build(); + } } diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java index cdc7efe60..182ce4ae5 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java @@ -102,11 +102,11 @@ public RestResultBean doGetProfileJsonData( final RestRequest restRequest ) { return doGetProfileDataImpl( restRequest, username ); } - catch ( PwmUnrecoverableException e ) + catch ( final PwmUnrecoverableException e ) { return RestResultBean.fromError( restRequest, e.getErrorInformation() ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error building json response: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); @@ -179,7 +179,7 @@ public RestResultBean doPostProfileData( final RestRequest restRequest ) throws { return doPostProfileDataImpl( restRequest, jsonInput ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error building json response: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java index 14a38c1f5..9556c1c8a 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestRandomPasswordServer.java @@ -113,12 +113,12 @@ public RestResultBean doPostRandomPasswordForm( final RestRequest restRequest ) final RestResultBean restResultBean = RestResultBean.withData( jsonOutput ); return restResultBean; } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( restRequest.getSessionLabel(), "error executing rest-json random password request: " + e.getMessage(), e ); return RestResultBean.fromError( restRequest, e.getErrorInformation() ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMessage = "unexpected error executing web service: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMessage ); @@ -155,7 +155,7 @@ public RestResultBean doPlainRandomPassword( final RestRequest restRequest ) final JsonOutput jsonOutput = doOperation( restRequest, jsonInput ); return RestResultBean.withData( jsonOutput.getPassword() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( restRequest.getSessionLabel(), "error executing rest-json random password request: " + e.getMessage(), e ); final String errorMessage = "unexpected error executing web service: " + e.getMessage(); @@ -176,12 +176,12 @@ public RestResultBean doPostRandomPasswordJson( final RestRequest restRequest ) final JsonOutput jsonOutput = doOperation( restRequest, jsonInput ); return RestResultBean.withData( jsonOutput ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( restRequest.getSessionLabel(), "error executing rest-form random password request: " + e.getMessage(), e ); return RestResultBean.fromError( restRequest, e.getErrorInformation() ); } - catch ( Exception e ) + catch ( final Exception e ) { LOGGER.error( restRequest.getSessionLabel(), "error executing rest-form random password request: " + e.getMessage(), e ); final String errorMessage = "unexpected error executing web service: " + e.getMessage(); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java index b0b955c33..a5dc456c6 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java @@ -215,12 +215,12 @@ private static RestResultBean doSetPassword( final JsonInputData jsonResultData = new JsonInputData( targetUserIdentity.getUserIdentity().toDelimitedKey(), null, random ); return RestResultBean.forSuccessMessage( jsonResultData, restRequest, Message.Success_PasswordChange ); } - catch ( PwmException e ) + catch ( final PwmException e ) { LOGGER.error( "error during set password REST operation: " + e.getMessage() ); return RestResultBean.fromError( restRequest, e.getErrorInformation() ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMessage = "unexpected error executing web service: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMessage ); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestStatisticsServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestStatisticsServer.java index 1203dc98e..fda5a23da 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestStatisticsServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestStatisticsServer.java @@ -315,7 +315,7 @@ private static RestResultBean dataOutput( final RestRequest restRequest ) final RestResultBean resultBean = RestResultBean.withData( jsonOutput ); return resultBean; } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error building json response: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestStatusServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestStatusServer.java index 554744e9f..09d2d6163 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestStatusServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestStatusServer.java @@ -104,11 +104,11 @@ public RestResultBean doGetStatusData( final RestRequest restRequest ) + TimeDuration.compactFromCurrent( startTime ) + ", result=" + JsonUtil.serialize( restResultBean ) ); return restResultBean; } - catch ( PwmException e ) + catch ( final PwmException e ) { return RestResultBean.fromError( e.getErrorInformation() ); } - catch ( Exception e ) + catch ( final Exception e ) { final String errorMsg = "unexpected error building json response: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java index a001b8774..e366b6a73 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java @@ -110,11 +110,11 @@ public RestResultBean doSetOtpDataJson( final RestRequest restRequest ) StatisticsManager.incrementStat( restRequest.getPwmApplication(), Statistic.REST_VERIFYOTP ); return RestResultBean.forSuccessMessage( verified, restRequest, Message.Success_Unknown ); } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } - catch ( PwmOperationalException e ) + catch ( final PwmOperationalException e ) { final String errorMsg = "unexpected error reading json input: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ); diff --git a/server/src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java b/server/src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java index 6a56eeb1b..68c930747 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java +++ b/server/src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java @@ -133,7 +133,7 @@ public RestResultBean doSetChallengeDataJson( final RestRequest restRequest ) th return restResultBean; } - catch ( ChaiUnavailableException e ) + catch ( final ChaiUnavailableException e ) { throw PwmUnrecoverableException.fromChaiException( e ); } diff --git a/server/src/main/java/password/pwm/ws/server/rest/bean/HealthData.java b/server/src/main/java/password/pwm/ws/server/rest/bean/HealthData.java index bca5597a3..946430e0d 100644 --- a/server/src/main/java/password/pwm/ws/server/rest/bean/HealthData.java +++ b/server/src/main/java/password/pwm/ws/server/rest/bean/HealthData.java @@ -20,43 +20,20 @@ package password.pwm.ws.server.rest.bean; +import lombok.Builder; +import lombok.Value; + import java.io.Serializable; import java.time.Instant; import java.util.List; +@Value +@Builder public class HealthData implements Serializable { - public Instant timestamp; + @Builder.Default + public Instant timestamp = Instant.now(); + public String overall; public List records; - - public Instant getTimestamp( ) - { - return timestamp; - } - - public void setTimestamp( final Instant timestamp ) - { - this.timestamp = timestamp; - } - - public String getOverall( ) - { - return overall; - } - - public void setOverall( final String overall ) - { - this.overall = overall; - } - - public List getRecords( ) - { - return records; - } - - public void setRecords( final List records ) - { - this.records = records; - } } diff --git a/server/src/main/resources/password/pwm/config/PwmSetting.xml b/server/src/main/resources/password/pwm/config/PwmSetting.xml index 89338ad9b..62c21ed09 100644 --- a/server/src/main/resources/password/pwm/config/PwmSetting.xml +++ b/server/src/main/resources/password/pwm/config/PwmSetting.xml @@ -3047,6 +3047,7 @@ - - - - {"filename":"mariadb-java-client-2.0.3.jar","filetype":"application/x-java-archive"} - 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 - - diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp index eaddf6fd9..eba41c50d 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp @@ -159,6 +159,7 @@ + diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configguide-database.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configguide-database.jsp index d1c8efef5..d607e2026 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configguide-database.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configguide-database.jsp @@ -67,10 +67,7 @@ Size<%=fileInfo.getSize()%> - md5<%=fileInfo.getMd5sum()%> - - - sha1<%=fileInfo.getSha1sum()%> + sha512<%=fileInfo.getSha512sum()%> <% } %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/configmanager-summary.jsp b/webapp/src/main/webapp/WEB-INF/jsp/configmanager-summary.jsp index 41b116453..a25344f27 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/configmanager-summary.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/configmanager-summary.jsp @@ -37,17 +37,15 @@ <%@ page import="java.util.Locale" %> <%@ page import="java.util.Map" %> <%@ page import="password.pwm.http.PwmRequestAttribute" %> +<%@ page import="java.util.TreeMap" %> <% - final List> settingData = new ArrayList>(); - final Map outputData = new HashMap(); + final Map outputData = new TreeMap<>(); try { final PwmRequest pwmRequest = PwmRequest.forRequest(request,response); outputData.putAll((Map)pwmRequest.getAttribute(PwmRequestAttribute.ConfigurationSummaryOutput)); - - settingData.addAll((List>)outputData.get("settings")); } catch (PwmException e) { - /* noop */ + /* noop */ } %> @@ -65,109 +63,26 @@

-
-

+

-

-
- <%=PwmConstants.PWM_APP_NAME%>   <%=PwmConstants.SERVLET_VERSION%> -
-
- Current Time: <%=JavaHelper.toIsoDate(new Date())%> -
-
-
- Only settings modified from their default value are shown. -
- -
- <% for (final Map record : settingData) { %> - - - - - - - - <% if (record.containsKey("profile")) { %> - - - - - <% } %> - <% if (record.containsKey("modifyTime")) { %> - - - - - <% } %> - <% if (record.containsKey("modifyUser")) { %> - - - - - <% } %> - - - - -
- Setting - - <%=record.get("label")%> -
- Profile - -
- <%=StringUtil.escapeHtml(record.get("profile"))%> -
-
- Modify Time - -
- <%=StringUtil.escapeHtml(record.get("modifyTime"))%> -
-
- Modified by - -
- <%=StringUtil.escapeHtml(record.get("modifyUser"))%> -
-
- Value - -
-
<%=StringUtil.escapeHtml(record.get("value"))%>
-
-
+ +
+ <%=PwmConstants.PWM_APP_NAME%>   <%=PwmConstants.SERVLET_VERSION%> +
+
+ Current Time: <%=JavaHelper.toIsoDate(new Date())%>

- <% } %> - <% final Configuration pwmConfig = JspUtility.getPwmRequest(pageContext).getConfig(); %> - <% final Map>> modifiedKeys = LocaleHelper.getModifiedKeysInConfig(pwmConfig); %> - <% if (modifiedKeys != null && !modifiedKeys.isEmpty()) { %> - <% for (final Map.Entry>> entry : modifiedKeys.entrySet()) { %> - <% final PwmLocaleBundle pwmLocaleBundle = entry.getKey(); %> - <% for (final Map.Entry> innerEntry : entry.getValue().entrySet()) { %> - <% final String key = innerEntry.getKey(); %> - - - - - <% for (final Locale locale : innerEntry.getValue()) { %> - - - - - <% } %> -
<%=pwmLocaleBundle.getTheClass().getSimpleName()%> - <%= key %>
<%=LocaleHelper.debugLabel(locale)%><%=LocaleHelper.getLocalizedMessage(locale,key,pwmConfig,pwmLocaleBundle.getTheClass())%>

- <% } %> - <% } %> - <% } %> + Only settings modified from their default value are shown.
+ <% for (final Map.Entry record : outputData.entrySet()) { %> +

+

<%=record.getKey()%>
+
<%=StringUtil.escapeHtml(record.getValue())%>
+

+ <% } %>
-
<%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %> diff --git a/webapp/src/main/webapp/WEB-INF/jsp/fragment/customlink.jsp b/webapp/src/main/webapp/WEB-INF/jsp/fragment/customlink.jsp index da2e17294..f7f4a3060 100644 --- a/webapp/src/main/webapp/WEB-INF/jsp/fragment/customlink.jsp +++ b/webapp/src/main/webapp/WEB-INF/jsp/fragment/customlink.jsp @@ -28,7 +28,7 @@ <%@ page import="java.util.List" %> <%@ page import="java.util.Locale" %> <%@ page import="password.pwm.http.PwmRequestAttribute" %> -<%@ page import="password.pwm.config.CustomLinkConfiguration" %> +<%@ page import="password.pwm.config.value.data.CustomLinkConfiguration" %> <%@ taglib uri="pwm" prefix="pwm" %> diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings-email.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings-email.js new file mode 100644 index 000000000..2affe3b80 --- /dev/null +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings-email.js @@ -0,0 +1,217 @@ +/* + * Password Management Servlets (PWM) + * http://www.pwm-project.org + * + * Copyright (c) 2006-2009 Novell, Inc. + * Copyright (c) 2009-2019 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. + */ + +// -------------------------- email table handler ------------------------------------ + +var EmailTableHandler = {}; +EmailTableHandler.defaultValue = { + to:"@User:Email@", + from:"@DefaultEmailFromAddress@", + subject:"Subject", + bodyPlain:"Body", + bodyHtml:"Body" +}; + +EmailTableHandler.init = function(keyName) { + console.log('EmailTableHandler init for ' + keyName); + PWM_CFGEDIT.readSetting(keyName, function(resultValue) { + PWM_VAR['clientSettingCache'][keyName] = resultValue; + EmailTableHandler.draw(keyName); + }); +}; + +EmailTableHandler.draw = function(settingKey) { + var resultValue = PWM_VAR['clientSettingCache'][settingKey]; + var parentDiv = 'table_setting_' + settingKey; + PWM_CFGEDIT.clearDivElements(parentDiv, true); + PWM_CFGEDIT.clearDivElements(parentDiv, false); + + var htmlBody = ''; + for (var localeName in resultValue) { + htmlBody += EmailTableHandler.drawRowHtml(settingKey,localeName) + } + var parentDivElement = PWM_MAIN.getObject(parentDiv); + parentDivElement.innerHTML = htmlBody; + + for (var localeName in resultValue) { + EmailTableHandler.instrumentRow(settingKey,localeName) + } + + if (PWM_MAIN.JSLibrary.isEmpty(resultValue)) { + var htmlBody = ''; + + var parentDivElement = PWM_MAIN.getObject(parentDiv); + parentDivElement.innerHTML = htmlBody; + + PWM_MAIN.addEventHandler('button-addValue-' + settingKey,'click',function(){ + PWM_CFGEDIT.resetSetting(settingKey,function(){PWM_CFGEDIT.loadMainPageBody()}); + }); + + } else { + var addLocaleFunction = function(localeValue) { + if (!PWM_VAR['clientSettingCache'][settingKey][localeValue]) { + PWM_VAR['clientSettingCache'][settingKey][localeValue] = EmailTableHandler.defaultValue; + EmailTableHandler.writeSetting(settingKey,true); + } + }; + UILibrary.addAddLocaleButtonRow(parentDiv, settingKey, addLocaleFunction, Object.keys(PWM_VAR['clientSettingCache'][settingKey])); + } +}; + +EmailTableHandler.drawRowHtml = function(settingKey, localeName) { + var localeLabel = localeName === '' ? 'Default Locale' : PWM_GLOBAL['localeInfo'][localeName] + " (" + localeName + ")"; + var idPrefix = "setting-" + localeName + "-" + settingKey; + var htmlBody = ''; + htmlBody += '
'; + htmlBody += ''; + if (PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) > 1) { + htmlBody += ''; + } + var outputFunction = function (labelText, typeText) { + htmlBody += ''; + htmlBody += ''; + htmlBody += ''; + }; + outputFunction('To', 'to'); + outputFunction('From', 'from'); + outputFunction('Subject', 'subject'); + outputFunction('Plain Body', 'bodyPlain'); + outputFunction('HTML Body', 'bodyHtml'); + + htmlBody += '
' + localeLabel + '
' + labelText + ''; + htmlBody += '
'; + if (localeName !== '' || PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) < 2) { // add remove locale x + htmlBody += '
'; + } + htmlBody += '

'; + return htmlBody; +}; + + +EmailTableHandler.instrumentRow = function(settingKey, localeName) { + var idPrefix = "setting-" + localeName + "-" + settingKey; + + UILibrary.addTextValueToElement('panel-to-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['to']); + PWM_MAIN.addEventHandler('button-to-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'to',PWM_CONFIG.showString('Instructions_Edit_Email')); }); + PWM_MAIN.addEventHandler('panel-to-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'to',PWM_CONFIG.showString('Instructions_Edit_Email')); }); + + UILibrary.addTextValueToElement('panel-from-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['from']); + PWM_MAIN.addEventHandler('button-from-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'from'); }); + PWM_MAIN.addEventHandler('panel-from-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'from'); }); + + UILibrary.addTextValueToElement('panel-subject-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['subject']); + PWM_MAIN.addEventHandler('button-subject-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'subject'); }); + PWM_MAIN.addEventHandler('panel-subject-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,false,'subject'); }); + + UILibrary.addTextValueToElement('panel-bodyPlain-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['bodyPlain']); + PWM_MAIN.addEventHandler('button-bodyPlain-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,true,'bodyPlain'); }); + PWM_MAIN.addEventHandler('panel-bodyPlain-' + idPrefix,'click',function(){ EmailTableHandler.editor(settingKey,localeName,true,'bodyPlain'); }); + + UILibrary.addTextValueToElement('panel-bodyHtml-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['bodyHtml']); + PWM_MAIN.addEventHandler('button-bodyHtml-' + idPrefix,'click',function(){ EmailTableHandler.htmlEditorChoice(settingKey,localeName,'bodyHtml'); }); + PWM_MAIN.addEventHandler('panel-bodyHtml-' + idPrefix,'click',function(){ EmailTableHandler.htmlEditorChoice(settingKey,localeName,'bodyHtml'); }); + + PWM_MAIN.addEventHandler("button-deleteRow-" + idPrefix,"click",function(){ + PWM_MAIN.showConfirmDialog({okAction:function(){ + delete PWM_VAR['clientSettingCache'][settingKey][localeName]; + EmailTableHandler.writeSetting(settingKey,true); + }}); + }); +}; + +EmailTableHandler.htmlEditorChoice = function(settingKey,localeName,type) { + var dialogBody = ''; + dialogBody += '
You can use either the HTML or plaintext editor to modify the HTML email body.
'; + dialogBody += '
'; + dialogBody += '
'; + + var addEventHandlers = function(){ + PWM_MAIN.addEventHandler('btn-editor-plain','click',function(){ EmailTableHandler.editor(settingKey,localeName,true,type); }); + PWM_MAIN.addEventHandler('btn-editor-html','click',function(){ EmailTableHandler.htmlBodyEditor(settingKey,localeName); }); + }; + + PWM_MAIN.showDialog({ + title: "HTML Editor Choice", + text: dialogBody, + showClose: true, + showOk: false, + loadFunction: addEventHandlers + }); +}; + + +EmailTableHandler.editor = function(settingKey, localeName, drawTextArea, type, instructions){ + var settingData = PWM_SETTINGS['settings'][settingKey]; + UILibrary.stringEditorDialog({ + title:'Edit Value - ' + settingData['label'], + instructions: instructions ? instructions : '', + textarea:drawTextArea, + value:PWM_VAR['clientSettingCache'][settingKey][localeName][type], + completeFunction:function(value){ + PWM_VAR['clientSettingCache'][settingKey][localeName][type] = value; + PWM_CFGEDIT.writeSetting(settingKey,PWM_VAR['clientSettingCache'][settingKey],function(){ + EmailTableHandler.init(settingKey); + }); + } + }); +}; + + +EmailTableHandler.htmlBodyEditor = function(keyName, localeName) { + // Grab the scope from the angular controller we created on the div element with ID: centerbody-config + var $scope = angular.element(document.getElementById("centerbody-config")).scope(); + var idValue = keyName + "_" + localeName + "_htmlEditor"; + var toolbarButtons = + "[" + + "['h1','h2','h3','h4','h5','h6','p','pre','quote']," + + "['bold','italics','underline','strikeThrough','ul','ol','undo','redo','clear']," + + "['justifyLeft','justifyCenter','justifyRight','justifyFull','indent','outdent']," + + "['html','insertImage','insertLink','insertVideo']" + + "]"; + + PWM_MAIN.showDialog({ + title: "HTML Editor", + text: '
', + showClose:true, + showCancel:true, + dialogClass: 'wide', + loadFunction: function(){ + // Put the existing value into the scope, and tell the controller to process the element with ID: idValue + $scope.htmlText = PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml']; + $scope.$broadcast("content-added", idValue); + }, + okAction:function(){ + PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'] = $scope.htmlText; + EmailTableHandler.writeSetting(keyName,true); + } + }); +}; + + +EmailTableHandler.writeSetting = function(settingKey, redraw) { + var currentValues = PWM_VAR['clientSettingCache'][settingKey]; + PWM_CFGEDIT.writeSetting(settingKey, currentValues, function(){ + if (redraw) { + EmailTableHandler.init(settingKey); + } + }); +}; diff --git a/webapp/src/main/webapp/public/resources/js/configeditor-settings.js b/webapp/src/main/webapp/public/resources/js/configeditor-settings.js index d18ee72a2..2c265f5ba 100644 --- a/webapp/src/main/webapp/public/resources/js/configeditor-settings.js +++ b/webapp/src/main/webapp/public/resources/js/configeditor-settings.js @@ -848,183 +848,6 @@ ChangePasswordHandler.changePasswordPopup = function(settingKey) { -// -------------------------- email table handler ------------------------------------ - -var EmailTableHandler = {}; -EmailTableHandler.defaultValue = { - to:"@User:Email@", - from:"@DefaultEmailFromAddress@", - subject:"Subject", - bodyPlain:"Body", - bodyHtml:"Body" -}; - -EmailTableHandler.init = function(keyName) { - console.log('EmailTableHandler init for ' + keyName); - PWM_CFGEDIT.readSetting(keyName, function(resultValue) { - PWM_VAR['clientSettingCache'][keyName] = resultValue; - EmailTableHandler.draw(keyName); - }); -}; - -EmailTableHandler.draw = function(settingKey) { - var resultValue = PWM_VAR['clientSettingCache'][settingKey]; - var parentDiv = 'table_setting_' + settingKey; - PWM_CFGEDIT.clearDivElements(parentDiv, true); - PWM_CFGEDIT.clearDivElements(parentDiv, false); - - var htmlBody = ''; - for (var localeName in resultValue) { - htmlBody += EmailTableHandler.drawRowHtml(settingKey,localeName) - } - var parentDivElement = PWM_MAIN.getObject(parentDiv); - parentDivElement.innerHTML = htmlBody; - - for (var localeName in resultValue) { - EmailTableHandler.instrumentRow(settingKey,localeName) - } - - if (PWM_MAIN.JSLibrary.isEmpty(resultValue)) { - var htmlBody = ''; - - var parentDivElement = PWM_MAIN.getObject(parentDiv); - parentDivElement.innerHTML = htmlBody; - - PWM_MAIN.addEventHandler('button-addValue-' + settingKey,'click',function(){ - PWM_CFGEDIT.resetSetting(settingKey,function(){PWM_CFGEDIT.loadMainPageBody()}); - }); - - } else { - var addLocaleFunction = function(localeValue) { - if (!PWM_VAR['clientSettingCache'][settingKey][localeValue]) { - PWM_VAR['clientSettingCache'][settingKey][localeValue] = EmailTableHandler.defaultValue; - EmailTableHandler.writeSetting(settingKey,true); - } - }; - UILibrary.addAddLocaleButtonRow(parentDiv, settingKey, addLocaleFunction, Object.keys(PWM_VAR['clientSettingCache'][settingKey])); - } -}; - -EmailTableHandler.drawRowHtml = function(settingKey, localeName) { - var localeLabel = localeName === '' ? 'Default Locale' : PWM_GLOBAL['localeInfo'][localeName] + " (" + localeName + ")"; - var idPrefix = "setting-" + localeName + "-" + settingKey; - var htmlBody = ''; - htmlBody += '
'; - htmlBody += ''; - if (PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) > 1) { - htmlBody += ''; - } - var outputFunction = function (labelText, typeText) { - htmlBody += ''; - htmlBody += ''; - htmlBody += ''; - }; - outputFunction('To', 'to'); - outputFunction('From', 'from'); - outputFunction('Subject', 'subject'); - outputFunction('Plain Body', 'bodyPlain'); - outputFunction('HTML Body', 'bodyHtml'); - - htmlBody += '
' + localeLabel + '
' + labelText + ''; - htmlBody += '
'; - if (localeName !== '' || PWM_MAIN.JSLibrary.itemCount(PWM_VAR['clientSettingCache'][settingKey]) < 2) { // add remove locale x - htmlBody += '
'; - } - htmlBody += '

'; - return htmlBody; -}; - - -EmailTableHandler.instrumentRow = function(settingKey, localeName) { - var settingData = PWM_SETTINGS['settings'][settingKey]; - var idPrefix = "setting-" + localeName + "-" + settingKey; - - var editor = function(drawTextArea, type, instructions){ - UILibrary.stringEditorDialog({ - title:'Edit Value - ' + settingData['label'], - instructions: instructions ? instructions : '', - textarea:drawTextArea, - value:PWM_VAR['clientSettingCache'][settingKey][localeName][type], - completeFunction:function(value){ - PWM_VAR['clientSettingCache'][settingKey][localeName][type] = value; - PWM_CFGEDIT.writeSetting(settingKey,PWM_VAR['clientSettingCache'][settingKey],function(){ - EmailTableHandler.init(settingKey); - }); - } - }); - }; - - UILibrary.addTextValueToElement('panel-to-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['to']); - PWM_MAIN.addEventHandler('button-to-' + idPrefix,'click',function(){ editor(false,'to',PWM_CONFIG.showString('Instructions_Edit_Email')); }); - PWM_MAIN.addEventHandler('panel-to-' + idPrefix,'click',function(){ editor(false,'to',PWM_CONFIG.showString('Instructions_Edit_Email')); }); - - UILibrary.addTextValueToElement('panel-from-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['from']); - PWM_MAIN.addEventHandler('button-from-' + idPrefix,'click',function(){ editor(false,'from'); }); - PWM_MAIN.addEventHandler('panel-from-' + idPrefix,'click',function(){ editor(false,'from'); }); - - UILibrary.addTextValueToElement('panel-subject-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['subject']); - PWM_MAIN.addEventHandler('button-subject-' + idPrefix,'click',function(){ editor(false,'subject'); }); - PWM_MAIN.addEventHandler('panel-subject-' + idPrefix,'click',function(){ editor(false,'subject'); }); - - UILibrary.addTextValueToElement('panel-bodyPlain-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['bodyPlain']); - PWM_MAIN.addEventHandler('button-bodyPlain-' + idPrefix,'click',function(){ editor(true,'bodyPlain'); }); - PWM_MAIN.addEventHandler('panel-bodyPlain-' + idPrefix,'click',function(){ editor(true,'bodyPlain'); }); - - UILibrary.addTextValueToElement('panel-bodyHtml-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['bodyHtml']); - PWM_MAIN.addEventHandler('button-bodyHtml-' + idPrefix,'click',function(){ EmailTableHandler.htmlBodyEditor(settingKey,localeName); }); - PWM_MAIN.addEventHandler('panel-bodyHtml-' + idPrefix,'click',function(){ EmailTableHandler.htmlBodyEditor(settingKey,localeName); }); - - PWM_MAIN.addEventHandler("button-deleteRow-" + idPrefix,"click",function(){ - PWM_MAIN.showConfirmDialog({okAction:function(){ - delete PWM_VAR['clientSettingCache'][settingKey][localeName]; - EmailTableHandler.writeSetting(settingKey,true); - }}); - }); -}; - - -EmailTableHandler.htmlBodyEditor = function(keyName, localeName) { - // Grab the scope from the angular controller we created on the div element with ID: centerbody-config - var $scope = angular.element(document.getElementById("centerbody-config")).scope(); - var idValue = keyName + "_" + localeName + "_htmlEditor"; - var toolbarButtons = - "[" + - "['h1','h2','h3','h4','h5','h6','p','pre','quote']," + - "['bold','italics','underline','strikeThrough','ul','ol','undo','redo','clear']," + - "['justifyLeft','justifyCenter','justifyRight','justifyFull','indent','outdent']," + - "['html','insertImage','insertLink','insertVideo']" + - "]"; - - PWM_MAIN.showDialog({ - title: "HTML Editor", - text: '
', - showClose:true, - showCancel:true, - dialogClass: 'wide', - loadFunction: function(){ - // Put the existing value into the scope, and tell the controller to process the element with ID: idValue - $scope.htmlText = PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml']; - $scope.$broadcast("content-added", idValue); - }, - okAction:function(){ - PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'] = $scope.htmlText; - EmailTableHandler.writeSetting(keyName,true); - } - }); -}; - - -EmailTableHandler.writeSetting = function(settingKey, redraw) { - var currentValues = PWM_VAR['clientSettingCache'][settingKey]; - PWM_CFGEDIT.writeSetting(settingKey, currentValues, function(){ - if (redraw) { - EmailTableHandler.init(settingKey); - } - }); -}; - // -------------------------- boolean handler ------------------------------------ var BooleanHandler = {}; diff --git a/webapp/src/main/webapp/public/resources/js/main.js b/webapp/src/main/webapp/public/resources/js/main.js index 1edd5a12a..e4a647eb4 100644 --- a/webapp/src/main/webapp/public/resources/js/main.js +++ b/webapp/src/main/webapp/public/resources/js/main.js @@ -382,7 +382,11 @@ PWM_MAIN.handleLoginFormSubmit = function(form, event) { options['content'] = domForm.toObject(form); delete options['content']['processAction']; delete options['content']['pwmFormID']; - var url = 'login?processAction=restLogin&skipCaptcha=' + options['content']['skipCaptcha']; + var url = 'login?processAction=restLogin'; + if (options['content']['skipCaptcha']) + { + PWM_MAIN.addParamToUrl( url, 'skipCaptcha', options['content']['skipCaptcha']); + } var loadFunction = function(data) { if (data['error'] === true) { PWM_MAIN.getObject('password').value = ''; diff --git a/webapp/src/main/webapp/public/resources/style.css b/webapp/src/main/webapp/public/resources/style.css index dd3c7014e..ef467208e 100644 --- a/webapp/src/main/webapp/public/resources/style.css +++ b/webapp/src/main/webapp/public/resources/style.css @@ -1744,3 +1744,7 @@ table.ias-table, table.ias-table td { #header-title > .title-short { display: none; } + +.pre-whitespace { + white-space: pre; +}