misc refactoring

This commit is contained in:
jrivard@gmail.com 2019-03-25 12:09:48 -04:00
parent 028a48c4e0
commit 1f881af29b
58 changed files with 1459 additions and 1528 deletions

41
build/checkstyle-jsp.xml Normal file
View file

@ -0,0 +1,41 @@
<?xml version="1.0"?>
<!--
~ Password Management Servlets (PWM)
~ http://www.pwm-project.org
~
~ Copyright (c) 2006-2009 Novell, Inc.
~ Copyright (c) 2009-2018 The PWM Project
~
~ This program is free software; you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation; either version 2 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program; if not, write to the Free Software
~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-->
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.2//EN"
"http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
<!--
PWM Checkstyle definition
-->
<module name="Checker">
<property name="fileExtensions" value="java, xml, jsp"/>
<module name="TreeWalker" >
<module name="Regexp">
<property name="format" value="WarnJavaScriptNotEnabledMessage"/>
<property name="illegalPattern" value="true"/>
</module>
</module>
</module>

View file

@ -22,13 +22,12 @@
package password.pwm.receiver;
import org.apache.commons.io.IOUtils;
import password.pwm.PwmConstants;
import password.pwm.bean.TelemetryPublishBean;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.i18n.Message;
import password.pwm.util.ServletUtility;
import password.pwm.util.java.JsonUtil;
import password.pwm.ws.server.RestResultBean;
@ -38,9 +37,6 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
@WebServlet(
name = "TelemetryRestReceiver",
@ -58,7 +54,7 @@ public class TelemetryRestReceiver extends HttpServlet
try
{
resp.setHeader( "Content", "application/json" );
final String input = readRequestBodyAsString( req, 1024 * 1024 );
final String input = ServletUtility.readRequestBodyAsString( req, 1024 * 1024 );
final TelemetryPublishBean telemetryPublishBean = JsonUtil.deserialize( input, TelemetryPublishBean.class );
final Storage stoage = ContextManager.getContextManager( this.getServletContext() ).getApp().getStorage();
stoage.store( telemetryPublishBean );
@ -74,36 +70,4 @@ public class TelemetryRestReceiver extends HttpServlet
resp.getWriter().print( restResultBean.toJson() );
}
}
private static String readRequestBodyAsString( final HttpServletRequest req, final int maxChars )
throws IOException, PwmUnrecoverableException
{
final StringWriter stringWriter = new StringWriter();
final Reader readerStream = new InputStreamReader(
req.getInputStream(),
PwmConstants.DEFAULT_CHARSET
);
try
{
IOUtils.copy( readerStream, stringWriter );
}
catch ( Exception e )
{
final String errorMsg = "error reading request body stream: " + e.getMessage();
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) );
}
finally
{
IOUtils.closeQuietly( readerStream );
}
final String stringValue = stringWriter.toString();
if ( stringValue.length() > maxChars )
{
final String msg = "input request body is to big, size=" + stringValue.length() + ", max=" + maxChars;
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, msg ) );
}
return stringValue;
}
}

View file

@ -34,7 +34,7 @@
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>1.0.0</version>
<version>1.0.2</version>
<executions>
<execution>
<id>make-docker-image</id>

62
pom.xml
View file

