misc refactoring
This commit is contained in:
parent
028a48c4e0
commit
1f881af29b
58 changed files with 1459 additions and 1528 deletions
41
build/checkstyle-jsp.xml
Normal file
41
build/checkstyle-jsp.xml
Normal 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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
62
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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" ),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" ),
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() )
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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( )
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
@ -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 );
|
74
server/src/main/java/password/pwm/util/ServletUtility.java
Normal file
74
server/src/main/java/password/pwm/util/ServletUtility.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
124
server/src/test/java/password/pwm/config/PwmSettingXmlTest.java
Normal file
124
server/src/test/java/password/pwm/config/PwmSettingXmlTest.java
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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(){
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue