123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- /*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2021 The PWM Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package password.pwm.svc.version;
- import lombok.Builder;
- import lombok.Value;
- import password.pwm.AppAttribute;
- import password.pwm.AppProperty;
- import password.pwm.PwmApplication;
- import password.pwm.PwmConstants;
- import password.pwm.bean.DomainID;
- import password.pwm.bean.VersionNumber;
- import password.pwm.bean.pub.PublishVersionBean;
- import password.pwm.config.AppConfig;
- import password.pwm.config.PwmSetting;
- import password.pwm.error.ErrorInformation;
- import password.pwm.error.PwmError;
- import password.pwm.error.PwmException;
- import password.pwm.error.PwmUnrecoverableException;
- import password.pwm.health.HealthMessage;
- import password.pwm.health.HealthRecord;
- import password.pwm.http.HttpContentType;
- import password.pwm.http.HttpHeader;
- import password.pwm.svc.AbstractPwmService;
- import password.pwm.svc.PwmService;
- import password.pwm.svc.httpclient.PwmHttpClient;
- import password.pwm.svc.httpclient.PwmHttpClientRequest;
- import password.pwm.svc.httpclient.PwmHttpClientResponse;
- import password.pwm.util.i18n.LocaleHelper;
- import password.pwm.util.java.CollectionUtil;
- import password.pwm.util.java.JavaHelper;
- import password.pwm.util.java.StringUtil;
- import password.pwm.util.java.TimeDuration;
- import password.pwm.util.json.JsonFactory;
- import password.pwm.util.localdb.LocalDB;
- import password.pwm.util.logging.PwmLogger;
- import password.pwm.ws.server.RestResultBean;
- import java.lang.reflect.Type;
- import java.time.Instant;
- import java.time.temporal.ChronoUnit;
- import java.util.Collections;
- import java.util.EnumMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Objects;
- public class VersionCheckService extends AbstractPwmService
- {
- private static final PwmLogger LOGGER = PwmLogger.forClass( VersionCheckService.class );
- private PwmApplication pwmApplication;
- private VersionCheckSettings settings;
- private VersionNumber runningVersion;
- private CacheHolder cacheHolder;
- private Instant nextScheduledCheck;
- private enum DebugKey
- {
- runningVersion,
- currentVersion,
- outdatedVersionFlag,
- lastCheckTime,
- nextCheckTime,
- lastError
- }
- @Override
- protected STATUS postAbstractInit( final PwmApplication pwmApplication, final DomainID domainID )
- throws PwmException
- {
- this.pwmApplication = Objects.requireNonNull( pwmApplication );
- this.settings = VersionCheckSettings.fromConfig( pwmApplication.getConfig() );
- initRunningVersion();
- if ( enabled() )
- {
- cacheHolder = new CacheHolder( pwmApplication );
- setStatus( STATUS.OPEN );
- scheduleNextCheck();
- return STATUS.OPEN;
- }
- return STATUS.CLOSED;
- }
- @Override
- protected void shutdownImpl()
- {
- }
- private Map<DebugKey, String> debugMap()
- {
- if ( status() != STATUS.OPEN )
- {
- return Collections.emptyMap();
- }
- final String notApplicable = LocaleHelper.valueNotApplicable( PwmConstants.DEFAULT_LOCALE );
- final VersionCheckInfoCache localCache = cacheHolder.getVersionCheckInfoCache();
- final Map<DebugKey, String> debugKeyMap = new EnumMap<>( DebugKey.class );
- debugKeyMap.put( DebugKey.runningVersion, runningVersion == null ? notApplicable : runningVersion.prettyVersionString() );
- debugKeyMap.put( DebugKey.currentVersion, localCache.getCurrentVersion() == null ? notApplicable : localCache.getCurrentVersion().prettyVersionString() );
- debugKeyMap.put( DebugKey.outdatedVersionFlag, LocaleHelper.valueBoolean( PwmConstants.DEFAULT_LOCALE, isOutdated() ) );
- debugKeyMap.put( DebugKey.lastError, localCache.getLastError() == null ? notApplicable : localCache.getLastError().toDebugStr() );
- debugKeyMap.put( DebugKey.lastCheckTime, localCache.getLastCheckTimestamp() == null ? notApplicable : StringUtil.toIsoDate( localCache.getLastCheckTimestamp() ) );
- debugKeyMap.put( DebugKey.nextCheckTime, nextScheduledCheck == null
- ? notApplicable
- : StringUtil.toIsoDate( nextScheduledCheck ) + " (" + TimeDuration.compactFromCurrent( nextScheduledCheck ) + ")" );
- return Collections.unmodifiableMap( debugKeyMap );
- }
- @Override
- public ServiceInfoBean serviceInfo()
- {
- return ServiceInfoBean.builder()
- .debugProperties( CollectionUtil.enumMapToStringMap( debugMap() ) )
- .build();
- }
- private void scheduleNextCheck()
- {
- if ( status() != PwmService.STATUS.OPEN )
- {
- return;
- }
- final VersionCheckInfoCache localCache = cacheHolder.getVersionCheckInfoCache();
- this.nextScheduledCheck = calculateNextScheduledCheck( localCache, settings );
- final TimeDuration delayUntilNextExecution = TimeDuration.fromCurrent( this.nextScheduledCheck );
- scheduleJob( new PeriodicCheck(), delayUntilNextExecution );
- LOGGER.trace( getSessionLabel(), () -> "scheduled next check execution at " + StringUtil.toIsoDate( nextScheduledCheck )
- + " in " + delayUntilNextExecution.asCompactString() );
- }
-
- private static Instant calculateNextScheduledCheck( final VersionCheckInfoCache localCache, final VersionCheckSettings settings )
- {
- final TimeDuration idealDurationUntilNextCheck = localCache.getLastError() != null && localCache.getCurrentVersion() == null
- ? settings.getCheckIntervalError()
- : settings.getCheckInterval();
- if ( localCache.getLastCheckTimestamp() == null )
- {
- return Instant.now().plus( 10, ChronoUnit.SECONDS );
- }
- else
- {
- final Instant nextIdealTimestamp = localCache.getLastCheckTimestamp().plus( idealDurationUntilNextCheck.asDuration() );
- return nextIdealTimestamp.isBefore( Instant.now() )
- ? Instant.now().plus( 10, ChronoUnit.SECONDS )
- : nextIdealTimestamp;
- }
- }
- private class PeriodicCheck implements Runnable
- {
- @Override
- public void run()
- {
- if ( status() != PwmService.STATUS.OPEN )
- {
- return;
- }
- try
- {
- processReturnedVersionBean( executeFetch() );
- }
- catch ( final PwmUnrecoverableException e )
- {
- cacheHolder.setVersionCheckInfoCache( VersionCheckInfoCache.builder()
- .lastError( e.getErrorInformation() )
- .lastCheckTimestamp( Instant.now() )
- .build() );
- }
- scheduleNextCheck();
- }
- }
- private void processReturnedVersionBean( final PublishVersionBean publishVersionBean )
- {
- final Instant startTime = Instant.now();
- final VersionNumber currentVersion = publishVersionBean.getVersions().get( PublishVersionBean.VersionKey.current );
- cacheHolder.setVersionCheckInfoCache( VersionCheckInfoCache.builder()
- .currentVersion( currentVersion )
- .lastCheckTimestamp( Instant.now() )
- .build() );
- LOGGER.trace( getSessionLabel(), () -> "successfully fetched current version information from cloud service: "
- + currentVersion, TimeDuration.fromCurrent( startTime ) );
- }
- private PublishVersionBean executeFetch()
- throws PwmUnrecoverableException
- {
- final Instant startTime = Instant.now();
- try
- {
- final PwmHttpClient pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( getSessionLabel() );
- final PwmHttpClientRequest request = PwmHttpClientRequest.builder()
- .url( settings.getUrl() )
- .header( HttpHeader.ContentType.getHttpName(), HttpContentType.json.getHeaderValueWithEncoding() )
- .header( HttpHeader.Accept.getHttpName(), HttpContentType.json.getHeaderValueWithEncoding() )
- .body( JsonFactory.get().serialize( makeRequestBody() ) )
- .build();
- LOGGER.trace( getSessionLabel(), () -> "sending cloud version request to: " + settings.getUrl() );
- final PwmHttpClientResponse response = pwmHttpClient.makeRequest( request );
- if ( response.getStatusCode() == 200 )
- {
- final Type restResultBeanType = JsonFactory.get().newParameterizedType( RestResultBean.class, PublishVersionBean.class );
- final String body = response.getBody();
- final RestResultBean<PublishVersionBean> restResultBean = JsonFactory.get().deserialize( body, restResultBeanType );
- return restResultBean.getData();
- }
- else
- {
- LOGGER.debug( getSessionLabel(), () -> "error reading cloud current version information: " + response );
- final String msg = "error reading cloud current version information: " + response.getStatusLine();
- throw PwmUnrecoverableException.newException( PwmError.ERROR_UNREACHABLE_CLOUD_SERVICE, msg );
- }
- }
- catch ( final Exception e )
- {
- final ErrorInformation errorInformation;
- if ( e instanceof PwmException )
- {
- errorInformation = ( ( PwmUnrecoverableException ) e ).getErrorInformation();
- }
- else
- {
- final String errorMsg = "error reading current version from cloud service: " + e.getMessage();
- errorInformation = new ErrorInformation( PwmError.ERROR_UNREACHABLE_CLOUD_SERVICE, errorMsg );
- }
- LOGGER.debug( getSessionLabel(), () -> "error fetching current version from cloud: "
- + e.getMessage(), TimeDuration.fromCurrent( startTime ) );
- throw new PwmUnrecoverableException( errorInformation );
- }
- }
- private PublishVersionBean makeRequestBody()
- {
- VersionNumber versionNumber = VersionNumber.ZERO;
- try
- {
- versionNumber = VersionNumber.parse( PwmConstants.BUILD_VERSION );
- }
- catch ( final Exception e )
- {
- LOGGER.trace( getSessionLabel(), () -> "error reading local version number " + e.getMessage() );
- }
- return new PublishVersionBean( Collections.singletonMap( PublishVersionBean.VersionKey.current, versionNumber ) );
- }
- @Override
- protected List<HealthRecord> serviceHealthCheck()
- {
- if ( status() != PwmService.STATUS.OPEN )
- {
- return Collections.emptyList();
- }
- final VersionCheckInfoCache localCache = cacheHolder.getVersionCheckInfoCache();
- if ( isOutdated() )
- {
- return Collections.singletonList( HealthRecord.forMessage(
- DomainID.systemId(),
- HealthMessage.Version_OutOfDate,
- PwmConstants.PWM_APP_NAME,
- localCache.getCurrentVersion().prettyVersionString() ) );
- }
- if ( localCache.getLastError() != null )
- {
- return Collections.singletonList( HealthRecord.forMessage(
- DomainID.systemId(),
- HealthMessage.Version_Unreachable,
- localCache.getLastError().toDebugStr() ) );
- }
- return Collections.emptyList();
- }
- private boolean isOutdated()
- {
- if ( status() != PwmService.STATUS.OPEN )
- {
- return false;
- }
- final VersionCheckInfoCache localCache = cacheHolder.getVersionCheckInfoCache();
- if ( runningVersion == null || localCache.getCurrentVersion() == null )
- {
- return false;
- }
- final int comparisonInt = runningVersion.compareTo( localCache.getCurrentVersion() );
- return comparisonInt < 0;
- }
- @Value
- @Builder
- private static class VersionCheckInfoCache
- {
- private final Instant lastCheckTimestamp;
- private final ErrorInformation lastError;
- private final VersionNumber currentVersion;
- }
- @Value
- @Builder
- private static class VersionCheckSettings
- {
- private static final int DEFAULT_INTERVAL_SECONDS = 3801;
- private final String url;
- private final TimeDuration checkInterval;
- private final TimeDuration checkIntervalError;
- static VersionCheckSettings fromConfig( final AppConfig appConfig )
- {
- final int checkSeconds = JavaHelper.silentParseInt( appConfig.readAppProperty( AppProperty.VERSION_CHECK_CHECK_INTERVAL_SECONDS ), DEFAULT_INTERVAL_SECONDS );
- final int checkSecondsError = JavaHelper.silentParseInt(
- appConfig.readAppProperty( AppProperty.VERSION_CHECK_CHECK_INTERVAL_ERROR_SECONDS ), DEFAULT_INTERVAL_SECONDS );
- return VersionCheckSettings.builder()
- .url( appConfig.readAppProperty( AppProperty.VERSION_CHECK_URL ) )
- .checkInterval( TimeDuration.of( checkSeconds, TimeDuration.Unit.SECONDS ) )
- .checkIntervalError( TimeDuration.of( checkSecondsError, TimeDuration.Unit.SECONDS ) )
- .build();
- }
- }
- private void initRunningVersion()
- {
- try
- {
- this.runningVersion = VersionNumber.parse( PwmConstants.BUILD_VERSION );
- }
- catch ( final Exception e )
- {
- LOGGER.error( getSessionLabel(), () -> "error parsing internal running version number: " + e.getMessage() );
- }
- }
- private boolean enabled()
- {
- return pwmApplication.getLocalDB() != null
- && runningVersion != null
- && pwmApplication.getLocalDB().status() == LocalDB.Status.OPEN
- && !pwmApplication.getPwmEnvironment().isInternalRuntimeInstance()
- && pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.VERSION_CHECK_ENABLE );
- }
- private static class CacheHolder
- {
- private final PwmApplication pwmApplication;
- private VersionCheckInfoCache versionCheckInfoCache;
- CacheHolder( final PwmApplication pwmApplication )
- {
- this.pwmApplication = pwmApplication;
- this.versionCheckInfoCache = pwmApplication.readAppAttribute( AppAttribute.VERSION_CHECK_CACHE, VersionCheckInfoCache.class )
- .orElse( VersionCheckInfoCache.builder().build() );
- }
- public VersionCheckInfoCache getVersionCheckInfoCache()
- {
- return versionCheckInfoCache == null ? VersionCheckInfoCache.builder().build() : versionCheckInfoCache;
- }
- public void setVersionCheckInfoCache( final VersionCheckInfoCache versionCheckInfoCache )
- {
- this.versionCheckInfoCache = versionCheckInfoCache;
- pwmApplication.writeAppAttribute( AppAttribute.VERSION_CHECK_CACHE, versionCheckInfoCache );
- }
- }
- }
|