@ -77,7 +77,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.1</version>
<version>3.1.0</version>
<executions>
<execution>
<goals>
@ -99,6 +99,7 @@
<Implementation-Version-Display>v${project.version} b${build.number} r${build.revision}</Implementation-Version-Display>
</manifestEntries>
</archive>
<source>8</source>
</configuration>
</execution>
</executions>
@ -180,7 +181,7 @@
<phase>validate</phase>
<configuration>
<propertyExpansion>basedir=${project.root.basedir}</propertyExpansion>
<configLocation>${project.root.basedir}/build/checkstyle.xml</configLocation>
<configLocation>build/checkstyle.xml</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<includeTestResources>true</includeTestResources>
@ -215,6 +216,27 @@
<goal>check</goal>
</goals>
</execution>
<execution>
<id>checkstyle-jsp</id>
<phase>validate</phase>
<configuration>
<propertyExpansion>basedir=${project.root.basedir}</propertyExpansion>
<configLocation>${project.root.basedir}/build/checkstyle-jsp.xml</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<includeTestResources>true</includeTestResources>
<failsOnError>true</failsOnError>
<includes>**/*.jsp</includes>
<sourceDirectories>
<directory>src</directory>
<directory>src/test</directory>
<directory>src/main/webapp</directory>
</sourceDirectories>
</configuration>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
@ -243,25 +265,33 @@
</execution>
</executions>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<phase>package</phase>
<configuration>
<goal>aggregate</goal>
</configuration>
</execution>
</executions>
</plugin>
<plugin> <!-- checks owsp vulnerability database -->
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>4.0.1</version>
<reportSets>
<reportSet>
<reports>
<report>aggregate</report>
</reports>
</reportSet>
</reportSets>
<version>5.0.0-M2</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</reporting>
</build>
<!-- common dependencies -->
<dependencies>

View file

@ -300,6 +300,7 @@ public enum AppProperty
RECAPTCHA_VALIDATE_URL ( "recaptcha.validateUrl" ),
REPORTING_LDAP_SEARCH_TIMEOUT ( "reporting.ldap.searchTimeoutMs" ),
REPORTING_LDAP_SEARCH_THREADS ( "reporting.ldap.searchThreads" ),
REPORTING_MAX_REPORT_AGE_SECONDS ( "reporting.maxReportAgeSeconds" ),
SECURITY_STRIP_INLINE_JAVASCRIPT ( "security.html.stripInlineJavascript" ),
SECURITY_HTTP_FORCE_REQUEST_SEQUENCING ( "security.http.forceRequestSequencing" ),
SECURITY_HTTP_STRIP_HEADER_REGEX ( "security.http.stripHeaderRegex" ),

View file

@ -25,6 +25,7 @@ package password.pwm;
import com.novell.ldapchai.ChaiUser;
import com.novell.ldapchai.exception.ChaiUnavailableException;
import com.novell.ldapchai.provider.ChaiProvider;
import org.jetbrains.annotations.NotNull;
import password.pwm.bean.SessionLabel;
import password.pwm.bean.SmsItemBean;
import password.pwm.bean.UserIdentity;
@ -43,7 +44,6 @@ import password.pwm.ldap.search.UserSearchEngine;
import password.pwm.svc.PwmService;
import password.pwm.svc.PwmServiceManager;
import password.pwm.svc.cache.CacheService;
import password.pwm.svc.node.NodeService;
import password.pwm.svc.email.EmailService;
import password.pwm.svc.event.AuditEvent;
import password.pwm.svc.event.AuditRecordFactory;
@ -51,6 +51,7 @@ import password.pwm.svc.event.AuditService;
import password.pwm.svc.event.SystemAuditRecord;
import password.pwm.svc.intruder.IntruderManager;
import password.pwm.svc.intruder.RecordType;
import password.pwm.svc.node.NodeService;
import password.pwm.svc.pwnotify.PwNotifyService;
import password.pwm.svc.report.ReportService;
import password.pwm.svc.sessiontrack.SessionTrackService;
@ -62,6 +63,7 @@ import password.pwm.svc.token.TokenService;
import password.pwm.svc.wordlist.SeedlistService;
import password.pwm.svc.wordlist.SharedHistoryManager;
import password.pwm.svc.wordlist.WordlistService;
import password.pwm.util.DailySummaryJob;
import password.pwm.util.MBeanUtility;
import password.pwm.util.PasswordData;
import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
@ -99,9 +101,10 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -298,8 +301,11 @@ public class PwmApplication
StatisticsManager.incrementStat( this, Statistic.PWM_STARTUPS );
LOGGER.debug( () -> "buildTime=" + PwmConstants.BUILD_TIME + ", javaLocale=" + Locale.getDefault() + ", DefaultLocale=" + PwmConstants.DEFAULT_LOCALE );
applicationExecutorService.execute( () -> postInitTasks() );
final ExecutorService executorService = JavaHelper.makeSingleThreadExecutorService( this, PwmApplication.class );
scheduleFutureJob( this::postInitTasks, executorService, TimeDuration.ZERO );
}
}
private void postInitTasks( )
@ -417,6 +423,11 @@ public class PwmApplication
LOGGER.debug( () -> "error initializing UserAgentUtils: " + e.getMessage() );
}
{
final ExecutorService executorService = JavaHelper.makeSingleThreadExecutorService( this, PwmApplication.class );
this.scheduleFixedRateJob( new DailySummaryJob( this ), executorService, TimeDuration.fromCurrent( JavaHelper.nextZuluZeroTime() ), TimeDuration.DAY );
}
LOGGER.trace( () -> "completed post init tasks in " + TimeDuration.fromCurrent( startTime ).asCompactString() );
}
@ -1024,18 +1035,25 @@ public class PwmApplication
return inprogressRequests;
}
public ScheduledFuture scheduleFutureJob(
public Future scheduleFutureJob(
final Runnable runnable,
final ExecutorService executor,
final TimeDuration delay
)
{
Objects.requireNonNull( runnable );
Objects.requireNonNull( executor );
Objects.requireNonNull( delay );
if ( applicationExecutorService.isShutdown() )
{
return null;
}
return applicationExecutorService.schedule( new WrappedRunner( runnable, executor ), delay.asMillis(), TimeUnit.MILLISECONDS );
final WrappedRunner wrappedRunner = new WrappedRunner( runnable, executor );
applicationExecutorService.schedule( wrappedRunner, delay.asMillis(), TimeUnit.MILLISECONDS );
return wrappedRunner.getFuture();
}
public void scheduleFixedRateJob(
@ -1068,6 +1086,8 @@ public class PwmApplication
{
private final Runnable runnable;
private final ExecutorService executor;
private volatile Future innerFuture;
private volatile boolean hasFailed;
WrappedRunner( final Runnable runnable, final ExecutorService executor )
{
@ -1075,28 +1095,62 @@ public class PwmApplication
this.executor = executor;
}
Future getFuture()
{
return new Future()
{
@Override
public boolean cancel( final boolean mayInterruptIfRunning )
{
return false;
}
@Override
public boolean isCancelled()
{
return hasFailed;
}
@Override
public boolean isDone()
{
return hasFailed || innerFuture != null && innerFuture.isDone();
}
@Override
public Object get()
{
return null;
}
@Override
public Object get( final long timeout, @NotNull final TimeUnit unit )
{
return null;
}
};
}
@Override
public void run()
{
if ( executor.isShutdown() )
{
return;
}
try
{
if ( !executor.isShutdown() )
{
executor.execute( runnable );
innerFuture = executor.submit( runnable );
}
else
{
hasFailed = true;
LOGGER.trace( () -> "skipping scheduled job " + runnable + " on shutdown executor + " + executor );
}
}
catch ( Throwable t )
{
LOGGER.error( "unexpected error running scheduled job: " + t.getMessage(), t );
hasFailed = true;
}
}
}

View file

@ -24,13 +24,13 @@ package password.pwm.bean;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import java.io.Serializable;
@Getter
@Value
@AllArgsConstructor
@Builder
@Builder( toBuilder = true )
public class EmailItemBean implements Serializable
{
private final String to;
@ -39,6 +39,14 @@ public class EmailItemBean implements Serializable
private final String bodyPlain;
private final String bodyHtml;
public EmailItemBean applyBodyReplacement( final CharSequence target, final CharSequence replacement )
{
return this.toBuilder()
.bodyPlain( this.getBodyPlain().replace( target, replacement ) )
.bodyHtml( this.getBodyHtml().replace( target, replacement ) )
.build();
}
public String toDebugString( )
{
return "from: " + from + ", to: " + to + ", subject: " + subject;

View file

@ -23,17 +23,14 @@
package password.pwm.bean;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Value;
import java.io.Serializable;
import java.time.Instant;
@Getter
@AllArgsConstructor
@Value
public class FormNonce implements Serializable
{
@SerializedName( "g" )
private final String sessionGUID;

View file

@ -23,8 +23,7 @@
package password.pwm.bean;
import com.google.gson.annotations.SerializedName;
import lombok.Getter;
import lombok.Setter;
import lombok.Data;
import password.pwm.PwmConstants;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.ldap.auth.AuthenticationType;
@ -46,8 +45,7 @@ import java.util.Set;
*
* <p>Short serialized names are used to shrink the effective size of the login cookie.</p>
*/
@Getter
@Setter
@Data
public class LoginInfoBean implements Serializable
{
@ -67,10 +65,10 @@ public class LoginInfoBean implements Serializable
private UserIdentity userIdentity;
@SerializedName( "a" )
private boolean auth;
private boolean authenticated;
@SerializedName( "p" )
private PasswordData pw;
private PasswordData userCurrentPassword;
@SerializedName( "t" )
private AuthenticationType type = AuthenticationType.UNAUTHENTICATED;
@ -105,141 +103,6 @@ public class LoginInfoBean implements Serializable
@SerializedName( "lf" )
private Set<LoginFlag> loginFlags = new HashSet<>();
public Instant getAuthTime( )
{
return authTime;
}
public void setAuthTime( final Instant authTime )
{
this.authTime = authTime;
}
public AuthenticationType getType( )
{
return type;
}
public void setType( final AuthenticationType type )
{
this.type = type;
}
public PasswordData getUserCurrentPassword( )
{
return pw;
}
public void setUserCurrentPassword( final PasswordData userCurrentPassword )
{
this.pw = userCurrentPassword;
}
public BasicAuthInfo getBasicAuth( )
{
return basicAuth;
}
public void setBasicAuth( final BasicAuthInfo basicAuth )
{
this.basicAuth = basicAuth;
}
public Instant getOauthExp( )
{
return oauthExp;
}
public void setOauthExp( final Instant oauthExp )
{
this.oauthExp = oauthExp;
}
public String getOauthRefToken( )
{
return oauthRefToken;
}
public void setOauthRefToken( final String oauthRefToken )
{
this.oauthRefToken = oauthRefToken;
}
public List<AuthenticationType> getAuthFlags( )
{
return authFlags;
}
public PwmAuthenticationSource getAuthSource( )
{
return authSource;
}
public void setAuthSource( final PwmAuthenticationSource authSource )
{
this.authSource = authSource;
}
public String getGuid( )
{
return guid;
}
public void setGuid( final String guid )
{
this.guid = guid;
}
public int getReqCounter( )
{
return reqCounter;
}
public void setReqCounter( final int reqCounter )
{
this.reqCounter = reqCounter;
}
public UserIdentity getUserIdentity( )
{
return userIdentity;
}
public void setUserIdentity( final UserIdentity userIdentity )
{
this.userIdentity = userIdentity;
}
public boolean isAuthenticated( )
{
return auth;
}
public void setAuthenticated( final boolean authenticated )
{
this.auth = authenticated;
}
public PasswordData getPw( )
{
return pw;
}
public void setPw( final PasswordData pw )
{
this.pw = pw;
}
public Instant getReqTime( )
{
return reqTime;
}
public void setReqTime( final Instant reqTime )
{
this.reqTime = reqTime;
}
public boolean isLoginFlag( final LoginFlag loginStateFlag )
{
return loginFlags.contains( loginStateFlag );

View file

@ -23,12 +23,12 @@
package password.pwm.bean;
import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import password.pwm.util.java.JsonUtil;
import java.io.Serializable;
@Getter
@Value
@Builder
public class PasswordStatus implements Serializable
{

View file

@ -23,11 +23,11 @@
package password.pwm.bean;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Value;
import java.io.Serializable;
@Getter
@Value
@AllArgsConstructor
public class SessionLabel implements Serializable
{

View file

@ -24,12 +24,12 @@ package password.pwm.bean;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Value;
import password.pwm.util.java.JsonUtil;
import java.io.Serializable;
@Getter
@Value
@AllArgsConstructor
public class SmsItemBean implements Serializable
{

View file

@ -23,14 +23,14 @@
package password.pwm.bean;
import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@Getter
@Value
@Builder
public class TelemetryPublishBean implements Serializable
{

View file

@ -33,6 +33,7 @@ import password.pwm.util.logging.PwmLogger;
import password.pwm.util.macro.MacroMachine;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@ -1129,16 +1130,14 @@ public enum PwmSetting
// reporting
REPORTING_ENABLE(
REPORTING_ENABLE_DAILY_JOB(
"reporting.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.REPORTING ),
REPORTING_USER_MATCH(
"reporting.ldap.userMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.REPORTING ),
REPORTING_MAX_CACHE_AGE(
"reporting.maxCacheAge", PwmSettingSyntax.DURATION, PwmSettingCategory.REPORTING ),
REPORTING_MAX_QUERY_SIZE(
"reporting.ldap.maxQuerySize", PwmSettingSyntax.NUMERIC, PwmSettingCategory.REPORTING ),
REPORTING_JOB_TIME_OFFSET(
"reporting.job.timeOffset", PwmSettingSyntax.DURATION, PwmSettingCategory.REPORTING ),
REPORTING_USER_MATCH(
"reporting.ldap.userMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.REPORTING ),
REPORTING_MAX_QUERY_SIZE(
"reporting.ldap.maxQuerySize", PwmSettingSyntax.NUMERIC, PwmSettingCategory.REPORTING ),
REPORTING_JOB_INTENSITY(
"reporting.job.intensity", PwmSettingSyntax.SELECT, PwmSettingCategory.REPORTING ),
REPORTING_SUMMARY_DAY_VALUES(
@ -1591,14 +1590,10 @@ public enum PwmSetting
public static PwmSetting forKey( final String key )
{
for ( final PwmSetting loopSetting : values() )
{
if ( loopSetting.getKey().equals( key ) )
{
return loopSetting;
}
}
return null;
return Arrays.stream( values() )
.filter( loopValue -> loopValue.getKey().equals( key ) )
.findFirst()
.orElse( null );
}
public String toMenuLocationDebug(

View file

@ -27,6 +27,7 @@ import password.pwm.util.i18n.LocaleHelper;
import password.pwm.util.java.XmlElement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@ -452,4 +453,11 @@ public enum PwmSettingCategory
return Collections.unmodifiableCollection( returnValues );
}
public static PwmSettingCategory forKey( final String key )
{
return Arrays.stream( values() )
.filter( loopValue -> loopValue.getKey().equals( key ) )
.findFirst()
.orElse( null );
}
}

View file

@ -43,7 +43,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class PwmSettingXml
{
private static final String SETTING_XML_FILENAME = ( PwmSetting.class.getPackage().getName()
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";

View file

@ -22,6 +22,7 @@
package password.pwm.health;
import lombok.Value;
import password.pwm.AppProperty;
import password.pwm.PwmApplication;
import password.pwm.error.PwmException;
@ -35,22 +36,19 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
public class HealthMonitor implements PwmService
{
private static final PwmLogger LOGGER = PwmLogger.forClass( HealthMonitor.class );
private PwmApplication pwmApplication;
private final Set<HealthRecord> healthRecords = new TreeSet<>();
private static final List<HealthChecker> HEALTH_CHECKERS;
static
@ -68,12 +66,11 @@ public class HealthMonitor implements PwmService
private ExecutorService executorService;
private HealthMonitorSettings settings;
private volatile Instant lastHealthCheckTime = Instant.ofEpochMilli( 0 );
private volatile Instant lastRequestedUpdateTime = Instant.ofEpochMilli( 0 );
private Map<HealthMonitorFlag, Serializable> healthProperties = new HashMap<>();
private Map<HealthMonitorFlag, Serializable> healthProperties = new ConcurrentHashMap<>();
private STATUS status = STATUS.NEW;
private PwmApplication pwmApplication;
private volatile HealthData healthData = emptyHealthData();
enum HealthMonitorFlag
{
@ -81,18 +78,6 @@ public class HealthMonitor implements PwmService
AdPasswordPolicyApiCheck,
}
public enum CheckTimeliness
{
/* Execute update immediately and wait for results */
Immediate,
/* Take current data unless its ancient */
CurrentButNotAncient,
/* Take current data even if its ancient and never block */
NeverBlock,
}
public HealthMonitor( )
{
}
@ -103,16 +88,17 @@ public class HealthMonitor implements PwmService
{
return null;
}
return lastHealthCheckTime;
final HealthData healthData = this.healthData;
return healthData != null ? healthData.getTimeStamp() : Instant.ofEpochMilli( 0 );
}
public HealthStatus getMostSevereHealthStatus( final CheckTimeliness timeliness )
public HealthStatus getMostSevereHealthStatus( )
{
if ( status != STATUS.OPEN )
{
return HealthStatus.GOOD;
}
return getMostSevereHealthStatus( getHealthRecords( timeliness ) );
return getMostSevereHealthStatus( getHealthRecords( ) );
}
public static HealthStatus getMostSevereHealthStatus( final Collection<HealthRecord> healthRecords )
@ -149,42 +135,38 @@ public class HealthMonitor implements PwmService
return;
}
executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, this.getClass() );
pwmApplication.scheduleFixedRateJob( new ScheduledUpdater(), executorService, TimeDuration.SECONDS_10, settings.getNominalCheckInterval() );
status = STATUS.OPEN;
}
public Set<HealthRecord> getHealthRecords( final CheckTimeliness timeliness )
public Set<HealthRecord> getHealthRecords( )
{
if ( status != STATUS.OPEN )
{
return Collections.emptySet();
}
lastRequestedUpdateTime = Instant.now();
if ( healthData.recordsAreOutdated() )
{
final Instant startTime = Instant.now();
LOGGER.trace( () -> "begin force immediate check" );
final Future future = pwmApplication.scheduleFutureJob( new ImmediateJob(), executorService, TimeDuration.ZERO );
JavaHelper.pause( settings.getMaximumForceCheckWait().asMillis(), 500, o -> future.isDone() );
LOGGER.trace( () -> "exit force immediate check, done=" + future.isDone() + ", " + TimeDuration.compactFromCurrent( startTime ) );
}
pwmApplication.scheduleFutureJob( new UpdateJob(), executorService, settings.getNominalCheckInterval() );
{
final boolean recordsAreStale = TimeDuration.fromCurrent( lastHealthCheckTime ).isLongerThan( settings.getMaximumRecordAge() );
if ( timeliness == CheckTimeliness.Immediate || ( timeliness == CheckTimeliness.CurrentButNotAncient && recordsAreStale ) )
final HealthData localHealthData = this.healthData;
if ( localHealthData.recordsAreOutdated() )
{
final ScheduledFuture updateTask = pwmApplication.scheduleFutureJob( new ImmediateUpdater(), executorService, TimeDuration.ZERO );
final Instant beginWaitTime = Instant.now();
while ( !updateTask.isDone() && TimeDuration.fromCurrent( beginWaitTime ).isShorterThan( settings.getMaximumForceCheckWait() ) )
{
JavaHelper.pause( 500 );
}
return Collections.singleton( HealthRecord.forMessage( HealthMessage.NoData ) );
}
}
final boolean recordsAreStale = TimeDuration.fromCurrent( lastHealthCheckTime ).isLongerThan( settings.getMaximumRecordAge() );
if ( recordsAreStale )
{
return Collections.singleton( HealthRecord.forMessage( HealthMessage.NoData ) );
return localHealthData.getHealthRecords();
}
return Collections.unmodifiableSet( healthRecords );
}
public void close( )
@ -193,30 +175,32 @@ public class HealthMonitor implements PwmService
{
executorService.shutdown();
}
healthRecords.clear();
healthData = emptyHealthData();
status = STATUS.CLOSED;
}
private HealthData emptyHealthData()
{
return new HealthData( Collections.emptySet(), Instant.ofEpochMilli( 0 ) );
}
public List<HealthRecord> healthCheck( )
{
return Collections.emptyList();
}
private AtomicInteger healthCheckCount = new AtomicInteger( 0 );
private void doHealthChecks( )
{
final int counter = healthCheckCount.getAndIncrement();
if ( status != STATUS.OPEN )
{
return;
}
final TimeDuration timeSinceLastUpdate = TimeDuration.fromCurrent( lastHealthCheckTime );
if ( timeSinceLastUpdate.isShorterThan( settings.getMinimumCheckInterval().asMillis(), TimeDuration.Unit.MILLISECONDS ) )
{
return;
}
final Instant startTime = Instant.now();
LOGGER.trace( () -> "beginning background health check process" );
LOGGER.trace( () -> "beginning health check execution (" + counter + ")" );
final List<HealthRecord> tempResults = new ArrayList<>();
for ( final HealthChecker loopChecker : HEALTH_CHECKERS )
{
@ -254,10 +238,9 @@ public class HealthMonitor implements PwmService
}
}
}
healthRecords.clear();
healthRecords.addAll( tempResults );
lastHealthCheckTime = Instant.now();
LOGGER.trace( () -> "health check process completed (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
healthData = new HealthData( Collections.unmodifiableSet( new TreeSet<>( tempResults ) ), Instant.now() );
LOGGER.trace( () -> "completed health check execution (" + counter + ") in " + TimeDuration.compactFromCurrent( startTime ) );
}
public ServiceInfoBean serviceInfo( )
@ -265,50 +248,55 @@ public class HealthMonitor implements PwmService
return new ServiceInfoBean( Collections.emptyList() );
}
public Map<HealthMonitorFlag, Serializable> getHealthProperties( )
Map<HealthMonitorFlag, Serializable> getHealthProperties( )
{
return healthProperties;
}
private class ScheduledUpdater implements Runnable
private class UpdateJob implements Runnable
{
@Override
public void run( )
{
final TimeDuration timeSinceLastRequest = TimeDuration.fromCurrent( lastRequestedUpdateTime );
if ( timeSinceLastRequest.isShorterThan( settings.getNominalCheckInterval().asMillis() + 1000, TimeDuration.Unit.MILLISECONDS ) )
if ( healthData.recordsAreStale() )
{
try
{
doHealthChecks();
}
catch ( Throwable e )
{
LOGGER.error( "error during health check execution: " + e.getMessage(), e );
}
new ImmediateJob().run();
}
}
}
private class ImmediateUpdater implements Runnable
private class ImmediateJob implements Runnable
{
@Override
public void run( )
{
final TimeDuration timeSinceLastUpdate = TimeDuration.fromCurrent( lastHealthCheckTime );
if ( timeSinceLastUpdate.isLongerThan( settings.getMinimumCheckInterval().asMillis(), TimeDuration.Unit.MILLISECONDS ) )
try
{
try
{
doHealthChecks();
}
catch ( Throwable e )
{
LOGGER.error( "error during health check execution: " + e.getMessage(), e );
}
final Instant startTime = Instant.now();
doHealthChecks();
LOGGER.trace( () -> "completed health check dredge " + TimeDuration.compactFromCurrent( startTime ) );
}
catch ( Throwable e )
{
LOGGER.error( "error during health check execution: " + e.getMessage(), e );
}
}
}
@Value
private class HealthData
{
private Set<HealthRecord> healthRecords;
private Instant timeStamp;
private boolean recordsAreStale()
{
return TimeDuration.fromCurrent( this.getTimeStamp() ).isLongerThan( settings.getNominalCheckInterval() );
}
private boolean recordsAreOutdated()
{
return TimeDuration.fromCurrent( this.getTimeStamp() ).isLongerThan( settings.getMaximumRecordAge() );
}
}
}

View file

@ -48,6 +48,7 @@ public enum HttpHeader
Origin( "Origin" ),
Referer( "Referer" ),
Server( "Server" ),
SetCookie( "Set-Cookie" ),
UserAgent( "User-Agent" ),
WWW_Authenticate( "WWW-Authenticate" ),
XContentTypeOptions( "X-Content-Type-Options" ),

View file

@ -22,14 +22,12 @@
package password.pwm.http;
import org.apache.commons.io.IOUtils;
import password.pwm.AppProperty;
import password.pwm.PwmConstants;
import password.pwm.config.Configuration;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.util.PasswordData;
import password.pwm.util.ServletUtility;
import password.pwm.util.Validator;
import password.pwm.util.java.JavaHelper;
import password.pwm.util.java.JsonUtil;
@ -39,9 +37,6 @@ import password.pwm.util.logging.PwmLogger;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
@ -117,41 +112,7 @@ public class PwmHttpRequestWrapper
public String readRequestBodyAsString( final int maxChars )
throws IOException, PwmUnrecoverableException
{
return readRequestBodyAsString( this.getHttpServletRequest(), maxChars );
}
public static String readRequestBodyAsString( final HttpServletRequest httpServletRequest, final int maxChars )
throws IOException, PwmUnrecoverableException
{
final StringWriter stringWriter = new StringWriter();
final Reader readerStream = new InputStreamReader(
httpServletRequest.getInputStream(),
PwmConstants.DEFAULT_CHARSET
);
try
{
IOUtils.copy( readerStream, stringWriter );
}
catch ( Exception e )
{
final String errorMsg = "error reading request body stream: " + e.getMessage();
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) );
}
finally
{
IOUtils.closeQuietly( readerStream );
}
final String stringValue = stringWriter.toString();
if ( stringValue.length() > maxChars )
{
throw new PwmUnrecoverableException( new ErrorInformation(
PwmError.ERROR_INTERNAL,
"input request body is to big, size=" + stringValue.length() + ", max=" + maxChars )
);
}
return stringValue;
return ServletUtility.readRequestBodyAsString( this.getHttpServletRequest(), maxChars );
}
public Map<String, String> readBodyAsJsonStringMap( final Flag... flags )

View file

@ -73,11 +73,11 @@ public class PwmRequest extends PwmHttpRequestWrapper
private static final PwmLogger LOGGER = PwmLogger.forClass( PwmRequest.class );
private final PwmResponse pwmResponse;
private final PwmURL pwmURL;
private transient PwmApplication pwmApplication;
private transient PwmSession pwmSession;
private PwmURL pwmURL;
private final Set<PwmRequestFlag> flags = new HashSet<>();
@ -110,6 +110,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
this.pwmResponse = new PwmResponse( httpServletResponse, this, pwmApplication.getConfig() );
this.pwmSession = pwmSession;
this.pwmApplication = pwmApplication;
this.pwmURL = new PwmURL( this.getHttpServletRequest() );
}
public PwmApplication getPwmApplication( )
@ -371,10 +372,6 @@ public class PwmRequest extends PwmHttpRequestWrapper
public PwmURL getURL( )
{
if ( pwmURL == null )
{
pwmURL = new PwmURL( this.getHttpServletRequest() );
}
return pwmURL;
}

View file

@ -123,7 +123,7 @@ public class SessionFilter extends AbstractPwmFilter
&& e.getCause() instanceof NoClassDefFoundError
&& e.getCause().getMessage() != null
&& e.getCause().getMessage().contains( "JaxbAnnotationIntrospector" )
)
)
{
// this is a jersey 1.18 bug that occurs once per execution
LOGGER.debug( pwmRequest, () -> "ignoring JaxbAnnotationIntrospector NoClassDefFoundError: " + e.getMessage() );
@ -323,8 +323,15 @@ public class SessionFilter extends AbstractPwmFilter
LOGGER.trace( pwmRequest, () -> "session has not been validated, redirecting with verification key to " + returnURL );
// better chance of detecting un-sticky sessions this way
pwmResponse.setHeader( HttpHeader.Connection, "close" );
{
final String httpVersion = pwmRequest.getHttpServletRequest().getProtocol();
if ( "HTTP/1.0".equals( httpVersion ) || "HTTP/1.1".equals( httpVersion ) )
{
// better chance of detecting un-sticky sessions this way
pwmResponse.setHeader( HttpHeader.Connection, "close" );
}
}
if ( mode == SessionVerificationMode.VERIFY_AND_CACHE )
{
req.setAttribute( "Location", returnURL );
@ -361,8 +368,7 @@ public class SessionFilter extends AbstractPwmFilter
{
final HttpServletRequest req = pwmRequest.getHttpServletRequest();
final StringBuilder sb = new StringBuilder();
sb.append( req.getRequestURL() );
String redirectURL = req.getRequestURI();
final String verificationParamName = pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_PARAM_SESSION_VERIFICATION );
@ -380,9 +386,7 @@ public class SessionFilter extends AbstractPwmFilter
for ( final Iterator<String> valueIter = paramValues.iterator(); valueIter.hasNext(); )
{
final String value = valueIter.next();
sb.append( sb.toString().contains( "?" ) ? "&" : "?" );
sb.append( StringUtil.urlEncode( paramName ) ).append( "=" );
sb.append( StringUtil.urlEncode( value ) );
redirectURL = PwmURL.appendAndEncodeUrlParameters( redirectURL, paramName, value );
}
}
}
@ -394,11 +398,10 @@ public class SessionFilter extends AbstractPwmFilter
if ( validationKey != null )
{
sb.append( sb.toString().contains( "?" ) ? "&" : "?" );
sb.append( verificationParamName ).append( "=" ).append( validationKey );
redirectURL = PwmURL.appendAndEncodeUrlParameters( redirectURL, verificationParamName, validationKey );
}
return sb.toString();
return redirectURL;
}

View file

@ -221,8 +221,7 @@ public class ClientApiServlet extends ControlledPwmServlet
{
final HealthData jsonOutput = RestHealthServer.processGetHealthCheckData(
pwmRequest.getPwmApplication(),
pwmRequest.getLocale(),
false );
pwmRequest.getLocale() );
final RestResultBean restResultBean = RestResultBean.withData( jsonOutput );
pwmRequest.outputJsonResult( restResultBean );
}

View file

@ -238,7 +238,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet
{
final MacroMachine macroMachine = pwmRequest.getPwmSession().getSessionManager().getMacroMachine( pwmApplication );
final String macroedUrl = macroMachine.expandMacros( nextUrl );
LOGGER.debug( pwmRequest, () -> "settinging forward url to post-delete next url: " + macroedUrl );
LOGGER.debug( pwmRequest, () -> "setting forward url to post-delete next url: " + macroedUrl );
pwmRequest.getPwmSession().getSessionStateBean().setForwardURL( macroedUrl );
}

View file

@ -63,7 +63,7 @@ public class ReportStatusBean implements Serializable
case RollOver:
{
presentableMap.add( new DisplayElement( "usersProcessed", DisplayElement.Type.string, "Users Processed",
numberFormat.format( reportService.getSummaryData().getTotalUsers() )
numberFormat.format( reportService.getSummaryData().getTotalUsers().intValue() )
+ " of " + numberFormat.format( reportService.getTotalRecords() ) ) );
availableCommands.add( ReportService.ReportCommand.Stop );
}
@ -132,11 +132,6 @@ public class ReportStatusBean implements Serializable
}
final long totalRecords = reportService.getTotalRecords();
presentableMap.add( new DisplayElement( "recordsInCache", DisplayElement.Type.string, "Records in Cache", numberFormat.format( totalRecords ) ) );
if ( totalRecords > 0 )
{
presentableMap.add( new DisplayElement( "meanRecordCacheTime", DisplayElement.Type.timestamp, "Mean Record Cache Time",
JavaHelper.toIsoDate( reportService.getSummaryData().getMeanCacheTime() ) ) );
}
}
return ReportStatusBean.builder()

View file

@ -33,7 +33,6 @@ import password.pwm.bean.UserIdentity;
import password.pwm.config.Configuration;
import password.pwm.config.stored.StoredConfigurationImpl;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.health.HealthMonitor;
import password.pwm.health.HealthRecord;
import password.pwm.http.ContextManager;
import password.pwm.http.PwmRequest;
@ -349,7 +348,7 @@ public class DebugItemGenerator
@Override
public void outputItem( final PwmApplication pwmApplication, final PwmRequest pwmRequest, final OutputStream outputStream ) throws Exception
{
final Set<HealthRecord> records = pwmApplication.getHealthMonitor().getHealthRecords( HealthMonitor.CheckTimeliness.CurrentButNotAncient );
final Set<HealthRecord> records = pwmApplication.getHealthMonitor().getHealthRecords();
final String recordJson = JsonUtil.serializeCollection( records, JsonUtil.Flag.PrettyPrint );
outputStream.write( recordJson.getBytes( PwmConstants.DEFAULT_CHARSET ) );
}

View file

@ -1134,7 +1134,10 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
{
if ( userInfo.isPasswordLocked() )
{
pwmRequest.setAttribute( PwmRequestAttribute.ForgottenPasswordInhibitPasswordReset, Boolean.TRUE );
final boolean inhibitReset = minLifetimeOption != RecoveryMinLifetimeOption.ALLOW
&& userInfo.isWithinPasswordMinimumLifetime();
pwmRequest.setAttribute( PwmRequestAttribute.ForgottenPasswordInhibitPasswordReset, inhibitReset );
pwmRequest.forwardToJsp( JspUrl.RECOVER_PASSWORD_ACTION_CHOICE );
return;
}

View file

@ -195,6 +195,8 @@ public class NewUserServlet extends ControlledPwmServlet
protected void nextStep( final PwmRequest pwmRequest )
throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
{
TimeDuration.of( 8, TimeDuration.Unit.SECONDS ).pause();
final NewUserBean newUserBean = getNewUserBean( pwmRequest );
final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
final PwmSession pwmSession = pwmRequest.getPwmSession();

View file

@ -0,0 +1,76 @@
/*
* Password Management Servlets (PWM)
* http://www.pwm-project.org
*
* Copyright (c) 2006-2009 Novell, Inc.
* Copyright (c) 2009-2018 The PWM Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package password.pwm.http.servlet.resource;
import password.pwm.PwmConstants;
import password.pwm.config.Configuration;
import password.pwm.config.PwmSetting;
import password.pwm.util.java.StringUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ConfigSettingFileResource implements FileResource
{
private final String bodyText;
private final String requestURI;
public ConfigSettingFileResource( final PwmSetting pwmSetting, final Configuration configuration, final String requestURI )
{
this.bodyText = configuration.readSettingAsString( pwmSetting );
this.requestURI = requestURI;
}
@Override
public InputStream getInputStream()
throws IOException
{
return new ByteArrayInputStream( bodyText.getBytes( PwmConstants.DEFAULT_CHARSET ) );
}
@Override
public long length()
{
return bodyText.length();
}
@Override
public long lastModified()
{
return 0;
}
@Override
public boolean exists()
{
return !StringUtil.isEmpty( bodyText );
}
@Override
public String getName()
{
return requestURI;
}
}

View file

@ -0,0 +1,403 @@
/*
* Password Management Servlets (PWM)
* http://www.pwm-project.org
*
* Copyright (c) 2006-2009 Novell, Inc.
* Copyright (c) 2009-2018 The PWM Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package password.pwm.http.servlet.resource;
import org.webjars.WebJarAssetLocator;
import password.pwm.config.Configuration;
import password.pwm.config.PwmSetting;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.http.HttpHeader;
import password.pwm.http.PwmHttpRequestWrapper;
import password.pwm.util.java.StringUtil;
import password.pwm.util.logging.PwmLogger;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
class ResourceFileRequest
{
private static final PwmLogger LOGGER = PwmLogger.forClass( ResourceFileRequest.class );
private static final Map<String, String> WEB_JAR_VERSION_MAP = Collections.unmodifiableMap( new HashMap<>( new WebJarAssetLocator().getWebJars() ) );
private static final Collection<String> WEB_JAR_ASSET_LIST = Collections.unmodifiableCollection( new ArrayList<>( new WebJarAssetLocator().getFullPathIndex().values() ) );
private final HttpServletRequest httpServletRequest;
private final Configuration configuration;
private final ResourceServletConfiguration resourceServletConfiguration;
private FileResource fileResource;
ResourceFileRequest(
final Configuration configuration,
final ResourceServletConfiguration resourceServletConfiguration,
final HttpServletRequest httpServletRequest
)
{
this.configuration = configuration;
this.resourceServletConfiguration = resourceServletConfiguration;
this.httpServletRequest = httpServletRequest;
}
HttpServletRequest getHttpServletRequest()
{
return httpServletRequest;
}
ResourceServletConfiguration getResourceServletConfiguration()
{
return resourceServletConfiguration;
}
String getRequestURI()
{
return stripNonceFromURI( figureRequestPathMinusContext() );
}
String getReturnContentType()
throws PwmUnrecoverableException
{
final boolean acceptsCompression = allowsCompression();
final String rawContentType = getRawMimeType();
return acceptsCompression ? rawContentType : rawContentType + ";charset=UTF-8";
}
FileResource getRequestedFileResource()
throws PwmUnrecoverableException
{
if ( fileResource == null )
{
final String resourcePathUri = this.getRequestURI();
final ServletContext servletContext = this.getHttpServletRequest().getServletContext();
fileResource = resolveRequestedResource( configuration, servletContext, resourcePathUri, resourceServletConfiguration );
}
return fileResource;
}
private String getRawMimeType()
throws PwmUnrecoverableException
{
final String filename = getRequestedFileResource().getName();
final String contentType = this.httpServletRequest.getServletContext().getMimeType( filename );
if ( contentType == null )
{
if ( filename.endsWith( ".woff2" ) )
{
return "font/woff2";
}
}
// If content type is unknown, then set the default value.
// For all content types, see: http://www.w3schools.com/media/media_mimeref.asp
// To add new content types, add new mime-mapping entry in web.xml.
return contentType == null ? "application/octet-stream" : contentType;
}
boolean allowsCompression()
throws PwmUnrecoverableException
{
// If content type is text, then determine whether GZIP content encoding is supported by
// the browser and expand content type with the one and right character encoding.
if ( resourceServletConfiguration.isEnableGzip() )
{
final String contentType = getRawMimeType();
if ( contentType.startsWith( "text" ) || contentType.contains( "javascript" ) )
{
final PwmHttpRequestWrapper pwmHttpRequestWrapper = new PwmHttpRequestWrapper( httpServletRequest, configuration );
final String acceptEncoding = pwmHttpRequestWrapper.readHeaderValueAsString( HttpHeader.AcceptEncoding );
return acceptEncoding != null && accepts( acceptEncoding, "gzip" );
}
}
return false;
}
private String stripNonceFromURI(
final String uriString
)
{
if ( !resourceServletConfiguration.isEnablePathNonce() )
{
return uriString;
}
final Matcher theMatcher = resourceServletConfiguration.getNoncePattern().matcher( uriString );
if ( theMatcher.find() )
{
return theMatcher.replaceFirst( "" );
}
return uriString;
}
private String figureRequestPathMinusContext()
{
final String requestURI = httpServletRequest.getRequestURI();
return requestURI.substring( httpServletRequest.getContextPath().length() );
}
static FileResource resolveRequestedResource(
final Configuration configuration,
final ServletContext servletContext,
final String resourcePathUri,
final ResourceServletConfiguration resourceServletConfiguration
)
throws PwmUnrecoverableException
{
// URL-decode the file name (might contain spaces and on) and prepare file object.
String filename = StringUtil.urlDecode( resourcePathUri );
// parse out the session key...
if ( filename.contains( ";" ) )
{
filename = filename.substring( 0, filename.indexOf( ";" ) );
}
if ( !filename.startsWith( ResourceFileServlet.RESOURCE_PATH ) )
{
LOGGER.warn( "illegal url request to " + filename );
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "illegal url request" ) );
}
{
final String embedThemeUrl = ResourceFileServlet.RESOURCE_PATH
+ ResourceFileServlet.THEME_CSS_PATH.replace( ResourceFileServlet.TOKEN_THEME, ResourceFileServlet.EMBED_THEME );
final String embedThemeMobileUrl = ResourceFileServlet.RESOURCE_PATH
+ ResourceFileServlet.THEME_CSS_MOBILE_PATH.replace( ResourceFileServlet.TOKEN_THEME, ResourceFileServlet.EMBED_THEME );
if ( filename.equalsIgnoreCase( embedThemeUrl ) )
{
return new ConfigSettingFileResource( PwmSetting.DISPLAY_CSS_EMBED, configuration, filename );
}
else if ( filename.equalsIgnoreCase( embedThemeMobileUrl ) )
{
return new ConfigSettingFileResource( PwmSetting.DISPLAY_CSS_MOBILE_EMBED, configuration, filename );
}
}
{
final FileResource resource = handleWebjarURIs( servletContext, resourcePathUri );
if ( resource != null )
{
return resource;
}
}
{
// check files system zip files.
final Map<String, ZipFile> zipResources = resourceServletConfiguration.getZipResources();
for ( final Map.Entry<String, ZipFile> entry : zipResources.entrySet() )
{
final String path = entry.getKey();
if ( filename.startsWith( path ) )
{
final String zipSubPath = filename.substring( path.length() + 1, filename.length() );
final ZipFile zipFile = entry.getValue();
final ZipEntry zipEntry = zipFile.getEntry( zipSubPath );
if ( zipEntry != null )
{
return new ZipFileResource( zipFile, zipEntry );
}
}
if ( filename.startsWith( zipResources.get( path ).getName() ) )
{
LOGGER.warn( "illegal url request to " + filename + " zip resource" );
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "illegal url request" ) );
}
}
}
// convert to file.
final String filePath = servletContext.getRealPath( filename );
final File file = new File( filePath );
// figure top-most path allowed by request
final String parentDirectoryPath = servletContext.getRealPath( ResourceFileServlet.RESOURCE_PATH );
final File parentDirectory = new File( parentDirectoryPath );
FileResource fileSystemResource = null;
{
//verify the requested page is a child of the servlet resource path.
int recursions = 0;
File recurseFile = file.getParentFile();
while ( recurseFile != null && recursions < 100 )
{
if ( parentDirectory.equals( recurseFile ) )
{
fileSystemResource = new RealFileResource( file );
break;
}
recurseFile = recurseFile.getParentFile();
recursions++;
}
}
if ( fileSystemResource == null )
{
LOGGER.warn( "attempt to access file outside of servlet path " + file.getAbsolutePath() );
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "illegal file path request" ) );
}
if ( !fileSystemResource.exists() )
{
// check custom (configuration defined) zip file bundles
final Map<String, FileResource> customResources = resourceServletConfiguration.getCustomFileBundle();
for ( final Map.Entry<String, FileResource> entry : customResources.entrySet() )
{
final String customFileName = entry.getKey();
final String testName = ResourceFileServlet.RESOURCE_PATH + "/" + customFileName;
if ( testName.equals( resourcePathUri ) )
{
return entry.getValue();
}
}
}
return fileSystemResource;
}
private static FileResource handleWebjarURIs(
final ServletContext servletContext,
final String resourcePathUri
)
throws PwmUnrecoverableException
{
if ( resourcePathUri.startsWith( ResourceFileServlet.WEBJAR_BASE_URL_PATH ) )
{
// This allows us to override a webjar file, if needed. Mostly helpful during development.
final File file = new File( servletContext.getRealPath( resourcePathUri ) );
if ( file.exists() )
{
return new RealFileResource( file );
}
final String remainingPath = resourcePathUri.substring( ResourceFileServlet.WEBJAR_BASE_URL_PATH.length(), resourcePathUri.length() );
final String webJarName;
final String webJarPath;
{
final int slashIndex = remainingPath.indexOf( "/" );
if ( slashIndex < 0 )
{
return null;
}
webJarName = remainingPath.substring( 0, slashIndex );
webJarPath = remainingPath.substring( slashIndex + 1, remainingPath.length() );
}
final String versionString = WEB_JAR_VERSION_MAP.get( webJarName );
if ( versionString == null )
{
return null;
}
final String fullPath = ResourceFileServlet.WEBJAR_BASE_FILE_PATH + "/" + webJarName + "/" + versionString + "/" + webJarPath;
if ( WEB_JAR_ASSET_LIST.contains( fullPath ) )
{
final ClassLoader classLoader = servletContext.getClassLoader();
final InputStream inputStream = classLoader.getResourceAsStream( fullPath );
if ( inputStream != null )
{
return new InputStreamFileResource( inputStream, fullPath );
}
}
}
return null;
}
private static class InputStreamFileResource implements FileResource
{
private final InputStream inputStream;
private final String fullPath;
InputStreamFileResource( final InputStream inputStream, final String fullPath )
{
this.inputStream = inputStream;
this.fullPath = fullPath;
}
@Override
public InputStream getInputStream( ) throws IOException
{
return inputStream;
}
@Override
public long length( )
{
return 0;
}
@Override
public long lastModified( )
{
return 0;
}
@Override
public boolean exists( )
{
return true;
}
@Override
public String getName( )
{
return fullPath;
}
}
/**
* Returns true if the given accept header accepts the given value.
*
* @param acceptHeader The accept header.
* @param toAccept The value to be accepted.
* @return True if the given accept header accepts the given value.
*/
private static boolean accepts( final String acceptHeader, final String toAccept )
{
final String[] acceptValues = acceptHeader.split( "\\s*(,|;)\\s*" );
Arrays.sort( acceptValues );
return Arrays.binarySearch( acceptValues, toAccept ) > -1
|| Arrays.binarySearch( acceptValues, toAccept.replaceAll( "/.*$", "/*" ) ) > -1
|| Arrays.binarySearch( acceptValues, "*/*" ) > -1;
}
}

View file

@ -23,11 +23,8 @@
package password.pwm.http.servlet.resource;
import com.github.benmanes.caffeine.cache.Cache;
import org.apache.commons.io.IOUtils;
import org.webjars.WebJarAssetLocator;
import password.pwm.PwmApplication;
import password.pwm.PwmConstants;
import password.pwm.config.PwmSetting;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
@ -38,10 +35,9 @@ import password.pwm.http.servlet.PwmServlet;
import password.pwm.svc.stats.EventRateMeter;
import password.pwm.svc.stats.Statistic;
import password.pwm.svc.stats.StatisticsManager;
import password.pwm.util.java.StringUtil;
import password.pwm.util.java.JavaHelper;
import password.pwm.util.logging.PwmLogger;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
@ -51,21 +47,12 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@WebServlet(
name = "ResourceFileServlet",
@ -78,6 +65,9 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
private static final PwmLogger LOGGER = PwmLogger.forClass( ResourceFileServlet.class );
public static final String RESOURCE_PATH = "/public/resources";
static final String WEBJAR_BASE_URL_PATH = RESOURCE_PATH + "/webjars/";
static final String WEBJAR_BASE_FILE_PATH = "META-INF/resources/webjars";
public static final String THEME_CSS_PATH = "/themes/%THEME%/style.css";
public static final String THEME_CSS_MOBILE_PATH = "/themes/%THEME%/mobileStyle.css";
public static final String THEME_CSS_CONFIG_PATH = "/themes/%THEME%/configStyle.css";
@ -85,13 +75,6 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
public static final String TOKEN_THEME = "%THEME%";
public static final String EMBED_THEME = "embed";
private static final String WEBJAR_BASE_FILE_PATH = "META-INF/resources/webjars";
private static final String WEBJAR_BASE_URL_PATH = RESOURCE_PATH + "/webjars/";
private static final Map<String, String> WEB_JAR_VERSION_MAP = Collections.unmodifiableMap( new HashMap<>( new WebJarAssetLocator().getWebJars() ) );
private static final Collection<String> WEB_JAR_ASSET_LIST = Collections.unmodifiableCollection( new ArrayList<>( new WebJarAssetLocator().getFullPathIndex().values() ) );
@Override
protected void doGet( final HttpServletRequest req, final HttpServletResponse resp )
throws ServletException, IOException
@ -134,11 +117,9 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
throws IOException, PwmUnrecoverableException
{
final FileResource file = resolveRequestedFile(
req.getServletContext(),
figureRequestPathMinusContext( req ),
ResourceServletConfiguration.defaultConfiguration()
);
final ResourceFileRequest resourceFileRequest = new ResourceFileRequest( null, ResourceServletConfiguration.defaultConfiguration(), req );
final FileResource file = resourceFileRequest.getRequestedFileResource();
if ( file == null || !file.exists() )
{
@ -149,9 +130,8 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
handleUncachedResponse( resp, file, false );
}
@SuppressWarnings( "checkstyle:MethodLength" )
protected void processAction( final PwmRequest pwmRequest )
throws ServletException, IOException, PwmUnrecoverableException
throws IOException, PwmUnrecoverableException
{
if ( pwmRequest.getMethod() != HttpMethod.GET )
{
@ -164,25 +144,13 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
final ResourceServletService resourceService = pwmApplication.getResourceServletService();
final ResourceServletConfiguration resourceConfiguration = resourceService.getResourceServletConfiguration();
final String requestURI = stripNonceFromURI( resourceConfiguration, figureRequestPathMinusContext( pwmRequest.getHttpServletRequest() ) );
try
{
if ( handleEmbeddedURIs( pwmApplication, requestURI, pwmRequest.getPwmResponse().getHttpServletResponse(), resourceConfiguration ) )
{
return;
}
}
catch ( Exception e )
{
LOGGER.error( pwmRequest, "unexpected error detecting/handling special request uri: " + e.getMessage() );
}
final ResourceFileRequest resourceFileRequest = new ResourceFileRequest( pwmApplication.getConfig(), resourceConfiguration, pwmRequest.getHttpServletRequest() );
final String requestURI = resourceFileRequest.getRequestURI();
final FileResource file;
try
{
file = resolveRequestedFile( this.getServletContext(), requestURI, resourceConfiguration );
file = resourceFileRequest.getRequestedFileResource();
}
catch ( PwmUnrecoverableException e )
{
@ -209,47 +177,14 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
}
// Get content type by file name and set default GZIP support and content disposition.
String contentType = getMimeType( file.getName() );
boolean acceptsGzip = false;
// If content type is unknown, then set the default value.
// For all content types, see: http://www.w3schools.com/media/media_mimeref.asp
// To add new content types, add new mime-mapping entry in web.xml.
if ( contentType == null )
{
contentType = "application/octet-stream";
}
// If content type is text, then determine whether GZIP content encoding is supported by
// the browser and expand content type with the one and right character encoding.
if ( resourceConfiguration.isEnableGzip() )
{
if ( contentType.startsWith( "text" ) || contentType.contains( "javascript" ) )
{
final String acceptEncoding = pwmRequest.readHeaderValueAsString( HttpHeader.AcceptEncoding );
acceptsGzip = acceptEncoding != null && accepts( acceptEncoding, "gzip" );
contentType += ";charset=UTF-8";
}
}
final String contentType = resourceFileRequest.getReturnContentType();
final boolean acceptsGzip = resourceFileRequest.allowsCompression();
final HttpServletResponse response = pwmRequest.getPwmResponse().getHttpServletResponse();
final String eTagValue = resourceConfiguration.getNonceValue();
if ( respondWithNotModified( pwmRequest, resourceConfiguration ) )
{
// reply back with etag.
final String ifNoneMatchValue = pwmRequest.readHeaderValueAsString( HttpHeader.If_None_Match );
if ( ifNoneMatchValue != null && ifNoneMatchValue.equals( eTagValue ) )
{
response.reset();
response.setStatus( HttpServletResponse.SC_NOT_MODIFIED );
try
{
pwmRequest.debugHttpRequestToLog( "returning HTTP 304 status" );
}
catch ( PwmUnrecoverableException e2 )
{ /* noop */ }
return;
}
return;
}
// Initialize response.
@ -260,54 +195,22 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
try
{
boolean fromCache = false;
StringBuilder debugText = new StringBuilder();
String debugText;
try
{
fromCache = handleCacheableResponse( resourceConfiguration, response, file, acceptsGzip, resourceService.getCacheMap() );
if ( fromCache || acceptsGzip )
{
debugText.append( "(" );
if ( fromCache )
{
debugText.append( "cached" );
}
if ( fromCache && acceptsGzip )
{
debugText.append( ", " );
}
if ( acceptsGzip )
{
debugText.append( "gzip" );
}
debugText.append( ")" );
}
else
{
debugText = new StringBuilder( "(not cached)" );
}
StatisticsManager.incrementStat( pwmApplication, Statistic.HTTP_RESOURCE_REQUESTS );
fromCache = handleCacheableResponse( resourceFileRequest, response, resourceService.getCacheMap() );
debugText = makeDebugText( fromCache, acceptsGzip, false );
}
catch ( UncacheableResourceException e )
{
handleUncachedResponse( response, file, acceptsGzip );
debugText = new StringBuilder();
debugText.append( "(uncacheable" );
if ( acceptsGzip )
{
debugText.append( ", gzip" );
}
debugText.append( ")" );
}
try
{
pwmRequest.debugHttpRequestToLog( debugText.toString() );
}
catch ( PwmUnrecoverableException e )
{
/* noop */
debugText = makeDebugText( fromCache, acceptsGzip, true );
}
pwmRequest.debugHttpRequestToLog( debugText );
final EventRateMeter.MovingAverage cacheHitRatio = resourceService.getCacheHitRatio();
StatisticsManager.incrementStat( pwmApplication, Statistic.HTTP_RESOURCE_REQUESTS );
cacheHitRatio.update( fromCache ? 1 : 0 );
}
catch ( Exception e )
@ -316,49 +219,82 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
}
}
private String makeDebugText( final boolean fromCache, final boolean acceptsGzip, final boolean uncacheable )
{
if ( uncacheable )
{
final StringBuilder debugText = new StringBuilder();
debugText.append( "(uncacheable" );
if ( acceptsGzip )
{
debugText.append( ", gzip" );
}
debugText.append( ")" );
return debugText.toString();
}
if ( fromCache || acceptsGzip )
{
final StringBuilder debugText = new StringBuilder();
debugText.append( "(" );
if ( fromCache )
{
debugText.append( "cached" );
}
if ( fromCache && acceptsGzip )
{
debugText.append( ", " );
}
if ( acceptsGzip )
{
debugText.append( "gzip" );
}
debugText.append( ")" );
return debugText.toString();
}
else
{
return "(not cached)";
}
}
private boolean handleCacheableResponse(
final ResourceServletConfiguration resourceServletConfiguration,
final ResourceFileRequest resourceFileRequest,
final HttpServletResponse response,
final FileResource file,
final boolean acceptsGzip,
final Cache<CacheKey, CacheEntry> responseCache
)
throws UncacheableResourceException, IOException
throws UncacheableResourceException, IOException, PwmUnrecoverableException
{
final FileResource file = resourceFileRequest.getRequestedFileResource();
if ( file.length() > resourceServletConfiguration.getMaxCacheBytes() )
if ( file.length() > resourceFileRequest.getResourceServletConfiguration().getMaxCacheBytes() )
{
throw new UncacheableResourceException( "file to large to cache" );
}
boolean fromCache = false;
final CacheKey cacheKey = new CacheKey( file, acceptsGzip );
final CacheKey cacheKey = new CacheKey( file, resourceFileRequest.allowsCompression() );
CacheEntry cacheEntry = responseCache.getIfPresent( cacheKey );
if ( cacheEntry == null )
{
final Map<String, String> headers = new HashMap<>();
final ByteArrayOutputStream tempOutputStream = new ByteArrayOutputStream();
final InputStream input = file.getInputStream();
try
try ( InputStream input = resourceFileRequest.getRequestedFileResource().getInputStream() )
{
if ( acceptsGzip )
if ( resourceFileRequest.allowsCompression() )
{
final GZIPOutputStream gzipOutputStream = new GZIPOutputStream( tempOutputStream );
headers.put( HttpHeader.ContentEncoding.getHttpName(), "gzip" );
copy( input, gzipOutputStream );
close( gzipOutputStream );
try ( GZIPOutputStream gzipOutputStream = new GZIPOutputStream( tempOutputStream ) )
{
headers.put( HttpHeader.ContentEncoding.getHttpName(), "gzip" );
JavaHelper.copy( input, gzipOutputStream );
}
}
else
{
copy( input, tempOutputStream );
JavaHelper.copy( input, tempOutputStream );
}
}
finally
{
close( input );
close( tempOutputStream );
}
final byte[] entity = tempOutputStream.toByteArray();
headers.put( HttpHeader.ContentLength.getHttpName(), String.valueOf( entity.length ) );
@ -375,14 +311,9 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
response.setHeader( key, cacheEntry.getHeaderStrings().get( key ) );
}
final OutputStream responseOutputStream = response.getOutputStream();
try
try ( OutputStream responseOutputStream = response.getOutputStream() )
{
copy( new ByteArrayInputStream( cacheEntry.getEntity() ), responseOutputStream );
}
finally
{
close( responseOutputStream );
JavaHelper.copy( new ByteArrayInputStream( cacheEntry.getEntity() ), responseOutputStream );
}
return fromCache;
@ -392,23 +323,18 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
final HttpServletResponse response,
final FileResource file,
final boolean acceptsGzip
) throws IOException
)
throws IOException
{
// Prepare streams.
OutputStream output = null;
InputStream input = null;
try
try (
OutputStream output = new BufferedOutputStream( response.getOutputStream() );
InputStream input = new BufferedInputStream( file.getInputStream() );
)
{
// Open streams.
input = new BufferedInputStream( file.getInputStream() );
output = new BufferedOutputStream( response.getOutputStream() );
if ( acceptsGzip )
{
// The browser accepts GZIP, so GZIP the content.
response.setHeader( HttpHeader.ContentEncoding.getHttpName(), "gzip" );
output = new GZIPOutputStream( output );
JavaHelper.copy( input, new GZIPOutputStream( output ) );
}
else
{
@ -418,335 +344,10 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
{
response.setHeader( HttpHeader.ContentLength.getHttpName(), String.valueOf( file.length() ) );
}
}
// Copy full range.
copy( input, output );
}
finally
{
// Gently close streams.
close( output );
close( input );
}
}
/**
* Returns true if the given accept header accepts the given value.
*
* @param acceptHeader The accept header.
* @param toAccept The value to be accepted.
* @return True if the given accept header accepts the given value.
*/
private static boolean accepts( final String acceptHeader, final String toAccept )
{
final String[] acceptValues = acceptHeader.split( "\\s*(,|;)\\s*" );
Arrays.sort( acceptValues );
return Arrays.binarySearch( acceptValues, toAccept ) > -1
|| Arrays.binarySearch( acceptValues, toAccept.replaceAll( "/.*$", "/*" ) ) > -1
|| Arrays.binarySearch( acceptValues, "*/*" ) > -1;
}
/**
* Copy the given byte range of the given input to the given output.
*
* @param input The input to copy the given range to the given output for.
* @param output The output to copy the given range from the given input for.
* @throws IOException If something fails at I/O level.
*/
private static void copy( final InputStream input, final OutputStream output )
throws IOException
{
IOUtils.copy( input, output );
}
/**
* Close the given resource.
*
* @param resource The resource to be closed.
*/
private static void close( final Closeable resource )
{
IOUtils.closeQuietly( resource );
}
static FileResource resolveRequestedFile(
final ServletContext servletContext,
final String resourcePathUri,
final ResourceServletConfiguration resourceServletConfiguration
)
throws PwmUnrecoverableException
{
// URL-decode the file name (might contain spaces and on) and prepare file object.
String filename = StringUtil.urlDecode( resourcePathUri );
// parse out the session key...
if ( filename.contains( ";" ) )
{
filename = filename.substring( 0, filename.indexOf( ";" ) );
}
if ( !filename.startsWith( RESOURCE_PATH ) )
{
LOGGER.warn( "illegal url request to " + filename );
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "illegal url request" ) );
}
{
final FileResource resource = handleWebjarURIs( servletContext, resourcePathUri );
if ( resource != null )
{
return resource;
JavaHelper.copy( input, output );
}
}
{
// check files system zip files.
final Map<String, ZipFile> zipResources = resourceServletConfiguration.getZipResources();
for ( final Map.Entry<String, ZipFile> entry : zipResources.entrySet() )
{
final String path = entry.getKey();
if ( filename.startsWith( path ) )
{
final String zipSubPath = filename.substring( path.length() + 1, filename.length() );
final ZipFile zipFile = entry.getValue();
final ZipEntry zipEntry = zipFile.getEntry( zipSubPath );
if ( zipEntry != null )
{
return new ZipFileResource( zipFile, zipEntry );
}
}
if ( filename.startsWith( zipResources.get( path ).getName() ) )
{
LOGGER.warn( "illegal url request to " + filename + " zip resource" );
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "illegal url request" ) );
}
}
}
// convert to file.
final String filePath = servletContext.getRealPath( filename );
final File file = new File( filePath );
// figure top-most path allowed by request
final String parentDirectoryPath = servletContext.getRealPath( RESOURCE_PATH );
final File parentDirectory = new File( parentDirectoryPath );
FileResource fileSystemResource = null;
{
//verify the requested page is a child of the servlet resource path.
int recursions = 0;
File recurseFile = file.getParentFile();
while ( recurseFile != null && recursions < 100 )
{
if ( parentDirectory.equals( recurseFile ) )
{
fileSystemResource = new RealFileResource( file );
break;
}
recurseFile = recurseFile.getParentFile();
recursions++;
}
}
if ( fileSystemResource == null )
{
LOGGER.warn( "attempt to access file outside of servlet path " + file.getAbsolutePath() );
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "illegal file path request" ) );
}
if ( !fileSystemResource.exists() )
{
// check custom (configuration defined) zip file bundles
final Map<String, FileResource> customResources = resourceServletConfiguration.getCustomFileBundle();
for ( final Map.Entry<String, FileResource> entry : customResources.entrySet() )
{
final String customFileName = entry.getKey();
final String testName = RESOURCE_PATH + "/" + customFileName;
if ( testName.equals( resourcePathUri ) )
{
return entry.getValue();
}
}
}
return fileSystemResource;
}
private boolean handleEmbeddedURIs(
final PwmApplication pwmApplication,
final String requestURI,
final HttpServletResponse response,
final ResourceServletConfiguration resourceServletConfiguration
)
throws PwmUnrecoverableException, IOException, ServletException
{
if ( requestURI != null )
{
final String embedThemeUrl = RESOURCE_PATH + THEME_CSS_PATH.replace( TOKEN_THEME, EMBED_THEME );
final String embedThemeMobileUrl = RESOURCE_PATH + THEME_CSS_MOBILE_PATH.replace( TOKEN_THEME, EMBED_THEME );
if ( requestURI.equalsIgnoreCase( embedThemeUrl ) )
{
writeConfigSettingToBody( pwmApplication, PwmSetting.DISPLAY_CSS_EMBED, response, resourceServletConfiguration );
return true;
}
else if ( requestURI.equalsIgnoreCase( embedThemeMobileUrl ) )
{
writeConfigSettingToBody( pwmApplication, PwmSetting.DISPLAY_CSS_MOBILE_EMBED, response, resourceServletConfiguration );
return true;
}
}
return false;
}
private void writeConfigSettingToBody(
final PwmApplication pwmApplication,
final PwmSetting pwmSetting,
final HttpServletResponse response,
final ResourceServletConfiguration resourceServletConfiguration
)
throws PwmUnrecoverableException, IOException
{
final String bodyText = pwmApplication.getConfig().readSettingAsString( pwmSetting );
try
{
response.setContentType( "text/css" );
addExpirationHeaders( resourceServletConfiguration, response );
if ( bodyText != null && bodyText.length() > 0 )
{
response.setIntHeader( "Content-Length", bodyText.length() );
copy( new ByteArrayInputStream( bodyText.getBytes( PwmConstants.DEFAULT_CHARSET ) ), response.getOutputStream() );
}
else
{
response.setIntHeader( "Content-Length", 0 );
}
}
finally
{
close( response.getOutputStream() );
}
}
private String stripNonceFromURI(
final ResourceServletConfiguration resourceServletConfiguration,
final String uriString
)
{
if ( !resourceServletConfiguration.isEnablePathNonce() )
{
return uriString;
}
final Matcher theMatcher = resourceServletConfiguration.getNoncePattern().matcher( uriString );
if ( theMatcher.find() )
{
return theMatcher.replaceFirst( "" );
}
return uriString;
}
private String figureRequestPathMinusContext( final HttpServletRequest httpServletRequest )
{
final String requestURI = httpServletRequest.getRequestURI();
return requestURI.substring( httpServletRequest.getContextPath().length(), requestURI.length() );
}
private static FileResource handleWebjarURIs(
final ServletContext servletContext,
final String resourcePathUri
)
throws PwmUnrecoverableException
{
if ( resourcePathUri.startsWith( WEBJAR_BASE_URL_PATH ) )
{
// This allows us to override a webjar file, if needed. Mostly helpful during development.
final File file = new File( servletContext.getRealPath( resourcePathUri ) );
if ( file.exists() )
{
return new RealFileResource( file );
}
final String remainingPath = resourcePathUri.substring( WEBJAR_BASE_URL_PATH.length(), resourcePathUri.length() );
final String webJarName;
final String webJarPath;
{
final int slashIndex = remainingPath.indexOf( "/" );
if ( slashIndex < 0 )
{
return null;
}
webJarName = remainingPath.substring( 0, slashIndex );
webJarPath = remainingPath.substring( slashIndex + 1, remainingPath.length() );
}
final String versionString = WEB_JAR_VERSION_MAP.get( webJarName );
if ( versionString == null )
{
return null;
}
final String fullPath = WEBJAR_BASE_FILE_PATH + "/" + webJarName + "/" + versionString + "/" + webJarPath;
if ( WEB_JAR_ASSET_LIST.contains( fullPath ) )
{
final ClassLoader classLoader = servletContext.getClassLoader();
final InputStream inputStream = classLoader.getResourceAsStream( fullPath );
if ( inputStream != null )
{
return new InputStreamFileResource( inputStream, fullPath );
}
}
}
return null;
}
private static class InputStreamFileResource implements FileResource
{
private final InputStream inputStream;
private final String fullPath;
InputStreamFileResource( final InputStream inputStream, final String fullPath )
{
this.inputStream = inputStream;
this.fullPath = fullPath;
}
@Override
public InputStream getInputStream( ) throws IOException
{
return inputStream;
}
@Override
public long length( )
{
return 0;
}
@Override
public long lastModified( )
{
return 0;
}
@Override
public boolean exists( )
{
return true;
}
@Override
public String getName( )
{
return fullPath;
}
}
private void addExpirationHeaders( final ResourceServletConfiguration resourceServletConfiguration, final HttpServletResponse httpResponse )
@ -756,16 +357,26 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
httpResponse.setHeader( "Vary", "Accept-Encoding" );
}
private String getMimeType( final String filename )
private boolean respondWithNotModified( final PwmRequest pwmRequest, final ResourceServletConfiguration resourceConfiguration )
{
final String contentType = getServletContext().getMimeType( filename );
if ( contentType == null )
final String eTagValue = resourceConfiguration.getNonceValue();
final HttpServletResponse response = pwmRequest.getPwmResponse().getHttpServletResponse();
final String ifNoneMatchValue = pwmRequest.readHeaderValueAsString( HttpHeader.If_None_Match );
if ( ifNoneMatchValue != null && ifNoneMatchValue.equals( eTagValue ) )
{
if ( filename.endsWith( ".woff2" ) )
// reply back with etag.
response.reset();
response.setStatus( HttpServletResponse.SC_NOT_MODIFIED );
try
{
return "font/woff2";
pwmRequest.debugHttpRequestToLog( "returning HTTP 304 status" );
}
catch ( PwmUnrecoverableException e2 )
{ /* noop */ }
return true;
}
return contentType;
return false;
}
}

View file

@ -271,7 +271,11 @@ public class ResourceServletService implements PwmService
for ( final String testUrl : testUrls )
{
final String themePathUrl = ResourceFileServlet.RESOURCE_PATH + testUrl.replace( ResourceFileServlet.TOKEN_THEME, themeName );
final FileResource resolvedFile = ResourceFileServlet.resolveRequestedFile( servletContext, themePathUrl, getResourceServletConfiguration() );
final FileResource resolvedFile = ResourceFileRequest.resolveRequestedResource(
pwmRequest.getConfig(),
servletContext,
themePathUrl,
getResourceServletConfiguration() );
if ( resolvedFile != null && resolvedFile.exists() )
{
LOGGER.debug( pwmRequest, () -> "check for theme validity of '" + themeName + "' returned true" );

View file

@ -364,7 +364,7 @@ public enum PwmIfTest
final HealthMonitor healthMonitor = pwmRequest.getPwmApplication().getHealthMonitor();
if ( healthMonitor != null && healthMonitor.status() == PwmService.STATUS.OPEN )
{
if ( healthMonitor.getMostSevereHealthStatus( HealthMonitor.CheckTimeliness.NeverBlock ) == HealthStatus.WARN )
if ( healthMonitor.getMostSevereHealthStatus() == HealthStatus.WARN )
{
return true;
}

View file

@ -109,4 +109,6 @@ public interface UserInfo
List<String> readMultiStringAttribute( String attribute ) throws PwmUnrecoverableException;
Map<String, String> readStringAttributes( Collection<String> attributes ) throws PwmUnrecoverableException;
Instant getPasswordExpirationNoticeSendTime( ) throws PwmUnrecoverableException;
}

View file

@ -46,7 +46,6 @@ import java.util.Map;
@Builder
public class UserInfoBean implements UserInfo
{
private final UserIdentity userIdentity;
private final String username;
private final String userEmailAddress;
@ -87,6 +86,7 @@ public class UserInfoBean implements UserInfo
private final Instant passwordLastModifiedTime;
private final Instant lastLdapLoginTime;
private final Instant accountExpirationTime;
private final Instant passwordExpirationNoticeSendTime;
private final boolean accountEnabled;
private final boolean accountExpired;
@ -148,6 +148,5 @@ public class UserInfoBean implements UserInfo
}
return Collections.unmodifiableMap( returnObj );
}
}

View file

@ -52,10 +52,11 @@ import password.pwm.error.PwmDataValidationException;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.svc.PwmService;
import password.pwm.util.i18n.LocaleHelper;
import password.pwm.svc.pwnotify.PwNotifyUserStatus;
import password.pwm.util.PasswordData;
import password.pwm.util.PwmPasswordRuleValidator;
import password.pwm.util.form.FormUtility;
import password.pwm.util.i18n.LocaleHelper;
import password.pwm.util.java.CachingProxyWrapper;
import password.pwm.util.java.JavaHelper;
import password.pwm.util.java.TimeDuration;
@ -74,6 +75,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class UserInfoReader implements UserInfo
@ -868,4 +870,19 @@ public class UserInfoReader implements UserInfo
{
return locale == null ? null : LocaleHelper.getBrowserLocaleString( locale );
}
@Override
public Instant getPasswordExpirationNoticeSendTime()
throws PwmUnrecoverableException
{
if ( pwmApplication.getPwNotifyService().status() == PwmService.STATUS.OPEN )
{
final Optional<PwNotifyUserStatus> optionalState = pwmApplication.getPwNotifyService().readUserNotificationState( userIdentity, sessionLabel );
if ( optionalState.isPresent() )
{
return optionalState.get().getExpireTime();
}
}
return null;
}
}

View file

@ -309,7 +309,12 @@ public class AuditService implements PwmService
body = StringUtil.mapToString( mapRecord, "=", "\n" );
}
final EmailItemBean emailItem = new EmailItemBean( toAddress, fromAddress, subject, body, null );
final EmailItemBean emailItem = EmailItemBean.builder()
.to( toAddress )
.from( fromAddress )
.subject( subject )
.bodyPlain( body )
.build();
pwmApplication.getEmailQueue().submitEmail( emailItem, null, macroMachine );
}

View file

@ -44,7 +44,6 @@ import java.util.Map;
public class NodeService implements PwmService
{
private static final PwmLogger LOGGER = PwmLogger.forClass( NodeService.class );
private PwmApplication pwmApplication;

View file

@ -75,7 +75,7 @@ public class ReportService implements PwmService
private PwmApplication pwmApplication;
private STATUS status = STATUS.NEW;
private boolean cancelFlag = false;
private ReportStatusInfo reportStatus = new ReportStatusInfo( "" );
private ReportStatusInfo reportStatus = ReportStatusInfo.builder().build();
private ReportSummaryData summaryData = ReportSummaryData.newSummaryData( null );
private ExecutorService executorService;
@ -201,7 +201,7 @@ public class ReportService implements PwmService
{
if ( reportStatus.getCurrentProcess() != ReportStatusInfo.ReportEngineProcess.ReadData
&& reportStatus.getCurrentProcess() != ReportStatusInfo.ReportEngineProcess.SearchLDAP
)
)
{
executorService.execute( new ClearTask() );
executorService.execute( new ReadLDAPTask() );
@ -292,31 +292,13 @@ public class ReportService implements PwmService
{
try
{
UserCacheRecord returnBean = null;
while ( returnBean == null && this.storageKeyIterator.hasNext() )
while ( this.storageKeyIterator.hasNext() )
{
final UserCacheService.StorageKey key = this.storageKeyIterator.next();
returnBean = userCacheService.readStorageKey( key );
final UserCacheRecord returnBean = userCacheService.readStorageKey( key );
if ( returnBean != null )
{
if ( returnBean.getCacheTimestamp() == null )
{
final UserCacheRecord finalBean = returnBean;
LOGGER.debug( SessionLabel.REPORTING_SESSION_LABEL, () -> "purging record due to missing cache timestamp: "
+ JsonUtil.serialize( finalBean ) );
userCacheService.removeStorageKey( key );
}
else if ( TimeDuration.fromCurrent( returnBean.getCacheTimestamp() ).isLongerThan( settings.getMaxCacheAge() ) )
{
final UserCacheRecord finalBean = returnBean;
LOGGER.debug( SessionLabel.REPORTING_SESSION_LABEL, () -> "purging record due to old age timestamp: "
+ JsonUtil.serialize( finalBean ) );
userCacheService.removeStorageKey( key );
}
else
{
return returnBean;
}
return returnBean;
}
}
@ -407,6 +389,7 @@ public class ReportService implements PwmService
}
catch ( Exception e )
{
boolean errorProcessed = false;
if ( e instanceof PwmException )
{
if ( ( ( PwmException ) e ).getErrorInformation().getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE )
@ -415,12 +398,14 @@ public class ReportService implements PwmService
{
LOGGER.error( SessionLabel.REPORTING_SESSION_LABEL, "directory unavailable error during background SearchLDAP, will retry; error: " + e.getMessage() );
pwmApplication.scheduleFutureJob( new ReadLDAPTask(), executorService, TimeDuration.of( 10, TimeDuration.Unit.MINUTES ) );
errorProcessed = true;
}
}
else
{
LOGGER.error( SessionLabel.REPORTING_SESSION_LABEL, "error during background ReadData: " + e.getMessage() );
}
}
if ( !errorProcessed )
{
LOGGER.error( SessionLabel.REPORTING_SESSION_LABEL, "error during background ReadData: " + e.getMessage() );
}
}
finally
@ -614,20 +599,13 @@ public class ReportService implements PwmService
private void updateCachedRecordFromLdap( final UserIdentity userIdentity )
throws ChaiUnavailableException, PwmUnrecoverableException, LocalDBException
throws PwmUnrecoverableException, LocalDBException
{
if ( status != STATUS.OPEN )
{
return;
}
final UserCacheService.StorageKey storageKey = UserCacheService.StorageKey.fromUserIdentity( pwmApplication, userIdentity );
final UserCacheRecord userCacheRecord = userCacheService.readStorageKey( storageKey );
if ( userCacheRecord != null )
{
summaryData.remove( userCacheRecord );
}
final UserInfo userInfo = UserInfoFactory.newUserInfoUsingProxyForOfflineUser(
pwmApplication,
SessionLabel.REPORTING_SESSION_LABEL,
@ -642,71 +620,29 @@ public class ReportService implements PwmService
}
}
private class RolloverTask implements Runnable
{
@Override
public void run( )
{
reportStatus.setCurrentProcess( ReportStatusInfo.ReportEngineProcess.RollOver );
try
{
summaryData = ReportSummaryData.newSummaryData( settings.getTrackDays() );
updateRestingCacheData();
}
finally
{
reportStatus.setCurrentProcess( ReportStatusInfo.ReportEngineProcess.None );
}
}
private void updateRestingCacheData( )
{
final Instant startTime = Instant.now();
int examinedRecords = 0;
try ( ClosableIterator<UserCacheRecord> iterator = iterator() )
{
final long totalRecords = userCacheService.size();
LOGGER.debug( SessionLabel.REPORTING_SESSION_LABEL, () -> "beginning cache review process of " + totalRecords + " records" );
Instant lastLogOutputTime = Instant.now();
while ( !cancelFlag && iterator.hasNext() && status == STATUS.OPEN )
{
// (purge routine is embedded in next();
final UserCacheRecord record = iterator.next();
if ( summaryData != null && record != null )
{
summaryData.update( record );
}
examinedRecords++;
if ( TimeDuration.fromCurrent( lastLogOutputTime ).isLongerThan( 30, TimeDuration.Unit.SECONDS ) )
{
final int finalExamined = examinedRecords;
LOGGER.trace( SessionLabel.REPORTING_SESSION_LABEL,
() -> "cache review process in progress, examined " + finalExamined
+ " in " + TimeDuration.compactFromCurrent( startTime ) );
lastLogOutputTime = Instant.now();
}
}
final int finalExamined = examinedRecords;
LOGGER.info( SessionLabel.REPORTING_SESSION_LABEL,
() -> "completed cache review process of " + finalExamined
+ " cached report records in " + TimeDuration.compactFromCurrent( startTime ) );
}
}
}
private class DailyJobExecuteTask implements Runnable
{
@Override
public void run( )
{
executorService.execute( new ClearTask() );
executorService.execute( new ReadLDAPTask() );
checkForOutdatedStoreData();
if ( settings.isDailyJobEnabled() )
{
executorService.execute( new ClearTask() );
executorService.execute( new ReadLDAPTask() );
}
}
private void checkForOutdatedStoreData()
{
final Instant lastFinishDate = reportStatus.getFinishDate();
if ( lastFinishDate != null && TimeDuration.fromCurrent( lastFinishDate ).isLongerThan( settings.getMaxCacheAge() ) )
{
executorService.execute( new ClearTask() );
}
}
}
private class InitializationTask implements Runnable
@ -725,17 +661,15 @@ public class ReportService implements PwmService
return;
}
final boolean reportingEnabled = pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.REPORTING_ENABLE );
final boolean reportingEnabled = pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.REPORTING_ENABLE_DAILY_JOB );
if ( reportingEnabled )
{
final Instant nextZuluZeroTime = JavaHelper.nextZuluZeroTime();
final long secondsUntilNextDredge = settings.getJobOffsetSeconds() + TimeDuration.fromCurrent( nextZuluZeroTime ).as( TimeDuration.Unit.SECONDS );
final TimeDuration initialDelay = TimeDuration.of( secondsUntilNextDredge, TimeDuration.Unit.SECONDS );
pwmApplication.scheduleFixedRateJob( new ProcessWorkQueueTask(), executorService, initialDelay, TimeDuration.DAY );
pwmApplication.scheduleFixedRateJob( new DailyJobExecuteTask(), executorService, initialDelay, TimeDuration.DAY );
LOGGER.debug( () -> "scheduled daily execution, next task will be at " + nextZuluZeroTime.toString() );
}
executorService.submit( new RolloverTask() );
executorService.submit( new ProcessWorkQueueTask() );
}
@ -769,7 +703,7 @@ public class ReportService implements PwmService
if ( clearFlag )
{
reportStatus = new ReportStatusInfo( settings.getSettingsHash() );
reportStatus = ReportStatusInfo.builder().settingsHash( settings.getSettingsHash() ).build();
executeCommand( ReportCommand.Clear );
}
}
@ -800,7 +734,7 @@ public class ReportService implements PwmService
userCacheService.clear();
}
summaryData = ReportSummaryData.newSummaryData( settings.getTrackDays() );
reportStatus = new ReportStatusInfo( settings.getSettingsHash() );
reportStatus = ReportStatusInfo.builder().settingsHash( settings.getSettingsHash() ).build();
LOGGER.debug( SessionLabel.REPORTING_SESSION_LABEL, () -> "finished clearing report " + TimeDuration.compactFromCurrent( startTime ) );
}
}

View file

@ -46,8 +46,10 @@ class ReportSettings implements Serializable
{
private static final PwmLogger LOGGER = PwmLogger.forClass( ReportSettings.class );
private boolean dailyJobEnabled;
@Builder.Default
private TimeDuration maxCacheAge = TimeDuration.of( TimeDuration.DAY.asMillis() * 90, TimeDuration.Unit.MILLISECONDS );
private TimeDuration maxCacheAge = TimeDuration.of( 10, TimeDuration.Unit.DAYS );
@Builder.Default
private List<UserPermission> searchFilter = Collections.emptyList();
@ -77,9 +79,10 @@ class ReportSettings implements Serializable
public static ReportSettings readSettingsFromConfig( final Configuration config )
{
final ReportSettings.ReportSettingsBuilder builder = ReportSettings.builder();
builder.maxCacheAge( TimeDuration.of( config.readSettingAsLong( PwmSetting.REPORTING_MAX_CACHE_AGE ), TimeDuration.Unit.SECONDS ) );
builder.maxCacheAge( TimeDuration.of( Long.parseLong( config.readAppProperty( AppProperty.REPORTING_MAX_REPORT_AGE_SECONDS ) ), TimeDuration.Unit.SECONDS ) );
builder.searchFilter( config.readSettingAsUserPermission( PwmSetting.REPORTING_USER_MATCH ) );
builder.maxSearchSize ( ( int ) config.readSettingAsLong( PwmSetting.REPORTING_MAX_QUERY_SIZE ) );
builder.dailyJobEnabled( config.readSettingAsBoolean( PwmSetting.REPORTING_ENABLE_DAILY_JOB ) );
if ( builder.searchFilter == null || builder.searchFilter.isEmpty() )
{

View file

@ -22,20 +22,29 @@
package password.pwm.svc.report;
import lombok.Builder;
import lombok.Data;
import password.pwm.error.ErrorInformation;
import password.pwm.util.java.TimeDuration;
import java.io.Serializable;
import java.time.Instant;
@Data
@Builder( toBuilder = true )
public class ReportStatusInfo implements Serializable
{
@Builder.Default
private TimeDuration jobDuration = TimeDuration.ZERO;
private Instant startDate;
private Instant finishDate;
private int count;
private int errors;
private ErrorInformation lastError;
private String settingsHash;
@Builder.Default
private ReportEngineProcess currentProcess = ReportEngineProcess.None;
public enum ReportEngineProcess
@ -58,76 +67,4 @@ public class ReportStatusInfo implements Serializable
return label;
}
}
public ReportStatusInfo( final String settingsHash )
{
this.settingsHash = settingsHash;
}
public String getSettingsHash( )
{
return settingsHash;
}
public TimeDuration getJobDuration( )
{
return jobDuration;
}
public void setJobDuration( final TimeDuration jobDuration )
{
this.jobDuration = jobDuration;
}
public Instant getFinishDate( )
{
return finishDate;
}
public void setFinishDate( final Instant finishDate )
{
this.finishDate = finishDate;
}
public int getCount( )
{
return count;
}
public void setCount( final int count )
{
this.count = count;
}
public int getErrors( )
{
return errors;
}
public void setErrors( final int errors )
{
this.errors = errors;
}
public ErrorInformation getLastError( )
{
return lastError;
}
public void setLastError( final ErrorInformation lastError )
{
this.lastError = lastError;
}
public ReportEngineProcess getCurrentProcess( )
{
return currentProcess;
}
public void setCurrentProcess( final ReportEngineProcess currentProcess )
{
this.currentProcess = currentProcess;
}
}

View file

@ -23,6 +23,8 @@
package password.pwm.svc.report;
import com.novell.ldapchai.cr.Answer;
import lombok.Builder;
import lombok.Value;
import password.pwm.config.Configuration;
import password.pwm.config.option.DataStorageMethod;
import password.pwm.i18n.Admin;
@ -44,12 +46,12 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Value
public class ReportSummaryData
{
private static final long MS_DAY = TimeDuration.DAY.asMillis();
private static final BigInteger TWO = new BigInteger( "2" );
private Instant meanCacheTime;
private final AtomicInteger totalUsers = new AtomicInteger( 0 );
private final AtomicInteger hasResponses = new AtomicInteger( 0 );
private final AtomicInteger hasResponseSetTime = new AtomicInteger( 0 );
@ -63,6 +65,7 @@ public class ReportSummaryData
private final AtomicInteger pwExpired = new AtomicInteger( 0 );
private final AtomicInteger pwPreExpired = new AtomicInteger( 0 );
private final AtomicInteger pwWarnPeriod = new AtomicInteger( 0 );
private final AtomicInteger hasReceivedPwExpireNotification = new AtomicInteger( 0 );
private final Map<DataStorageMethod, AtomicInteger> responseStorage = new ConcurrentHashMap<>();
private final Map<Answer.FormatType, AtomicInteger> responseFormatType = new ConcurrentHashMap<>();
@ -73,6 +76,7 @@ public class ReportSummaryData
private final Map<Integer, AtomicInteger> responseSetDays = new ConcurrentHashMap<>();
private final Map<Integer, AtomicInteger> otpSetDays = new ConcurrentHashMap<>();
private final Map<Integer, AtomicInteger> loginDays = new ConcurrentHashMap<>();
private final Map<Integer, AtomicInteger> pwExpireNotificationDays = new ConcurrentHashMap<>();
private ReportSummaryData( )
{
@ -92,27 +96,13 @@ public class ReportSummaryData
reportSummaryData.responseSetDays.put( day, new AtomicInteger( 0 ) );
reportSummaryData.otpSetDays.put( day, new AtomicInteger( 0 ) );
reportSummaryData.loginDays.put( day, new AtomicInteger( 0 ) );
reportSummaryData.pwExpireNotificationDays.put( day, new AtomicInteger( 0 ) );
}
}
return reportSummaryData;
}
public int getTotalUsers( )
{
return totalUsers.get();
}
public int getHasResponses( )
{
return hasResponses.get();
}
public int getHasPasswordExpirationTime( )
{
return hasPasswordExpirationTime.get();
}
public Map<DataStorageMethod, Integer> getResponseStorage( )
{
return Collections.unmodifiableMap( responseStorage.entrySet()
@ -129,141 +119,77 @@ public class ReportSummaryData
e -> e.getValue().get() ) ) );
}
public Instant getMeanCacheTime( )
{
return meanCacheTime;
}
void update( final UserCacheRecord userCacheRecord )
{
update( userCacheRecord, true );
}
totalUsers.incrementAndGet();
void remove( final UserCacheRecord userCacheRecord )
{
update( userCacheRecord, false );
}
@SuppressWarnings( "checkstyle:MethodLength" )
private void update( final UserCacheRecord userCacheRecord, final boolean adding )
{
final int modifier = adding ? 1 : -1;
totalUsers.addAndGet( modifier );
updateMeanTime( userCacheRecord.cacheTimestamp, adding );
if ( userCacheRecord.hasResponses )
if ( userCacheRecord.isHasResponses() )
{
hasResponses.addAndGet( modifier );
hasResponses.incrementAndGet();
}
if ( userCacheRecord.hasHelpdeskResponses )
if ( userCacheRecord.isHasHelpdeskResponses() )
{
hasHelpdeskResponses.addAndGet( modifier );
hasHelpdeskResponses.incrementAndGet();
}
if ( userCacheRecord.responseSetTime != null )
if ( userCacheRecord.getResponseSetTime() != null )
{
hasResponseSetTime.addAndGet( modifier );
hasResponseSetTime.incrementAndGet();
incrementIfWithinTimeWindow( userCacheRecord, responseSetDays );
}
for ( final Map.Entry<Integer, AtomicInteger> entry : responseSetDays.entrySet() )
if ( userCacheRecord.getPasswordExpirationTime() != null )
{
hasPasswordExpirationTime.incrementAndGet();
incrementIfWithinTimeWindow( userCacheRecord, pwExpireDays );
}
if ( userCacheRecord.getAccountExpirationTime() != null )
{
hasAccountExpirationTime.incrementAndGet();
incrementIfWithinTimeWindow( userCacheRecord, accountExpireDays );
}
if ( userCacheRecord.getLastLoginTime() != null )
{
hasLoginTime.incrementAndGet();
incrementIfWithinTimeWindow( userCacheRecord, loginDays );
}
if ( userCacheRecord.getPasswordChangeTime() != null )
{
hasChangePwTime.incrementAndGet();
incrementIfWithinTimeWindow( userCacheRecord, changePwDays );
}
if ( userCacheRecord.getPasswordExpirationNoticeSendTime() != null )
{
hasReceivedPwExpireNotification.incrementAndGet();
incrementIfWithinTimeWindow( userCacheRecord, pwExpireNotificationDays );
}
if ( userCacheRecord.getPasswordStatus() != null )
{
if ( userCacheRecord.getPasswordStatus().isExpired() )
{
final Integer day = entry.getKey();
entry.getValue().addAndGet( calcTimeWindow( userCacheRecord.responseSetTime, MS_DAY * day, adding ) );
pwExpired.incrementAndGet();
}
if ( userCacheRecord.getPasswordStatus().isPreExpired() )
{
pwPreExpired.incrementAndGet();
}
if ( userCacheRecord.getPasswordStatus().isWarnPeriod() )
{
pwWarnPeriod.incrementAndGet();
}
}
if ( userCacheRecord.passwordExpirationTime != null )
if ( userCacheRecord.getResponseStorageMethod() != null )
{
hasPasswordExpirationTime.addAndGet( modifier );
for ( final Map.Entry<Integer, AtomicInteger> entry : pwExpireDays.entrySet() )
{
final Integer day = entry.getKey();
entry.getValue().addAndGet( calcTimeWindow( userCacheRecord.passwordExpirationTime, MS_DAY * day, adding ) );
}
}
if ( userCacheRecord.accountExpirationTime != null )
{
hasAccountExpirationTime.addAndGet( modifier );
for ( final Map.Entry<Integer, AtomicInteger> entry : accountExpireDays.entrySet() )
{
final Integer day = entry.getKey();
entry.getValue().addAndGet( calcTimeWindow( userCacheRecord.accountExpirationTime, MS_DAY * day, adding ) );
}
}
if ( userCacheRecord.lastLoginTime != null )
{
hasLoginTime.addAndGet( modifier );
for ( final Map.Entry<Integer, AtomicInteger> entry : loginDays.entrySet() )
{
final Integer day = entry.getKey();
entry.getValue().addAndGet( calcTimeWindow( userCacheRecord.lastLoginTime, MS_DAY * day, adding ) );
}
}
if ( userCacheRecord.passwordChangeTime != null )
{
hasChangePwTime.addAndGet( modifier );
for ( final Map.Entry<Integer, AtomicInteger> entry : changePwDays.entrySet() )
{
final Integer day = entry.getKey();
entry.getValue().addAndGet( calcTimeWindow( userCacheRecord.passwordChangeTime, MS_DAY * day, adding ) );
}
}
if ( userCacheRecord.passwordStatus != null )
{
if ( adding )
{
if ( userCacheRecord.passwordStatus.isExpired() )
{
pwExpired.incrementAndGet();
}
if ( userCacheRecord.passwordStatus.isPreExpired() )
{
pwPreExpired.incrementAndGet();
}
if ( userCacheRecord.passwordStatus.isWarnPeriod() )
{
pwWarnPeriod.incrementAndGet();
}
}
else
{
if ( userCacheRecord.passwordStatus.isExpired() )
{
pwExpired.decrementAndGet();
}
if ( userCacheRecord.passwordStatus.isPreExpired() )
{
pwPreExpired.decrementAndGet();
}
if ( userCacheRecord.passwordStatus.isWarnPeriod() )
{
pwWarnPeriod.decrementAndGet();
}
}
}
if ( userCacheRecord.responseStorageMethod != null )
{
final DataStorageMethod method = userCacheRecord.responseStorageMethod;
final DataStorageMethod method = userCacheRecord.getResponseStorageMethod();
responseStorage.putIfAbsent( method, new AtomicInteger( 0 ) );
if ( adding )
{
responseStorage.get( method ).incrementAndGet();
}
else
{
responseStorage.get( method ).decrementAndGet();
}
responseStorage.get( method ).incrementAndGet();
}
if ( userCacheRecord.getLdapProfile() != null )
@ -273,86 +199,54 @@ public class ReportSummaryData
{
ldapProfile.put( userProfile, new AtomicInteger( 0 ) );
}
if ( adding )
{
ldapProfile.get( userProfile ).incrementAndGet();
}
else
{
ldapProfile.get( userProfile ).decrementAndGet();
}
ldapProfile.get( userProfile ).incrementAndGet();
}
if ( userCacheRecord.responseFormatType != null )
if ( userCacheRecord.getResponseFormatType() != null )
{
final Answer.FormatType type = userCacheRecord.responseFormatType;
final Answer.FormatType type = userCacheRecord.getResponseFormatType();
responseFormatType.putIfAbsent( type, new AtomicInteger( 0 ) );
if ( adding )
{
responseFormatType.get( type ).incrementAndGet();
}
else
{
responseFormatType.get( type ).decrementAndGet();
}
responseFormatType.get( type ).incrementAndGet();
}
if ( userCacheRecord.isHasOtpSecret() )
{
hasOtpSecret.addAndGet( modifier );
hasOtpSecret.incrementAndGet();
}
if ( userCacheRecord.getOtpSecretSetTime() != null )
{
hasOtpSecretSetTime.addAndGet( modifier );
for ( final Map.Entry<Integer, AtomicInteger> entry : otpSetDays.entrySet() )
{
final int day = entry.getKey();
entry.getValue().addAndGet( calcTimeWindow( userCacheRecord.getOtpSecretSetTime(), MS_DAY * day, adding ) );
}
hasOtpSecretSetTime.incrementAndGet();
incrementIfWithinTimeWindow( userCacheRecord, otpSetDays );
}
}
private void updateMeanTime( final Instant newTime, final boolean adding )
private void incrementIfWithinTimeWindow(
final UserCacheRecord userCacheRecord,
final Map<Integer, AtomicInteger> map
)
{
if ( meanCacheTime == null )
for ( final Map.Entry<Integer, AtomicInteger> entry : map.entrySet() )
{
if ( adding )
final int day = entry.getKey();
final Instant eventDate = userCacheRecord.getOtpSecretSetTime();
final long timeWindow = MS_DAY * day;
final AtomicInteger number = entry.getValue();
if ( eventDate != null )
{
meanCacheTime = newTime;
final TimeDuration timeBoundary = TimeDuration.of( timeWindow, TimeDuration.Unit.MILLISECONDS );
final TimeDuration eventDifference = TimeDuration.fromCurrent( eventDate );
if (
( timeWindow >= 0 && eventDate.isAfter( Instant.now() ) && eventDifference.isShorterThan( timeBoundary ) )
|| ( timeWindow < 0 && eventDate.isBefore( Instant.now() ) && eventDifference.isShorterThan( timeBoundary ) )
)
{
number.incrementAndGet();
}
}
return;
}
final BigInteger currentMillis = BigInteger.valueOf( meanCacheTime.toEpochMilli() );
final BigInteger newMillis = BigInteger.valueOf( newTime.toEpochMilli() );
final BigInteger combinedMillis = currentMillis.add( newMillis );
final BigInteger halvedMillis = combinedMillis.divide( TWO );
meanCacheTime = Instant.ofEpochMilli( halvedMillis.longValue() );
}
private int calcTimeWindow( final Instant eventDate, final long timeWindow, final boolean adding )
{
if ( eventDate == null )
{
return 0;
}
final TimeDuration timeBoundary = TimeDuration.of( timeWindow, TimeDuration.Unit.MILLISECONDS );
final TimeDuration eventDifference = TimeDuration.fromCurrent( eventDate );
if ( timeWindow >= 0 && eventDate.isAfter( Instant.now() ) && eventDifference.isShorterThan( timeBoundary ) )
{
return adding ? 1 : -1;
}
if ( timeWindow < 0 && eventDate.isBefore( Instant.now() ) && eventDifference.isShorterThan( timeBoundary ) )
{
return adding ? 1 : -1;
}
return 0;
}
@ -450,65 +344,54 @@ public class ReportSummaryData
}
}
if ( this.hasReceivedPwExpireNotification.get() > 0 )
{
returnCollection.add( new PresentationRow( "Has Received PwExpiry Notice", Integer.toString( this.hasReceivedPwExpireNotification.get() ), null ) );
for ( final Integer day : new TreeSet<>( pwExpireNotificationDays.keySet() ) )
{
if ( day < 0 )
{
returnCollection.add( new PresentationRow( "PwExpireNotice " + day, Integer.toString( this.pwExpireNotificationDays.get( day ).get() ), null ) );
}
}
}
return returnCollection;
}
@Value
@Builder( toBuilder = true )
public static class PresentationRow
{
private String label;
private String count;
private String pct;
public PresentationRow(
final String label,
final String count,
final String pct
)
{
this.label = label;
this.count = count;
this.pct = pct;
}
public String getLabel( )
{
return label;
}
public String getCount( )
{
return count;
}
public String getPct( )
{
return pct;
}
}
@Value
public static class PresentationRowBuilder
{
private final Configuration config;
private final int totalUsers;
private final Locale locale;
public PresentationRowBuilder(
final Configuration config,
final int totalUsers,
final Locale locale
)
{
this.config = config;
this.totalUsers = totalUsers;
this.locale = locale;
}
public PresentationRow makeRow( final String labelKey, final int valueCount )
PresentationRow makeRow( final String labelKey, final int valueCount )
{
return makeRow( labelKey, valueCount, null );
}
public PresentationRow makeRow( final String labelKey, final int valueCount, final String replacement )
PresentationRow makeRow( final String labelKey, final int valueCount, final String replacement )
{
return makeRowImpl( labelKey, valueCount, replacement );
}
PresentationRow makeNoPctRow( final String labelKey, final int valueCount, final String replacement )
{
return makeRowImpl( labelKey, valueCount, replacement ).toBuilder().pct( null ).build();
}
private PresentationRow makeRowImpl( final String labelKey, final int valueCount, final String replacement )
{
final String display = replacement == null
? LocaleHelper.getLocalizedMessage( locale, labelKey, config, Admin.class )
@ -522,19 +405,5 @@ public class ReportSummaryData
final String formattedCount = numberFormat.format( valueCount );
return new PresentationRow( display, formattedCount, pct );
}
public PresentationRow makeNoPctRow( final String labelKey, final int valueCount, final String replacement )
{
final String display = replacement == null
? LocaleHelper.getLocalizedMessage( locale, labelKey, config, Admin.class )
: LocaleHelper.getLocalizedMessage( locale, labelKey, config, Admin.class, new String[]
{
replacement,
}
);
final PwmNumberFormat numberFormat = PwmNumberFormat.forLocale( locale );
final String formattedCount = numberFormat.format( valueCount );
return new PresentationRow( display, formattedCount, null );
}
}
}

View file

@ -23,9 +23,8 @@
package password.pwm.svc.report;
import com.novell.ldapchai.cr.Answer;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.Builder;
import lombok.Value;
import password.pwm.bean.PasswordStatus;
import password.pwm.config.option.DataStorageMethod;
import password.pwm.error.PwmUnrecoverableException;
@ -34,85 +33,91 @@ import password.pwm.ldap.UserInfo;
import java.io.Serializable;
import java.time.Instant;
@Getter
@Setter( AccessLevel.PRIVATE )
@Value
@Builder
public class UserCacheRecord implements Serializable
{
public String userDN;
public String ldapProfile;
public String userGUID;
private String userDN;
private String ldapProfile;
private String userGUID;
public String username;
public String email;
private String username;
private String email;
public Instant cacheTimestamp = Instant.now();
private PasswordStatus passwordStatus;
private Instant passwordExpirationTime;
private Instant passwordChangeTime;
private Instant lastLoginTime;
private Instant accountExpirationTime;
private Instant passwordExpirationNoticeSendTime;
public PasswordStatus passwordStatus;
public Instant passwordExpirationTime;
public Instant passwordChangeTime;
public Instant lastLoginTime;
public Instant accountExpirationTime;
private boolean hasResponses;
private boolean hasHelpdeskResponses;
private Instant responseSetTime;
private DataStorageMethod responseStorageMethod;
private Answer.FormatType responseFormatType;
public boolean hasResponses;
public boolean hasHelpdeskResponses;
public Instant responseSetTime;
public DataStorageMethod responseStorageMethod;
public Answer.FormatType responseFormatType;
private boolean hasOtpSecret;
private Instant otpSecretSetTime;
public boolean hasOtpSecret;
public Instant otpSecretSetTime;
private boolean requiresPasswordUpdate;
private boolean requiresResponseUpdate;
private boolean requiresProfileUpdate;
public boolean requiresPasswordUpdate;
public boolean requiresResponseUpdate;
public boolean requiresProfileUpdate;
private Instant cacheTimestamp;
void addUiBeanData(
static UserCacheRecord fromUserInfo(
final UserInfo userInfo
)
throws PwmUnrecoverableException
{
this.setUserDN( userInfo.getUserIdentity().getUserDN() );
this.setLdapProfile( userInfo.getUserIdentity().getLdapProfileID() );
this.setUsername( userInfo.getUsername() );
this.setEmail( userInfo.getUserEmailAddress() );
this.setUserGUID( userInfo.getUserGuid() );
final UserCacheRecordBuilder builder = new UserCacheRecordBuilder();
builder.userDN( userInfo.getUserIdentity().getUserDN() );
builder.ldapProfile( userInfo.getUserIdentity().getLdapProfileID() );
builder.username( userInfo.getUsername() );
builder.email( userInfo.getUserEmailAddress() );
builder.userGUID( userInfo.getUserGuid() );
this.setPasswordStatus( userInfo.getPasswordStatus() );
builder.passwordStatus( userInfo.getPasswordStatus() );
this.setPasswordChangeTime( userInfo.getPasswordLastModifiedTime() );
this.setPasswordExpirationTime( userInfo.getPasswordExpirationTime() );
this.setLastLoginTime( userInfo.getLastLdapLoginTime() );
this.setAccountExpirationTime( userInfo.getAccountExpirationTime() );
builder.passwordChangeTime( userInfo.getPasswordLastModifiedTime() );
builder.passwordExpirationTime( userInfo.getPasswordExpirationTime() );
builder.lastLoginTime( userInfo.getLastLdapLoginTime() );
builder.accountExpirationTime( userInfo.getAccountExpirationTime() );
builder.passwordExpirationNoticeSendTime( userInfo.getPasswordExpirationNoticeSendTime() );
this.setHasResponses( userInfo.getResponseInfoBean() != null );
this.setResponseSetTime( userInfo.getResponseInfoBean() != null
builder.hasResponses( userInfo.getResponseInfoBean() != null );
builder.responseSetTime( userInfo.getResponseInfoBean() != null
? userInfo.getResponseInfoBean().getTimestamp()
: null
);
this.setResponseStorageMethod( userInfo.getResponseInfoBean() != null
builder.responseStorageMethod( userInfo.getResponseInfoBean() != null
? userInfo.getResponseInfoBean().getDataStorageMethod()
: null
);
this.setResponseFormatType( userInfo.getResponseInfoBean() != null
builder.responseFormatType( userInfo.getResponseInfoBean() != null
? userInfo.getResponseInfoBean().getFormatType()
: null
);
this.setRequiresPasswordUpdate( userInfo.isRequiresNewPassword() );
this.setRequiresResponseUpdate( userInfo.isRequiresResponseConfig() );
this.setRequiresProfileUpdate( userInfo.isRequiresUpdateProfile() );
this.setCacheTimestamp( Instant.now() );
builder.requiresPasswordUpdate( userInfo.isRequiresNewPassword() );
builder.requiresResponseUpdate( userInfo.isRequiresResponseConfig() );
builder.requiresProfileUpdate( userInfo.isRequiresUpdateProfile() );
this.setHasOtpSecret( userInfo.getOtpUserRecord() != null );
this.setOtpSecretSetTime( userInfo.getOtpUserRecord() != null && userInfo.getOtpUserRecord().getTimestamp() != null
builder.hasOtpSecret( userInfo.getOtpUserRecord() != null );
builder.otpSecretSetTime( userInfo.getOtpUserRecord() != null && userInfo.getOtpUserRecord().getTimestamp() != null
? userInfo.getOtpUserRecord().getTimestamp()
: null
);
this.setHasHelpdeskResponses( userInfo.getResponseInfoBean() != null
builder.hasHelpdeskResponses( userInfo.getResponseInfoBean() != null
&& userInfo.getResponseInfoBean().getHelpdeskCrMap() != null
&& !userInfo.getResponseInfoBean().getHelpdeskCrMap().isEmpty()
);
builder.cacheTimestamp( Instant.now() );
return builder.build();
}
}

View file

@ -59,24 +59,14 @@ public class UserCacheService implements PwmService
return status;
}
public UserCacheRecord updateUserCache( final UserInfo userInfo )
UserCacheRecord updateUserCache( final UserInfo userInfo )
throws PwmUnrecoverableException
{
final StorageKey storageKey = StorageKey.fromUserInfo( userInfo, pwmApplication );
boolean preExisting = false;
try
{
UserCacheRecord userCacheRecord = readStorageKey( storageKey );
if ( userCacheRecord == null )
{
userCacheRecord = new UserCacheRecord();
}
else
{
preExisting = true;
}
userCacheRecord.addUiBeanData( userInfo );
final UserCacheRecord userCacheRecord = UserCacheRecord.fromUserInfo( userInfo );
store( userCacheRecord );
return userCacheRecord;
}
@ -86,24 +76,17 @@ public class UserCacheService implements PwmService
}
{
final boolean finalPreExisting = preExisting;
LOGGER.trace( () -> "updateCache: " + ( finalPreExisting ? "updated existing" : "created new" ) + " user cache for "
LOGGER.trace( () -> "updateCache: read user cache for "
+ userInfo.getUserIdentity() + " user key " + storageKey.getKey() );
}
return null;
}
public UserCacheRecord readStorageKey( final StorageKey storageKey ) throws LocalDBException
UserCacheRecord readStorageKey( final StorageKey storageKey ) throws LocalDBException
{
return cacheStore.read( storageKey );
}
public boolean removeStorageKey( final StorageKey storageKey )
throws LocalDBException
{
return cacheStore.remove( storageKey );
}
public void store( final UserCacheRecord userCacheRecord )
throws LocalDBException, PwmUnrecoverableException
{
@ -208,14 +191,14 @@ public class UserCacheService implements PwmService
return key;
}
public static StorageKey fromUserInfo( final UserInfo userInfo, final PwmApplication pwmApplication )
static StorageKey fromUserInfo( final UserInfo userInfo, final PwmApplication pwmApplication )
throws PwmUnrecoverableException
{
final String userGUID = userInfo.getUserGuid();
return fromUserGUID( userGUID, pwmApplication );
}
public static StorageKey fromUserIdentity( final PwmApplication pwmApplication, final UserIdentity userIdentity )
static StorageKey fromUserIdentity( final PwmApplication pwmApplication, final UserIdentity userIdentity )
throws ChaiUnavailableException, PwmUnrecoverableException
{
final String userGUID = LdapOperationsHelper.readLdapGuidValue( pwmApplication, null, userIdentity, true );

View file

@ -30,7 +30,6 @@ import password.pwm.error.PwmException;
import password.pwm.health.HealthRecord;
import password.pwm.http.PwmRequest;
import password.pwm.svc.PwmService;
import password.pwm.util.AlertHandler;
import password.pwm.util.java.JavaHelper;
import password.pwm.util.java.JsonUtil;
import password.pwm.util.java.TimeDuration;
@ -329,7 +328,7 @@ public class StatisticsManager implements PwmService
localDB.put( LocalDB.DB.PWM_STATS, DB_KEY_INITIAL_DAILY_KEY, initialDailyKey.toString() );
{
// setup a timer to roll over at 0 Zula and one to write current stats every 10 seconds
// setup a timer to roll over at 0 Zulu and one to write current stats regularly
executorService = JavaHelper.makeBackgroundExecutor( pwmApplication, this.getClass() );
pwmApplication.scheduleFixedRateJob( new FlushTask(), executorService, DB_WRITE_FREQUENCY, DB_WRITE_FREQUENCY );
final TimeDuration delayTillNextZulu = TimeDuration.fromCurrent( JavaHelper.nextZuluZeroTime() );
@ -367,25 +366,21 @@ public class StatisticsManager implements PwmService
}
public Map<String, String> dailyStatisticsAsLabelValueMap()
{
final Map<String, String> emailValues = new LinkedHashMap<>();
for ( final Statistic statistic : Statistic.values() )
{
final String key = statistic.getLabel( PwmConstants.DEFAULT_LOCALE );
final String value = statsDaily.getStatistic( statistic );
emailValues.put( key, value );
}
return Collections.unmodifiableMap( emailValues );
}
private void resetDailyStats( )
{
try
{
final Map<String, String> emailValues = new LinkedHashMap<>();
for ( final Statistic statistic : Statistic.values() )
{
final String key = statistic.getLabel( PwmConstants.DEFAULT_LOCALE );
final String value = statsDaily.getStatistic( statistic );
emailValues.put( key, value );
}
AlertHandler.alertDailyStats( pwmApplication, emailValues );
}
catch ( Exception e )
{
LOGGER.error( "error while generating daily alert statistics: " + e.getMessage() );
}
currentDailyKey = new DailyKey( new Date() );
statsDaily = new StatisticsBundle();
LOGGER.debug( () -> "reset daily statistics" );
@ -452,7 +447,7 @@ public class StatisticsManager implements PwmService
public DailyKey( final String value )
{
final String strippedValue = value.substring( DB_KEY_PREFIX_DAILY.length(), value.length() );
final String strippedValue = value.substring( DB_KEY_PREFIX_DAILY.length() );
final String[] splitValue = strippedValue.split( "_" );
year = Integer.parseInt( splitValue[ 0 ] );
day = Integer.parseInt( splitValue[ 1 ] );
@ -465,7 +460,7 @@ public class StatisticsManager implements PwmService
@Override
public String toString( )
{
return DB_KEY_PREFIX_DAILY + String.valueOf( year ) + "_" + String.valueOf( day );
return DB_KEY_PREFIX_DAILY + year + "_" + day;
}
public DailyKey previous( )

View file

@ -634,7 +634,7 @@ public class TokenService implements PwmService
&& tokenPayload.getUserIdentity() != null
&& tokenPayload.getData() != null
&& tokenPayload.getData().containsKey( PwmConstants.TOKEN_KEY_PWD_CHG_DATE )
)
)
{
try
{
@ -739,13 +739,15 @@ public class TokenService implements PwmService
pwmApplication.getIntruderManager().mark( RecordType.TOKEN_DEST, toAddress, null );
final EmailItemBean configuredEmailSetting = tokenSendInfo.getConfiguredEmailSetting();
pwmApplication.getEmailQueue().submitEmailImmediate( new EmailItemBean(
toAddress,
configuredEmailSetting.getFrom(),
configuredEmailSetting.getSubject(),
configuredEmailSetting.getBodyPlain().replace( "%TOKEN%", tokenSendInfo.getTokenKey() ),
configuredEmailSetting.getBodyHtml().replace( "%TOKEN%", tokenSendInfo.getTokenKey() )
), tokenSendInfo.getUserInfo(), tokenSendInfo.getMacroMachine() );
final EmailItemBean tokenizedEmail = configuredEmailSetting.applyBodyReplacement(
"%TOKEN%",
tokenSendInfo.getTokenKey() );
pwmApplication.getEmailQueue().submitEmailImmediate(
tokenizedEmail,
tokenSendInfo.getUserInfo(),
tokenSendInfo.getMacroMachine() );
LOGGER.debug( () -> "token email added to send queue for " + toAddress );
return true;
}
@ -771,7 +773,7 @@ public class TokenService implements PwmService
LOGGER.debug( () -> "token SMS added to send queue for " + smsNumber );
return true;
}
}
}
static TimeDuration maxTokenAge( final Configuration configuration )
{

View file

@ -29,9 +29,9 @@ import password.pwm.PwmConstants;
import password.pwm.bean.EmailItemBean;
import password.pwm.config.PwmSetting;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.health.HealthMonitor;
import password.pwm.health.HealthRecord;
import password.pwm.i18n.Display;
import password.pwm.svc.PwmService;
import password.pwm.svc.report.ReportSummaryData;
import password.pwm.util.java.JavaHelper;
import password.pwm.util.java.TimeDuration;
@ -46,21 +46,53 @@ import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
public abstract class AlertHandler
public class DailySummaryJob implements Runnable
{
private static final PwmLogger LOGGER = PwmLogger.forClass( AlertHandler.class );
private static final PwmLogger LOGGER = PwmLogger.forClass( DailySummaryJob.class );
private final PwmApplication pwmApplication;
public DailySummaryJob( final PwmApplication pwmApplication )
{
this.pwmApplication = pwmApplication;
}
@Override
public void run()
{
try
{
alertDailyStats();
}
catch ( Exception e )
{
LOGGER.error( "error while generating daily alert statistics: " + e.getMessage() );
}
public static void alertDailyStats(
final PwmApplication pwmApplication,
final Map<String, String> dailyStatistics
) throws PwmUnrecoverableException
}
private void alertDailyStats(
)
throws PwmUnrecoverableException
{
if ( !checkIfEnabled( pwmApplication, PwmSetting.EVENTS_ALERT_DAILY_SUMMARY ) )
{
LOGGER.debug( () -> "skipping daily summary alert job, setting "
+ PwmSetting.EVENTS_ALERT_DAILY_SUMMARY.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE )
+ " not configured" );
return;
}
if ( pwmApplication.getStatisticsManager().status() != PwmService.STATUS.OPEN )
{
LOGGER.debug( () -> "skipping daily summary alert job, statistics service is not open" );
return;
}
final Map<String, String> dailyStatistics = pwmApplication.getStatisticsManager().dailyStatisticsAsLabelValueMap();
final Locale locale = PwmConstants.DEFAULT_LOCALE;
for ( final String toAddress : pwmApplication.getConfig().readSettingAsStringArray( PwmSetting.AUDIT_EMAIL_SYSTEM_TO ) )
@ -108,7 +140,7 @@ public abstract class AlertHandler
{
// health check data
final Collection<HealthRecord> healthRecords = pwmApplication.getHealthMonitor().getHealthRecords( HealthMonitor.CheckTimeliness.Immediate );
final Collection<HealthRecord> healthRecords = pwmApplication.getHealthMonitor().getHealthRecords();
textBody.append( "-- Health Check Results --\n" );
htmlBody.append( "<h2>Health Check Results</h2>" );
@ -157,7 +189,7 @@ public abstract class AlertHandler
textBody.append( "\n" );
htmlBody.append( "<br/>" );
if ( pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.REPORTING_ENABLE ) )
if ( pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.REPORTING_ENABLE_DAILY_JOB ) )
{
final List<ReportSummaryData.PresentationRow> summaryData = pwmApplication.getReportService()
.getSummaryData().asPresentableCollection( pwmApplication.getConfig(), locale );

View file

@ -0,0 +1,74 @@
/*
* Password Management Servlets (PWM)
* http://www.pwm-project.org
*
* Copyright (c) 2006-2009 Novell, Inc.
* Copyright (c) 2009-2018 The PWM Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package password.pwm.util;
import org.apache.commons.io.IOUtils;
import password.pwm.PwmConstants;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
public final class ServletUtility
{
private ServletUtility()
{
}
public static String readRequestBodyAsString( final HttpServletRequest req, final int maxChars )
throws IOException, PwmUnrecoverableException
{
final StringWriter stringWriter = new StringWriter();
final Reader readerStream = new InputStreamReader(
req.getInputStream(),
PwmConstants.DEFAULT_CHARSET
);
try
{
IOUtils.copy( readerStream, stringWriter );
}
catch ( Exception e )
{
final String errorMsg = "error reading request body stream: " + e.getMessage();
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg ) );
}
finally
{
IOUtils.closeQuietly( readerStream );
}
final String stringValue = stringWriter.toString();
if ( stringValue.length() > maxChars )
{
final String msg = "input request body is to big, size=" + stringValue.length() + ", max=" + maxChars;
throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, msg ) );
}
return stringValue;
}
}

View file

@ -41,6 +41,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
/**
* <p>An immutable class representing a time period. The internal value of the time period is
@ -329,8 +330,8 @@ public class TimeDuration implements Comparable, Serializable
segments.add( fractionalTimeDetail.days
+ " "
+ ( fractionalTimeDetail.days == 1
? LocaleHelper.getLocalizedMessage( locale, Display.Display_Day, null )
: LocaleHelper.getLocalizedMessage( locale, Display.Display_Days, null ) )
? LocaleHelper.getLocalizedMessage( locale, Display.Display_Day, null )
: LocaleHelper.getLocalizedMessage( locale, Display.Display_Days, null ) )
);
}
@ -340,8 +341,8 @@ public class TimeDuration implements Comparable, Serializable
segments.add( fractionalTimeDetail.hours
+ " "
+ ( fractionalTimeDetail.hours == 1
? LocaleHelper.getLocalizedMessage( locale, Display.Display_Hour, null )
: LocaleHelper.getLocalizedMessage( locale, Display.Display_Hours, null ) )
? LocaleHelper.getLocalizedMessage( locale, Display.Display_Hour, null )
: LocaleHelper.getLocalizedMessage( locale, Display.Display_Hours, null ) )
);
}
@ -351,8 +352,8 @@ public class TimeDuration implements Comparable, Serializable
segments.add( fractionalTimeDetail.minutes
+ " "
+ ( fractionalTimeDetail.minutes == 1
? LocaleHelper.getLocalizedMessage( locale, Display.Display_Minute, null )
: LocaleHelper.getLocalizedMessage( locale, Display.Display_Minutes, null ) )
? LocaleHelper.getLocalizedMessage( locale, Display.Display_Minute, null )
: LocaleHelper.getLocalizedMessage( locale, Display.Display_Minutes, null ) )
);
}
@ -430,36 +431,6 @@ public class TimeDuration implements Comparable, Serializable
return "TimeDuration[" + this.asCompactString() + "]";
}
/**
* Pause the calling thread the specified amount of time.
*
* @param sleepTimeMS - a time duration in milliseconds
* @return time actually spent sleeping
*/
public static TimeDuration pause( final long sleepTimeMS )
{
if ( sleepTimeMS < 1 )
{
return TimeDuration.ZERO;
}
final long startTime = System.currentTimeMillis();
do
{
try
{
final long sleepTime = sleepTimeMS - ( System.currentTimeMillis() - startTime );
Thread.sleep( sleepTime > 0 ? sleepTime : 5 );
}
catch ( InterruptedException e )
{
//who cares
}
}
while ( ( System.currentTimeMillis() - startTime ) < sleepTimeMS );
return TimeDuration.fromCurrent( startTime );
}
/**
* Pause the calling thread the specified amount of time.
@ -469,7 +440,39 @@ public class TimeDuration implements Comparable, Serializable
@CheckReturnValue( when = When.NEVER )
public TimeDuration pause( )
{
return pause( this.as( Unit.MILLISECONDS ) );
return pause( this, () -> false );
}
@CheckReturnValue( when = When.NEVER )
public TimeDuration pause(
final BooleanSupplier interruptBoolean
)
{
return pause( this, interruptBoolean );
}
@CheckReturnValue( when = When.NEVER )
public TimeDuration pause(
final TimeDuration predicateCheckInterval,
final BooleanSupplier interruptBoolean
)
{
final long startTime = System.currentTimeMillis();
final long pauseTime = JavaHelper.rangeCheck( this.asMillis(), this.asMillis(), predicateCheckInterval.asMillis() );
while ( ( System.currentTimeMillis() - startTime ) < this.asMillis() && !interruptBoolean.getAsBoolean() )
{
try
{
Thread.sleep( pauseTime );
}
catch ( InterruptedException e )
{
// ignore
}
}
return TimeDuration.fromCurrent( startTime );
}
public Duration asDuration()

View file

@ -184,7 +184,7 @@ public class PasswordUtility
final String toAddress,
final Locale userLocale
)
throws PwmOperationalException, PwmUnrecoverableException
throws PwmUnrecoverableException
{
final Configuration config = pwmApplication.getConfig();
final EmailItemBean configuredEmailSetting = config.readSettingAsEmail( PwmSetting.EMAIL_SENDPASSWORD, userLocale );
@ -195,18 +195,14 @@ public class PasswordUtility
return new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg );
}
final EmailItemBean emailItemBean = new EmailItemBean(
configuredEmailSetting.getTo(),
configuredEmailSetting.getFrom(),
configuredEmailSetting.getSubject(),
configuredEmailSetting.getBodyPlain().replace( "%TOKEN%", newPassword.getStringValue() ),
configuredEmailSetting.getBodyHtml().replace( "%TOKEN%", newPassword.getStringValue() )
);
final EmailItemBean emailItemBean = configuredEmailSetting.applyBodyReplacement(
"%TOKEN%",
newPassword.getStringValue() );
pwmApplication.getEmailQueue().submitEmail(
emailItemBean,
userInfo,
macroMachine
);
macroMachine );
LOGGER.debug( () -> "new password email to " + userInfo.getUserIdentity() + " added to send queue for " + toAddress );

View file

@ -23,19 +23,18 @@
package password.pwm.ws.client.rest.form;
import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import password.pwm.config.value.data.FormConfiguration;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
@Getter
@Value
@Builder
public class FormDataRequestBean implements Serializable
{
@Getter
@Value
@Builder
public static class FormInfo implements Serializable
{

View file

@ -23,12 +23,12 @@
package password.pwm.ws.client.rest.form;
import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import java.io.Serializable;
import java.util.Map;
@Getter
@Value
@Builder
public class FormDataResponseBean implements Serializable
{
@ -36,5 +36,4 @@ public class FormDataResponseBean implements Serializable
private String errorMessage;
private String errorDetail;
private Map<String, String> formValues;
}

View file

@ -70,12 +70,9 @@ public class RestHealthServer extends RestServlet
private RestResultBean doPwmHealthPlainGet( final RestRequest restRequest )
throws PwmUnrecoverableException
{
final boolean requestImmediateParam = restRequest.readParameterAsBoolean( PARAM_IMMEDIATE_REFRESH );
try
{
final HealthMonitor.CheckTimeliness timeliness = determineDataTimeliness( requestImmediateParam );
final String resultString = restRequest.getPwmApplication().getHealthMonitor().getMostSevereHealthStatus( timeliness ).toString() + "\n";
final String resultString = restRequest.getPwmApplication().getHealthMonitor().getMostSevereHealthStatus().toString() + "\n";
StatisticsManager.incrementStat( restRequest.getPwmApplication(), Statistic.REST_HEALTH );
return RestResultBean.withData( resultString );
}
@ -91,38 +88,24 @@ public class RestHealthServer extends RestServlet
private RestResultBean doPwmHealthJsonGet( final RestRequest restRequest )
throws PwmUnrecoverableException, IOException
{
final boolean requestImmediateParam = restRequest.readParameterAsBoolean( PARAM_IMMEDIATE_REFRESH );
final HealthData jsonOutput = processGetHealthCheckData( restRequest.getPwmApplication(), restRequest.getLocale(), requestImmediateParam );
final HealthData jsonOutput = processGetHealthCheckData( restRequest.getPwmApplication(), restRequest.getLocale() );
StatisticsManager.incrementStat( restRequest.getPwmApplication(), Statistic.REST_HEALTH );
return RestResultBean.withData( jsonOutput );
}
private static HealthMonitor.CheckTimeliness determineDataTimeliness(
final boolean refreshImmediate
)
throws PwmUnrecoverableException
{
return refreshImmediate
? HealthMonitor.CheckTimeliness.Immediate
: HealthMonitor.CheckTimeliness.CurrentButNotAncient;
}
public static HealthData processGetHealthCheckData(
final PwmApplication pwmApplication,
final Locale locale,
final boolean refreshImmediate
final Locale locale
)
throws IOException, PwmUnrecoverableException
{
final HealthMonitor healthMonitor = pwmApplication.getHealthMonitor();
final HealthMonitor.CheckTimeliness timeliness = determineDataTimeliness( refreshImmediate );
final List<password.pwm.health.HealthRecord> healthRecords = new ArrayList<>( healthMonitor.getHealthRecords( timeliness ) );
final List<password.pwm.health.HealthRecord> healthRecords = new ArrayList<>( healthMonitor.getHealthRecords() );
final List<HealthRecord> healthRecordBeans = HealthRecord.fromHealthRecords( healthRecords, locale,
pwmApplication.getConfig() );
final HealthData healthData = new HealthData();
healthData.timestamp = healthMonitor.getLastHealthCheckTime();
healthData.overall = healthMonitor.getMostSevereHealthStatus( timeliness ).toString();
healthData.overall = healthMonitor.getMostSevereHealthStatus().toString();
healthData.records = healthRecordBeans;
return healthData;
}

View file

@ -276,6 +276,7 @@ queue.syslog.maxAgeMs=2592000000
queue.syslog.maxCount=100000
reporting.ldap.searchTimeoutMs=1800000
reporting.ldap.searchThreads=8
reporting.maxReportAgeSeconds=864000
recaptcha.clientJsUrl=//www.google.com/recaptcha/api.js
recaptcha.clientIframeUrl=//www.google.com/recaptcha/api/noscript
recaptcha.validateUrl=https://www.google.com/recaptcha/api/siteverify

View file

@ -711,12 +711,6 @@
<property key="Maximum">65535</property>
</properties>
</setting>
<setting hidden="false" key="email.default.fromAddresses" level="1">
<flag>emailSyntax</flag>
<default>
<value>noreply@example.org</value>
</default>
</setting>
<setting hidden="false" key="email.smtp.username" level="1">
<default>
<value />
@ -2583,11 +2577,6 @@
<ldapPermission actor="proxy" access="read"/>
<default />
</setting>
<setting hidden="false" key="challenge.token.enable" level="1" required="true">
<default>
<value>false</value>
</default>
</setting>
<setting hidden="false" key="challenge.token.sendMethod" level="1" required="true">
<default>
<value><![CDATA[EMAILONLY]]></value>
@ -2893,13 +2882,6 @@
<option value="checkbox">checkbox</option>
</options>
</setting>
<setting hidden="false" key="guest.creationUniqueAttributes" level="1">
<regex>^[a-zA-Z][a-zA-Z0-9-]*$</regex>
<default>
<value><![CDATA[cn]]></value>
<value><![CDATA[mail]]></value>
</default>
</setting>
<setting hidden="false" key="guest.writeAttributes" level="1">
<ldapPermission actor="guestManager" access="write"/>
<default />
@ -2938,11 +2920,6 @@
<value>true</value>
</default>
</setting>
<setting hidden="false" key="activateUser.token.verification" level="1" required="true">
<default>
<value>false</value>
</default>
</setting>
<setting hidden="false" key="display.activateUser.agreement" level="1">
<flag>MacroSupport</flag>
<default/>
@ -3880,11 +3857,6 @@
<value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
</default>
</setting>
<setting hidden="false" key="reporting.maxCacheAge" level="1">
<default>
<value>2592000</value>
</default>
</setting>
<setting hidden="false" key="reporting.ldap.maxQuerySize" level="2">
<default>
<value>100000</value>
@ -4173,8 +4145,6 @@
<category hidden="false" key="EMAIL_SERVERS">
<profile setting="email.profile.list"/>
</category>
<category hidden="false" key="EMAIL_PROFILE_SETTING">
</category>
<category hidden="false" key="EMAIL">
</category>
<category hidden="false" key="EMAIL_SETTINGS">

View file

@ -659,7 +659,6 @@ Setting_Description_reporting.job.timeOffset=Specify the number of seconds past
Setting_Description_reporting.ldap.maxQuerySize=Specify the maximum number of records read during a reporting query search. Setting this value to a larger sizes requires more Java heap memory.
Setting_Description_reporting.ldap.searchFilter=Add an LDAP search filter @PwmAppName@ uses when running a reporting job. If blank @PwmAppName@ auto-generates a filter based on the login query setting.
Setting_Description_reporting.ldap.userMatch=Select users to include in the reporting job.
Setting_Description_reporting.maxCacheAge=Specify the maximum age of a cached report record before @PwmAppName@ discards it. @PwmAppName@ periodically purges records older than this age from the local report data cache. @PwmAppName@ does this so that the deleted records that it deletes from the LDAP directory are eventually removed from the report cache. The default value of 2592000 seconds is equal to 30 days.<br/><br/>Specify the value in seconds.
Setting_Description_reporting.summary.dayValues=Specify day intervals to include in report summary data.
Setting_Description_response.hashMethod=<p>Select the method of hashing @PwmAppName@ uses to store responses. Storing the responses as plaintext might facilitate synchronization or migration to other systems but is not secure.</p><p>This setting only controls how @PwmAppName@ writes the responses. @PwmAppName@ can always read stored responses in other formats. @PwmAppName@ cannot convert existing responses until a user re-saves their responses. You can use the reporting engine to identify and count the hash types in use.</p>
Setting_Description_security.cspHeader=Set the HTTP Content-Security-Policy header. This header instructs the browser to limit the locations from which it loads fonts, scripts, and CSS files.
@ -1180,7 +1179,6 @@ Setting_Label_reporting.job.timeOffset=Reporting Job Time Offset
Setting_Label_reporting.ldap.maxQuerySize=Maximum LDAP Query Size
Setting_Label_reporting.ldap.searchFilter=Reporting Search Filter
Setting_Label_reporting.ldap.userMatch=Reporting User Match
Setting_Label_reporting.maxCacheAge=Maximum Cache Age
Setting_Label_reporting.summary.dayValues=Reporting Summary Day Intervals
Setting_Label_response.hashMethod=Responses Storage Hashing Method
Setting_Label_security.cspHeader=HTTP Content Security Policy Header

View file

@ -0,0 +1,124 @@
/*
* Password Management Servlets (PWM)
* http://www.pwm-project.org
*
* Copyright (c) 2006-2009 Novell, Inc.
* Copyright (c) 2009-2018 The PWM Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package password.pwm.config;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
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 java.io.InputStream;
import java.util.List;
public class PwmSettingXmlTest
{
private static XmlDocument xmlDocument;
@BeforeClass
public static void setUp() throws Exception
{
try ( InputStream inputStream = PwmSetting.class.getClassLoader().getResourceAsStream( PwmSettingXml.SETTING_XML_FILENAME ) )
{
xmlDocument = XmlFactory.getFactory().parseXml( inputStream );
}
}
@AfterClass
public static void tearDown()
{
xmlDocument = null;
}
@Test
public void testSettingElementIsInXml()
{
for ( final PwmSetting pwmSetting : PwmSetting.values() )
{
final XmlElement element = PwmSettingXml.readSettingXml( pwmSetting );
Assert.assertNotNull( "no XML settings node in PwmSetting.xml for setting " + pwmSetting.getKey(), element );
}
}
@Test
public void testXmlElementIsInSettings()
{
final List<XmlElement> settingElements = xmlDocument.evaluateXpathToElements( "/settings/setting" );
Assert.assertFalse( settingElements.isEmpty() );
for ( final XmlElement element : settingElements )
{
final String key = element.getAttributeValue( "key" );
final String errorMsg = "PwmSetting.xml contains setting key of '"
+ key + "' which does not exist in PwmSetting.java";
Assert.assertNotNull( errorMsg, PwmSetting.forKey( key ) );
}
}
@Test
public void testCategoryElementIsInXml()
{
for ( final PwmSettingCategory pwmSettingCategory : PwmSettingCategory.values() )
{
final XmlElement element = PwmSettingXml.readCategoryXml( pwmSettingCategory );
Assert.assertNotNull( "no XML category node in PwmSetting.xml for setting " + pwmSettingCategory.getKey(), element );
}
}
@Test
public void testXmlElementIsInCategory()
{
final List<XmlElement> categoryElements = xmlDocument.evaluateXpathToElements( "/settings/category" );
Assert.assertFalse( categoryElements.isEmpty() );
for ( final XmlElement element : categoryElements )
{
final String key = element.getAttributeValue( "key" );
final PwmSettingCategory category = JavaHelper.readEnumFromString( PwmSettingCategory.class, null, key );
final String errorMsg = "PwmSetting.xml contains category key of '"
+ key + "' which does not exist in PwmSettingCategory.java";
Assert.assertNotNull( errorMsg, category );
}
}
@Test
public void testXmlCategoryProfileElementIsValidSetting()
{
final List<XmlElement> profileElements = xmlDocument.evaluateXpathToElements( "/settings/category/profile" );
Assert.assertFalse( profileElements.isEmpty() );
for ( final XmlElement element : profileElements )
{
final String settingKey = element.getAttributeValue( "setting" );
final PwmSetting setting = PwmSetting.forKey( settingKey );
final String errorMsg = "PwmSetting.xml contains category/profile@setting key of '"
+ settingKey + "' which does not exist in PwmSetting.java";
Assert.assertNotNull( errorMsg, setting );
}
}
}

View file

@ -66,8 +66,6 @@
</div>
<div class="push"></div>
</div>
configeditor-settings-challenges.js
<pwm:script>
<script type="text/javascript">
PWM_GLOBAL['startupFunctions'].push(function(){

View file

@ -216,8 +216,8 @@ PWM_MAIN.initPage = function() {
PWM_MAIN.TimestampHandler.initAllElements();
ShowHidePasswordHandler.initAllForms();
PWM_MAIN.log('initPage completed');
var loadTime = window.performance.timing.domContentLoadedEventEnd-window.performance.timing.navigationStart;
PWM_MAIN.log('initPage completed [load time=' + loadTime + ']');
};
PWM_MAIN.initDisplayTabPreferences = function() {
@ -271,6 +271,7 @@ PWM_MAIN.applyFormAttributes = function() {
var hrefValue = linkElement.getAttribute('href');
if (hrefValue && hrefValue.charAt(0) !== '#') {
PWM_MAIN.addEventHandler(linkElement, "click", function (event) {
console.log('intercepted anchor click event');
event.preventDefault();
PWM_MAIN.showWaitDialog({loadFunction: function () {
PWM_MAIN.gotoUrl(hrefValue);